diff --git a/README.md b/README.md index 781b6cdfc50ff8c9e17988a412ea35ceff912f25..c6af48b592e5e91191969d33ea10bc27c1fa0134 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,235 @@ ---- -title: Flow -emoji: 🔄 -colorFrom: blue -colorTo: purple -sdk: docker -app_port: 7860 -pinned: false ---- - # Flow -**Evaluate and Optimize Coding Agent Configurations** +> [!NOTE] +> Flow is an experimental prototype and changing rapidly. + +Flow helps you find the best configuration for your AI coding agent. Define your agent spec, provide evaluation tasks, and Flow automatically generates variants, scores them, and shows you the quality vs. cost tradeoffs. -Flow is a framework for running experiments on LLM coding agents. Compare context engineering strategies (message compaction, agent memory, sub-agents), evaluate results with LLM-as-Judge, and find optimal configurations that balance quality and token cost. +- **Simplified experimentation** — Automates the search for optimal agent configurations +- **Transparency** — See exactly what was tested, scores, and tradeoffs on a Pareto chart +- **User control** — Choose your tasks, evaluation criteria, and approve variants +- **Framework agnostic** — Standardized agent spec with pluggable runtime adapters (MAF built-in, extensible) ![Flow UI](docs/flow.png) -## Features +## How It Works + +```mermaid +flowchart LR + A[Agent Spec] --> D[Optimizer] + B[Tasks] --> D + C[Evaluator] --> D + D --> E[Agent Variants/Candidates] + E --> F[Pareto Graph] +``` + +## Core Concepts -- **Ablation Studies**: Test different agent configurations side-by-side -- **LLM-as-Judge Evaluation**: Automatically score agent outputs for correctness -- **Pareto Analysis**: Find optimal quality vs. cost tradeoffs -- **Web UI**: Visual interface for managing experiments and viewing results -- **Config Export**: Export winning configurations for production use +| Component | What It Is | +| -------------- | ----------------------------------------------------------------------------------- | +| **Agent Spec** | Agent configuration (model, tools, compaction, instructions) with pluggable runtime | +| **Task** | A coding challenge with evaluation criteria | +| **Evaluator** | Scores agent output (LLM-as-Judge, heuristics, or trace-based) | +| **Optimizer** | Generates variants and runs experiments (GridSearch, extensible) | ## Quick Start ### 1. Install ```bash -# Clone and install with uv git clone https://github.com/victordibia/flow cd flow uv sync ``` -### 2. Configure Azure OpenAI +### 2. Configure + +Create a `.env` file in the project root: ```bash -export AZURE_OPENAI_API_KEY="your-api-key" -export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" -export AZURE_OPENAI_DEPLOYMENT="gpt-4o" +AZURE_OPENAI_API_KEY=your-api-key-here +AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/ +AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=gpt-4o-mini ``` -### 3. Run Optimization +**Important:** Make sure your Azure OpenAI deployment has adequate rate limits: +- **Minimum:** 10,000 tokens per minute (TPM) +- **Recommended:** 30,000+ TPM for optimization runs + +See [Azure Portal](https://portal.azure.com) → Your OpenAI resource → Deployments to adjust rate limits. + +### 3. Test Your Setup + +Before running optimization, verify your Azure OpenAI connection: ```bash -# Run with built-in task suite -uv run flow optimize --suite coding +# Test Azure OpenAI connection +uv run python scripts/test_azure_connection.py + +# Test basic agent execution +uv run python scripts/test_basic_agent.py -# Or with custom tasks -uv run flow optimize --tasks my_tasks.jsonl +# Test LLM evaluator +uv run python scripts/test_evaluator.py ``` -### 4. Launch Web UI +All tests should pass with non-zero scores and token counts. + +### 4. Run ```bash +# Launch the web UI uv run flow serve -# Opens at http://localhost:8091 + +# Or run optimization from CLI (base agent + variations + tasks) +uv run flow optimize --agent base.yaml --vary compaction,memory --tasks tasks.jsonl ``` -## CLI Commands +## Agent Spec -```bash -flow optimize [OPTIONS] # Run optimization experiments -flow serve # Start the web UI -flow run [TASK] # Run a single agent task -flow config # Show current configuration -flow init # Initialize Flow directories +Define your agent configuration: + +```python +from flow.experiments.models import Agent, CompactionConfig + +agent = Agent( + name="my-agent", + framework="maf", # default; extensible to other runtimes + instructions="You are a coding assistant", + tools="standard", # or "minimal", "full", "readonly" + compaction=CompactionConfig.head_tail(10, 40), # keep first 10 + last 40 messages +) ``` -## What Gets Optimized +Flow tests variations like: -Flow tests different **context engineering strategies**: +- **Compaction strategies** — `none`, `head_tail(N, M)`, `last_n(N)` +- **Tool configurations** — different tool sets +- **Instructions** — prompt variations -| Strategy | Description | -|----------|-------------| -| **Message Compaction** | Keep first N + last M messages, discard middle | -| **Agent Memory** | Persistent storage the agent controls | -| **Sub-Agent Isolation** | Delegate research to isolated sub-agent | +## Task Format -Example configurations: +Tasks are JSONL with evaluation criteria: -```python -from flow.experiments.models import Agent, CompactionConfig, GridSearchStrategy +```json +{ + "name": "fizzbuzz", + "prompt": "Create fizzbuzz.py and run it", + "criteria": [ + { "name": "correct", "instruction": "Output shows FizzBuzz pattern" } + ] +} +``` + +## Web UI -# Define a base agent -base = Agent(name="my_agent", enable_memory=True) +Launch with `uv run flow serve`. Create agents, import task suites, run optimization jobs, and view results with Pareto analysis. Test agents interactively with live trace streaming. -# Generate candidates via grid search -strategy = GridSearchStrategy(variations={ - "enable_memory": [True, False], - "compaction": [CompactionConfig.head_tail(10, 40), CompactionConfig.none()], -}) -candidates = strategy.generate(base, budget=10) +## CLI Commands + +```bash +# Web UI +flow serve # Start the web UI + +# Optimization +flow optimize --agent base.yaml --tasks tasks.jsonl # Optimize base agent +flow optimize --vary compaction,memory # Vary specific parameters +flow optimize --suite coding # Use built-in task suite + +# Single Task Execution +flow run "Create hello.py" # Run a single task +flow run --config best.yaml "task" # Run with optimized config + +# Testing & Diagnostics +python scripts/test_azure_connection.py # Test Azure OpenAI connection +python scripts/test_basic_agent.py # Test basic agent execution +python scripts/test_evaluator.py # Test LLM evaluator ``` -## Task Format +## Optimizer -Tasks are defined in JSONL format: +Flow includes multiple optimization strategies for finding the best agent configuration. -```json -{"name": "fizzbuzz", "prompt": "Create fizzbuzz.py and run it", "criteria": [{"name": "correct", "instruction": "Output shows FizzBuzz pattern"}]} +### Grid Search (Default) + +Test predefined variations of your agent: + +```bash +# Vary compaction and memory settings +flow optimize --agent examples/base_agent.yaml --vary compaction,memory --tasks examples/coding_tasks.jsonl + +# Or define variations in a config file +flow optimize --config variations.yaml --agent base_agent.yaml --tasks tasks.jsonl ``` -## Development +### GEPA (Active Learning) + +Use GEPA (Generative Evolutionary Prompt Adjustment) for automatic prompt optimization: ```bash -# Install dev dependencies -uv sync --dev +# Run GEPA optimization +flow optimize \ + --config examples/gepa_strategy.yaml \ + --agent examples/base_agent.yaml \ + --tasks examples/coding_tasks.jsonl \ + --budget 10 \ + --parallel 2 +``` -# Run tests -uv run pytest tests/ -v +**GEPA Configuration:** + +1. **Strategy Config** (`examples/gepa_strategy.yaml`): + ```yaml + strategy_type: gepa + config: + reflection_lm: gpt-4o-mini # Model for GEPA's reflection + ``` + +2. **Base Agent** (`examples/base_agent.yaml`): + ```yaml + name: coding-assistant + model: gpt-4o-mini # Model for agent execution + tools: standard + instructions: | + Your initial prompt that GEPA will optimize... + ``` + +3. **Run Optimization:** + - `--budget`: Number of optimization iterations (default: 10) + - `--parallel`: Concurrent evaluations (default: 4) + - Tasks must include evaluation criteria for LLM scoring + +**Example Output:** +``` +[1/10] coding-assistant_gepa_eval/fibonacci: ✓ score=0.85 tokens=1,245 +[2/10] coding-assistant_gepa_eval/palindrome: ✓ score=0.78 tokens=982 +... +Best agent exported to: ~/.flow/optimizations//agents/best_score.yaml +``` + +### Requirements for Optimization + +- **Azure OpenAI Deployment:** Create a deployment with your chosen model (e.g., `gpt-4o-mini`) +- **Rate Limits:** Minimum 10K TPM; 30K+ recommended for smooth runs +- **Task Criteria:** Tasks need evaluation criteria for LLM-based scoring: + ```json + { + "name": "task_name", + "prompt": "Task description", + "criteria": [ + {"name": "correctness", "instruction": "Solution is correct", "weight": 1.0}, + {"name": "quality", "instruction": "Code is clean and documented", "weight": 0.7} + ] + } + ``` -# Type checking -uv run pyright src/ +## Development -# Linting -uv run ruff check src/ -uv run ruff format src/ +```bash +uv sync --dev # Install dev dependencies +uv run pytest tests/ -v # Run tests +uv run pyright src/ # Type checking +uv run ruff check src/ # Linting ``` ## License diff --git a/pyproject.toml b/pyproject.toml index 8ca49adf86f7f93ea41b28ea478a885d14d10781..ad7c9cbb91559d019c57b47fecb0d48ea32dd6d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "typer>=0.9.0", "httpx>=0.25.0", "python-dotenv>=1.0.0", - "agent-framework-core>=1.0.0b0", + "agent-framework-core>=1.0.0b5", "azure-identity>=1.15.0", "pyyaml>=6.0.0", # OpenTelemetry for experiments tracing @@ -38,14 +38,21 @@ dependencies = [ "uvicorn>=0.27.0", "sqlmodel>=0.0.14", "aiosqlite>=0.19.0", + "tiktoken>=0.12.0", ] [project.optional-dependencies] # Optional features research = ["beautifulsoup4>=4.12.0", "html2text>=2024.2.26"] +langgraph = [ + "langgraph>=0.2.0", + "langchain-core>=0.3.0", + "langchain-openai>=0.2.0", +] +optimizer = ["gepa>=0.0.20"] # Bundles -all = ["flow-agent[research]"] +all = ["flow-agent[research,langgraph,optimizer]"] dev = [ "pytest>=8.0.0", "pytest-asyncio>=0.23.0", diff --git a/src/flow/cli/app.py b/src/flow/cli/app.py index 253fafe548d9060ce044be2c3965d4d9455ba387..98399fab6c498413fbb8b1b237bc2d0ab2fa0d03 100644 --- a/src/flow/cli/app.py +++ b/src/flow/cli/app.py @@ -11,6 +11,7 @@ from pathlib import Path from typing import Annotated import typer +from dotenv import load_dotenv from rich.console import Console from flow import __version__ @@ -61,6 +62,10 @@ def run( Path | None, typer.Option("--config", "-c", help="Config file from optimization (YAML)"), ] = None, + framework: Annotated[ + str, + typer.Option("--framework", "-f", help="Agent framework: 'maf', 'miniagent', or 'langgraph'"), + ] = "maf", interactive: Annotated[ bool, typer.Option("--interactive/--no-interactive", "-i", help="Interactive mode"), @@ -82,9 +87,14 @@ def run( workspace_path.mkdir(parents=True, exist_ok=True) memory_path.mkdir(parents=True, exist_ok=True) + # Validate framework + if framework not in ("maf", "miniagent", "langgraph"): + console.print(f"[red]Error:[/] Unknown framework '{framework}'. Use 'maf', 'miniagent', or 'langgraph'.") + raise typer.Exit(1) + if task: # Single task mode - asyncio.run(_run_single_task(workspace_path, memory_path, task, config)) + asyncio.run(_run_single_task(workspace_path, memory_path, task, config, framework)) elif interactive: # Interactive REPL mode from flow.cli.repl import FlowREPL @@ -100,22 +110,47 @@ async def _run_single_task( memory_path: Path, task: str, config_path: Path | None = None, + framework: str = "maf", ) -> None: """Run a single task and print the result.""" from flow.cli.output import print_event from flow.harness.base import EventType - from flow.harness.maf import MAFHarness + + # Import harness modules to register them + import flow.harness.maf # noqa: F401 + import flow.harness.miniagent # noqa: F401 # pyright: ignore[reportUnusedImport] + + if framework == "langgraph": + try: + import flow.harness.langgraph # noqa: F401 + except ImportError: + console.print("[red]Error:[/] LangGraph dependencies not installed.") + console.print("[dim]Install with: pip install flow-agent[langgraph][/]") + raise typer.Exit(1) + + from flow.harness import create_harness + from flow.experiments.models import Agent if config_path: # Load agent config from optimization result from flow.experiments.models import load_agent - from flow.experiments.ablation import create_harness_from_agent agent_config = load_agent(config_path) - console.print(f"[dim]Using agent config: {agent_config.name}[/]") - harness = create_harness_from_agent(agent_config, workspace) + # Override framework if specified + if framework != "maf": + agent_config = Agent( + name=agent_config.name, + framework=framework, + tools=agent_config.tools, + model=agent_config.model, + instructions=agent_config.instructions, + compaction=agent_config.compaction, + ) + console.print(f"[dim]Using agent config: {agent_config.name} ({framework})[/]") + harness = create_harness(agent_config, workspace) else: - harness = MAFHarness(workspace=workspace, memory_path=memory_path) + agent = Agent(name="flow-cli", framework=framework) + harness = create_harness(agent, workspace) try: console.print("\n[bold blue]Flow[/] - Executing task...\n") @@ -237,7 +272,7 @@ def config() -> None: table.add_row("Workspace", str(DEFAULT_WORKSPACE)) table.add_row("Memory Path", str(DEFAULT_MEMORY_PATH)) table.add_row("Azure Endpoint", os.environ.get("AZURE_OPENAI_ENDPOINT", "(not set)")) - table.add_row("Azure Deployment", os.environ.get("AZURE_OPENAI_DEPLOYMENT", "(not set)")) + table.add_row("Azure Deployment", os.environ.get("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", "(not set)")) console.print(table) @@ -256,7 +291,7 @@ def init() -> None: console.print(" 1. Set your Azure OpenAI credentials:") console.print(" [dim]export AZURE_OPENAI_API_KEY=your-key[/]") console.print(" [dim]export AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/[/]") - console.print(" [dim]export AZURE_OPENAI_DEPLOYMENT=your-deployment[/]") + console.print(" [dim]export AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=your-deployment[/]") console.print("\n 2. Run Flow:") console.print(' [dim]flow run "Create a hello world Python script"[/]') console.print(" [dim]flow run -i # Interactive mode[/]") @@ -264,6 +299,8 @@ def init() -> None: def main() -> None: """Main entry point.""" + # Load environment variables from .env file if present + load_dotenv() app() diff --git a/src/flow/cli/optimize.py b/src/flow/cli/optimize.py index fb0a044cd6ba69334d29b938e7dba5a795e57c47..75c54261c7b3b0cb191c73558d3bb5323ff3f1ee 100644 --- a/src/flow/cli/optimize.py +++ b/src/flow/cli/optimize.py @@ -6,6 +6,7 @@ from __future__ import annotations import asyncio import importlib.util +import logging import sys from pathlib import Path from typing import Annotated, Any @@ -13,7 +14,15 @@ from typing import Annotated, Any import typer from rich.console import Console -from flow.experiments.models import Agent, Candidate, CompactionConfig, GridSearchStrategy +from flow.experiments.models import ( + Agent, + Candidate, + CompactionConfig, + Experiment, + ExperimentResult, + GridSearchStrategy, + load_experiment, +) from flow.experiments.optimizer import FlowOptimizer, load_tasks_from_jsonl from flow.experiments.types import Task, get_task_suite @@ -32,7 +41,14 @@ def optimize( Path | None, typer.Option( "--config", "-c", - help="Path to Python config file with CANDIDATES or VARIATIONS", + help="Path to config file (YAML or Python) with STRATEGY, CANDIDATES, or VARIATIONS", + ), + ] = None, + experiment: Annotated[ + Path | None, + typer.Option( + "--experiment", "-e", + help="Path to experiment YAML file (defines agent, tasks, and variations)", ), ] = None, agent: Annotated[ @@ -60,7 +76,7 @@ def optimize( str | None, typer.Option( "--vary", "-v", - help="Comma-separated params to vary: compaction,memory,subagent", + help="Comma-separated params to vary: compaction, strategy, tools, head, tail", ), ] = None, output: Annotated[ @@ -92,24 +108,31 @@ def optimize( Examples: + # Use experiment YAML (recommended - defines agent, tasks, and variations) + flow optimize --experiment experiment.yaml + # Run with task file and default candidates flow optimize --tasks tasks.jsonl - # Use custom candidates from Python file - flow optimize --config my_configs.py --tasks tasks.jsonl - # Vary specific parameters - flow optimize --vary compaction,memory --tasks tasks.jsonl + flow optimize --vary compaction,tools --tasks tasks.jsonl + + # Test all compaction strategies + flow optimize --vary strategy --suite coding # Use built-in task suite flow optimize --suite coding --parallel 2 # Start from a base agent definition - flow optimize --agent base_agent.yaml --vary compaction,memory --tasks tasks.jsonl + flow optimize --agent base_agent.yaml --vary compaction,tools --tasks tasks.jsonl + + # Use GEPA for active prompt optimization (via YAML config) + flow optimize --config gepa_strategy.yaml --agent base_agent.yaml --tasks tasks.jsonl """ asyncio.run(_run_optimize( tasks_path=tasks, config_path=config, + experiment_path=experiment, agent_path=agent, suite=suite, parallel=parallel, @@ -123,6 +146,7 @@ def optimize( async def _run_optimize( tasks_path: Path | None, config_path: Path | None, + experiment_path: Path | None, agent_path: Path | None, suite: str | None, parallel: int, @@ -132,6 +156,11 @@ async def _run_optimize( budget: int, ) -> None: """Run the optimization.""" + # If experiment YAML provided, use it as the source of truth + if experiment_path: + await _run_from_experiment(experiment_path, output_dir) + return + # Load tasks tasks = _load_tasks(tasks_path, suite) if not tasks: @@ -141,8 +170,24 @@ async def _run_optimize( # Load base agent base = _load_base_agent(agent_path) - # Load/generate candidates - candidates = _load_candidates(config_path, vary, base, budget) + # Load candidates and check if a strategy is defined in config + candidates, strategy_instance = _load_candidates_and_strategy(config_path, vary, base, budget) + + # If a strategy was provided (like GepaStrategy), run it directly + if strategy_instance is not None: + console.print("\n[bold]Running active optimization strategy...[/]") + await _run_active_strategy( + strategy=strategy_instance, + base_agent=base, + tasks=tasks, + output_dir=output_dir, + parallel=parallel, + use_llm_eval=use_llm_eval, + budget=budget + ) + return + + # Otherwise, use traditional grid search with candidates if not candidates: console.print("[red]Error:[/] No candidates to test. Use --config or --vary") raise typer.Exit(1) @@ -176,6 +221,94 @@ async def _run_optimize( raise typer.Exit(1) +async def _run_from_experiment(experiment_path: Path, output_dir: Path | None) -> None: + """Run optimization from an experiment YAML file. + + The experiment YAML defines: + - base_agent: Path to agent YAML + - suite/tasks: Which tasks to run + - variations: Parameter variations for grid search + - parallel, budget, use_llm_eval: Optimization settings + """ + if not experiment_path.exists(): + console.print(f"[red]Error:[/] Experiment file not found: {experiment_path}") + raise typer.Exit(1) + + exp = load_experiment(experiment_path) + + # Load base agent + if exp.base_agent: + base_agent_path = Path(exp.base_agent) + # Handle relative paths (relative to experiment file) + if not base_agent_path.is_absolute(): + base_agent_path = experiment_path.parent / base_agent_path + if not base_agent_path.exists(): + console.print(f"[red]Error:[/] Base agent file not found: {base_agent_path}") + raise typer.Exit(1) + from flow.experiments.models import load_agent + base = load_agent(base_agent_path) + else: + base = Agent(name="flow_agent") + + # Load tasks + tasks: list[Task] = [] + if exp.tasks: + tasks_path = Path(exp.tasks) + if not tasks_path.is_absolute(): + tasks_path = experiment_path.parent / tasks_path + if not tasks_path.exists(): + console.print(f"[red]Error:[/] Tasks file not found: {tasks_path}") + raise typer.Exit(1) + tasks = load_tasks_from_jsonl(tasks_path) + elif exp.suite: + try: + tasks = get_task_suite(exp.suite) + except ValueError as e: + console.print(f"[red]Error:[/] {e}") + raise typer.Exit(1) + else: + console.print("[red]Error:[/] Experiment must specify 'suite' or 'tasks'") + raise typer.Exit(1) + + # Generate candidates from variations + if exp.variations: + strategy = GridSearchStrategy(exp.variations) + candidates = strategy.generate(base, exp.budget) + else: + candidates = [Candidate(agent=base, mutations={}, rationale="baseline")] + + console.print(f"\n[bold]Experiment:[/] {experiment_path.name}") + console.print(f"[bold]Base Agent:[/] {base.name}") + console.print(f"\n[bold]Tasks:[/] {len(tasks)}") + for t in tasks: + console.print(f" - {t.name}") + + console.print(f"\n[bold]Variations:[/]") + for key, values in exp.variations.items(): + console.print(f" - {key}: {len(values)} variants") + + console.print(f"\n[bold]Candidates:[/] {len(candidates)}") + + # Run optimizer + optimizer = FlowOptimizer( + parallel=exp.parallel, + use_llm_evaluator=exp.use_llm_eval, + output_dir=output_dir, + ) + + try: + result = await optimizer.optimize(candidates, tasks) + + console.print("\n[bold green]Optimization complete![/]") + console.print(f"\nBest agents exported to: [cyan]{result.output_dir / 'agents'}[/]") + console.print("\nTo use an agent config:") + console.print(f" [dim]flow run --config {result.output_dir / 'agents' / 'best_score.yaml'} \"your task\"[/]") + + except KeyboardInterrupt: + console.print("\n[yellow]Optimization cancelled.[/]") + raise typer.Exit(1) + + def _load_tasks(tasks_path: Path | None, suite: str | None) -> list[Task]: """Load tasks from file or built-in suite.""" if tasks_path: @@ -211,47 +344,119 @@ def _load_base_agent(agent_path: Path | None) -> Agent: return Agent(name="flow_agent") -def _load_candidates( +def _load_candidates_and_strategy( config_path: Path | None, vary: str | None, base: Agent, budget: int, -) -> list[Candidate]: - """Load candidates from file or generate from variations.""" +) -> tuple[list[Candidate], Any | None]: + """Load candidates from file or generate from variations. + + Supports both YAML and Python config files: + - YAML: strategy configuration (strategy_type, config) + - Python: STRATEGY object, CANDIDATES list, or VARIATIONS dict + + Returns: + Tuple of (candidates, strategy_instance) + - If a STRATEGY is defined in config, returns ([], strategy_instance) + - Otherwise returns (candidates, None) for traditional grid search + """ if config_path: if not config_path.exists(): console.print(f"[red]Error:[/] Config file not found: {config_path}") raise typer.Exit(1) - candidates, variations = _load_python_config(config_path) - + # Check file extension to determine format + if config_path.suffix in (".yaml", ".yml"): + strategy_obj = _load_yaml_strategy(config_path) + if strategy_obj is not None: + return [], strategy_obj + # YAML files currently only support strategy definitions + console.print("[red]Error:[/] YAML config must define a strategy") + raise typer.Exit(1) + + # Python config file + candidates, variations, strategy_obj = _load_python_config(config_path) + + # If a strategy object was provided (e.g., GepaStrategy), return it + if strategy_obj is not None: + return [], strategy_obj + if variations: strategy = GridSearchStrategy(variations) - return strategy.generate(base, budget) + return strategy.generate(base, budget), None elif candidates: - return candidates + return candidates, None else: - console.print("[red]Error:[/] Config file has no CANDIDATES or VARIATIONS") + console.print("[red]Error:[/] Config file has no CANDIDATES, VARIATIONS, or STRATEGY") raise typer.Exit(1) if vary: variations = _parse_vary_flag(vary) strategy = GridSearchStrategy(variations) - return strategy.generate(base, budget) + return strategy.generate(base, budget), None # Default: explore context engineering dimensions strategy = GridSearchStrategy(variations={ - "enable_memory": [True, False], "compaction": [ CompactionConfig.head_tail(10, 40), CompactionConfig.none(), ], + "tools": ["minimal", "standard"], }) - return strategy.generate(base, budget) + return strategy.generate(base, budget), None + + +def _load_yaml_strategy(path: Path) -> Any | None: + """Load strategy configuration from a YAML file. + + Expected YAML format: + ```yaml + strategy_type: gepa # or other strategy types + config: + reflection_lm: gpt-4o + population_size: 5 + optimize_fields: + - instructions + ``` + + Returns: + Strategy instance or None if file doesn't define a strategy + """ + import yaml + + with open(path) as f: + data = yaml.safe_load(f) + + if not data or "strategy_type" not in data: + return None + + strategy_type = data["strategy_type"].lower() + strategy_config = data.get("config", {}) + + if strategy_type == "gepa": + try: + from flow.optimizers import GepaStrategy + return GepaStrategy(config=strategy_config) + except ImportError: + console.print("[red]Error:[/] GEPA optimizer not available.") + console.print("[dim]Install with: pip install flow-agent[optimizer][/]") + raise typer.Exit(1) + else: + console.print(f"[red]Error:[/] Unknown strategy type: {strategy_type}") + console.print("[dim]Supported: gepa[/]") + raise typer.Exit(1) -def _load_python_config(path: Path) -> tuple[list[Candidate], dict[str, Any]]: - """Load CANDIDATES and VARIATIONS from a Python file.""" +def _load_python_config(path: Path) -> tuple[list[Candidate], dict[str, Any], Any | None]: + """Load CANDIDATES, VARIATIONS, and STRATEGY from a Python file. + + Returns: + Tuple of (candidates, variations, strategy) + - candidates: List of Candidate objects + - variations: Dict of parameter variations for GridSearchStrategy + - strategy: Strategy instance (e.g., GepaStrategy) or None + """ spec = importlib.util.spec_from_file_location("config_module", path) if spec is None or spec.loader is None: raise ValueError(f"Cannot load {path}") @@ -262,12 +467,21 @@ def _load_python_config(path: Path) -> tuple[list[Candidate], dict[str, Any]]: candidates = getattr(module, "CANDIDATES", []) variations = getattr(module, "VARIATIONS", {}) + strategy = getattr(module, "STRATEGY", None) - return candidates, variations + return candidates, variations, strategy def _parse_vary_flag(vary: str) -> dict[str, Any]: - """Parse --vary flag into variations dict.""" + """Parse --vary flag into variations dict. + + Supported parameters: + compaction, compact: Test head_tail vs none + strategy: Test all compaction strategies (none, head_tail, sliding_window, summarization) + tools: Test minimal vs standard tool sets + head, head_size: Vary head sizes (5, 10, 20) + tail, tail_size: Vary tail sizes (20, 40, 60) + """ variations: dict[str, Any] = {} for param in vary.split(","): @@ -278,10 +492,17 @@ def _parse_vary_flag(vary: str) -> dict[str, Any]: CompactionConfig.head_tail(10, 40), CompactionConfig.none(), ] - elif param in ("memory", "mem"): - variations["enable_memory"] = [True, False] - elif param in ("subagent", "sub"): - variations["enable_sub_agent"] = [True, False] + elif param in ("strategy", "strategies"): + # Test all compaction strategies + variations["compaction"] = [ + CompactionConfig.none(), + CompactionConfig.head_tail(10, 40), + CompactionConfig(strategy="sliding_window", token_budget=50_000), + CompactionConfig(strategy="summarization", token_budget=50_000), + ] + elif param in ("tools", "toolset"): + # Tool variations - memory and subagent are just tools + variations["tools"] = ["minimal", "standard"] elif param in ("head", "head_size"): variations["compaction"] = [ CompactionConfig.head_tail(h, 40) for h in [5, 10, 20] @@ -294,3 +515,139 @@ def _parse_vary_flag(vary: str) -> dict[str, Any]: console.print(f"[yellow]Warning:[/] Unknown vary param: {param}") return variations + + +async def _run_active_strategy( + strategy: Any, + base_agent: Agent, + tasks: list[Task], + output_dir: Path | None, + parallel: int, + use_llm_eval: bool, + budget: int, +) -> None: + """Run an active optimization strategy (like GEPA).""" + logger = logging.getLogger(__name__) + + # Create optimizer instance to run evaluations + optimizer_runner = FlowOptimizer( + parallel=parallel, + use_llm_evaluator=use_llm_eval, + output_dir=None, # Don't export every intermediate run result + ) + + + main_loop = asyncio.get_running_loop() + + # Define evaluator function to inject into strategy + def evaluator(candidate: Candidate, minibatch: list[Task] | None = None) -> ExperimentResult: + """Evaluate a candidate on a minibatch of tasks.""" + eval_tasks = minibatch if minibatch else tasks + + logger.info(f"[EVALUATOR] Evaluating candidate '{candidate.agent.name}' on {len(eval_tasks)} tasks") + logger.info(f"[EVALUATOR] Using LLM evaluator: {use_llm_eval}") + logger.debug(f"[EVALUATOR] Tasks: {[t.name for t in eval_tasks]}") + + try: + # Run async evaluation on the main loop and wait for result + # This is safe because strategy.generate (which calls this) + # is running in an executor thread. + future = asyncio.run_coroutine_threadsafe( + optimizer_runner.optimize([candidate], eval_tasks), + main_loop + ) + optimization_result = future.result() + + # Check if we got any results + if not optimization_result.summaries: + logger.warning(f"[EVALUATOR] Optimization produced no summaries for candidate '{candidate.agent.name}'") + # Return a fallback result with zero score instead of raising + return ExperimentResult( + candidate=candidate, + run_result=None, + metrics={"score": 0.0, "error": "No summaries produced"}, + eval_score=0.0, + eval_passed=False, + eval_reasoning="Evaluation failed to produce results", + traces={} + ) + + summary = optimization_result.summaries[0] + logger.info(f"[EVALUATOR] Candidate '{candidate.agent.name}' avg_score={summary.avg_score:.3f}, pass_rate={summary.pass_rate:.2f}") + + # Log individual task results for debugging + if summary.task_results: + for tr in summary.task_results: + logger.info(f"[EVALUATOR] Task '{tr.task_name}': score={tr.eval_score:.3f}, passed={tr.eval_passed}") + logger.debug(f"[EVALUATOR] Reasoning: '{tr.eval_reasoning[:150]}'") + logger.debug(f"[EVALUATOR] Metrics: tokens={tr.metrics.total_tokens}, duration={tr.run_result.duration_seconds if tr.run_result else 0:.2f}s") + + # Convert CandidateSummary to ExperimentResult for GEPA + + if summary.task_results: + tr = summary.task_results[0] + return ExperimentResult( + candidate=candidate, + run_result=tr.run_result, + metrics=tr.metrics, + eval_score=tr.eval_score, + eval_passed=tr.eval_passed, + eval_reasoning=tr.eval_reasoning, + traces=tr.run_result.trace if tr.run_result and isinstance(tr.run_result.trace, dict) else {} + ) + + # Fallback to aggregate metrics if no individual task results + return ExperimentResult( + candidate=candidate, + run_result=None, + metrics={"score": summary.avg_score}, + eval_score=summary.avg_score, + eval_passed=summary.pass_rate > 0.5, + eval_reasoning=f"Aggregate pass rate: {summary.pass_rate}", + traces={} + ) + + except Exception as e: + logger.error(f"Error evaluating candidate '{candidate.agent.name}': {e}", exc_info=True) + # Return a fallback result instead of propagating the exception + return ExperimentResult( + candidate=candidate, + run_result=None, + metrics={"score": 0.0, "error": str(e)}, + eval_score=0.0, + eval_passed=False, + eval_reasoning=f"Evaluation error: {str(e)}", + traces={} + ) + + + # Inject dependencies into strategy if supported + # GepaStrategy accepts them in __init__, but we might have loaded it from config + # without them. + if hasattr(strategy, "evaluator") and strategy.evaluator is None: + strategy.evaluator = evaluator + if hasattr(strategy, "dataset") and strategy.dataset is None: + strategy.dataset = tasks + + # Execute strategy (blocking/sync) + # We should run this in an executor to avoid blocking the main async loop + # if we were doing other async things, but here we just wait for it. + loop = asyncio.get_running_loop() + candidates = await loop.run_in_executor(None, strategy.generate, base_agent, budget) + + console.print("\n[bold green]Optimization complete![/]") + console.print(f"Generated {len(candidates)} candidates.") + + # Export results + if output_dir: + from flow.experiments.models import export_agent + output_dir.mkdir(parents=True, exist_ok=True) + (output_dir / "agents").mkdir(exist_ok=True) + + for i, cand in enumerate(candidates): + # Basic export + name = cand.agent.name or f"candidate_{i}" + export_agent(cand.agent, output_dir / "agents" / f"{name}.yaml", metrics={"rationale": cand.rationale}) + + console.print(f"\nAgents exported to: [cyan]{output_dir / 'agents'}[/]") + diff --git a/src/flow/cli/repl.py b/src/flow/cli/repl.py index c6f9301f8fa563feb70420c5437c7165ebf3045a..5dc63cf0fff0f19603a9980267f932cfd2a6040e 100644 --- a/src/flow/cli/repl.py +++ b/src/flow/cli/repl.py @@ -11,8 +11,8 @@ from pathlib import Path from rich.console import Console from flow.cli.output import print_event, print_welcome -from flow.harness.base import EventType -from flow.harness.maf import MAFHarness +from flow.experiments.models import Agent +from flow.harness.base import BaseHarness, EventType # Default paths DEFAULT_WORKSPACE = Path.home() / ".flow" / "workspace" @@ -40,16 +40,18 @@ class FlowREPL: self._workspace = workspace or DEFAULT_WORKSPACE self._memory_path = memory_path or DEFAULT_MEMORY_PATH self._console = Console() - self._harness: MAFHarness | None = None + self._harness: BaseHarness | None = None self._thread_id: str | None = None - def _get_harness(self) -> MAFHarness: + def _get_harness(self) -> BaseHarness: """Get or create the harness instance.""" if self._harness is None: - self._harness = MAFHarness( - workspace=self._workspace, - memory_path=self._memory_path, - ) + # Import maf module to register the harness, then use registry + import flow.harness.maf # noqa: F401 + from flow.harness import create_harness + + agent = Agent(name="flow-repl") + self._harness = create_harness(agent, self._workspace) return self._harness async def run(self) -> None: @@ -112,7 +114,7 @@ class FlowREPL: except EOFError: return None - async def _run_task(self, harness: MAFHarness, task: str) -> None: + async def _run_task(self, harness: BaseHarness, task: str) -> None: """Run a task and stream the output. Args: @@ -122,7 +124,7 @@ class FlowREPL: self._console.print() # Blank line before output try: - async for event in harness.run_stream(task, self._thread_id): + async for event in harness.run_stream(task): print_event(self._console, event) # Store thread ID for conversation continuity diff --git a/src/flow/experiments/__init__.py b/src/flow/experiments/__init__.py index 0af4c5ceba26d851536cb9fb61fc91ed7fed2d86..74dc6dfd5d2fab3c4a54ad43f2893dd230f7cf18 100644 --- a/src/flow/experiments/__init__.py +++ b/src/flow/experiments/__init__.py @@ -52,7 +52,6 @@ from .models import ( # Experiment runner + Pareto analysis from .ablation import ( compute_pareto_frontier, - create_harness_from_agent, generate_recommendation, run_experiments, run_single_experiment, @@ -146,7 +145,6 @@ __all__ = [ # noqa: RUF022 # Intentionally grouped by category "print_comparison_table", "print_eval_result", # Experiment runner - "create_harness_from_agent", "run_experiments", "run_single_experiment", "compute_pareto_frontier", diff --git a/src/flow/experiments/ablation.py b/src/flow/experiments/ablation.py index 5f209c509307ea21c1992ab7eee291d7e4dadd75..99c7dcf804471a5d888afda5d8ce9515816019b1 100644 --- a/src/flow/experiments/ablation.py +++ b/src/flow/experiments/ablation.py @@ -19,46 +19,17 @@ from typing import TYPE_CHECKING, Any from .evaluators import HeuristicEvaluator from .metrics import extract_metrics, metrics_to_dict -from .models import Agent, Candidate, ExperimentResult +from .models import Candidate, ExperimentResult from .reporters import print_comparison_table, save_run_result from .runner import FlowExperimentRunner, setup_tracing from .types import EvalCriterion, Task if TYPE_CHECKING: - from flow.harness.maf import MAFHarness - from .optimizer import CandidateSummary logger = logging.getLogger(__name__) -def create_harness_from_agent(agent: Agent, workspace: Path) -> MAFHarness: - """Create a MAFHarness from an Agent definition. - - Args: - agent: The agent definition - workspace: Working directory - - Returns: - A configured MAFHarness - """ - from flow.experiments.models import resolve_tools - from flow.harness.maf import MAFHarness - - # Resolve tools to dict form - tools_spec = resolve_tools(agent.tools) - - return MAFHarness( - workspace=workspace, - memory_path=workspace / "memory", - enable_compaction=agent.compaction.enabled, - compaction_head_size=agent.compaction.head_size, - compaction_tail_size=agent.compaction.tail_size, - tools=tools_spec, - instructions=agent.instructions, - ) - - async def run_single_experiment( candidate: Candidate, task: Task, @@ -74,7 +45,15 @@ async def run_single_experiment( Returns: ExperimentResult with metrics and evaluation """ - harness = create_harness_from_agent(candidate.agent, workspace) + # Import harness modules to register them, then use registry + import flow.harness.maf # noqa: F401 + try: + import flow.harness.miniagent # noqa: F401 + except ImportError: + pass # miniagent harness is optional + from flow.harness import create_harness + + harness = create_harness(candidate.agent, workspace) try: runner = FlowExperimentRunner(keep_workspace=True) diff --git a/src/flow/experiments/data/tasks/coding.jsonl b/src/flow/experiments/data/tasks/coding.jsonl index 7b6dceb1bdfd9f9d47bc022dae18f5173c06503d..6af07c43003707c9dbd50833fdf0ad2903e7005f 100644 --- a/src/flow/experiments/data/tasks/coding.jsonl +++ b/src/flow/experiments/data/tasks/coding.jsonl @@ -1,10 +1,5 @@ -{"name": "fizzbuzz", "prompt": "Create fizzbuzz.py that prints FizzBuzz 1-100 and run it.", "criteria": [{"name": "correct", "instruction": "Correct FizzBuzz output"}], "category": "short"} -{"name": "rest_api", "prompt": "Create a FastAPI CRUD TODO app with GET/POST/DELETE endpoints.", "criteria": [{"name": "has_crud", "instruction": "Has working CRUD"}], "category": "medium"} -{"name": "cli_tool", "prompt": "Create an argparse CLI that counts lines/words/chars in a file.", "criteria": [{"name": "works", "instruction": "CLI works correctly"}], "category": "medium"} -{"name": "data_pipeline", "prompt": "Create a script that reads CSV data, filters rows, aggregates, and outputs JSON.", "criteria": [{"name": "works", "instruction": "Pipeline produces correct output"}], "category": "medium"} -{"name": "unit_tests", "prompt": "Create calc.py with math functions and test_calc.py with pytest tests.", "criteria": [{"name": "tests_pass", "instruction": "Tests pass"}], "category": "medium"} -{"name": "web_scraper", "prompt": "Create a script that fetches a webpage and extracts all links.", "criteria": [{"name": "extracts_links", "instruction": "Extracts links correctly"}], "category": "medium"} -{"name": "async_downloader", "prompt": "Create an async script that downloads multiple URLs concurrently using aiohttp.", "criteria": [{"name": "uses_async", "instruction": "Uses async/await correctly"}], "category": "complex"} -{"name": "database_orm", "prompt": "Create a SQLAlchemy model for Users with CRUD operations.", "criteria": [{"name": "has_orm", "instruction": "Uses SQLAlchemy ORM correctly"}], "category": "complex"} -{"name": "decorator_lib", "prompt": "Create a library with timing, retry, and caching decorators.", "criteria": [{"name": "decorators_work", "instruction": "Decorators function correctly"}], "category": "complex"} -{"name": "config_parser", "prompt": "Create a config parser that supports YAML, JSON, and env vars with validation.", "criteria": [{"name": "multi_format", "instruction": "Supports multiple formats"}], "category": "complex"} +{"name": "repo_documentation", "prompt": "Clone the repository https://github.com/microsoft-foundry/ai-tutorials and generate comprehensive documentation.\n\nSTEP 1: Clone the repository\n- Use bash to clone: git clone https://github.com/microsoft-foundry/ai-tutorials\n- Confirm the clone succeeded by listing the directory contents\n\nSTEP 2: Explore the structure\n- List all files and directories recursively\n- Identify the main components, tutorials, and examples\n\nSTEP 3: Generate documentation\nFor EVERY file in the repository:\n1. Read the complete file\n2. Document its purpose (1-2 sentences)\n3. List key functions/classes if code, or sections if markdown\n4. Note dependencies or prerequisites\n\nSTEP 4: Create a comprehensive report\n- Overall repository purpose and structure\n- Table of contents of all tutorials/examples\n- Prerequisites for running each tutorial\n- Suggested learning path for beginners\n\nBe thorough. Read every file completely. Document everything.", "criteria": [{"name": "clone_success", "instruction": "Repository was successfully cloned", "weight": 1.0}, {"name": "file_coverage", "instruction": "All files in the repository were read and documented", "weight": 0.9}, {"name": "documentation_quality", "instruction": "Each file has meaningful description, not just filenames", "weight": 0.8}, {"name": "synthesis", "instruction": "Final report provides useful overview and learning path", "weight": 0.7}], "metadata": {"expected_iterations": 20, "min_tokens": 50000, "category": "context_stress"}} +{"name": "code_review", "prompt": "Clone https://github.com/microsoft-foundry/ai-tutorials and perform an exhaustive code review.\n\nSTEP 1: Clone the repository\n- git clone https://github.com/microsoft-foundry/ai-tutorials\n- Verify the clone succeeded\n\nSTEP 2: Inventory all code files\n- Find all Python files (.py)\n- Find all Jupyter notebooks (.ipynb)\n- Find all configuration files\n\nSTEP 3: Review each code file\nFor EVERY Python file and notebook:\n1. Read the complete file\n2. For each function/method, document:\n - Name and signature\n - What it does (1-2 sentences)\n - Any potential issues (edge cases, missing error handling)\n3. For each class, document:\n - Purpose\n - All methods with their purposes\n\nSTEP 4: Synthesize findings\n- Summary table of all modules and their relationships\n- Top 10 code quality issues found\n- Recommendations for improvement\n- Best practices observed worth replicating\n\nRead every file. Be thorough and systematic.", "criteria": [{"name": "clone_success", "instruction": "Repository was successfully cloned", "weight": 1.0}, {"name": "completeness", "instruction": "All code files were read and reviewed", "weight": 0.9}, {"name": "depth", "instruction": "Each function/class has meaningful analysis, not just signatures", "weight": 0.8}, {"name": "issues_found", "instruction": "Identified real code quality issues with specific examples", "weight": 0.7}], "metadata": {"expected_iterations": 22, "min_tokens": 55000, "category": "context_stress"}} +{"name": "tutorial_analysis", "prompt": "Clone https://github.com/microsoft-foundry/ai-tutorials and analyze the tutorial content for educational effectiveness.\n\nSTEP 1: Clone the repository\n- git clone https://github.com/microsoft-foundry/ai-tutorials\n- List the repository contents to understand structure\n\nSTEP 2: Read ALL tutorials\nFor EACH tutorial or example:\n1. Read the complete content\n2. Identify the learning objectives\n3. List prerequisites assumed\n4. Note the teaching approach used\n\nSTEP 3: Evaluate educational quality\nFor each tutorial, assess:\n- Clarity of explanations\n- Code-to-explanation ratio\n- Progression of difficulty\n- Hands-on exercises included\n- Common pitfalls addressed\n\nSTEP 4: Create improvement report\n- Rank tutorials by educational effectiveness\n- Identify gaps in coverage\n- Suggest specific improvements for each tutorial\n- Recommend additional tutorials that should be added\n- Create an optimal learning sequence\n\nBe thorough. Read every file. Provide specific examples.", "criteria": [{"name": "clone_success", "instruction": "Repository was successfully cloned", "weight": 1.0}, {"name": "tutorial_coverage", "instruction": "All tutorials were read and analyzed", "weight": 0.9}, {"name": "evaluation_depth", "instruction": "Evaluation criteria applied consistently across tutorials", "weight": 0.8}, {"name": "actionable_recommendations", "instruction": "Improvement suggestions are specific and implementable", "weight": 0.7}], "metadata": {"expected_iterations": 20, "min_tokens": 50000, "category": "context_stress"}} +{"name": "dependency_audit", "prompt": "Clone https://github.com/microsoft-foundry/ai-tutorials and perform a thorough dependency and compatibility audit.\n\nSTEP 1: Clone the repository\n- git clone https://github.com/microsoft-foundry/ai-tutorials\n- Confirm successful clone\n\nSTEP 2: Find all dependency specifications\n- Search for requirements.txt files\n- Search for pyproject.toml files\n- Search for setup.py files\n- Search for environment.yml files\n- Check imports in Python files for implicit dependencies\n\nSTEP 3: Analyze each dependency\nFor EVERY dependency found:\n1. Current version specified (or 'unpinned' if none)\n2. Latest available version\n3. Known security vulnerabilities\n4. Compatibility with Python 3.10, 3.11, 3.12\n5. Transitive dependencies introduced\n\nSTEP 4: Generate audit report\n- Dependency tree visualization (text format)\n- Security vulnerabilities found with severity\n- Version conflicts or incompatibilities\n- Recommendations for updates\n- Suggested requirements.txt with pinned versions\n\nRead all relevant files. Be thorough and specific.", "criteria": [{"name": "clone_success", "instruction": "Repository was successfully cloned", "weight": 1.0}, {"name": "dependency_discovery", "instruction": "All dependency specifications were found and analyzed", "weight": 0.9}, {"name": "analysis_depth", "instruction": "Each dependency was analyzed for versions and compatibility", "weight": 0.8}, {"name": "actionable_report", "instruction": "Report includes specific version recommendations", "weight": 0.7}], "metadata": {"expected_iterations": 18, "min_tokens": 45000, "category": "context_stress"}} +{"name": "architecture_analysis", "prompt": "Clone https://github.com/microsoft-foundry/ai-tutorials and analyze the overall architecture and design patterns.\n\nSTEP 1: Clone the repository\n- git clone https://github.com/microsoft-foundry/ai-tutorials\n- Verify clone success\n\nSTEP 2: Map the repository structure\n- Create a complete directory tree\n- Identify major components/modules\n- Document file organization patterns\n\nSTEP 3: Analyze design patterns\nFor EACH significant code file:\n1. Read the complete file\n2. Identify design patterns used (factory, singleton, observer, etc.)\n3. Note coding conventions and style\n4. Document error handling approaches\n5. Analyze how components interact\n\nSTEP 4: Create architecture document\n- High-level architecture diagram (text format)\n- Component interaction map\n- Data flow descriptions\n- Design pattern catalog with examples from code\n- Evaluation of architectural decisions\n- Suggestions for architectural improvements\n\nRead every file. Document patterns with specific code references.", "criteria": [{"name": "clone_success", "instruction": "Repository was successfully cloned", "weight": 1.0}, {"name": "structure_mapped", "instruction": "Complete directory structure documented", "weight": 0.8}, {"name": "patterns_identified", "instruction": "Design patterns identified with specific code examples", "weight": 0.9}, {"name": "architecture_doc", "instruction": "Architecture document is comprehensive and accurate", "weight": 0.8}], "metadata": {"expected_iterations": 22, "min_tokens": 55000, "category": "context_stress"}} diff --git a/src/flow/experiments/data/tasks/gaia_all.jsonl b/src/flow/experiments/data/tasks/gaia_all.jsonl new file mode 100644 index 0000000000000000000000000000000000000000..2bb67b33d7424905632a516655126283d311bcbc --- /dev/null +++ b/src/flow/experiments/data/tasks/gaia_all.jsonl @@ -0,0 +1,330 @@ +{"name": "e1fc63a2-da7a-432f-be78-7c4a95598703", "prompt": "If Eliud Kipchoge could maintain his record-making marathon pace indefinitely, how many thousand hours would it take him to run the distance between the Earth and the Moon its closest approach? Please use the minimum perigee value on the Wikipedia page for the Moon when carrying out your calculation. Round your result to the nearest 1000 hours and do not use any comma separators if necessary.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "17", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8e867cd7-cff9-4e6c-867a-ff5ddc2550be", "prompt": "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ec09fa32-d03f-4bf8-84b0-1f16922c3ae4", "prompt": "Here's a fun riddle that I think you'll enjoy.\n\nYou have been selected to play the final round of the hit new game show \"Pick That Ping-Pong\". In this round, you will be competing for a large cash prize. Your job will be to pick one of several different numbered ping-pong balls, and then the game will commence. The host describes how the game works.\n\nA device consisting of a winding clear ramp and a series of pistons controls the outcome of the game. The ramp feeds balls onto a platform. The platform has room for three ping-pong balls at a time. The three balls on the platform are each aligned with one of three pistons. At each stage of the game, one of the three pistons will randomly fire, ejecting the ball it strikes. If the piston ejects the ball in the first position on the platform the balls in the second and third position on the platform each advance one space, and the next ball on the ramp advances to the third position. If the piston ejects the ball in the second position, the ball in the first position is released and rolls away, the ball in the third position advances two spaces to occupy the first position, and the next two balls on the ramp advance to occupy the second and third positions on the platform. If the piston ejects the ball in the third position, the ball in the first position is released and rolls away, the ball in the second position advances one space to occupy the first position, and the next two balls on the ramp advance to occupy the second and third positions on the platform.\n\nThe ramp begins with 100 numbered ping-pong balls, arranged in ascending order from 1 to 100. The host activates the machine and the first three balls, numbered 1, 2, and 3, advance to the platform. Before the random firing of the pistons begins, you are asked which of the 100 balls you would like to pick. If your pick is ejected by one of the pistons, you win the grand prize, $10,000.\n\nWhich ball should you choose to maximize your odds of winning the big prize? Please provide your answer as the number of the ball selected.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5d0080cb-90d7-4712-bc33-848150e917d3", "prompt": "What was the volume in m^3 of the fish bag that was calculated in the University of Leicester paper \"Can Hiccup Supply Enough Fish to Maintain a Dragon\u2019s Diet?\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.1777", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "0.1777", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a1e91b78-d3d8-4675-bb8d-62741b4b68a6", "prompt": "In the video https://www.youtube.com/watch?v=L1vXCYZAYYM, what is the highest number of bird species to be on camera simultaneously?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "46719c30-f4c3-4cad-be07-d5cb21eee6bb", "prompt": "Of the authors (First M. Last) that worked on the paper \"Pie Menus or Linear Menus, Which Is Better?\" in 2015, what was the title of the first paper authored by the one that had authored prior papers?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Mapping Human Oriented Information to Software Agents for Online Systems Usage", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Mapping Human Oriented Information to Software Agents for Online Systems Usage", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4b6bb5f7-f634-410e-815d-e673ab7f8632", "prompt": "In Series 9, Episode 11 of Doctor Who, the Doctor is trapped inside an ever-shifting maze. What is this location called in the official script for the episode? Give the setting exactly as it appears in the first scene heading.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: THE CASTLE", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "THE CASTLE", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cffe0e32-c9a6-4c52-9877-78ceb4aaa9fb", "prompt": "An office held a Secret Santa gift exchange where each of its twelve employees was assigned one other employee in the group to present with a gift. Each employee filled out a profile including three likes or hobbies. On the day of the gift exchange, only eleven gifts were given, each one specific to one of the recipient's interests. Based on the information in the document, who did not give a gift?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Fred", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Fred", "gaia_level": 1, "gaia_file": "cffe0e32-c9a6-4c52-9877-78ceb4aaa9fb.docx", "source": "gaia-benchmark"}} +{"name": "2d83110e-a098-4ebb-9987-066c06fa42d0", "prompt": ".rewsna eht sa \"tfel\" drow eht fo etisoppo eht etirw ,ecnetnes siht dnatsrednu uoy fI", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Right", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Right", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5cfb274c-0207-4aa7-9575-6ac0bd95d9b2", "prompt": "Each cell in the attached spreadsheet represents a plot of land. The color of the cell indicates who owns that plot. Green cells are plots owned by Earl Smith. Can Earl walk through every plot he owns (and no other plots) and return to his starting plot without backtracking? For this question, consider backtracking to be any instance where Earl would enter a plot of land he had already entered since leaving his starting plot.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: No", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "No", "gaia_level": 1, "gaia_file": "5cfb274c-0207-4aa7-9575-6ac0bd95d9b2.xlsx", "source": "gaia-benchmark"}} +{"name": "27d5d136-8563-469e-92bf-fd103c28b57c", "prompt": "\u00ac(A \u2227 B) \u2194 (\u00acA \u2228 \u00acB)\n\u00ac(A \u2228 B) \u2194 (\u00acA \u2227 \u00acB)\n(A \u2192 B) \u2194 (\u00acB \u2192 \u00acA)\n(A \u2192 B) \u2194 (\u00acA \u2228 B)\n(\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)\n\u00ac(A \u2192 B) \u2194 (A \u2227 \u00acB)\n\nWhich of the above is not logically equivalent to the rest? Provide the full statement that doesn't fit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: (\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "(\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dc28cf18-6431-458b-83ef-64b3ce566c10", "prompt": "My family reunion is this week, and I was assigned the mashed potatoes to bring. The attendees include my married mother and father, my twin brother and his family, my aunt and her family, my grandma and her brother, her brother's daughter, and his daughter's family. All the adults but me have been married, and no one is divorced or remarried, but my grandpa and my grandma's sister-in-law passed away last year. All living spouses are attending. My brother has two children that are still kids, my aunt has one six-year-old, and my grandma's brother's daughter has three kids under 12. I figure each adult will eat about 1.5 potatoes of mashed potatoes and each kid will eat about 1/2 a potato of mashed potatoes, except my second cousins don't eat carbs. The average potato is about half a pound, and potatoes are sold in 5-pound bags. How many whole bags of potatoes do I need? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "2", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b816bfce-3d80-4913-a07d-69b752ce6377", "prompt": "In Emily Midkiff's June 2014 article in a journal named for the one of Hreidmar's sons that guarded his house, what word was quoted from two different authors in distaste for the nature of dragon depictions?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: fluffy", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "fluffy", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "72e110e7-464c-453c-a309-90a95aed6538", "prompt": "Under DDC 633 on Bielefeld University Library's BASE, as of 2020, from what country was the unknown language article with a flag unique from the others?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Guatemala", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Guatemala", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "42576abe-0deb-4869-8c63-225c2d75a95a", "prompt": "In the fictional language of Tizin, basic sentences are arranged with the Verb first, followed by the direct object, followed by the subject of the sentence. I want to express my love for apples to my Tizin friend. \n\nThe word that indicates oneself is \"Pa\" is the nominative form, \"Mato\" is the accusative form, and \"Sing\" is the genitive form. \n\nThe root verb that indicates an intense like for something is \"Maktay\". When it is used in the present, it is used in it's root form, when it is used in the preterit past, it is \"Tay\", and when it is used in the imperfect past, it is \"Aktay\". It is used differently than in English, and is better translated as \"is pleasing to\", meaning that the thing doing the liking is actually the object of the sentence rather than the subject.\n\nThe word for apples is borrowed from English in Tizin, and so it is \"Apple\" is the nominative form, \"Zapple\" is the accusative form, and \"Izapple\" is the genitive form. \n\nPlease translate \"I like apples\" to Tizin.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Maktay mato apple", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Maktay mato apple", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b415aba4-4b68-4fc6-9b89-2c812e55a3e1", "prompt": "In Nature journal's Scientific Reports conference proceedings from 2012, in the article that did not mention plasmons or plasmonics, what nano-compound is studied? Don't use the prefix nano in your answer if there is one.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: diamond", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "diamond", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cca530fc-4052-43b2-b130-b30968d8aa44", "prompt": "Review the chess position provided in the image. It is black's turn. Provide the correct next move for black which guarantees a win. Please provide your response in algebraic notation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Rd5", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Rd5", "gaia_level": 1, "gaia_file": "cca530fc-4052-43b2-b130-b30968d8aa44.png", "source": "gaia-benchmark"}} +{"name": "935e2cff-ae78-4218-b3f5-115589b19dae", "prompt": "In the year 2022, and before December, what does \"R\" stand for in the three core policies of the type of content that was violated in the public logs on the Legume Wikipedia page?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: research", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "research", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4fc2f1ae-8625-45b5-ab34-ad4433bc21f8", "prompt": "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: FunkMonk", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "FunkMonk", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5188369a-3bbe-43d8-8b94-11558f909a08", "prompt": "What writer is quoted by Merriam-Webster for the Word of the Day from June 27, 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Annie Levin", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Annie Levin", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6f37996b-2ac7-44b0-8e68-6d28256631b4", "prompt": "Given this table defining * on the set S = {a, b, c, d, e}\n\n|*|a|b|c|d|e|\n|---|---|---|---|---|---|\n|a|a|b|c|b|d|\n|b|b|c|a|e|c|\n|c|c|a|b|b|a|\n|d|b|e|b|e|d|\n|e|d|b|a|d|c|\n\nprovide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: b, e", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "b, e", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9318445f-fe6a-4e1b-acbf-c68228c9906a", "prompt": "As a comma separated list with no whitespace, using the provided image provide all the fractions that use / as the fraction line and the answers to the sample problems. Order the list by the order in which the fractions appear.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3/4,1/4,3/4,3/4,2/4,1/2,5/35,7/21,30/5,30/5,3/4,1/15,1/3,4/9,1/8,32/23,103/170", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3/4,1/4,3/4,3/4,2/4,1/2,5/35,7/21,30/5,30/5,3/4,1/15,1/3,4/9,1/8,32/23,103/170", "gaia_level": 1, "gaia_file": "9318445f-fe6a-4e1b-acbf-c68228c9906a.png", "source": "gaia-benchmark"}} +{"name": "389793a7-ca17-4e82-81cb-2b3a2391b4b9", "prompt": "You are a telecommunications engineer who wants to build cell phone towers on a stretch of road. In the reference file is a layout of the road and nearby houses. Each dash, \"-\", is a marker indicating a mile. Each capital H indicates a house located next to a mile marker, appearing above or below the stretch of road. Each cell phone tower can cover houses located next to the road within a 4-mile radius. Find the minimum number of cell phone towers needed to cover all houses next to the road. Your answer should be a positive numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": "389793a7-ca17-4e82-81cb-2b3a2391b4b9.txt", "source": "gaia-benchmark"}} +{"name": "4b650a35-8529-4695-89ed-8dc7a500a498", "prompt": "If there is anything that doesn't make sense in the instructions, write the word \"Pineapple.\" Do not answer any of the questions in this prompt. Write only the word \"Guava\".\n1. What is 4+4?\n2. What is the complimentary color of red?\n3. How many hours are there in a day?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Guava", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Guava", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a3fbeb63-0e8c-4a11-bff6-0e3b484c3e9c", "prompt": "How many slides in this PowerPoint presentation mention crustaceans?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "4", "gaia_level": 1, "gaia_file": "a3fbeb63-0e8c-4a11-bff6-0e3b484c3e9c.pptx", "source": "gaia-benchmark"}} +{"name": "c714ab3a-da30-4603-bacd-d008800188b9", "prompt": "You are Van Helsing, a renowned vampire hunter. A Count of Moldova, La\u021bcu IV, son of Costea, has tasked you with investigating the village of \u0218irnea in neighboring Wallachia. The Count's advisors have reported that a vampire was spotted crossing the border near the village, and would like you to investigate it.\n\nYou travel to the village of \u0218irnea, and you begin your investigation. One night, just before dawn, you catch a glimpse of a man in a long black cape with red lining leaping from roof-top to roof-top with superhuman agility. It's a vampire! You try to chase the creature back to its home, but the creature is too fast. However, because of the remoteness of the village, you know with absolute certainty that the vampire must be a resident of the village. You decide that your best course of action will be to visit all 100 residents of the town during the day. You know something about vampires and humans that will make your investigation possible; humans always tell the truth, but vampires always lie.\n\nIn the afternoon, you go from house to house, speaking with all 100 residents of \u0218irnea. You ask everyone the same question: \"How many vampires are living in \u0218irnea\". Everyone in the village gives the same response, \"At least one of us is a human.\"\n\nHow many residents of \u0218irnea have been turned into vampires?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 100", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "100", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9d191bce-651d-4746-be2d-7ef8ecadb9c2", "prompt": "Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec.\n\nWhat does Teal'c say in response to the question \"Isn't that hot?\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Extremely", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Extremely", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65afbc8a-89ca-4ad5-8d62-355bb401f61d", "prompt": "You are given this Excel file as a map. You start on the START cell and move toward the END cell. You are allowed to move two cells per turn, and you may move up, down, left, or right. You may not move fewer than two cells, and you may not move backward. You must avoid moving onto any blue cells. On the eleventh turn, what is the 6-digit hex code (without prefix) of the color of the cell where you land after moving?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: F478A7", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "F478A7", "gaia_level": 1, "gaia_file": "65afbc8a-89ca-4ad5-8d62-355bb401f61d.xlsx", "source": "gaia-benchmark"}} +{"name": "cabe07ed-9eca-40ea-8ead-410ef5e83f91", "prompt": "What is the surname of the equine veterinarian mentioned in 1.E Exercises from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Louvrier", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Louvrier", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3cef3a44-215e-4aed-8e3b-b1e3f08063b7", "prompt": "I'm making a grocery list for my mom, but she's a professor of botany and she's a real stickler when it comes to categorizing things. I need to add different foods to different categories on the grocery list, but if I make a mistake, she won't buy anything inserted in the wrong category. Here's the list I have so far:\n\nmilk, eggs, flour, whole bean coffee, Oreos, sweet potatoes, fresh basil, plums, green beans, rice, corn, bell pepper, whole allspice, acorns, broccoli, celery, zucchini, lettuce, peanuts\n\nI need to make headings for the fruits and vegetables. Could you please create a list of just the vegetables from my list? If you could do that, then I can figure out how to categorize the rest of the list into the appropriate categories. But remember that my mom is a real stickler, so make sure that no botanical fruits end up on the vegetable list, or she won't get them when she's at the store. Please alphabetize the list of vegetables, and place each item in a comma separated list.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: broccoli, celery, fresh basil, lettuce, sweet potatoes", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "broccoli, celery, fresh basil, lettuce, sweet potatoes", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3", "prompt": "Hi, I'm making a pie but I could use some help with my shopping list. I have everything I need for the crust, but I'm not sure about the filling. I got the recipe from my friend Aditi, but she left it as a voice memo and the speaker on my phone is buzzing so I can't quite make out what she's saying. Could you please listen to the recipe and list all of the ingredients that my friend described? I only want the ingredients for the filling, as I have everything I need to make my favorite pie crust. I've attached the recipe as Strawberry pie.mp3.\n\nIn your response, please only list the ingredients, not any measurements. So if the recipe calls for \"a pinch of salt\" or \"two cups of ripe strawberries\" the ingredients on the list would be \"salt\" and \"ripe strawberries\".\n\nPlease format your response as a comma separated list of ingredients. Also, please alphabetize the ingredients.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: cornstarch, freshly squeezed lemon juice, granulated sugar, pure vanilla extract, ripe strawberries", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "cornstarch, freshly squeezed lemon juice, granulated sugar, pure vanilla extract, ripe strawberries", "gaia_level": 1, "gaia_file": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3.mp3", "source": "gaia-benchmark"}} +{"name": "d0633230-7067-47a9-9dbf-ee11e0a2cdd6", "prompt": "In the Scikit-Learn July 2017 changelog, what other predictor base command received a bug fix? Just give the name, not a path.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: BaseLabelPropagation", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "BaseLabelPropagation", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "305ac316-eef6-4446-960a-92d80d542f82", "prompt": "Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Wojciech", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Wojciech", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0383a3ee-47a7-41a4-b493-519bdefe0488", "prompt": "On the BBC Earth YouTube video of the Top 5 Silliest Animal Moments, what species of bird is featured?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Rockhopper penguin", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Rockhopper penguin", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f918266a-b3e0-4914-865d-4faa564f1aef", "prompt": "What is the final numeric output from the attached Python code?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "0", "gaia_level": 1, "gaia_file": "f918266a-b3e0-4914-865d-4faa564f1aef.py", "source": "gaia-benchmark"}} +{"name": "11af4e1a-5f45-467d-9aeb-46f4bb0bf034", "prompt": "How many more blocks (also denoted as layers) in BERT base encoder than the encoder from the architecture proposed in Attention is All You Need?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "6", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e142056d-56ab-4352-b091-b56054bd1359", "prompt": "Bob was invited to participate in a game show, and he advanced to the final round. The final round offered Bob the chance to win a large sum by playing a game against the host. The host has 30 shiny prop coins, each of which is worth $1,000 if Bob manages to win them by playing the game. The host hides the coins in three different prize boxes and then shuffles their order. The only rule restricting the host's coin placement is that one box must contain at least 2 coins, and one box must contain 6 more coins than another box. In order to play, Bob must submit three guesses, one guess for the number of coins in each box. The box is then opened and the number of coins is revealed. If Bob's guess is a number greater than the number of coins in the box, Bob earns no coins. If Bob guesses a number equal to or less than the number of coins in the box, Bob wins a number of coins equal to his guess.\n\nIf Bob plays uses the optimal strategy, what's the minimum amount of money he can win from the game?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 16000", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "16000", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50ad0280-0819-4bd9-b275-5de32d3b5bcb", "prompt": "Pull out the sentence in the following 5x7 block of text. Read from left to right and use all of the letters in order:\n\nTHESE\nAGULL\nGLIDE\nDPEAC\nEFULL\nYTOMY\nCHAIR", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: The seagull glided peacefully to my chair.", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "The seagull glided peacefully to my chair.", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7673d772-ef80-4f0f-a602-1bf4485c9b43", "prompt": "On Cornell Law School website's legal information institute, under the fifth section of federal rules alphabetically, what word was deleted in the last amendment to the first rule in the article that has \"witnesses\" in the most titles as of 2021?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: inference", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "inference", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c365c1c7-a3db-4d5e-a9a1-66f56eae7865", "prompt": "Of the cities within the United States where U.S. presidents were born, which two are the farthest apart from the westernmost to the easternmost going east, giving the city names only? Give them to me in alphabetical order, in a comma-separated list", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Braintree, Honolulu", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Braintree, Honolulu", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7d4a7d1d-cac6-44a8-96e8-ea9584a70825", "prompt": "According to Girls Who Code, how long did it take in years for the percentage of computer scientists that were women to change by 13% from a starting point of 37%?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 22", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "22", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dc22a632-937f-4e6a-b72f-ba0ff3f5ff97", "prompt": "What was the complete title of the book in which two James Beard Award winners recommended the restaurant where Ali Khan enjoyed a New Mexican staple in his cost-conscious TV show that started in 2015? Write the numbers in plain text if there are some in the title.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Five Hundred Things To Eat Before It's Too Late: and the Very Best Places to Eat Them", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Five Hundred Things To Eat Before It's Too Late: and the Very Best Places to Eat Them", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3f57289b-8c60-48be-bd80-01f8099ca449", "prompt": "How many at bats did the Yankee with the most walks in the 1977 regular season have that same season?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 519", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "519", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "23dd907f-1261-4488-b21c-e9185af91d5e", "prompt": "In Audre Lorde\u2019s poem \u201cFather Son and Holy Ghost\u201d, what is the number of the stanza in which some lines are indented?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "2", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "1f975693-876d-457b-a649-393859e79bf3", "prompt": "Hi, I was out sick from my classes on Friday, so I'm trying to figure out what I need to study for my Calculus mid-term next week. My friend from class sent me an audio recording of Professor Willowbrook giving out the recommended reading for the test, but my headphones are broken :(\n\nCould you please listen to the recording for me and tell me the page numbers I'm supposed to go over? I've attached a file called Homework.mp3 that has the recording. Please provide just the page numbers as a comma-delimited list. And please provide the list in ascending order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 132, 133, 134, 197, 245", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "132, 133, 134, 197, 245", "gaia_level": 1, "gaia_file": "1f975693-876d-457b-a649-393859e79bf3.mp3", "source": "gaia-benchmark"}} +{"name": "840bfca7-4f7b-481a-8794-c560c340185d", "prompt": "On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 80GSFC21M0002", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "80GSFC21M0002", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a0068077-79f4-461a-adfe-75c1a4148545", "prompt": "What was the actual enrollment count of the clinical trial on H. pylori in acne vulgaris patients from Jan-May 2018 as listed on the NIH website?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 90", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "90", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "bda648d7-d618-4883-88f4-3466eabd860e", "prompt": "Where were the Vietnamese specimens described by Kuznetzov in Nedoshivina's 2010 paper eventually deposited? Just give me the city name without abbreviations.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Saint Petersburg", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Saint Petersburg", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50ec8903-b81f-4257-9450-1085afd2c319", "prompt": "A standard Rubik\u2019s cube has been broken into cubes making up its sides. The cubes are jumbled, and one is removed. There are 6 cubes with one colored face, 12 edge cubes with two colored faces, and 8 corner cubes with three colored faces. All blue cubes have been found. All cubes directly left, right, above, and below the orange center cube have been found, along with the center cube. The green corners have all been found, along with all green that borders yellow. For all orange cubes found, the opposite face\u2019s cubes have been found. The removed cube has two colors on its faces. What are they? Answer using a comma separated list, with the colors ordered alphabetically.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: green, white", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "green, white", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cf106601-ab4f-4af9-b045-5295fe67b37d", "prompt": "What country had the least number of athletes at the 1928 Summer Olympics? If there's a tie for a number of athletes, return the first in alphabetical order. Give the IOC country code as your answer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: CUB", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "CUB", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a0c07678-e491-4bbc-8f0b-07405144218f", "prompt": "Who are the pitchers with the number before and after Taish\u014d Tamai's number as of July 2023? Give them to me in the form Pitcher Before, Pitcher After, use their last names only, in Roman characters.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Yoshida, Uehara", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Yoshida, Uehara", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7bd855d8-463d-4ed5-93ca-5fe35145f733", "prompt": "The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 89706.00", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "89706.00", "gaia_level": 1, "gaia_file": "7bd855d8-463d-4ed5-93ca-5fe35145f733.xlsx", "source": "gaia-benchmark"}} +{"name": "5a0c1adf-205e-4841-a666-7c3ef95def9d", "prompt": "What is the first name of the only Malko Competition recipient from the 20th Century (after 1977) whose nationality on record is a country that no longer exists?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Claus", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Claus", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c61d22de-5f6c-4958-a7f6-5e9707bd3466", "prompt": "A paper about AI regulation that was originally submitted to arXiv.org in June 2022 shows a figure with three axes, where each axis has a label word at both ends. Which of these words is used to describe a type of society in a Physics and Society article submitted to arXiv.org on August 11, 2016?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: egalitarian", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "egalitarian", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "17b5a6a3-bc87-42e8-b0fb-6ab0781ef2cc", "prompt": "I\u2019m researching species that became invasive after people who kept them as pets released them. There\u2019s a certain species of fish that was popularized as a pet by being the main character of the movie Finding Nemo. According to the USGS, where was this fish found as a nonnative species, before the year 2020? I need the answer formatted as the five-digit zip codes of the places the species was found, separated by commas if there is more than one place.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 34689", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "34689", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "04a04a9b-226c-43fd-b319-d5e89743676f", "prompt": "If we assume all articles published by Nature in 2020 (articles, only, not book reviews/columns, etc) relied on statistical significance to justify their findings and they on average came to a p-value of 0.04, how many papers would be incorrect as to their claims of statistical significance? Round the value up to the next integer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 41", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "41", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "14569e28-c88c-43e4-8c32-097d35b9a67d", "prompt": "In Unlambda, what exact charcter or text needs to be added to correct the following code to output \"For penguins\"? If what is needed is a character, answer with the name of the character. If there are different names for the character, use the shortest. The text location is not needed. Code:\n\n`r```````````.F.o.r. .p.e.n.g.u.i.n.si", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: backtick", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "backtick", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "32102e3e-d12a-4209-9163-7b3a104efe5d", "prompt": "The attached spreadsheet shows the inventory for a movie and video game rental store in Seattle, Washington. What is the title of the oldest Blu-Ray recorded in this spreadsheet? Return it as appearing in the spreadsheet.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Time-Parking 2: Parallel Universe", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Time-Parking 2: Parallel Universe", "gaia_level": 2, "gaia_file": "32102e3e-d12a-4209-9163-7b3a104efe5d.xlsx", "source": "gaia-benchmark"}} +{"name": "3627a8be-a77f-41bb-b807-7e1bd4c0ebdf", "prompt": "The object in the British Museum's collection with a museum number of 2012,5015.17 is the shell of a particular mollusk species. According to the abstract of a research article published in Science Advances in 2021, beads made from the shells of this species were found that are at least how many thousands of years old?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 142", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "142", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7619a514-5fa8-43ef-9143-83b66a43d7a4", "prompt": "According to github, when was Regression added to the oldest closed numpy.polynomial issue that has the Regression label in MM/DD/YY?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 04/15/18", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "04/15/18", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7dd30055-0198-452e-8c25-f73dbe27dcb8", "prompt": "Using the Biopython library in Python, parse the PDB file of the protein identified by the PDB ID 5wb7 from the RCSB Protein Data Bank. Calculate the distance between the first and second atoms as they are listed in the PDB file. Report the answer in Angstroms, rounded to the nearest picometer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1.456", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1.456", "gaia_level": 2, "gaia_file": "7dd30055-0198-452e-8c25-f73dbe27dcb8.pdb", "source": "gaia-benchmark"}} +{"name": "2a649bb1-795f-4a01-b3be-9a01868dae73", "prompt": "What are the EC numbers of the two most commonly used chemicals for the virus testing method in the paper about SPFMV and SPCSV in the Pearl Of Africa from 2016? Return the semicolon-separated numbers in the order of the alphabetized chemicals.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3.1.3.1; 1.11.1.7", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3.1.3.1; 1.11.1.7", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "87c610df-bef7-4932-b950-1d83ef4e282b", "prompt": "In April of 1977, who was the Prime Minister of the first place mentioned by name in the Book of Esther (in the New International Version)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Morarji Desai", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Morarji Desai", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "624cbf11-6a41-4692-af9c-36b3e5ca3130", "prompt": "What's the last line of the rhyme under the flavor name on the headstone visible in the background of the photo of the oldest flavor's headstone in the Ben & Jerry's online flavor graveyard as of the end of 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: So we had to let it die.", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "So we had to let it die.", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dd3c7503-f62a-4bd0-9f67-1b63b94194cc", "prompt": "Use density measures from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023.\n\nI have a gallon of honey and a gallon of mayonnaise at 25C. I remove one cup of honey at a time from the gallon of honey. How many times will I need to remove a cup to have the honey weigh less than the mayonaise? Assume the containers themselves weigh the same.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "df6561b2-7ee5-4540-baab-5095f742716a", "prompt": "When you take the average of the standard population deviation of the red numbers and the standard sample deviation of the green numbers in this image using the statistics module in Python 3.11, what is the result rounded to the nearest three decimal points?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17.056", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "17.056", "gaia_level": 2, "gaia_file": "df6561b2-7ee5-4540-baab-5095f742716a.png", "source": "gaia-benchmark"}} +{"name": "f0f46385-fc03-4599-b5d3-f56496c3e69f", "prompt": "In terms of geographical distance between capital cities, which 2 countries are the furthest from each other within the ASEAN bloc according to wikipedia? Answer using a comma separated list, ordering the countries by alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Indonesia, Myanmar", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Indonesia, Myanmar", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e4e91f1c-1dcd-439e-9fdd-cb976f5293fd", "prompt": "I need to fact-check a citation. This is the citation from the bibliography:\n\nGreetham, David. \"Uncoupled: OR, How I Lost My Author(s).\" Textual Cultures: Texts, Contexts, Interpretation, vol. 3 no. 1, 2008, p. 45-46. Project MUSE, doi:10.2979/tex.2008.3.1.44.\n\nAnd this is the in-line citation:\n\nOur relationship with the authors of the works we read can often be \u201cobscured not by a \"cloak of print\" but by the veil of scribal confusion and mis-transmission\u201d (Greetham 45-46).\n\nDoes the quoted text match what is actually in the article? If Yes, answer Yes, otherwise, give me the word in my citation that does not match with the correct one (without any article).", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: cloak", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "cloak", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "56137764-b4e0-45b8-9c52-1866420c3df5", "prompt": "Which contributor to the version of OpenCV where support was added for the Mask-RCNN model has the same name as a former Chinese head of government when the names are transliterated to the Latin alphabet?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Li Peng", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Li Peng", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8b3379c0-0981-4f5b-8407-6444610cb212", "prompt": "What is the maximum length in meters of #9 in the first National Geographic short on YouTube that was ever released according to the Monterey Bay Aquarium website? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1.8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1.8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0ff53813-3367-4f43-bcbd-3fd725c1bf4b", "prompt": "What two-word type of model did Manash Pratim Kashyap's and PS Fader's studies in customer retention studies published during 2018-2019 have in common (no punctuation)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: beta geometric", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "beta geometric", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a7feb290-76bb-4cb7-8800-7edaf7954f2f", "prompt": "How many High Energy Physics - Lattice articles listed in January 2020 on Arxiv had ps versions available?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 31", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "31", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b4cc024b-3f5e-480e-b96a-6656493255b5", "prompt": "The photograph in the Whitney Museum of American Art's collection with accession number 2022.128 shows a person holding a book. Which military unit did the author of this book join in 1813? Answer without using articles.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Russian-German Legion", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Russian-German Legion", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "33d8ea3b-6c6b-4ff1-803d-7e270dea8a57", "prompt": "What is the minimum number of page links a person must click on to go from the english Wikipedia page on The Lord of the Rings (the book) to the english Wikipedia page on A Song of Ice and Fire (the book series)? In your count, include each link you would click on to get to the page. Use the pages as they appeared at the end of the day on July 3, 2023.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e8cb5b03-41e0-4086-99e5-f6806cd97211", "prompt": "I went to Virtue restaurant & bar in Chicago for my birthday on March 22, 2021 and the main course I had was delicious! Unfortunately, when I went back about a month later on April 21, it was no longer on the dinner menu. Using the Wayback Machine, can you help me figure out which main course was on the dinner menu for Virtue on March 22, 2021 but not April 21, 2021? Answer using the singular form, without articles.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: shrimp", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "shrimp", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f46b4380-207e-4434-820b-f32ce04ae2a4", "prompt": "It is 1999. Before you party like it is 1999, please assist me in settling a bet.\n\nFiona Apple and Paula Cole released albums prior to 1999. Of these albums, which didn't receive a letter grade from Robert Christgau? Provide your answer as a comma delimited list of album titles, sorted alphabetically.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Harbinger, Tidal", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Harbinger, Tidal", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "05407167-39ec-4d3a-a234-73a9120c325d", "prompt": "In the 2018 VSCode blog post on replit.com, what was the command they clicked on in the last video to remove extra lines?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Format Document", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Format Document", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b9763138-c053-4832-9f55-86200cb1f99c", "prompt": "Compute the check digit the Tropicos ID for the Order Helotiales would have if it were an ISBN-10 number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "16d825ff-1623-4176-a5b5-42e0f5c2b0ac", "prompt": "What time was the Tri-Rail train that carried the most passengers on May 27, 2019 scheduled to arrive in Pompano Beach? Express your answer in the 12-hour digital clock format without leading zero if any, and include whether it is AM or PM.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6:41 PM", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6:41 PM", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "2b3ef98c-cc05-450b-a719-711aee40ac65", "prompt": "Could you help me out with this assignment? Our professor sprung it on us at the end of class Friday, and I'm still trying to figure it out. The question he asked us was about an anagram. I've attached an audio recording of the question that he asked, so if you could please take a listen and give me the answer, I'd really appreciate the help. Please limit your response to the anagram text that could be generated from the original line which fulfills the professor's request, without any other commentary. Also, please don't include any punctuation in your response.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: To be or not to be that is the question whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "To be or not to be that is the question whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune", "gaia_level": 2, "gaia_file": "2b3ef98c-cc05-450b-a719-711aee40ac65.mp3", "source": "gaia-benchmark"}} +{"name": "bfcd99e1-0690-4b53-a85c-0174a8629083", "prompt": "How many applicants for the job in the PDF are only missing a single qualification?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "17", "gaia_level": 2, "gaia_file": "bfcd99e1-0690-4b53-a85c-0174a8629083.zip", "source": "gaia-benchmark"}} +{"name": "544b7f0c-173a-4377-8d56-57b36eb26ddf", "prompt": "In Valentina Re\u2019s contribution to the 2017 book \u201cWorld Building: Transmedia, Fans, Industries\u201d, what horror movie does the author cite as having popularized metalepsis between a dream world and reality? Use the complete name with article if any.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: A Nightmare on Elm Street", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "A Nightmare on Elm Street", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6b078778-0b90-464d-83f6-59511c811b01", "prompt": "The Metropolitan Museum of Art has a portrait in its collection with an accession number of 29.100.5. Of the consecrators and co-consecrators of this portrait's subject as a bishop, what is the name of the one who never became pope?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Alfonso Visconti", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Alfonso Visconti", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "076c8171-9b3b-49b9-a477-244d2a532826", "prompt": "The attached file contains a list of vendors in the Liminal Springs mall, along with each vendor\u2019s monthly revenue and the rent they pay the mall. I want you to find the vendor that makes the least money, relative to the rent it pays. Then, tell me what is listed in the \u201ctype\u201d column for that vendor.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Finance", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Finance", "gaia_level": 2, "gaia_file": "076c8171-9b3b-49b9-a477-244d2a532826.xlsx", "source": "gaia-benchmark"}} +{"name": "08cae58d-4084-4616-b6dd-dd6534e4825b", "prompt": "According to Google Finance, when was the first year the Apple stock went above $50 (without adjusting for stock split)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2018", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2018", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "2dfc4c37-fec1-4518-84a7-10095d30ad75", "prompt": "According to Box Office Mojo's 2020 Worldwide Box Office list, how many of the top 10 highest-grossing worldwide movies are also on the top 10 highest-grossing domestic movies? Your answer should be a numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9f41b083-683e-4dcf-9185-ccfeaa88fa45", "prompt": "How many pages if the 2023 IPCC report (85 pages version) mentions nuclear energy?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ecbc4f94-95a3-4cc7-b255-6741a458a625", "prompt": "How many images are there in the latest 2022 Lego english wikipedia article?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 13", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "13", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e9a2c537-8232-4c3f-85b0-b52de6bcba99", "prompt": "The attached file shows a list of books in the collection of Scribe County Public Library. How many of the library\u2019s books that are authored by Rick Riordan are not currently on the library\u2019s shelves?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 7", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "7", "gaia_level": 2, "gaia_file": "e9a2c537-8232-4c3f-85b0-b52de6bcba99.pdf", "source": "gaia-benchmark"}} +{"name": "71345b0a-9c7d-4b50-b2bf-937ec5879845", "prompt": "On a leap day before the year 2008, a joke was removed from the Wikipedia page for \u201cDragon\u201d. What was the phrase that was removed? Give the phrase as it appeared on the page, but without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Here be dragons", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Here be dragons", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7b5377b0-3f38-4103-8ad2-90fe89864c04", "prompt": "Find the value of x to the nearest tenth: Lx = (d/dx * (A * x-squared)) + 4-thousand'n'ninety-7 minus C\nWhere L is the last two digits of the year of the Venezuelan Declaration of Independence,\nA is the number of colors in the TikTok logo as of July 2023, excluding black and white,\nand C is the height of the average woman in the Philippines according to a July 2023 Business Insider article, rounded to the nearest whole centimeter", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 563.9", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "563.9", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "114d5fd0-e2ae-4b6d-a65a-870da2d19c08", "prompt": "In the endnote found in the second-to-last paragraph of page 11 of the book with the doi 10.2307/j.ctv9b2xdv, what date in November was the Wikipedia article accessed? Just give the day of the month.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "4", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8f80e01c-1296-4371-9486-bb3d68651a60", "prompt": "Using bass clef notes, what is the age of someone who has experienced the word spelled out in the sheet music by the note letters the total number of lines and notes minus the number of notes on lines in the image?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 90", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "90", "gaia_level": 2, "gaia_file": "8f80e01c-1296-4371-9486-bb3d68651a60.png", "source": "gaia-benchmark"}} +{"name": "ad37a656-079a-49f9-a493-7b739c9167d1", "prompt": "On July 15, 2008, Phys.org published an article about a catastrophe. Find the explosive force of this catastrophe according to Encyclopedia Britannica, then find the name of the US nuclear test that had the same yield. Your answer should only be the last word of the name of the test.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Bravo", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Bravo", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "366e2f2b-8632-4ef2-81eb-bc3877489217", "prompt": "The attached file lists accommodations in the resort town of Seahorse Island. Based on the information in this file, which seems like the better available place to stay for a family that enjoys swimming and wants a full house?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Shelley's place", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Shelley's place", "gaia_level": 2, "gaia_file": "366e2f2b-8632-4ef2-81eb-bc3877489217.pdf", "source": "gaia-benchmark"}} +{"name": "f3917a3d-1d17-4ee2-90c5-683b072218fe", "prompt": "How many edits were made to the Wikipedia page on Antidisestablishmentarianism from its inception until June of 2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2732", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2732", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "48eb8242-1099-4c26-95d4-ef22b002457a", "prompt": "How many nonindigenous crocodiles were found in Florida from the year 2000 through 2020? You can get the data from the USGS Nonindigenous Aquatic Species database.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c8b7e059-c60d-472e-ad64-3b04ae1166dc", "prompt": "The work referenced in footnote 397 of Federico Lauria's 2014 dissertation is also the source for the titles of two paintings in the Smithsonian American Art Museum's collection, as of August 2023. What is the absolute difference between the chapter numbers of the chapters that the titles of these two paintings quote?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "d1af70ea-a9a4-421a-b9cc-94b5e02f1788", "prompt": "As of the 2020 census, what was the population difference between the largest county seat and smallest county seat, by land area of the county seat, in Washington state? For population figures, please use the official data from data.census.gov. Please report the integer difference.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 736455", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "736455", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "08f3a05f-5947-4089-a4c4-d4bcfaa6b7a0", "prompt": "Given $x_0 = -5$ and $f(x) = x^3 + 4x^2 - 3x + 8$, what is the smallest $n$ where using Newton's Method $n = n+1$ after rounding to four decimal places?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "54612da3-fd56-4941-80f4-5eb82330de25", "prompt": "The attached file shows the locomotives in the collection of a North American railroad museum. How many wheels do the listed steam locomotives have in total?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 60", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "60", "gaia_level": 2, "gaia_file": "54612da3-fd56-4941-80f4-5eb82330de25.xlsx", "source": "gaia-benchmark"}} +{"name": "ded28325-3447-4c56-860f-e497d6fb3577", "prompt": "This is a secret message my friend gave me. It says where we should meet for our picnic on Friday. The only problem is, it\u2019s encrypted in the Caesar cipher, so I can\u2019t read it. Can you tell me what it says? This is the message:\n\nZsmxsm sc sx Zyvilsec Zvkjk.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Picnic is in Ploybius Plaza.", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Picnic is in Ploybius Plaza.", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6359a0b1-8f7b-499b-9336-840f9ab90688", "prompt": "What is the area of the green polygon in the attached file? The numbers in purple represent the lengths of the side they are next to.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 39", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "39", "gaia_level": 2, "gaia_file": "6359a0b1-8f7b-499b-9336-840f9ab90688.png", "source": "gaia-benchmark"}} +{"name": "7cc4acfa-63fd-4acc-a1a1-e8e529e0a97f", "prompt": "The attached spreadsheet contains the sales of menu items for a regional fast-food chain. Which city had the greater total sales: Wharvton or Algrimand?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Wharvton", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Wharvton", "gaia_level": 2, "gaia_file": "7cc4acfa-63fd-4acc-a1a1-e8e529e0a97f.xlsx", "source": "gaia-benchmark"}} +{"name": "d700d50d-c707-4dca-90dc-4528cddd0c80", "prompt": "Who composed the song that was performed by a rooster and a hamster in separate animated videos at separate tempos with different lyrics? Answer using the format First name Last name.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Roger Miller", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Roger Miller", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0a3cd321-3e76-4622-911b-0fda2e5d6b1a", "prompt": "According to the World Bank, which countries had gross savings of over 35% of GDP for every year in the period 2001-2010? Give your answer as a comma-separated list of countries in alphabetical order. Use the countries most common names in english when answering.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Brunei, China, Morocco, Singapore", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Brunei, China, Morocco, Singapore", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f2feb6a4-363c-4c09-a804-0db564eafd68", "prompt": "I\u2019m thinking about selling my home, so I want to learn more about how homes in my area sold recently. I live in Pearl City, Hawaii, which is on the island of Oahu. I know two homes near me that sold in 2022 were 2072 Akaikai Loop, and 2017 Komo Mai Drive. Find which of those homes sold for more in 2022, and tell me how much it sold for. Don\u2019t put commas or decimal places in the answer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 900000", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "900000", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0b260a57-3f3a-4405-9f29-6d7a1012dbfb", "prompt": "On ScienceDirect, what is the difference to 3 decimal places in the sample standard deviations of the number of Reference Works in each Life Science domain compared to Health Sciences as of 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.269", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0.269", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ed58682d-bc52-4baa-9eb0-4eb81e1edacc", "prompt": "What is the last word before the second chorus of the King of Pop's fifth single from his sixth studio album?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: stare", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "stare", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cca70ce6-1952-45d2-acd4-80c903b0bc49", "prompt": "Look at the attached image. The quiz is scored as follows:\n\nProblems that ask the student to add or subtract fractions: 5 points\nProblems that ask the student to multiply or divide fractions: 10 points\nProblems that ask the student to form an improper fraction: 15 points\nProblems that ask the student to form a mixed number: 20 points\n\nDue to a technical issue that delayed having students take the quiz, the teacher is giving everyone 5 bonus points.\n\nIf you graded the quiz in the attached image, how many points would the student have earned? There is no partial credit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 85", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "85", "gaia_level": 2, "gaia_file": "cca70ce6-1952-45d2-acd4-80c903b0bc49.png", "source": "gaia-benchmark"}} +{"name": "b7f857e4-d8aa-4387-af2a-0e844df5b9d8", "prompt": "The attached image contains a Python script. Run the Python code against an array of strings, listed below. The output of the Python script will be a URL containing C++ source code. Compile and run this C++ code against the array [35, 12, 8, 99, 21, 5] and return the sum of the third and fifth integers in the sorted list.\n\narr = ['_alg', 'ghi', 'C++', 'jkl', 'tps', '/Q', 'pqr', 'stu', ':', '//', 'rose', 'vwx', 'yz1', '234', 'tta', '567', '890', 'cod', 'e.', 'or', 'g/', 'wiki', '/', 'ing', 'sort', 'abc' , 'or', 'it', 'hms', 'mno' , 'uic', 'ksort', '#', 'ht' ]", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 47", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "47", "gaia_level": 2, "gaia_file": "b7f857e4-d8aa-4387-af2a-0e844df5b9d8.png", "source": "gaia-benchmark"}} +{"name": "d8152ad6-e4d5-4c12-8bb7-8d57dc10c6de", "prompt": "I have the Standard plan in the image below, and I just uploaded 60 equally sized files and got a message that I'm 100GB over the limit. I have 980 more files of the same size to upload. What is the average additional cost per file in dollar that goes over my current plan limit rounded to the nearest cent if I have to upgrade to the minimum possible plan to store them all? Answer with the following format: x.xx", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.03", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0.03", "gaia_level": 2, "gaia_file": "d8152ad6-e4d5-4c12-8bb7-8d57dc10c6de.png", "source": "gaia-benchmark"}} +{"name": "67e8878b-5cef-4375-804e-e6291fdbe78a", "prompt": "The attached PDF lists accommodations in the resort community of Seahorse Island. Which type of accommodation has a higher average rating in Seahorse Island?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Hotels", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Hotels", "gaia_level": 2, "gaia_file": "67e8878b-5cef-4375-804e-e6291fdbe78a.pdf", "source": "gaia-benchmark"}} +{"name": "023e9d44-96ae-4eed-b912-244ee8c3b994", "prompt": "It's May 2023, and I'm about to drive across the U.S. from California to Maine. I always recycle my water bottles at the end of a trip, and I drink 5 12-ounce water bottles for every 100 miles I travel, rounded to the nearest 100. Assuming I follow I-40 from Los Angeles to Cincinnati, then take I-90 from Cincinnati to Augusta, how many dollars will I get back according to Wikipedia?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0e9e85b8-52b9-4de4-b402-5f635ab9631f", "prompt": "What is the latest chronological year date written in the image on the webpage found when following the first citation reference link on the latest version of Carl Nebel's Wikipedia page as of August 2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1927", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1927", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "20194330-9976-4043-8632-f8485c6c71b2", "prompt": "The YouTube channel Game Grumps began a Let\u2019s Play of the game Sonic the Hedgehog (2006) in the year 2012. Thirty seconds into the first episode, a phrase is shown on the screen in white letters on a red background. How many times does the letter \"E\" appear in this phrase?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "4", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2", "prompt": "This spreadsheet contains a list of clients for a retractable awning company. Each client has ordered a new awning for the back of their house within the last 90 days. The company makes different designs depending on whether the awning is made to block sunrises or sunsets. In this region, houses with odd-numbered street addresses face east, and houses with even-numbered street addresses face west. How many of these clients will be receiving the sunset awning design?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2.xlsx", "source": "gaia-benchmark"}} +{"name": "65638e28-7f37-4fa7-b7b9-8c19bb609879", "prompt": "The book with the doi 10.1353/book.24372 concerns a certain neurologist. According to chapter 2 of the book, what author influenced this neurologist\u2019s belief in \u201cendopsychic myths\u201d? Give the last name only.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Kleinpaul", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Kleinpaul", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3ff6b7a9-a5bd-4412-ad92-0cd0d45c0fee", "prompt": "The longest-lived vertebrate is named after an island. According to Wikipedia as of January 1, 2021, what is the 2020 estimated population of that island, to the nearest thousand?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 56000", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "56000", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "708b99c5-e4a7-49cb-a5cf-933c8d46470d", "prompt": "On the DeepFruits fruit detection graph on Connected Papers from 2016, what feature caused the largest bubble to be the size it is?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Citations", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Citations", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0a65cb96-cb6e-4a6a-8aae-c1084f613456", "prompt": "During the first week of August 2015, one of the NASA Astronomy Pictures of the Day shows the lights of a city on the horizon. The namesake of this city also has a landmark building in Chicago named after him. What is the name of the architectural firm that designed this landmark building? Give the first name appearing in the name of the firm as of June 2023.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Holabird", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Holabird", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65da0822-a48a-4a68-bbad-8ed1b835a834", "prompt": "All of the individuals who formally held the position of United States secretary of homeland security prior to April 2019, excluding those who held the position in an acting capacity, have a bachelor's degree. Of the universities that these bachelor's degrees were from, which is the westernmost university and which is the easternmost university? Give them to me as a comma-separated list, I only want the name of the cities where the universities are located, with the westernmost city listed first.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Santa Clara, Boston", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Santa Clara, Boston", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0bb3b44a-ede5-4db5-a520-4e844b0079c5", "prompt": "Consider the following symbols: \ud809\udc1c \ud809\udc10\ud809\udc1a\n\nThis is a number written using the Mesopotamian/Babylonian number system and represented with Sumerian cuneiform. Convert this number into Arabic numerals as a decimal number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 536", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "536", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "73c1b9fe-ee1d-4cf4-96ca-35c08f97b054", "prompt": "According to the USGS, in what year was the American Alligator first found west of Texas (not including Texas)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1954", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1954", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e2d69698-bc99-4e85-9880-67eaccd66e6c", "prompt": "As of August 2023, who is the only winner of the US version of Survivor to be born in the month of May?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Michele Fitzgerald", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Michele Fitzgerald", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a56f1527-3abf-41d6-91f8-7296d6336c3f", "prompt": "The cover of the August 2021 issue of Vogue shows a famous landmark in the background behind some trees. How tall is this monument in yards, rounded to the nearest yard? Give the number only.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 185", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "185", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "42d4198c-5895-4f0a-b0c0-424a66465d83", "prompt": "I'm curious about how much information is available for popular video games before their release. Find the Wikipedia page for the 2019 game that won the British Academy Games Awards. How many revisions did that page have before the month listed as the game's release date on that Wikipedia page (as of the most recent entry from 2022)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 60", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "60", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "edd4d4f2-1a58-45c4-b038-67337af4e029", "prompt": "The attached spreadsheet lists the locomotives owned by a local railroad museum. What is the typical American name for the type of locomotive this museum uses for the Murder Mystery Express?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Berkshire", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Berkshire", "gaia_level": 2, "gaia_file": "edd4d4f2-1a58-45c4-b038-67337af4e029.xlsx", "source": "gaia-benchmark"}} +{"name": "a26649c6-1cb2-470a-871e-6910c64c3e53", "prompt": "What is the absolute difference in tens of thousands between the population of chinstrap penguins on the Wikipedia page for penguin species populations as of the end of 2018 and the population recorded in the Nature.com \"global population assessment of the Chinstrap penguin\" article from 2020, assuming two penguins per breeding pair?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 116", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "116", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4d0aa727-86b1-406b-9b33-f870dd14a4a5", "prompt": "The attached file lists the locomotives owned by a local railroad museum. It gives each locomotive\u2019s identifying number, operating status, and the name of the daily excursion it heads, if operational. What are the odds that today\u2019s Sunset Picnic Trip will use a steam locomotive? Assume that each day\u2019s excursion picks one of its assigned locomotives at random, and express the answer in the form \u201c1 in 4\u201d, \u201c1 in 5\u201d, etc.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1 in 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1 in 3", "gaia_level": 2, "gaia_file": "4d0aa727-86b1-406b-9b33-f870dd14a4a5.xlsx", "source": "gaia-benchmark"}} +{"name": "d5141ca5-e7a0-469f-bf3e-e773507c86e2", "prompt": "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect? Answer using the format DD/MM/YYYY.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 19/02/2009", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "19/02/2009", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "1dcc160f-c187-48c2-b68e-319bd4354f3d", "prompt": "According to Openreview.net, at the NeurIPS 2022 Conference, how many papers by an author named Yuri were accepted with a \"certain\" recommendation?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b2c257e0-3ad7-4f05-b8e3-d9da973be36e", "prompt": "If this whole pint is made up of ice cream, how many percent above or below the US federal standards for butterfat content is it when using the standards as reported by Wikipedia in 2020? Answer as + or - a number rounded to one decimal place.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: +4.6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "+4.6", "gaia_level": 2, "gaia_file": "b2c257e0-3ad7-4f05-b8e3-d9da973be36e.jpg", "source": "gaia-benchmark"}} +{"name": "e0c10771-d627-4fd7-9694-05348e54ee36", "prompt": "Take the gender split from the 2011 Bulgarian census about those who have completed tertiary education. Subtract the smaller number from the larger number, then return the difference in thousands of women. So if there were 30.1 thousand more men, you'd give \"30.1\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 234.9", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "234.9", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e29834fd-413a-455c-a33e-c3915b07401c", "prompt": "I'd like to learn more about some popular reality television competition shows. As of the end of the 44th season of the American version of Survivor, how many more unique winners have there been compared to the number of winners of American Idol?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 21", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "21", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "08c0b6e9-1b43-4c2e-ae55-4e3fce2c2715", "prompt": "In the film Goldfinger, what color was the object that James Bond concealed himself and his companion Pussy Galore at the end of the film? If there are multiple colors, put them in a comma-separated list in alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: orange, white", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "orange, white", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "db4fd70a-2d37-40ea-873f-9433dc5e301f", "prompt": "As of May 2023, how many stops are between South Station and Windsor Gardens on MBTA\u2019s Franklin-Foxboro line (not included)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 10", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "10", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "853c8244-429e-46ca-89f2-addf40dfb2bd", "prompt": "In the 2015 Metropolitan Museum of Art exhibition titled after the Chinese zodiac animal of 2015, how many of the \"twelve animals of the Chinese zodiac\" have a hand visible?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 11", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "11", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7a4a336d-dcfa-45a0-b014-824c7619e8de", "prompt": "At the two-minute mark in the YouTube video uploaded by the channel \u201cGameGrumps\u201d on May 14, 2017 as part of their playthrough of the game Mario Kart 8 Deluxe, the shows\u2019 hosts are competing on one of the game\u2019s racetracks. What was the world record time for that track in the game\u2019s 150cc mode as of June 7, 2023? Express your answer in minutes and seconds, rounding the seconds to the nearest hundredth, e.g. 1:01.001.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1:41.614", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1:41.614", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "676e5e31-a554-4acc-9286-b60d90a92d26", "prompt": "In July 2, 1959 United States standards for grades of processed fruits, vegetables, and certain other products listed as dehydrated, consider the items in the \"dried and dehydrated section\" specifically marked as dehydrated along with any items in the Frozen/Chilled section that contain the whole name of the item, but not if they're marked Chilled. As of August 2023, what is the percentage (to the nearest percent) of those standards that have been superseded by a new version since the date given in the 1959 standards?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 86", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "86", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "bec74516-02fc-48dc-b202-55e78d0e17cf", "prompt": "What is the average number of pre-2020 works on the open researcher and contributor identification pages of the people whose identification is in this file?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 26.4", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "26.4", "gaia_level": 3, "gaia_file": "bec74516-02fc-48dc-b202-55e78d0e17cf.jsonld", "source": "gaia-benchmark"}} +{"name": "00d579ea-0889-4fd9-a771-2c8d79835c8d", "prompt": "Assuming scientists in the famous youtube video The Thinking Machine (Artificial Intelligence in the 1960s) were interviewed the same year, what is the name of the scientist predicting the sooner thinking machines or robots? Answer using the format First name Last name", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Claude Shannon", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Claude Shannon", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "384d0dd8-e8a4-4cfe-963c-d37f256e7662", "prompt": "In the NCATS PubChem compound database for Food Additive Status classification, find the compound that has a molecular weight of 100 g/mol or less, 6 heavy atoms, 1 or fewer hydrogen bond acceptors, and a complexity between 10 and 15. Of the shared gene-chemical co-occurrences between its two possible enzyme transformations, what is the PubChem CID of the heaviest by molecular weight?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4192", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "4192", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "de9887f5-ead8-4727-876f-5a4078f8598c", "prompt": "What integer-rounded percentage of the total length of the harlequin shrimp recorded in Omar Valencfia-Mendez 2017 paper was the sea star fed to the same type of shrimp in G. Curt Fiedler's 2002 paper?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 22", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "22", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "983bba7c-c092-455f-b6c9-7857003d48fc", "prompt": "What animals that were mentioned in both Ilias Lagkouvardos's and Olga Tapia's papers on the alvei species of the genus named for Copenhagen outside the bibliographies were also present in the 2021 article cited on the alvei species' Wikipedia page about a multicenter, randomized, double-blind study?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: mice", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "mice", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9b54f9d9-35ee-4a14-b62f-d130ea00317f", "prompt": "Which of the text elements under CATEGORIES in the XML would contain the one food in the spreadsheet that does not appear a second time under a different name?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Soups and Stews", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Soups and Stews", "gaia_level": 3, "gaia_file": "9b54f9d9-35ee-4a14-b62f-d130ea00317f.zip", "source": "gaia-benchmark"}} +{"name": "56db2318-640f-477a-a82f-bc93ad13e882", "prompt": "The following numbers function similarly to ISBN 13 numbers, however, their validation methods are slightly different. Rather than using alternate weights of 1 and 3, the checksum digit is calculated with an alternate weight of 1 and some other positive integer less than 10. Otherwise, the checksum digit is calculated as expected. Unfortunately, there is an error in the data. Two adjacent columns have been transposed. These errored columns do not involve the final column or one of the first three columns. Using this information, please provide all potential solutions with the unknown weight and the smaller index of the two errored columns (assume we start our indexing at 0 and ignore hyphens). Give your answer in the form x, y where x is the weight and y is the smaller index of the two transposed columns.\n\n978-354181391-9\n978-946669746-1\n978-398036139-6\n978-447656680-4\n978-279586664-7\n978-595073693-3\n978-976647652-6\n978-591178125-5\n978-728465924-5\n978-414825155-9", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 7, 9", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "7, 9", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8131e2c0-0083-4265-9ce7-78c2d568425d", "prompt": "I was trying to remember how well the Cheater Beater performed in comparison to the Cheater when James tested it on his channel. I know that the Cheater still outperformed the Cheater Beater in terms of CFM. Could you please look that up for me, and report the CFM of both the Cheater and the Cheater Beater? I'm not sure if he made any changes to his testing, but this was back in season 4, so just report the value from that season. Please format your response like this: CFM number for Cheater, CFM number for Cheater beater", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 101.376, 84.348", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "101.376, 84.348", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "72c06643-a2fa-4186-aa5c-9ec33ae9b445", "prompt": "What is the volume in milliliters of a system comprised of 0.312 kg Freon-12 refrigerant when placed at the bottom of the Marianas Trench and allowed to stabilize at the Trench's peak temperature, rounded to the nearest mL? Provide your answer as just an integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 55", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "55", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ebbc1f13-d24d-40df-9068-adcf735b4240", "prompt": "The Latin root of the Yola word \"gimlie\" shares a spelling with a Spanish word. What is the Google translation of the source title for the 1994 example sentence for that word in the Collins Spanish-to-English dictionary online? Answer in plain text, without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: The World of the Twenty First Century", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "The World of the Twenty First Century", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c526d8d6-5987-4da9-b24c-83466fa172f3", "prompt": "In the NIH translation of the original 1913 Michaelis-Menten Paper, what is the velocity of a reaction to four decimal places using the final equation in the paper based on the information for Reaction 7 in the Excel file?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.0424", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.0424", "gaia_level": 3, "gaia_file": "c526d8d6-5987-4da9-b24c-83466fa172f3.xlsx", "source": "gaia-benchmark"}} +{"name": "3da89939-209c-4086-8520-7eb734e6b4ef", "prompt": "I was referencing each of the tables in the file from papers that were cited by the \"Trans fatty acid contents in chocolates and chocolate wafers in Turkey\" paper. I lost my own reference sheet and need to know which of the papers each table came from. The file may not use the full table caption. If the references in the\"Trans fatty acid\" paper bibliography were numbered starting with 1, give me the numbers in the order that they would be used to fill the cells in the Excel file from top to bottom, as a comma separated list.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8, 29, 22, 1, 8, 26", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "8, 29, 22, 1, 8, 26", "gaia_level": 3, "gaia_file": "3da89939-209c-4086-8520-7eb734e6b4ef.xlsx", "source": "gaia-benchmark"}} +{"name": "8d46b8d6-b38a-47ff-ac74-cda14cf2d19b", "prompt": "What percentage of the total penguin population according to the upper estimates on english Wikipedia at the end of 2012 is made up by the penguins in this file that don't live on Dream Island or have beaks longer than 42mm? Round to the nearest five decimal places.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.00033", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.00033", "gaia_level": 3, "gaia_file": "8d46b8d6-b38a-47ff-ac74-cda14cf2d19b.csv", "source": "gaia-benchmark"}} +{"name": "e961a717-6b25-4175-8a68-874d28190ee4", "prompt": "According to wikipedia, how many Asian countries still have a monarchy and access to the sea in 2021?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 12", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "12", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "851e570a-e3de-4d84-bcfa-cc85578baa59", "prompt": "I thought we could try a fun word puzzle together :)\n\nI've got a Boggle board here:\n\nABRL\nEITE\nIONS\nFPEI\n\nI'd like to know the longest word that can be generated from the board. Please find the longest English language word that can be generated from this board. If more than one word of the same length exists at the maximum word length, please report the longest word that comes first, alphabetically. Oh, and I know that there might be different wordlists available for Boggle, so let's please just use the words_alpha dictionary found at https://github.com/dwyl/english-words as the dictionary for our game.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Briniest", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Briniest", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50f58759-7bd6-406f-9b0d-5692beb2a926", "prompt": "How many times was a Twitter/X post cited as a reference on the english Wikipedia pages for each day of August in the last June 2023 versions of the pages?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "3", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "872bfbb1-9ccf-49f6-8c5f-aa22818ccd66", "prompt": "Which of the fruits shown in the 2008 painting \"Embroidery from Uzbekistan\" were served as part of the October 1949 breakfast menu for the ocean liner that was later used as a floating prop for the film \"The Last Voyage\"? Give the items as a comma-separated list, ordering them in clockwise order based on their arrangement in the painting starting from the 12 o'clock position. Use the plural form of each fruit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: pears, bananas", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "pears, bananas", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c3a79cfe-8206-451f-aca8-3fec8ebe51d3", "prompt": "The year is 2022. I am at the National Air and Space Museum east of the Potomac River. I want to go to Fire Station 301 DCA ARFF using the metro. I go in the wrong direction and end up at the station closest to Cleveland Elementary School. How many metro stations am I away from my original destination if I don't change lines? Your answer should be a numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "8", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "da52d699-e8d2-4dc5-9191-a2199e0b6a9b", "prompt": "The attached spreadsheet contains a list of books I read in the year 2022. What is the title of the book that I read the slowest, using the rate of words per day?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Out of the Silent Planet", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Out of the Silent Planet", "gaia_level": 3, "gaia_file": "da52d699-e8d2-4dc5-9191-a2199e0b6a9b.xlsx", "source": "gaia-benchmark"}} +{"name": "ad2b4d70-9314-4fe6-bfbe-894a45f6055f", "prompt": "Eva Draconis has a personal website which can be accessed on her YouTube page. What is the meaning of the only symbol seen in the top banner that has a curved line that isn't a circle or a portion of a circle? Answer without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: War is not here this is a land of peace", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "War is not here this is a land of peace", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5b2a14e8-6e59-479c-80e3-4696e8980152", "prompt": "The brand that makes these harnesses the dogs are wearing in the attached pic shares stories from their ambassadors on their website. What meat is mentioned in the story added Dec 8th 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: bacon", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "bacon", "gaia_level": 3, "gaia_file": "5b2a14e8-6e59-479c-80e3-4696e8980152.jpg", "source": "gaia-benchmark"}} +{"name": "9e1fc53b-46ff-49a1-9d05-9e6faac34cc5", "prompt": "A 5-man group made up of one tank, one healer, and three DPS is doing a dungeon that was just released in World of Warcraft. Two are plate wearers and two are cloth wearers. At the final boss, both the tank and the healer are casting holy spells. Ice and fire are being used, each one by a different DPS. A bear from the group is attacking the boss. Metamorphosis is cast. The Kilt of the Forgotten One drops as loot, but no one can use it. If all classes were using their class abilities and all classes are unique, what are the five classes in the group in alphabetical order separated by commas?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Death Knight, Hunter, Paladin, Priest, Warlock", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Death Knight, Hunter, Paladin, Priest, Warlock", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5f982798-16b9-4051-ab57-cfc7ebdb2a91", "prompt": "I read a paper about multiwavelength observations of fast radio bursts back in March 2021 on Arxiv, and it had a fascinating diagram of an X-ray time profile. There was a similar burst-1 diagram in another paper from one of the same authors about fast radio bursts back in July 2020, but I can't recall what the difference in seconds in the measured time span was. How many more seconds did one measure than the other? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.2", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.2", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0512426f-4d28-49f0-be77-06d05daec096", "prompt": "In the YouTube 360 VR video from March 2018 narrated by the voice actor of Lord of the Rings' Gollum, what number was mentioned by the narrator directly after dinosaurs were first shown in the video?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 100000000", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "100000000", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0bdb7c40-671d-4ad1-9ce3-986b159c0ddc", "prompt": "In NASA's Astronomy Picture of the Day on 2006 January 21, two astronauts are visible, with one appearing much smaller than the other. As of August 2023, out of the astronauts in the NASA Astronaut Group that the smaller astronaut was a member of, which one spent the least time in space, and how many minutes did he spend in space, rounded to the nearest minute? Exclude any astronauts who did not spend any time in space. Give the last name of the astronaut, separated from the number of minutes by a semicolon.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: White; 5876", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "White; 5876", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c61d22de-5f6c-4958-a7f6-5e9707bd3466", "prompt": "A paper about AI regulation that was originally submitted to arXiv.org in June 2022 shows a figure with three axes, where each axis has a label word at both ends. Which of these words is used to describe a type of society in a Physics and Society article submitted to arXiv.org on August 11, 2016?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: egalitarian", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "egalitarian", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "17b5a6a3-bc87-42e8-b0fb-6ab0781ef2cc", "prompt": "I\u2019m researching species that became invasive after people who kept them as pets released them. There\u2019s a certain species of fish that was popularized as a pet by being the main character of the movie Finding Nemo. According to the USGS, where was this fish found as a nonnative species, before the year 2020? I need the answer formatted as the five-digit zip codes of the places the species was found, separated by commas if there is more than one place.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 34689", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "34689", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "04a04a9b-226c-43fd-b319-d5e89743676f", "prompt": "If we assume all articles published by Nature in 2020 (articles, only, not book reviews/columns, etc) relied on statistical significance to justify their findings and they on average came to a p-value of 0.04, how many papers would be incorrect as to their claims of statistical significance? Round the value up to the next integer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 41", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "41", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "14569e28-c88c-43e4-8c32-097d35b9a67d", "prompt": "In Unlambda, what exact charcter or text needs to be added to correct the following code to output \"For penguins\"? If what is needed is a character, answer with the name of the character. If there are different names for the character, use the shortest. The text location is not needed. Code:\n\n`r```````````.F.o.r. .p.e.n.g.u.i.n.si", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: backtick", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "backtick", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e1fc63a2-da7a-432f-be78-7c4a95598703", "prompt": "If Eliud Kipchoge could maintain his record-making marathon pace indefinitely, how many thousand hours would it take him to run the distance between the Earth and the Moon its closest approach? Please use the minimum perigee value on the Wikipedia page for the Moon when carrying out your calculation. Round your result to the nearest 1000 hours and do not use any comma separators if necessary.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "17", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "32102e3e-d12a-4209-9163-7b3a104efe5d", "prompt": "The attached spreadsheet shows the inventory for a movie and video game rental store in Seattle, Washington. What is the title of the oldest Blu-Ray recorded in this spreadsheet? Return it as appearing in the spreadsheet.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Time-Parking 2: Parallel Universe", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Time-Parking 2: Parallel Universe", "gaia_level": 2, "gaia_file": "32102e3e-d12a-4209-9163-7b3a104efe5d.xlsx", "source": "gaia-benchmark"}} +{"name": "8e867cd7-cff9-4e6c-867a-ff5ddc2550be", "prompt": "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3627a8be-a77f-41bb-b807-7e1bd4c0ebdf", "prompt": "The object in the British Museum's collection with a museum number of 2012,5015.17 is the shell of a particular mollusk species. According to the abstract of a research article published in Science Advances in 2021, beads made from the shells of this species were found that are at least how many thousands of years old?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 142", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "142", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7619a514-5fa8-43ef-9143-83b66a43d7a4", "prompt": "According to github, when was Regression added to the oldest closed numpy.polynomial issue that has the Regression label in MM/DD/YY?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 04/15/18", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "04/15/18", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ec09fa32-d03f-4bf8-84b0-1f16922c3ae4", "prompt": "Here's a fun riddle that I think you'll enjoy.\n\nYou have been selected to play the final round of the hit new game show \"Pick That Ping-Pong\". In this round, you will be competing for a large cash prize. Your job will be to pick one of several different numbered ping-pong balls, and then the game will commence. The host describes how the game works.\n\nA device consisting of a winding clear ramp and a series of pistons controls the outcome of the game. The ramp feeds balls onto a platform. The platform has room for three ping-pong balls at a time. The three balls on the platform are each aligned with one of three pistons. At each stage of the game, one of the three pistons will randomly fire, ejecting the ball it strikes. If the piston ejects the ball in the first position on the platform the balls in the second and third position on the platform each advance one space, and the next ball on the ramp advances to the third position. If the piston ejects the ball in the second position, the ball in the first position is released and rolls away, the ball in the third position advances two spaces to occupy the first position, and the next two balls on the ramp advance to occupy the second and third positions on the platform. If the piston ejects the ball in the third position, the ball in the first position is released and rolls away, the ball in the second position advances one space to occupy the first position, and the next two balls on the ramp advance to occupy the second and third positions on the platform.\n\nThe ramp begins with 100 numbered ping-pong balls, arranged in ascending order from 1 to 100. The host activates the machine and the first three balls, numbered 1, 2, and 3, advance to the platform. Before the random firing of the pistons begins, you are asked which of the 100 balls you would like to pick. If your pick is ejected by one of the pistons, you win the grand prize, $10,000.\n\nWhich ball should you choose to maximize your odds of winning the big prize? Please provide your answer as the number of the ball selected.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "676e5e31-a554-4acc-9286-b60d90a92d26", "prompt": "In July 2, 1959 United States standards for grades of processed fruits, vegetables, and certain other products listed as dehydrated, consider the items in the \"dried and dehydrated section\" specifically marked as dehydrated along with any items in the Frozen/Chilled section that contain the whole name of the item, but not if they're marked Chilled. As of August 2023, what is the percentage (to the nearest percent) of those standards that have been superseded by a new version since the date given in the 1959 standards?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 86", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "86", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7dd30055-0198-452e-8c25-f73dbe27dcb8", "prompt": "Using the Biopython library in Python, parse the PDB file of the protein identified by the PDB ID 5wb7 from the RCSB Protein Data Bank. Calculate the distance between the first and second atoms as they are listed in the PDB file. Report the answer in Angstroms, rounded to the nearest picometer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1.456", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1.456", "gaia_level": 2, "gaia_file": "7dd30055-0198-452e-8c25-f73dbe27dcb8.pdb", "source": "gaia-benchmark"}} +{"name": "2a649bb1-795f-4a01-b3be-9a01868dae73", "prompt": "What are the EC numbers of the two most commonly used chemicals for the virus testing method in the paper about SPFMV and SPCSV in the Pearl Of Africa from 2016? Return the semicolon-separated numbers in the order of the alphabetized chemicals.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3.1.3.1; 1.11.1.7", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3.1.3.1; 1.11.1.7", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "87c610df-bef7-4932-b950-1d83ef4e282b", "prompt": "In April of 1977, who was the Prime Minister of the first place mentioned by name in the Book of Esther (in the New International Version)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Morarji Desai", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Morarji Desai", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "624cbf11-6a41-4692-af9c-36b3e5ca3130", "prompt": "What's the last line of the rhyme under the flavor name on the headstone visible in the background of the photo of the oldest flavor's headstone in the Ben & Jerry's online flavor graveyard as of the end of 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: So we had to let it die.", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "So we had to let it die.", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dd3c7503-f62a-4bd0-9f67-1b63b94194cc", "prompt": "Use density measures from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023.\n\nI have a gallon of honey and a gallon of mayonnaise at 25C. I remove one cup of honey at a time from the gallon of honey. How many times will I need to remove a cup to have the honey weigh less than the mayonaise? Assume the containers themselves weigh the same.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5d0080cb-90d7-4712-bc33-848150e917d3", "prompt": "What was the volume in m^3 of the fish bag that was calculated in the University of Leicester paper \"Can Hiccup Supply Enough Fish to Maintain a Dragon\u2019s Diet?\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.1777", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "0.1777", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "bec74516-02fc-48dc-b202-55e78d0e17cf", "prompt": "What is the average number of pre-2020 works on the open researcher and contributor identification pages of the people whose identification is in this file?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 26.4", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "26.4", "gaia_level": 3, "gaia_file": "bec74516-02fc-48dc-b202-55e78d0e17cf.jsonld", "source": "gaia-benchmark"}} +{"name": "a1e91b78-d3d8-4675-bb8d-62741b4b68a6", "prompt": "In the video https://www.youtube.com/watch?v=L1vXCYZAYYM, what is the highest number of bird species to be on camera simultaneously?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "46719c30-f4c3-4cad-be07-d5cb21eee6bb", "prompt": "Of the authors (First M. Last) that worked on the paper \"Pie Menus or Linear Menus, Which Is Better?\" in 2015, what was the title of the first paper authored by the one that had authored prior papers?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Mapping Human Oriented Information to Software Agents for Online Systems Usage", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Mapping Human Oriented Information to Software Agents for Online Systems Usage", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "df6561b2-7ee5-4540-baab-5095f742716a", "prompt": "When you take the average of the standard population deviation of the red numbers and the standard sample deviation of the green numbers in this image using the statistics module in Python 3.11, what is the result rounded to the nearest three decimal points?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17.056", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "17.056", "gaia_level": 2, "gaia_file": "df6561b2-7ee5-4540-baab-5095f742716a.png", "source": "gaia-benchmark"}} +{"name": "00d579ea-0889-4fd9-a771-2c8d79835c8d", "prompt": "Assuming scientists in the famous youtube video The Thinking Machine (Artificial Intelligence in the 1960s) were interviewed the same year, what is the name of the scientist predicting the sooner thinking machines or robots? Answer using the format First name Last name", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Claude Shannon", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Claude Shannon", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4b6bb5f7-f634-410e-815d-e673ab7f8632", "prompt": "In Series 9, Episode 11 of Doctor Who, the Doctor is trapped inside an ever-shifting maze. What is this location called in the official script for the episode? Give the setting exactly as it appears in the first scene heading.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: THE CASTLE", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "THE CASTLE", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f0f46385-fc03-4599-b5d3-f56496c3e69f", "prompt": "In terms of geographical distance between capital cities, which 2 countries are the furthest from each other within the ASEAN bloc according to wikipedia? Answer using a comma separated list, ordering the countries by alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Indonesia, Myanmar", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Indonesia, Myanmar", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "384d0dd8-e8a4-4cfe-963c-d37f256e7662", "prompt": "In the NCATS PubChem compound database for Food Additive Status classification, find the compound that has a molecular weight of 100 g/mol or less, 6 heavy atoms, 1 or fewer hydrogen bond acceptors, and a complexity between 10 and 15. Of the shared gene-chemical co-occurrences between its two possible enzyme transformations, what is the PubChem CID of the heaviest by molecular weight?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4192", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "4192", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e4e91f1c-1dcd-439e-9fdd-cb976f5293fd", "prompt": "I need to fact-check a citation. This is the citation from the bibliography:\n\nGreetham, David. \"Uncoupled: OR, How I Lost My Author(s).\" Textual Cultures: Texts, Contexts, Interpretation, vol. 3 no. 1, 2008, p. 45-46. Project MUSE, doi:10.2979/tex.2008.3.1.44.\n\nAnd this is the in-line citation:\n\nOur relationship with the authors of the works we read can often be \u201cobscured not by a \"cloak of print\" but by the veil of scribal confusion and mis-transmission\u201d (Greetham 45-46).\n\nDoes the quoted text match what is actually in the article? If Yes, answer Yes, otherwise, give me the word in my citation that does not match with the correct one (without any article).", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: cloak", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "cloak", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "56137764-b4e0-45b8-9c52-1866420c3df5", "prompt": "Which contributor to the version of OpenCV where support was added for the Mask-RCNN model has the same name as a former Chinese head of government when the names are transliterated to the Latin alphabet?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Li Peng", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Li Peng", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "de9887f5-ead8-4727-876f-5a4078f8598c", "prompt": "What integer-rounded percentage of the total length of the harlequin shrimp recorded in Omar Valencfia-Mendez 2017 paper was the sea star fed to the same type of shrimp in G. Curt Fiedler's 2002 paper?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 22", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "22", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cffe0e32-c9a6-4c52-9877-78ceb4aaa9fb", "prompt": "An office held a Secret Santa gift exchange where each of its twelve employees was assigned one other employee in the group to present with a gift. Each employee filled out a profile including three likes or hobbies. On the day of the gift exchange, only eleven gifts were given, each one specific to one of the recipient's interests. Based on the information in the document, who did not give a gift?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Fred", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Fred", "gaia_level": 1, "gaia_file": "cffe0e32-c9a6-4c52-9877-78ceb4aaa9fb.docx", "source": "gaia-benchmark"}} +{"name": "8b3379c0-0981-4f5b-8407-6444610cb212", "prompt": "What is the maximum length in meters of #9 in the first National Geographic short on YouTube that was ever released according to the Monterey Bay Aquarium website? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1.8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1.8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0ff53813-3367-4f43-bcbd-3fd725c1bf4b", "prompt": "What two-word type of model did Manash Pratim Kashyap's and PS Fader's studies in customer retention studies published during 2018-2019 have in common (no punctuation)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: beta geometric", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "beta geometric", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "983bba7c-c092-455f-b6c9-7857003d48fc", "prompt": "What animals that were mentioned in both Ilias Lagkouvardos's and Olga Tapia's papers on the alvei species of the genus named for Copenhagen outside the bibliographies were also present in the 2021 article cited on the alvei species' Wikipedia page about a multicenter, randomized, double-blind study?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: mice", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "mice", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a7feb290-76bb-4cb7-8800-7edaf7954f2f", "prompt": "How many High Energy Physics - Lattice articles listed in January 2020 on Arxiv had ps versions available?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 31", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "31", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b4cc024b-3f5e-480e-b96a-6656493255b5", "prompt": "The photograph in the Whitney Museum of American Art's collection with accession number 2022.128 shows a person holding a book. Which military unit did the author of this book join in 1813? Answer without using articles.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Russian-German Legion", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Russian-German Legion", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "2d83110e-a098-4ebb-9987-066c06fa42d0", "prompt": ".rewsna eht sa \"tfel\" drow eht fo etisoppo eht etirw ,ecnetnes siht dnatsrednu uoy fI", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Right", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Right", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "33d8ea3b-6c6b-4ff1-803d-7e270dea8a57", "prompt": "What is the minimum number of page links a person must click on to go from the english Wikipedia page on The Lord of the Rings (the book) to the english Wikipedia page on A Song of Ice and Fire (the book series)? In your count, include each link you would click on to get to the page. Use the pages as they appeared at the end of the day on July 3, 2023.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5cfb274c-0207-4aa7-9575-6ac0bd95d9b2", "prompt": "Each cell in the attached spreadsheet represents a plot of land. The color of the cell indicates who owns that plot. Green cells are plots owned by Earl Smith. Can Earl walk through every plot he owns (and no other plots) and return to his starting plot without backtracking? For this question, consider backtracking to be any instance where Earl would enter a plot of land he had already entered since leaving his starting plot.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: No", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "No", "gaia_level": 1, "gaia_file": "5cfb274c-0207-4aa7-9575-6ac0bd95d9b2.xlsx", "source": "gaia-benchmark"}} +{"name": "9b54f9d9-35ee-4a14-b62f-d130ea00317f", "prompt": "Which of the text elements under CATEGORIES in the XML would contain the one food in the spreadsheet that does not appear a second time under a different name?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Soups and Stews", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Soups and Stews", "gaia_level": 3, "gaia_file": "9b54f9d9-35ee-4a14-b62f-d130ea00317f.zip", "source": "gaia-benchmark"}} +{"name": "e8cb5b03-41e0-4086-99e5-f6806cd97211", "prompt": "I went to Virtue restaurant & bar in Chicago for my birthday on March 22, 2021 and the main course I had was delicious! Unfortunately, when I went back about a month later on April 21, it was no longer on the dinner menu. Using the Wayback Machine, can you help me figure out which main course was on the dinner menu for Virtue on March 22, 2021 but not April 21, 2021? Answer using the singular form, without articles.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: shrimp", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "shrimp", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "27d5d136-8563-469e-92bf-fd103c28b57c", "prompt": "\u00ac(A \u2227 B) \u2194 (\u00acA \u2228 \u00acB)\n\u00ac(A \u2228 B) \u2194 (\u00acA \u2227 \u00acB)\n(A \u2192 B) \u2194 (\u00acB \u2192 \u00acA)\n(A \u2192 B) \u2194 (\u00acA \u2228 B)\n(\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)\n\u00ac(A \u2192 B) \u2194 (A \u2227 \u00acB)\n\nWhich of the above is not logically equivalent to the rest? Provide the full statement that doesn't fit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: (\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "(\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dc28cf18-6431-458b-83ef-64b3ce566c10", "prompt": "My family reunion is this week, and I was assigned the mashed potatoes to bring. The attendees include my married mother and father, my twin brother and his family, my aunt and her family, my grandma and her brother, her brother's daughter, and his daughter's family. All the adults but me have been married, and no one is divorced or remarried, but my grandpa and my grandma's sister-in-law passed away last year. All living spouses are attending. My brother has two children that are still kids, my aunt has one six-year-old, and my grandma's brother's daughter has three kids under 12. I figure each adult will eat about 1.5 potatoes of mashed potatoes and each kid will eat about 1/2 a potato of mashed potatoes, except my second cousins don't eat carbs. The average potato is about half a pound, and potatoes are sold in 5-pound bags. How many whole bags of potatoes do I need? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "2", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b816bfce-3d80-4913-a07d-69b752ce6377", "prompt": "In Emily Midkiff's June 2014 article in a journal named for the one of Hreidmar's sons that guarded his house, what word was quoted from two different authors in distaste for the nature of dragon depictions?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: fluffy", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "fluffy", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f46b4380-207e-4434-820b-f32ce04ae2a4", "prompt": "It is 1999. Before you party like it is 1999, please assist me in settling a bet.\n\nFiona Apple and Paula Cole released albums prior to 1999. Of these albums, which didn't receive a letter grade from Robert Christgau? Provide your answer as a comma delimited list of album titles, sorted alphabetically.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Harbinger, Tidal", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Harbinger, Tidal", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "72e110e7-464c-453c-a309-90a95aed6538", "prompt": "Under DDC 633 on Bielefeld University Library's BASE, as of 2020, from what country was the unknown language article with a flag unique from the others?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Guatemala", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Guatemala", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "05407167-39ec-4d3a-a234-73a9120c325d", "prompt": "In the 2018 VSCode blog post on replit.com, what was the command they clicked on in the last video to remove extra lines?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Format Document", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Format Document", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b9763138-c053-4832-9f55-86200cb1f99c", "prompt": "Compute the check digit the Tropicos ID for the Order Helotiales would have if it were an ISBN-10 number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "16d825ff-1623-4176-a5b5-42e0f5c2b0ac", "prompt": "What time was the Tri-Rail train that carried the most passengers on May 27, 2019 scheduled to arrive in Pompano Beach? Express your answer in the 12-hour digital clock format without leading zero if any, and include whether it is AM or PM.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6:41 PM", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6:41 PM", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "2b3ef98c-cc05-450b-a719-711aee40ac65", "prompt": "Could you help me out with this assignment? Our professor sprung it on us at the end of class Friday, and I'm still trying to figure it out. The question he asked us was about an anagram. I've attached an audio recording of the question that he asked, so if you could please take a listen and give me the answer, I'd really appreciate the help. Please limit your response to the anagram text that could be generated from the original line which fulfills the professor's request, without any other commentary. Also, please don't include any punctuation in your response.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: To be or not to be that is the question whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "To be or not to be that is the question whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune", "gaia_level": 2, "gaia_file": "2b3ef98c-cc05-450b-a719-711aee40ac65.mp3", "source": "gaia-benchmark"}} +{"name": "bfcd99e1-0690-4b53-a85c-0174a8629083", "prompt": "How many applicants for the job in the PDF are only missing a single qualification?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "17", "gaia_level": 2, "gaia_file": "bfcd99e1-0690-4b53-a85c-0174a8629083.zip", "source": "gaia-benchmark"}} +{"name": "544b7f0c-173a-4377-8d56-57b36eb26ddf", "prompt": "In Valentina Re\u2019s contribution to the 2017 book \u201cWorld Building: Transmedia, Fans, Industries\u201d, what horror movie does the author cite as having popularized metalepsis between a dream world and reality? Use the complete name with article if any.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: A Nightmare on Elm Street", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "A Nightmare on Elm Street", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "42576abe-0deb-4869-8c63-225c2d75a95a", "prompt": "In the fictional language of Tizin, basic sentences are arranged with the Verb first, followed by the direct object, followed by the subject of the sentence. I want to express my love for apples to my Tizin friend. \n\nThe word that indicates oneself is \"Pa\" is the nominative form, \"Mato\" is the accusative form, and \"Sing\" is the genitive form. \n\nThe root verb that indicates an intense like for something is \"Maktay\". When it is used in the present, it is used in it's root form, when it is used in the preterit past, it is \"Tay\", and when it is used in the imperfect past, it is \"Aktay\". It is used differently than in English, and is better translated as \"is pleasing to\", meaning that the thing doing the liking is actually the object of the sentence rather than the subject.\n\nThe word for apples is borrowed from English in Tizin, and so it is \"Apple\" is the nominative form, \"Zapple\" is the accusative form, and \"Izapple\" is the genitive form. \n\nPlease translate \"I like apples\" to Tizin.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Maktay mato apple", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Maktay mato apple", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6b078778-0b90-464d-83f6-59511c811b01", "prompt": "The Metropolitan Museum of Art has a portrait in its collection with an accession number of 29.100.5. Of the consecrators and co-consecrators of this portrait's subject as a bishop, what is the name of the one who never became pope?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Alfonso Visconti", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Alfonso Visconti", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b415aba4-4b68-4fc6-9b89-2c812e55a3e1", "prompt": "In Nature journal's Scientific Reports conference proceedings from 2012, in the article that did not mention plasmons or plasmonics, what nano-compound is studied? Don't use the prefix nano in your answer if there is one.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: diamond", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "diamond", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "076c8171-9b3b-49b9-a477-244d2a532826", "prompt": "The attached file contains a list of vendors in the Liminal Springs mall, along with each vendor\u2019s monthly revenue and the rent they pay the mall. I want you to find the vendor that makes the least money, relative to the rent it pays. Then, tell me what is listed in the \u201ctype\u201d column for that vendor.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Finance", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Finance", "gaia_level": 2, "gaia_file": "076c8171-9b3b-49b9-a477-244d2a532826.xlsx", "source": "gaia-benchmark"}} +{"name": "08cae58d-4084-4616-b6dd-dd6534e4825b", "prompt": "According to Google Finance, when was the first year the Apple stock went above $50 (without adjusting for stock split)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2018", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2018", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cca530fc-4052-43b2-b130-b30968d8aa44", "prompt": "Review the chess position provided in the image. It is black's turn. Provide the correct next move for black which guarantees a win. Please provide your response in algebraic notation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Rd5", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Rd5", "gaia_level": 1, "gaia_file": "cca530fc-4052-43b2-b130-b30968d8aa44.png", "source": "gaia-benchmark"}} +{"name": "2dfc4c37-fec1-4518-84a7-10095d30ad75", "prompt": "According to Box Office Mojo's 2020 Worldwide Box Office list, how many of the top 10 highest-grossing worldwide movies are also on the top 10 highest-grossing domestic movies? Your answer should be a numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "935e2cff-ae78-4218-b3f5-115589b19dae", "prompt": "In the year 2022, and before December, what does \"R\" stand for in the three core policies of the type of content that was violated in the public logs on the Legume Wikipedia page?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: research", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "research", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4fc2f1ae-8625-45b5-ab34-ad4433bc21f8", "prompt": "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: FunkMonk", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "FunkMonk", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5188369a-3bbe-43d8-8b94-11558f909a08", "prompt": "What writer is quoted by Merriam-Webster for the Word of the Day from June 27, 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Annie Levin", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Annie Levin", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9f41b083-683e-4dcf-9185-ccfeaa88fa45", "prompt": "How many pages if the 2023 IPCC report (85 pages version) mentions nuclear energy?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6f37996b-2ac7-44b0-8e68-6d28256631b4", "prompt": "Given this table defining * on the set S = {a, b, c, d, e}\n\n|*|a|b|c|d|e|\n|---|---|---|---|---|---|\n|a|a|b|c|b|d|\n|b|b|c|a|e|c|\n|c|c|a|b|b|a|\n|d|b|e|b|e|d|\n|e|d|b|a|d|c|\n\nprovide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: b, e", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "b, e", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "56db2318-640f-477a-a82f-bc93ad13e882", "prompt": "The following numbers function similarly to ISBN 13 numbers, however, their validation methods are slightly different. Rather than using alternate weights of 1 and 3, the checksum digit is calculated with an alternate weight of 1 and some other positive integer less than 10. Otherwise, the checksum digit is calculated as expected. Unfortunately, there is an error in the data. Two adjacent columns have been transposed. These errored columns do not involve the final column or one of the first three columns. Using this information, please provide all potential solutions with the unknown weight and the smaller index of the two errored columns (assume we start our indexing at 0 and ignore hyphens). Give your answer in the form x, y where x is the weight and y is the smaller index of the two transposed columns.\n\n978-354181391-9\n978-946669746-1\n978-398036139-6\n978-447656680-4\n978-279586664-7\n978-595073693-3\n978-976647652-6\n978-591178125-5\n978-728465924-5\n978-414825155-9", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 7, 9", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "7, 9", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ecbc4f94-95a3-4cc7-b255-6741a458a625", "prompt": "How many images are there in the latest 2022 Lego english wikipedia article?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 13", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "13", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e9a2c537-8232-4c3f-85b0-b52de6bcba99", "prompt": "The attached file shows a list of books in the collection of Scribe County Public Library. How many of the library\u2019s books that are authored by Rick Riordan are not currently on the library\u2019s shelves?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 7", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "7", "gaia_level": 2, "gaia_file": "e9a2c537-8232-4c3f-85b0-b52de6bcba99.pdf", "source": "gaia-benchmark"}} +{"name": "8131e2c0-0083-4265-9ce7-78c2d568425d", "prompt": "I was trying to remember how well the Cheater Beater performed in comparison to the Cheater when James tested it on his channel. I know that the Cheater still outperformed the Cheater Beater in terms of CFM. Could you please look that up for me, and report the CFM of both the Cheater and the Cheater Beater? I'm not sure if he made any changes to his testing, but this was back in season 4, so just report the value from that season. Please format your response like this: CFM number for Cheater, CFM number for Cheater beater", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 101.376, 84.348", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "101.376, 84.348", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9318445f-fe6a-4e1b-acbf-c68228c9906a", "prompt": "As a comma separated list with no whitespace, using the provided image provide all the fractions that use / as the fraction line and the answers to the sample problems. Order the list by the order in which the fractions appear.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3/4,1/4,3/4,3/4,2/4,1/2,5/35,7/21,30/5,30/5,3/4,1/15,1/3,4/9,1/8,32/23,103/170", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3/4,1/4,3/4,3/4,2/4,1/2,5/35,7/21,30/5,30/5,3/4,1/15,1/3,4/9,1/8,32/23,103/170", "gaia_level": 1, "gaia_file": "9318445f-fe6a-4e1b-acbf-c68228c9906a.png", "source": "gaia-benchmark"}} +{"name": "71345b0a-9c7d-4b50-b2bf-937ec5879845", "prompt": "On a leap day before the year 2008, a joke was removed from the Wikipedia page for \u201cDragon\u201d. What was the phrase that was removed? Give the phrase as it appeared on the page, but without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Here be dragons", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Here be dragons", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "72c06643-a2fa-4186-aa5c-9ec33ae9b445", "prompt": "What is the volume in milliliters of a system comprised of 0.312 kg Freon-12 refrigerant when placed at the bottom of the Marianas Trench and allowed to stabilize at the Trench's peak temperature, rounded to the nearest mL? Provide your answer as just an integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 55", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "55", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ebbc1f13-d24d-40df-9068-adcf735b4240", "prompt": "The Latin root of the Yola word \"gimlie\" shares a spelling with a Spanish word. What is the Google translation of the source title for the 1994 example sentence for that word in the Collins Spanish-to-English dictionary online? Answer in plain text, without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: The World of the Twenty First Century", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "The World of the Twenty First Century", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7b5377b0-3f38-4103-8ad2-90fe89864c04", "prompt": "Find the value of x to the nearest tenth: Lx = (d/dx * (A * x-squared)) + 4-thousand'n'ninety-7 minus C\nWhere L is the last two digits of the year of the Venezuelan Declaration of Independence,\nA is the number of colors in the TikTok logo as of July 2023, excluding black and white,\nand C is the height of the average woman in the Philippines according to a July 2023 Business Insider article, rounded to the nearest whole centimeter", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 563.9", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "563.9", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "114d5fd0-e2ae-4b6d-a65a-870da2d19c08", "prompt": "In the endnote found in the second-to-last paragraph of page 11 of the book with the doi 10.2307/j.ctv9b2xdv, what date in November was the Wikipedia article accessed? Just give the day of the month.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "4", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8f80e01c-1296-4371-9486-bb3d68651a60", "prompt": "Using bass clef notes, what is the age of someone who has experienced the word spelled out in the sheet music by the note letters the total number of lines and notes minus the number of notes on lines in the image?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 90", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "90", "gaia_level": 2, "gaia_file": "8f80e01c-1296-4371-9486-bb3d68651a60.png", "source": "gaia-benchmark"}} +{"name": "ad37a656-079a-49f9-a493-7b739c9167d1", "prompt": "On July 15, 2008, Phys.org published an article about a catastrophe. Find the explosive force of this catastrophe according to Encyclopedia Britannica, then find the name of the US nuclear test that had the same yield. Your answer should only be the last word of the name of the test.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Bravo", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Bravo", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "366e2f2b-8632-4ef2-81eb-bc3877489217", "prompt": "The attached file lists accommodations in the resort town of Seahorse Island. Based on the information in this file, which seems like the better available place to stay for a family that enjoys swimming and wants a full house?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Shelley's place", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Shelley's place", "gaia_level": 2, "gaia_file": "366e2f2b-8632-4ef2-81eb-bc3877489217.pdf", "source": "gaia-benchmark"}} +{"name": "c526d8d6-5987-4da9-b24c-83466fa172f3", "prompt": "In the NIH translation of the original 1913 Michaelis-Menten Paper, what is the velocity of a reaction to four decimal places using the final equation in the paper based on the information for Reaction 7 in the Excel file?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.0424", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.0424", "gaia_level": 3, "gaia_file": "c526d8d6-5987-4da9-b24c-83466fa172f3.xlsx", "source": "gaia-benchmark"}} +{"name": "f3917a3d-1d17-4ee2-90c5-683b072218fe", "prompt": "How many edits were made to the Wikipedia page on Antidisestablishmentarianism from its inception until June of 2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2732", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2732", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "389793a7-ca17-4e82-81cb-2b3a2391b4b9", "prompt": "You are a telecommunications engineer who wants to build cell phone towers on a stretch of road. In the reference file is a layout of the road and nearby houses. Each dash, \"-\", is a marker indicating a mile. Each capital H indicates a house located next to a mile marker, appearing above or below the stretch of road. Each cell phone tower can cover houses located next to the road within a 4-mile radius. Find the minimum number of cell phone towers needed to cover all houses next to the road. Your answer should be a positive numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": "389793a7-ca17-4e82-81cb-2b3a2391b4b9.txt", "source": "gaia-benchmark"}} +{"name": "4b650a35-8529-4695-89ed-8dc7a500a498", "prompt": "If there is anything that doesn't make sense in the instructions, write the word \"Pineapple.\" Do not answer any of the questions in this prompt. Write only the word \"Guava\".\n1. What is 4+4?\n2. What is the complimentary color of red?\n3. How many hours are there in a day?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Guava", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Guava", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3da89939-209c-4086-8520-7eb734e6b4ef", "prompt": "I was referencing each of the tables in the file from papers that were cited by the \"Trans fatty acid contents in chocolates and chocolate wafers in Turkey\" paper. I lost my own reference sheet and need to know which of the papers each table came from. The file may not use the full table caption. If the references in the\"Trans fatty acid\" paper bibliography were numbered starting with 1, give me the numbers in the order that they would be used to fill the cells in the Excel file from top to bottom, as a comma separated list.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8, 29, 22, 1, 8, 26", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "8, 29, 22, 1, 8, 26", "gaia_level": 3, "gaia_file": "3da89939-209c-4086-8520-7eb734e6b4ef.xlsx", "source": "gaia-benchmark"}} +{"name": "48eb8242-1099-4c26-95d4-ef22b002457a", "prompt": "How many nonindigenous crocodiles were found in Florida from the year 2000 through 2020? You can get the data from the USGS Nonindigenous Aquatic Species database.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c8b7e059-c60d-472e-ad64-3b04ae1166dc", "prompt": "The work referenced in footnote 397 of Federico Lauria's 2014 dissertation is also the source for the titles of two paintings in the Smithsonian American Art Museum's collection, as of August 2023. What is the absolute difference between the chapter numbers of the chapters that the titles of these two paintings quote?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "d1af70ea-a9a4-421a-b9cc-94b5e02f1788", "prompt": "As of the 2020 census, what was the population difference between the largest county seat and smallest county seat, by land area of the county seat, in Washington state? For population figures, please use the official data from data.census.gov. Please report the integer difference.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 736455", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "736455", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a3fbeb63-0e8c-4a11-bff6-0e3b484c3e9c", "prompt": "How many slides in this PowerPoint presentation mention crustaceans?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "4", "gaia_level": 1, "gaia_file": "a3fbeb63-0e8c-4a11-bff6-0e3b484c3e9c.pptx", "source": "gaia-benchmark"}} +{"name": "8d46b8d6-b38a-47ff-ac74-cda14cf2d19b", "prompt": "What percentage of the total penguin population according to the upper estimates on english Wikipedia at the end of 2012 is made up by the penguins in this file that don't live on Dream Island or have beaks longer than 42mm? Round to the nearest five decimal places.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.00033", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.00033", "gaia_level": 3, "gaia_file": "8d46b8d6-b38a-47ff-ac74-cda14cf2d19b.csv", "source": "gaia-benchmark"}} +{"name": "08f3a05f-5947-4089-a4c4-d4bcfaa6b7a0", "prompt": "Given $x_0 = -5$ and $f(x) = x^3 + 4x^2 - 3x + 8$, what is the smallest $n$ where using Newton's Method $n = n+1$ after rounding to four decimal places?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c714ab3a-da30-4603-bacd-d008800188b9", "prompt": "You are Van Helsing, a renowned vampire hunter. A Count of Moldova, La\u021bcu IV, son of Costea, has tasked you with investigating the village of \u0218irnea in neighboring Wallachia. The Count's advisors have reported that a vampire was spotted crossing the border near the village, and would like you to investigate it.\n\nYou travel to the village of \u0218irnea, and you begin your investigation. One night, just before dawn, you catch a glimpse of a man in a long black cape with red lining leaping from roof-top to roof-top with superhuman agility. It's a vampire! You try to chase the creature back to its home, but the creature is too fast. However, because of the remoteness of the village, you know with absolute certainty that the vampire must be a resident of the village. You decide that your best course of action will be to visit all 100 residents of the town during the day. You know something about vampires and humans that will make your investigation possible; humans always tell the truth, but vampires always lie.\n\nIn the afternoon, you go from house to house, speaking with all 100 residents of \u0218irnea. You ask everyone the same question: \"How many vampires are living in \u0218irnea\". Everyone in the village gives the same response, \"At least one of us is a human.\"\n\nHow many residents of \u0218irnea have been turned into vampires?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 100", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "100", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9d191bce-651d-4746-be2d-7ef8ecadb9c2", "prompt": "Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec.\n\nWhat does Teal'c say in response to the question \"Isn't that hot?\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Extremely", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Extremely", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "54612da3-fd56-4941-80f4-5eb82330de25", "prompt": "The attached file shows the locomotives in the collection of a North American railroad museum. How many wheels do the listed steam locomotives have in total?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 60", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "60", "gaia_level": 2, "gaia_file": "54612da3-fd56-4941-80f4-5eb82330de25.xlsx", "source": "gaia-benchmark"}} +{"name": "ded28325-3447-4c56-860f-e497d6fb3577", "prompt": "This is a secret message my friend gave me. It says where we should meet for our picnic on Friday. The only problem is, it\u2019s encrypted in the Caesar cipher, so I can\u2019t read it. Can you tell me what it says? This is the message:\n\nZsmxsm sc sx Zyvilsec Zvkjk.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Picnic is in Ploybius Plaza.", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Picnic is in Ploybius Plaza.", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6359a0b1-8f7b-499b-9336-840f9ab90688", "prompt": "What is the area of the green polygon in the attached file? The numbers in purple represent the lengths of the side they are next to.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 39", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "39", "gaia_level": 2, "gaia_file": "6359a0b1-8f7b-499b-9336-840f9ab90688.png", "source": "gaia-benchmark"}} +{"name": "e961a717-6b25-4175-8a68-874d28190ee4", "prompt": "According to wikipedia, how many Asian countries still have a monarchy and access to the sea in 2021?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 12", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "12", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7cc4acfa-63fd-4acc-a1a1-e8e529e0a97f", "prompt": "The attached spreadsheet contains the sales of menu items for a regional fast-food chain. Which city had the greater total sales: Wharvton or Algrimand?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Wharvton", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Wharvton", "gaia_level": 2, "gaia_file": "7cc4acfa-63fd-4acc-a1a1-e8e529e0a97f.xlsx", "source": "gaia-benchmark"}} +{"name": "d700d50d-c707-4dca-90dc-4528cddd0c80", "prompt": "Who composed the song that was performed by a rooster and a hamster in separate animated videos at separate tempos with different lyrics? Answer using the format First name Last name.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Roger Miller", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Roger Miller", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65afbc8a-89ca-4ad5-8d62-355bb401f61d", "prompt": "You are given this Excel file as a map. You start on the START cell and move toward the END cell. You are allowed to move two cells per turn, and you may move up, down, left, or right. You may not move fewer than two cells, and you may not move backward. You must avoid moving onto any blue cells. On the eleventh turn, what is the 6-digit hex code (without prefix) of the color of the cell where you land after moving?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: F478A7", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "F478A7", "gaia_level": 1, "gaia_file": "65afbc8a-89ca-4ad5-8d62-355bb401f61d.xlsx", "source": "gaia-benchmark"}} +{"name": "851e570a-e3de-4d84-bcfa-cc85578baa59", "prompt": "I thought we could try a fun word puzzle together :)\n\nI've got a Boggle board here:\n\nABRL\nEITE\nIONS\nFPEI\n\nI'd like to know the longest word that can be generated from the board. Please find the longest English language word that can be generated from this board. If more than one word of the same length exists at the maximum word length, please report the longest word that comes first, alphabetically. Oh, and I know that there might be different wordlists available for Boggle, so let's please just use the words_alpha dictionary found at https://github.com/dwyl/english-words as the dictionary for our game.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Briniest", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Briniest", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cabe07ed-9eca-40ea-8ead-410ef5e83f91", "prompt": "What is the surname of the equine veterinarian mentioned in 1.E Exercises from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Louvrier", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Louvrier", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0a3cd321-3e76-4622-911b-0fda2e5d6b1a", "prompt": "According to the World Bank, which countries had gross savings of over 35% of GDP for every year in the period 2001-2010? Give your answer as a comma-separated list of countries in alphabetical order. Use the countries most common names in english when answering.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Brunei, China, Morocco, Singapore", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Brunei, China, Morocco, Singapore", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f2feb6a4-363c-4c09-a804-0db564eafd68", "prompt": "I\u2019m thinking about selling my home, so I want to learn more about how homes in my area sold recently. I live in Pearl City, Hawaii, which is on the island of Oahu. I know two homes near me that sold in 2022 were 2072 Akaikai Loop, and 2017 Komo Mai Drive. Find which of those homes sold for more in 2022, and tell me how much it sold for. Don\u2019t put commas or decimal places in the answer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 900000", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "900000", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3cef3a44-215e-4aed-8e3b-b1e3f08063b7", "prompt": "I'm making a grocery list for my mom, but she's a professor of botany and she's a real stickler when it comes to categorizing things. I need to add different foods to different categories on the grocery list, but if I make a mistake, she won't buy anything inserted in the wrong category. Here's the list I have so far:\n\nmilk, eggs, flour, whole bean coffee, Oreos, sweet potatoes, fresh basil, plums, green beans, rice, corn, bell pepper, whole allspice, acorns, broccoli, celery, zucchini, lettuce, peanuts\n\nI need to make headings for the fruits and vegetables. Could you please create a list of just the vegetables from my list? If you could do that, then I can figure out how to categorize the rest of the list into the appropriate categories. But remember that my mom is a real stickler, so make sure that no botanical fruits end up on the vegetable list, or she won't get them when she's at the store. Please alphabetize the list of vegetables, and place each item in a comma separated list.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: broccoli, celery, fresh basil, lettuce, sweet potatoes", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "broccoli, celery, fresh basil, lettuce, sweet potatoes", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50f58759-7bd6-406f-9b0d-5692beb2a926", "prompt": "How many times was a Twitter/X post cited as a reference on the english Wikipedia pages for each day of August in the last June 2023 versions of the pages?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "3", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0b260a57-3f3a-4405-9f29-6d7a1012dbfb", "prompt": "On ScienceDirect, what is the difference to 3 decimal places in the sample standard deviations of the number of Reference Works in each Life Science domain compared to Health Sciences as of 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.269", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0.269", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ed58682d-bc52-4baa-9eb0-4eb81e1edacc", "prompt": "What is the last word before the second chorus of the King of Pop's fifth single from his sixth studio album?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: stare", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "stare", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cca70ce6-1952-45d2-acd4-80c903b0bc49", "prompt": "Look at the attached image. The quiz is scored as follows:\n\nProblems that ask the student to add or subtract fractions: 5 points\nProblems that ask the student to multiply or divide fractions: 10 points\nProblems that ask the student to form an improper fraction: 15 points\nProblems that ask the student to form a mixed number: 20 points\n\nDue to a technical issue that delayed having students take the quiz, the teacher is giving everyone 5 bonus points.\n\nIf you graded the quiz in the attached image, how many points would the student have earned? There is no partial credit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 85", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "85", "gaia_level": 2, "gaia_file": "cca70ce6-1952-45d2-acd4-80c903b0bc49.png", "source": "gaia-benchmark"}} +{"name": "872bfbb1-9ccf-49f6-8c5f-aa22818ccd66", "prompt": "Which of the fruits shown in the 2008 painting \"Embroidery from Uzbekistan\" were served as part of the October 1949 breakfast menu for the ocean liner that was later used as a floating prop for the film \"The Last Voyage\"? Give the items as a comma-separated list, ordering them in clockwise order based on their arrangement in the painting starting from the 12 o'clock position. Use the plural form of each fruit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: pears, bananas", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "pears, bananas", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3", "prompt": "Hi, I'm making a pie but I could use some help with my shopping list. I have everything I need for the crust, but I'm not sure about the filling. I got the recipe from my friend Aditi, but she left it as a voice memo and the speaker on my phone is buzzing so I can't quite make out what she's saying. Could you please listen to the recipe and list all of the ingredients that my friend described? I only want the ingredients for the filling, as I have everything I need to make my favorite pie crust. I've attached the recipe as Strawberry pie.mp3.\n\nIn your response, please only list the ingredients, not any measurements. So if the recipe calls for \"a pinch of salt\" or \"two cups of ripe strawberries\" the ingredients on the list would be \"salt\" and \"ripe strawberries\".\n\nPlease format your response as a comma separated list of ingredients. Also, please alphabetize the ingredients.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: cornstarch, freshly squeezed lemon juice, granulated sugar, pure vanilla extract, ripe strawberries", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "cornstarch, freshly squeezed lemon juice, granulated sugar, pure vanilla extract, ripe strawberries", "gaia_level": 1, "gaia_file": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3.mp3", "source": "gaia-benchmark"}} +{"name": "b7f857e4-d8aa-4387-af2a-0e844df5b9d8", "prompt": "The attached image contains a Python script. Run the Python code against an array of strings, listed below. The output of the Python script will be a URL containing C++ source code. Compile and run this C++ code against the array [35, 12, 8, 99, 21, 5] and return the sum of the third and fifth integers in the sorted list.\n\narr = ['_alg', 'ghi', 'C++', 'jkl', 'tps', '/Q', 'pqr', 'stu', ':', '//', 'rose', 'vwx', 'yz1', '234', 'tta', '567', '890', 'cod', 'e.', 'or', 'g/', 'wiki', '/', 'ing', 'sort', 'abc' , 'or', 'it', 'hms', 'mno' , 'uic', 'ksort', '#', 'ht' ]", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 47", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "47", "gaia_level": 2, "gaia_file": "b7f857e4-d8aa-4387-af2a-0e844df5b9d8.png", "source": "gaia-benchmark"}} +{"name": "d8152ad6-e4d5-4c12-8bb7-8d57dc10c6de", "prompt": "I have the Standard plan in the image below, and I just uploaded 60 equally sized files and got a message that I'm 100GB over the limit. I have 980 more files of the same size to upload. What is the average additional cost per file in dollar that goes over my current plan limit rounded to the nearest cent if I have to upgrade to the minimum possible plan to store them all? Answer with the following format: x.xx", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.03", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0.03", "gaia_level": 2, "gaia_file": "d8152ad6-e4d5-4c12-8bb7-8d57dc10c6de.png", "source": "gaia-benchmark"}} +{"name": "67e8878b-5cef-4375-804e-e6291fdbe78a", "prompt": "The attached PDF lists accommodations in the resort community of Seahorse Island. Which type of accommodation has a higher average rating in Seahorse Island?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Hotels", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Hotels", "gaia_level": 2, "gaia_file": "67e8878b-5cef-4375-804e-e6291fdbe78a.pdf", "source": "gaia-benchmark"}} +{"name": "c3a79cfe-8206-451f-aca8-3fec8ebe51d3", "prompt": "The year is 2022. I am at the National Air and Space Museum east of the Potomac River. I want to go to Fire Station 301 DCA ARFF using the metro. I go in the wrong direction and end up at the station closest to Cleveland Elementary School. How many metro stations am I away from my original destination if I don't change lines? Your answer should be a numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "8", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "d0633230-7067-47a9-9dbf-ee11e0a2cdd6", "prompt": "In the Scikit-Learn July 2017 changelog, what other predictor base command received a bug fix? Just give the name, not a path.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: BaseLabelPropagation", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "BaseLabelPropagation", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "023e9d44-96ae-4eed-b912-244ee8c3b994", "prompt": "It's May 2023, and I'm about to drive across the U.S. from California to Maine. I always recycle my water bottles at the end of a trip, and I drink 5 12-ounce water bottles for every 100 miles I travel, rounded to the nearest 100. Assuming I follow I-40 from Los Angeles to Cincinnati, then take I-90 from Cincinnati to Augusta, how many dollars will I get back according to Wikipedia?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "305ac316-eef6-4446-960a-92d80d542f82", "prompt": "Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Wojciech", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Wojciech", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0e9e85b8-52b9-4de4-b402-5f635ab9631f", "prompt": "What is the latest chronological year date written in the image on the webpage found when following the first citation reference link on the latest version of Carl Nebel's Wikipedia page as of August 2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1927", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1927", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "20194330-9976-4043-8632-f8485c6c71b2", "prompt": "The YouTube channel Game Grumps began a Let\u2019s Play of the game Sonic the Hedgehog (2006) in the year 2012. Thirty seconds into the first episode, a phrase is shown on the screen in white letters on a red background. How many times does the letter \"E\" appear in this phrase?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "4", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2", "prompt": "This spreadsheet contains a list of clients for a retractable awning company. Each client has ordered a new awning for the back of their house within the last 90 days. The company makes different designs depending on whether the awning is made to block sunrises or sunsets. In this region, houses with odd-numbered street addresses face east, and houses with even-numbered street addresses face west. How many of these clients will be receiving the sunset awning design?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2.xlsx", "source": "gaia-benchmark"}} +{"name": "0383a3ee-47a7-41a4-b493-519bdefe0488", "prompt": "On the BBC Earth YouTube video of the Top 5 Silliest Animal Moments, what species of bird is featured?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Rockhopper penguin", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Rockhopper penguin", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65638e28-7f37-4fa7-b7b9-8c19bb609879", "prompt": "The book with the doi 10.1353/book.24372 concerns a certain neurologist. According to chapter 2 of the book, what author influenced this neurologist\u2019s belief in \u201cendopsychic myths\u201d? Give the last name only.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Kleinpaul", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Kleinpaul", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3ff6b7a9-a5bd-4412-ad92-0cd0d45c0fee", "prompt": "The longest-lived vertebrate is named after an island. According to Wikipedia as of January 1, 2021, what is the 2020 estimated population of that island, to the nearest thousand?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 56000", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "56000", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f918266a-b3e0-4914-865d-4faa564f1aef", "prompt": "What is the final numeric output from the attached Python code?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "0", "gaia_level": 1, "gaia_file": "f918266a-b3e0-4914-865d-4faa564f1aef.py", "source": "gaia-benchmark"}} +{"name": "708b99c5-e4a7-49cb-a5cf-933c8d46470d", "prompt": "On the DeepFruits fruit detection graph on Connected Papers from 2016, what feature caused the largest bubble to be the size it is?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Citations", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Citations", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0a65cb96-cb6e-4a6a-8aae-c1084f613456", "prompt": "During the first week of August 2015, one of the NASA Astronomy Pictures of the Day shows the lights of a city on the horizon. The namesake of this city also has a landmark building in Chicago named after him. What is the name of the architectural firm that designed this landmark building? Give the first name appearing in the name of the firm as of June 2023.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Holabird", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Holabird", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "11af4e1a-5f45-467d-9aeb-46f4bb0bf034", "prompt": "How many more blocks (also denoted as layers) in BERT base encoder than the encoder from the architecture proposed in Attention is All You Need?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "6", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e142056d-56ab-4352-b091-b56054bd1359", "prompt": "Bob was invited to participate in a game show, and he advanced to the final round. The final round offered Bob the chance to win a large sum by playing a game against the host. The host has 30 shiny prop coins, each of which is worth $1,000 if Bob manages to win them by playing the game. The host hides the coins in three different prize boxes and then shuffles their order. The only rule restricting the host's coin placement is that one box must contain at least 2 coins, and one box must contain 6 more coins than another box. In order to play, Bob must submit three guesses, one guess for the number of coins in each box. The box is then opened and the number of coins is revealed. If Bob's guess is a number greater than the number of coins in the box, Bob earns no coins. If Bob guesses a number equal to or less than the number of coins in the box, Bob wins a number of coins equal to his guess.\n\nIf Bob plays uses the optimal strategy, what's the minimum amount of money he can win from the game?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 16000", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "16000", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50ad0280-0819-4bd9-b275-5de32d3b5bcb", "prompt": "Pull out the sentence in the following 5x7 block of text. Read from left to right and use all of the letters in order:\n\nTHESE\nAGULL\nGLIDE\nDPEAC\nEFULL\nYTOMY\nCHAIR", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: The seagull glided peacefully to my chair.", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "The seagull glided peacefully to my chair.", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65da0822-a48a-4a68-bbad-8ed1b835a834", "prompt": "All of the individuals who formally held the position of United States secretary of homeland security prior to April 2019, excluding those who held the position in an acting capacity, have a bachelor's degree. Of the universities that these bachelor's degrees were from, which is the westernmost university and which is the easternmost university? Give them to me as a comma-separated list, I only want the name of the cities where the universities are located, with the westernmost city listed first.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Santa Clara, Boston", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Santa Clara, Boston", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "da52d699-e8d2-4dc5-9191-a2199e0b6a9b", "prompt": "The attached spreadsheet contains a list of books I read in the year 2022. What is the title of the book that I read the slowest, using the rate of words per day?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Out of the Silent Planet", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Out of the Silent Planet", "gaia_level": 3, "gaia_file": "da52d699-e8d2-4dc5-9191-a2199e0b6a9b.xlsx", "source": "gaia-benchmark"}} +{"name": "0bb3b44a-ede5-4db5-a520-4e844b0079c5", "prompt": "Consider the following symbols: \ud809\udc1c \ud809\udc10\ud809\udc1a\n\nThis is a number written using the Mesopotamian/Babylonian number system and represented with Sumerian cuneiform. Convert this number into Arabic numerals as a decimal number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 536", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "536", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7673d772-ef80-4f0f-a602-1bf4485c9b43", "prompt": "On Cornell Law School website's legal information institute, under the fifth section of federal rules alphabetically, what word was deleted in the last amendment to the first rule in the article that has \"witnesses\" in the most titles as of 2021?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: inference", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "inference", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "73c1b9fe-ee1d-4cf4-96ca-35c08f97b054", "prompt": "According to the USGS, in what year was the American Alligator first found west of Texas (not including Texas)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1954", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1954", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c365c1c7-a3db-4d5e-a9a1-66f56eae7865", "prompt": "Of the cities within the United States where U.S. presidents were born, which two are the farthest apart from the westernmost to the easternmost going east, giving the city names only? Give them to me in alphabetical order, in a comma-separated list", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Braintree, Honolulu", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Braintree, Honolulu", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ad2b4d70-9314-4fe6-bfbe-894a45f6055f", "prompt": "Eva Draconis has a personal website which can be accessed on her YouTube page. What is the meaning of the only symbol seen in the top banner that has a curved line that isn't a circle or a portion of a circle? Answer without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: War is not here this is a land of peace", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "War is not here this is a land of peace", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5b2a14e8-6e59-479c-80e3-4696e8980152", "prompt": "The brand that makes these harnesses the dogs are wearing in the attached pic shares stories from their ambassadors on their website. What meat is mentioned in the story added Dec 8th 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: bacon", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "bacon", "gaia_level": 3, "gaia_file": "5b2a14e8-6e59-479c-80e3-4696e8980152.jpg", "source": "gaia-benchmark"}} +{"name": "7d4a7d1d-cac6-44a8-96e8-ea9584a70825", "prompt": "According to Girls Who Code, how long did it take in years for the percentage of computer scientists that were women to change by 13% from a starting point of 37%?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 22", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "22", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dc22a632-937f-4e6a-b72f-ba0ff3f5ff97", "prompt": "What was the complete title of the book in which two James Beard Award winners recommended the restaurant where Ali Khan enjoyed a New Mexican staple in his cost-conscious TV show that started in 2015? Write the numbers in plain text if there are some in the title.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Five Hundred Things To Eat Before It's Too Late: and the Very Best Places to Eat Them", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Five Hundred Things To Eat Before It's Too Late: and the Very Best Places to Eat Them", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e2d69698-bc99-4e85-9880-67eaccd66e6c", "prompt": "As of August 2023, who is the only winner of the US version of Survivor to be born in the month of May?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Michele Fitzgerald", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Michele Fitzgerald", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3f57289b-8c60-48be-bd80-01f8099ca449", "prompt": "How many at bats did the Yankee with the most walks in the 1977 regular season have that same season?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 519", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "519", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a56f1527-3abf-41d6-91f8-7296d6336c3f", "prompt": "The cover of the August 2021 issue of Vogue shows a famous landmark in the background behind some trees. How tall is this monument in yards, rounded to the nearest yard? Give the number only.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 185", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "185", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "23dd907f-1261-4488-b21c-e9185af91d5e", "prompt": "In Audre Lorde\u2019s poem \u201cFather Son and Holy Ghost\u201d, what is the number of the stanza in which some lines are indented?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "2", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "42d4198c-5895-4f0a-b0c0-424a66465d83", "prompt": "I'm curious about how much information is available for popular video games before their release. Find the Wikipedia page for the 2019 game that won the British Academy Games Awards. How many revisions did that page have before the month listed as the game's release date on that Wikipedia page (as of the most recent entry from 2022)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 60", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "60", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "edd4d4f2-1a58-45c4-b038-67337af4e029", "prompt": "The attached spreadsheet lists the locomotives owned by a local railroad museum. What is the typical American name for the type of locomotive this museum uses for the Murder Mystery Express?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Berkshire", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Berkshire", "gaia_level": 2, "gaia_file": "edd4d4f2-1a58-45c4-b038-67337af4e029.xlsx", "source": "gaia-benchmark"}} +{"name": "a26649c6-1cb2-470a-871e-6910c64c3e53", "prompt": "What is the absolute difference in tens of thousands between the population of chinstrap penguins on the Wikipedia page for penguin species populations as of the end of 2018 and the population recorded in the Nature.com \"global population assessment of the Chinstrap penguin\" article from 2020, assuming two penguins per breeding pair?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 116", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "116", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4d0aa727-86b1-406b-9b33-f870dd14a4a5", "prompt": "The attached file lists the locomotives owned by a local railroad museum. It gives each locomotive\u2019s identifying number, operating status, and the name of the daily excursion it heads, if operational. What are the odds that today\u2019s Sunset Picnic Trip will use a steam locomotive? Assume that each day\u2019s excursion picks one of its assigned locomotives at random, and express the answer in the form \u201c1 in 4\u201d, \u201c1 in 5\u201d, etc.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1 in 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1 in 3", "gaia_level": 2, "gaia_file": "4d0aa727-86b1-406b-9b33-f870dd14a4a5.xlsx", "source": "gaia-benchmark"}} +{"name": "1f975693-876d-457b-a649-393859e79bf3", "prompt": "Hi, I was out sick from my classes on Friday, so I'm trying to figure out what I need to study for my Calculus mid-term next week. My friend from class sent me an audio recording of Professor Willowbrook giving out the recommended reading for the test, but my headphones are broken :(\n\nCould you please listen to the recording for me and tell me the page numbers I'm supposed to go over? I've attached a file called Homework.mp3 that has the recording. Please provide just the page numbers as a comma-delimited list. And please provide the list in ascending order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 132, 133, 134, 197, 245", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "132, 133, 134, 197, 245", "gaia_level": 1, "gaia_file": "1f975693-876d-457b-a649-393859e79bf3.mp3", "source": "gaia-benchmark"}} +{"name": "d5141ca5-e7a0-469f-bf3e-e773507c86e2", "prompt": "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect? Answer using the format DD/MM/YYYY.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 19/02/2009", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "19/02/2009", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9e1fc53b-46ff-49a1-9d05-9e6faac34cc5", "prompt": "A 5-man group made up of one tank, one healer, and three DPS is doing a dungeon that was just released in World of Warcraft. Two are plate wearers and two are cloth wearers. At the final boss, both the tank and the healer are casting holy spells. Ice and fire are being used, each one by a different DPS. A bear from the group is attacking the boss. Metamorphosis is cast. The Kilt of the Forgotten One drops as loot, but no one can use it. If all classes were using their class abilities and all classes are unique, what are the five classes in the group in alphabetical order separated by commas?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Death Knight, Hunter, Paladin, Priest, Warlock", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Death Knight, Hunter, Paladin, Priest, Warlock", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "840bfca7-4f7b-481a-8794-c560c340185d", "prompt": "On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 80GSFC21M0002", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "80GSFC21M0002", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "1dcc160f-c187-48c2-b68e-319bd4354f3d", "prompt": "According to Openreview.net, at the NeurIPS 2022 Conference, how many papers by an author named Yuri were accepted with a \"certain\" recommendation?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b2c257e0-3ad7-4f05-b8e3-d9da973be36e", "prompt": "If this whole pint is made up of ice cream, how many percent above or below the US federal standards for butterfat content is it when using the standards as reported by Wikipedia in 2020? Answer as + or - a number rounded to one decimal place.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: +4.6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "+4.6", "gaia_level": 2, "gaia_file": "b2c257e0-3ad7-4f05-b8e3-d9da973be36e.jpg", "source": "gaia-benchmark"}} +{"name": "e0c10771-d627-4fd7-9694-05348e54ee36", "prompt": "Take the gender split from the 2011 Bulgarian census about those who have completed tertiary education. Subtract the smaller number from the larger number, then return the difference in thousands of women. So if there were 30.1 thousand more men, you'd give \"30.1\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 234.9", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "234.9", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a0068077-79f4-461a-adfe-75c1a4148545", "prompt": "What was the actual enrollment count of the clinical trial on H. pylori in acne vulgaris patients from Jan-May 2018 as listed on the NIH website?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 90", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "90", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e29834fd-413a-455c-a33e-c3915b07401c", "prompt": "I'd like to learn more about some popular reality television competition shows. As of the end of the 44th season of the American version of Survivor, how many more unique winners have there been compared to the number of winners of American Idol?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 21", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "21", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "bda648d7-d618-4883-88f4-3466eabd860e", "prompt": "Where were the Vietnamese specimens described by Kuznetzov in Nedoshivina's 2010 paper eventually deposited? Just give me the city name without abbreviations.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Saint Petersburg", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Saint Petersburg", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50ec8903-b81f-4257-9450-1085afd2c319", "prompt": "A standard Rubik\u2019s cube has been broken into cubes making up its sides. The cubes are jumbled, and one is removed. There are 6 cubes with one colored face, 12 edge cubes with two colored faces, and 8 corner cubes with three colored faces. All blue cubes have been found. All cubes directly left, right, above, and below the orange center cube have been found, along with the center cube. The green corners have all been found, along with all green that borders yellow. For all orange cubes found, the opposite face\u2019s cubes have been found. The removed cube has two colors on its faces. What are they? Answer using a comma separated list, with the colors ordered alphabetically.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: green, white", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "green, white", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cf106601-ab4f-4af9-b045-5295fe67b37d", "prompt": "What country had the least number of athletes at the 1928 Summer Olympics? If there's a tie for a number of athletes, return the first in alphabetical order. Give the IOC country code as your answer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: CUB", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "CUB", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5f982798-16b9-4051-ab57-cfc7ebdb2a91", "prompt": "I read a paper about multiwavelength observations of fast radio bursts back in March 2021 on Arxiv, and it had a fascinating diagram of an X-ray time profile. There was a similar burst-1 diagram in another paper from one of the same authors about fast radio bursts back in July 2020, but I can't recall what the difference in seconds in the measured time span was. How many more seconds did one measure than the other? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.2", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.2", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a0c07678-e491-4bbc-8f0b-07405144218f", "prompt": "Who are the pitchers with the number before and after Taish\u014d Tamai's number as of July 2023? Give them to me in the form Pitcher Before, Pitcher After, use their last names only, in Roman characters.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Yoshida, Uehara", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Yoshida, Uehara", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7bd855d8-463d-4ed5-93ca-5fe35145f733", "prompt": "The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 89706.00", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "89706.00", "gaia_level": 1, "gaia_file": "7bd855d8-463d-4ed5-93ca-5fe35145f733.xlsx", "source": "gaia-benchmark"}} +{"name": "5a0c1adf-205e-4841-a666-7c3ef95def9d", "prompt": "What is the first name of the only Malko Competition recipient from the 20th Century (after 1977) whose nationality on record is a country that no longer exists?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Claus", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Claus", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0512426f-4d28-49f0-be77-06d05daec096", "prompt": "In the YouTube 360 VR video from March 2018 narrated by the voice actor of Lord of the Rings' Gollum, what number was mentioned by the narrator directly after dinosaurs were first shown in the video?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 100000000", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "100000000", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0bdb7c40-671d-4ad1-9ce3-986b159c0ddc", "prompt": "In NASA's Astronomy Picture of the Day on 2006 January 21, two astronauts are visible, with one appearing much smaller than the other. As of August 2023, out of the astronauts in the NASA Astronaut Group that the smaller astronaut was a member of, which one spent the least time in space, and how many minutes did he spend in space, rounded to the nearest minute? Exclude any astronauts who did not spend any time in space. Give the last name of the astronaut, separated from the number of minutes by a semicolon.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: White; 5876", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "White; 5876", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "08c0b6e9-1b43-4c2e-ae55-4e3fce2c2715", "prompt": "In the film Goldfinger, what color was the object that James Bond concealed himself and his companion Pussy Galore at the end of the film? If there are multiple colors, put them in a comma-separated list in alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: orange, white", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "orange, white", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "db4fd70a-2d37-40ea-873f-9433dc5e301f", "prompt": "As of May 2023, how many stops are between South Station and Windsor Gardens on MBTA\u2019s Franklin-Foxboro line (not included)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 10", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "10", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "853c8244-429e-46ca-89f2-addf40dfb2bd", "prompt": "In the 2015 Metropolitan Museum of Art exhibition titled after the Chinese zodiac animal of 2015, how many of the \"twelve animals of the Chinese zodiac\" have a hand visible?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 11", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "11", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7a4a336d-dcfa-45a0-b014-824c7619e8de", "prompt": "At the two-minute mark in the YouTube video uploaded by the channel \u201cGameGrumps\u201d on May 14, 2017 as part of their playthrough of the game Mario Kart 8 Deluxe, the shows\u2019 hosts are competing on one of the game\u2019s racetracks. What was the world record time for that track in the game\u2019s 150cc mode as of June 7, 2023? Express your answer in minutes and seconds, rounding the seconds to the nearest hundredth, e.g. 1:01.001.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1:41.614", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1:41.614", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} diff --git a/src/flow/experiments/data/tasks/gaia_level1.jsonl b/src/flow/experiments/data/tasks/gaia_level1.jsonl new file mode 100644 index 0000000000000000000000000000000000000000..82ce1418851bb1bd9ff07aeca3766884a9f25987 --- /dev/null +++ b/src/flow/experiments/data/tasks/gaia_level1.jsonl @@ -0,0 +1,106 @@ +{"name": "e1fc63a2-da7a-432f-be78-7c4a95598703", "prompt": "If Eliud Kipchoge could maintain his record-making marathon pace indefinitely, how many thousand hours would it take him to run the distance between the Earth and the Moon its closest approach? Please use the minimum perigee value on the Wikipedia page for the Moon when carrying out your calculation. Round your result to the nearest 1000 hours and do not use any comma separators if necessary.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "17", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8e867cd7-cff9-4e6c-867a-ff5ddc2550be", "prompt": "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ec09fa32-d03f-4bf8-84b0-1f16922c3ae4", "prompt": "Here's a fun riddle that I think you'll enjoy.\n\nYou have been selected to play the final round of the hit new game show \"Pick That Ping-Pong\". In this round, you will be competing for a large cash prize. Your job will be to pick one of several different numbered ping-pong balls, and then the game will commence. The host describes how the game works.\n\nA device consisting of a winding clear ramp and a series of pistons controls the outcome of the game. The ramp feeds balls onto a platform. The platform has room for three ping-pong balls at a time. The three balls on the platform are each aligned with one of three pistons. At each stage of the game, one of the three pistons will randomly fire, ejecting the ball it strikes. If the piston ejects the ball in the first position on the platform the balls in the second and third position on the platform each advance one space, and the next ball on the ramp advances to the third position. If the piston ejects the ball in the second position, the ball in the first position is released and rolls away, the ball in the third position advances two spaces to occupy the first position, and the next two balls on the ramp advance to occupy the second and third positions on the platform. If the piston ejects the ball in the third position, the ball in the first position is released and rolls away, the ball in the second position advances one space to occupy the first position, and the next two balls on the ramp advance to occupy the second and third positions on the platform.\n\nThe ramp begins with 100 numbered ping-pong balls, arranged in ascending order from 1 to 100. The host activates the machine and the first three balls, numbered 1, 2, and 3, advance to the platform. Before the random firing of the pistons begins, you are asked which of the 100 balls you would like to pick. If your pick is ejected by one of the pistons, you win the grand prize, $10,000.\n\nWhich ball should you choose to maximize your odds of winning the big prize? Please provide your answer as the number of the ball selected.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5d0080cb-90d7-4712-bc33-848150e917d3", "prompt": "What was the volume in m^3 of the fish bag that was calculated in the University of Leicester paper \"Can Hiccup Supply Enough Fish to Maintain a Dragon\u2019s Diet?\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.1777", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "0.1777", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a1e91b78-d3d8-4675-bb8d-62741b4b68a6", "prompt": "In the video https://www.youtube.com/watch?v=L1vXCYZAYYM, what is the highest number of bird species to be on camera simultaneously?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "46719c30-f4c3-4cad-be07-d5cb21eee6bb", "prompt": "Of the authors (First M. Last) that worked on the paper \"Pie Menus or Linear Menus, Which Is Better?\" in 2015, what was the title of the first paper authored by the one that had authored prior papers?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Mapping Human Oriented Information to Software Agents for Online Systems Usage", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Mapping Human Oriented Information to Software Agents for Online Systems Usage", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4b6bb5f7-f634-410e-815d-e673ab7f8632", "prompt": "In Series 9, Episode 11 of Doctor Who, the Doctor is trapped inside an ever-shifting maze. What is this location called in the official script for the episode? Give the setting exactly as it appears in the first scene heading.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: THE CASTLE", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "THE CASTLE", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cffe0e32-c9a6-4c52-9877-78ceb4aaa9fb", "prompt": "An office held a Secret Santa gift exchange where each of its twelve employees was assigned one other employee in the group to present with a gift. Each employee filled out a profile including three likes or hobbies. On the day of the gift exchange, only eleven gifts were given, each one specific to one of the recipient's interests. Based on the information in the document, who did not give a gift?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Fred", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Fred", "gaia_level": 1, "gaia_file": "cffe0e32-c9a6-4c52-9877-78ceb4aaa9fb.docx", "source": "gaia-benchmark"}} +{"name": "2d83110e-a098-4ebb-9987-066c06fa42d0", "prompt": ".rewsna eht sa \"tfel\" drow eht fo etisoppo eht etirw ,ecnetnes siht dnatsrednu uoy fI", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Right", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Right", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5cfb274c-0207-4aa7-9575-6ac0bd95d9b2", "prompt": "Each cell in the attached spreadsheet represents a plot of land. The color of the cell indicates who owns that plot. Green cells are plots owned by Earl Smith. Can Earl walk through every plot he owns (and no other plots) and return to his starting plot without backtracking? For this question, consider backtracking to be any instance where Earl would enter a plot of land he had already entered since leaving his starting plot.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: No", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "No", "gaia_level": 1, "gaia_file": "5cfb274c-0207-4aa7-9575-6ac0bd95d9b2.xlsx", "source": "gaia-benchmark"}} +{"name": "27d5d136-8563-469e-92bf-fd103c28b57c", "prompt": "\u00ac(A \u2227 B) \u2194 (\u00acA \u2228 \u00acB)\n\u00ac(A \u2228 B) \u2194 (\u00acA \u2227 \u00acB)\n(A \u2192 B) \u2194 (\u00acB \u2192 \u00acA)\n(A \u2192 B) \u2194 (\u00acA \u2228 B)\n(\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)\n\u00ac(A \u2192 B) \u2194 (A \u2227 \u00acB)\n\nWhich of the above is not logically equivalent to the rest? Provide the full statement that doesn't fit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: (\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "(\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dc28cf18-6431-458b-83ef-64b3ce566c10", "prompt": "My family reunion is this week, and I was assigned the mashed potatoes to bring. The attendees include my married mother and father, my twin brother and his family, my aunt and her family, my grandma and her brother, her brother's daughter, and his daughter's family. All the adults but me have been married, and no one is divorced or remarried, but my grandpa and my grandma's sister-in-law passed away last year. All living spouses are attending. My brother has two children that are still kids, my aunt has one six-year-old, and my grandma's brother's daughter has three kids under 12. I figure each adult will eat about 1.5 potatoes of mashed potatoes and each kid will eat about 1/2 a potato of mashed potatoes, except my second cousins don't eat carbs. The average potato is about half a pound, and potatoes are sold in 5-pound bags. How many whole bags of potatoes do I need? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "2", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b816bfce-3d80-4913-a07d-69b752ce6377", "prompt": "In Emily Midkiff's June 2014 article in a journal named for the one of Hreidmar's sons that guarded his house, what word was quoted from two different authors in distaste for the nature of dragon depictions?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: fluffy", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "fluffy", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "72e110e7-464c-453c-a309-90a95aed6538", "prompt": "Under DDC 633 on Bielefeld University Library's BASE, as of 2020, from what country was the unknown language article with a flag unique from the others?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Guatemala", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Guatemala", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "42576abe-0deb-4869-8c63-225c2d75a95a", "prompt": "In the fictional language of Tizin, basic sentences are arranged with the Verb first, followed by the direct object, followed by the subject of the sentence. I want to express my love for apples to my Tizin friend. \n\nThe word that indicates oneself is \"Pa\" is the nominative form, \"Mato\" is the accusative form, and \"Sing\" is the genitive form. \n\nThe root verb that indicates an intense like for something is \"Maktay\". When it is used in the present, it is used in it's root form, when it is used in the preterit past, it is \"Tay\", and when it is used in the imperfect past, it is \"Aktay\". It is used differently than in English, and is better translated as \"is pleasing to\", meaning that the thing doing the liking is actually the object of the sentence rather than the subject.\n\nThe word for apples is borrowed from English in Tizin, and so it is \"Apple\" is the nominative form, \"Zapple\" is the accusative form, and \"Izapple\" is the genitive form. \n\nPlease translate \"I like apples\" to Tizin.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Maktay mato apple", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Maktay mato apple", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b415aba4-4b68-4fc6-9b89-2c812e55a3e1", "prompt": "In Nature journal's Scientific Reports conference proceedings from 2012, in the article that did not mention plasmons or plasmonics, what nano-compound is studied? Don't use the prefix nano in your answer if there is one.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: diamond", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "diamond", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cca530fc-4052-43b2-b130-b30968d8aa44", "prompt": "Review the chess position provided in the image. It is black's turn. Provide the correct next move for black which guarantees a win. Please provide your response in algebraic notation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Rd5", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Rd5", "gaia_level": 1, "gaia_file": "cca530fc-4052-43b2-b130-b30968d8aa44.png", "source": "gaia-benchmark"}} +{"name": "935e2cff-ae78-4218-b3f5-115589b19dae", "prompt": "In the year 2022, and before December, what does \"R\" stand for in the three core policies of the type of content that was violated in the public logs on the Legume Wikipedia page?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: research", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "research", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4fc2f1ae-8625-45b5-ab34-ad4433bc21f8", "prompt": "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: FunkMonk", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "FunkMonk", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5188369a-3bbe-43d8-8b94-11558f909a08", "prompt": "What writer is quoted by Merriam-Webster for the Word of the Day from June 27, 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Annie Levin", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Annie Levin", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6f37996b-2ac7-44b0-8e68-6d28256631b4", "prompt": "Given this table defining * on the set S = {a, b, c, d, e}\n\n|*|a|b|c|d|e|\n|---|---|---|---|---|---|\n|a|a|b|c|b|d|\n|b|b|c|a|e|c|\n|c|c|a|b|b|a|\n|d|b|e|b|e|d|\n|e|d|b|a|d|c|\n\nprovide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: b, e", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "b, e", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9318445f-fe6a-4e1b-acbf-c68228c9906a", "prompt": "As a comma separated list with no whitespace, using the provided image provide all the fractions that use / as the fraction line and the answers to the sample problems. Order the list by the order in which the fractions appear.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3/4,1/4,3/4,3/4,2/4,1/2,5/35,7/21,30/5,30/5,3/4,1/15,1/3,4/9,1/8,32/23,103/170", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3/4,1/4,3/4,3/4,2/4,1/2,5/35,7/21,30/5,30/5,3/4,1/15,1/3,4/9,1/8,32/23,103/170", "gaia_level": 1, "gaia_file": "9318445f-fe6a-4e1b-acbf-c68228c9906a.png", "source": "gaia-benchmark"}} +{"name": "389793a7-ca17-4e82-81cb-2b3a2391b4b9", "prompt": "You are a telecommunications engineer who wants to build cell phone towers on a stretch of road. In the reference file is a layout of the road and nearby houses. Each dash, \"-\", is a marker indicating a mile. Each capital H indicates a house located next to a mile marker, appearing above or below the stretch of road. Each cell phone tower can cover houses located next to the road within a 4-mile radius. Find the minimum number of cell phone towers needed to cover all houses next to the road. Your answer should be a positive numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": "389793a7-ca17-4e82-81cb-2b3a2391b4b9.txt", "source": "gaia-benchmark"}} +{"name": "4b650a35-8529-4695-89ed-8dc7a500a498", "prompt": "If there is anything that doesn't make sense in the instructions, write the word \"Pineapple.\" Do not answer any of the questions in this prompt. Write only the word \"Guava\".\n1. What is 4+4?\n2. What is the complimentary color of red?\n3. How many hours are there in a day?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Guava", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Guava", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a3fbeb63-0e8c-4a11-bff6-0e3b484c3e9c", "prompt": "How many slides in this PowerPoint presentation mention crustaceans?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "4", "gaia_level": 1, "gaia_file": "a3fbeb63-0e8c-4a11-bff6-0e3b484c3e9c.pptx", "source": "gaia-benchmark"}} +{"name": "c714ab3a-da30-4603-bacd-d008800188b9", "prompt": "You are Van Helsing, a renowned vampire hunter. A Count of Moldova, La\u021bcu IV, son of Costea, has tasked you with investigating the village of \u0218irnea in neighboring Wallachia. The Count's advisors have reported that a vampire was spotted crossing the border near the village, and would like you to investigate it.\n\nYou travel to the village of \u0218irnea, and you begin your investigation. One night, just before dawn, you catch a glimpse of a man in a long black cape with red lining leaping from roof-top to roof-top with superhuman agility. It's a vampire! You try to chase the creature back to its home, but the creature is too fast. However, because of the remoteness of the village, you know with absolute certainty that the vampire must be a resident of the village. You decide that your best course of action will be to visit all 100 residents of the town during the day. You know something about vampires and humans that will make your investigation possible; humans always tell the truth, but vampires always lie.\n\nIn the afternoon, you go from house to house, speaking with all 100 residents of \u0218irnea. You ask everyone the same question: \"How many vampires are living in \u0218irnea\". Everyone in the village gives the same response, \"At least one of us is a human.\"\n\nHow many residents of \u0218irnea have been turned into vampires?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 100", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "100", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9d191bce-651d-4746-be2d-7ef8ecadb9c2", "prompt": "Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec.\n\nWhat does Teal'c say in response to the question \"Isn't that hot?\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Extremely", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Extremely", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65afbc8a-89ca-4ad5-8d62-355bb401f61d", "prompt": "You are given this Excel file as a map. You start on the START cell and move toward the END cell. You are allowed to move two cells per turn, and you may move up, down, left, or right. You may not move fewer than two cells, and you may not move backward. You must avoid moving onto any blue cells. On the eleventh turn, what is the 6-digit hex code (without prefix) of the color of the cell where you land after moving?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: F478A7", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "F478A7", "gaia_level": 1, "gaia_file": "65afbc8a-89ca-4ad5-8d62-355bb401f61d.xlsx", "source": "gaia-benchmark"}} +{"name": "cabe07ed-9eca-40ea-8ead-410ef5e83f91", "prompt": "What is the surname of the equine veterinarian mentioned in 1.E Exercises from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Louvrier", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Louvrier", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3cef3a44-215e-4aed-8e3b-b1e3f08063b7", "prompt": "I'm making a grocery list for my mom, but she's a professor of botany and she's a real stickler when it comes to categorizing things. I need to add different foods to different categories on the grocery list, but if I make a mistake, she won't buy anything inserted in the wrong category. Here's the list I have so far:\n\nmilk, eggs, flour, whole bean coffee, Oreos, sweet potatoes, fresh basil, plums, green beans, rice, corn, bell pepper, whole allspice, acorns, broccoli, celery, zucchini, lettuce, peanuts\n\nI need to make headings for the fruits and vegetables. Could you please create a list of just the vegetables from my list? If you could do that, then I can figure out how to categorize the rest of the list into the appropriate categories. But remember that my mom is a real stickler, so make sure that no botanical fruits end up on the vegetable list, or she won't get them when she's at the store. Please alphabetize the list of vegetables, and place each item in a comma separated list.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: broccoli, celery, fresh basil, lettuce, sweet potatoes", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "broccoli, celery, fresh basil, lettuce, sweet potatoes", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3", "prompt": "Hi, I'm making a pie but I could use some help with my shopping list. I have everything I need for the crust, but I'm not sure about the filling. I got the recipe from my friend Aditi, but she left it as a voice memo and the speaker on my phone is buzzing so I can't quite make out what she's saying. Could you please listen to the recipe and list all of the ingredients that my friend described? I only want the ingredients for the filling, as I have everything I need to make my favorite pie crust. I've attached the recipe as Strawberry pie.mp3.\n\nIn your response, please only list the ingredients, not any measurements. So if the recipe calls for \"a pinch of salt\" or \"two cups of ripe strawberries\" the ingredients on the list would be \"salt\" and \"ripe strawberries\".\n\nPlease format your response as a comma separated list of ingredients. Also, please alphabetize the ingredients.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: cornstarch, freshly squeezed lemon juice, granulated sugar, pure vanilla extract, ripe strawberries", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "cornstarch, freshly squeezed lemon juice, granulated sugar, pure vanilla extract, ripe strawberries", "gaia_level": 1, "gaia_file": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3.mp3", "source": "gaia-benchmark"}} +{"name": "d0633230-7067-47a9-9dbf-ee11e0a2cdd6", "prompt": "In the Scikit-Learn July 2017 changelog, what other predictor base command received a bug fix? Just give the name, not a path.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: BaseLabelPropagation", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "BaseLabelPropagation", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "305ac316-eef6-4446-960a-92d80d542f82", "prompt": "Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Wojciech", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Wojciech", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0383a3ee-47a7-41a4-b493-519bdefe0488", "prompt": "On the BBC Earth YouTube video of the Top 5 Silliest Animal Moments, what species of bird is featured?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Rockhopper penguin", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Rockhopper penguin", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f918266a-b3e0-4914-865d-4faa564f1aef", "prompt": "What is the final numeric output from the attached Python code?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "0", "gaia_level": 1, "gaia_file": "f918266a-b3e0-4914-865d-4faa564f1aef.py", "source": "gaia-benchmark"}} +{"name": "11af4e1a-5f45-467d-9aeb-46f4bb0bf034", "prompt": "How many more blocks (also denoted as layers) in BERT base encoder than the encoder from the architecture proposed in Attention is All You Need?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "6", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e142056d-56ab-4352-b091-b56054bd1359", "prompt": "Bob was invited to participate in a game show, and he advanced to the final round. The final round offered Bob the chance to win a large sum by playing a game against the host. The host has 30 shiny prop coins, each of which is worth $1,000 if Bob manages to win them by playing the game. The host hides the coins in three different prize boxes and then shuffles their order. The only rule restricting the host's coin placement is that one box must contain at least 2 coins, and one box must contain 6 more coins than another box. In order to play, Bob must submit three guesses, one guess for the number of coins in each box. The box is then opened and the number of coins is revealed. If Bob's guess is a number greater than the number of coins in the box, Bob earns no coins. If Bob guesses a number equal to or less than the number of coins in the box, Bob wins a number of coins equal to his guess.\n\nIf Bob plays uses the optimal strategy, what's the minimum amount of money he can win from the game?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 16000", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "16000", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50ad0280-0819-4bd9-b275-5de32d3b5bcb", "prompt": "Pull out the sentence in the following 5x7 block of text. Read from left to right and use all of the letters in order:\n\nTHESE\nAGULL\nGLIDE\nDPEAC\nEFULL\nYTOMY\nCHAIR", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: The seagull glided peacefully to my chair.", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "The seagull glided peacefully to my chair.", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7673d772-ef80-4f0f-a602-1bf4485c9b43", "prompt": "On Cornell Law School website's legal information institute, under the fifth section of federal rules alphabetically, what word was deleted in the last amendment to the first rule in the article that has \"witnesses\" in the most titles as of 2021?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: inference", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "inference", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c365c1c7-a3db-4d5e-a9a1-66f56eae7865", "prompt": "Of the cities within the United States where U.S. presidents were born, which two are the farthest apart from the westernmost to the easternmost going east, giving the city names only? Give them to me in alphabetical order, in a comma-separated list", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Braintree, Honolulu", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Braintree, Honolulu", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7d4a7d1d-cac6-44a8-96e8-ea9584a70825", "prompt": "According to Girls Who Code, how long did it take in years for the percentage of computer scientists that were women to change by 13% from a starting point of 37%?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 22", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "22", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dc22a632-937f-4e6a-b72f-ba0ff3f5ff97", "prompt": "What was the complete title of the book in which two James Beard Award winners recommended the restaurant where Ali Khan enjoyed a New Mexican staple in his cost-conscious TV show that started in 2015? Write the numbers in plain text if there are some in the title.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Five Hundred Things To Eat Before It's Too Late: and the Very Best Places to Eat Them", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Five Hundred Things To Eat Before It's Too Late: and the Very Best Places to Eat Them", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3f57289b-8c60-48be-bd80-01f8099ca449", "prompt": "How many at bats did the Yankee with the most walks in the 1977 regular season have that same season?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 519", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "519", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "23dd907f-1261-4488-b21c-e9185af91d5e", "prompt": "In Audre Lorde\u2019s poem \u201cFather Son and Holy Ghost\u201d, what is the number of the stanza in which some lines are indented?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "2", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "1f975693-876d-457b-a649-393859e79bf3", "prompt": "Hi, I was out sick from my classes on Friday, so I'm trying to figure out what I need to study for my Calculus mid-term next week. My friend from class sent me an audio recording of Professor Willowbrook giving out the recommended reading for the test, but my headphones are broken :(\n\nCould you please listen to the recording for me and tell me the page numbers I'm supposed to go over? I've attached a file called Homework.mp3 that has the recording. Please provide just the page numbers as a comma-delimited list. And please provide the list in ascending order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 132, 133, 134, 197, 245", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "132, 133, 134, 197, 245", "gaia_level": 1, "gaia_file": "1f975693-876d-457b-a649-393859e79bf3.mp3", "source": "gaia-benchmark"}} +{"name": "840bfca7-4f7b-481a-8794-c560c340185d", "prompt": "On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 80GSFC21M0002", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "80GSFC21M0002", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a0068077-79f4-461a-adfe-75c1a4148545", "prompt": "What was the actual enrollment count of the clinical trial on H. pylori in acne vulgaris patients from Jan-May 2018 as listed on the NIH website?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 90", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "90", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "bda648d7-d618-4883-88f4-3466eabd860e", "prompt": "Where were the Vietnamese specimens described by Kuznetzov in Nedoshivina's 2010 paper eventually deposited? Just give me the city name without abbreviations.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Saint Petersburg", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Saint Petersburg", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50ec8903-b81f-4257-9450-1085afd2c319", "prompt": "A standard Rubik\u2019s cube has been broken into cubes making up its sides. The cubes are jumbled, and one is removed. There are 6 cubes with one colored face, 12 edge cubes with two colored faces, and 8 corner cubes with three colored faces. All blue cubes have been found. All cubes directly left, right, above, and below the orange center cube have been found, along with the center cube. The green corners have all been found, along with all green that borders yellow. For all orange cubes found, the opposite face\u2019s cubes have been found. The removed cube has two colors on its faces. What are they? Answer using a comma separated list, with the colors ordered alphabetically.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: green, white", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "green, white", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cf106601-ab4f-4af9-b045-5295fe67b37d", "prompt": "What country had the least number of athletes at the 1928 Summer Olympics? If there's a tie for a number of athletes, return the first in alphabetical order. Give the IOC country code as your answer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: CUB", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "CUB", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a0c07678-e491-4bbc-8f0b-07405144218f", "prompt": "Who are the pitchers with the number before and after Taish\u014d Tamai's number as of July 2023? Give them to me in the form Pitcher Before, Pitcher After, use their last names only, in Roman characters.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Yoshida, Uehara", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Yoshida, Uehara", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7bd855d8-463d-4ed5-93ca-5fe35145f733", "prompt": "The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 89706.00", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "89706.00", "gaia_level": 1, "gaia_file": "7bd855d8-463d-4ed5-93ca-5fe35145f733.xlsx", "source": "gaia-benchmark"}} +{"name": "5a0c1adf-205e-4841-a666-7c3ef95def9d", "prompt": "What is the first name of the only Malko Competition recipient from the 20th Century (after 1977) whose nationality on record is a country that no longer exists?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Claus", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Claus", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e1fc63a2-da7a-432f-be78-7c4a95598703", "prompt": "If Eliud Kipchoge could maintain his record-making marathon pace indefinitely, how many thousand hours would it take him to run the distance between the Earth and the Moon its closest approach? Please use the minimum perigee value on the Wikipedia page for the Moon when carrying out your calculation. Round your result to the nearest 1000 hours and do not use any comma separators if necessary.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "17", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8e867cd7-cff9-4e6c-867a-ff5ddc2550be", "prompt": "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ec09fa32-d03f-4bf8-84b0-1f16922c3ae4", "prompt": "Here's a fun riddle that I think you'll enjoy.\n\nYou have been selected to play the final round of the hit new game show \"Pick That Ping-Pong\". In this round, you will be competing for a large cash prize. Your job will be to pick one of several different numbered ping-pong balls, and then the game will commence. The host describes how the game works.\n\nA device consisting of a winding clear ramp and a series of pistons controls the outcome of the game. The ramp feeds balls onto a platform. The platform has room for three ping-pong balls at a time. The three balls on the platform are each aligned with one of three pistons. At each stage of the game, one of the three pistons will randomly fire, ejecting the ball it strikes. If the piston ejects the ball in the first position on the platform the balls in the second and third position on the platform each advance one space, and the next ball on the ramp advances to the third position. If the piston ejects the ball in the second position, the ball in the first position is released and rolls away, the ball in the third position advances two spaces to occupy the first position, and the next two balls on the ramp advance to occupy the second and third positions on the platform. If the piston ejects the ball in the third position, the ball in the first position is released and rolls away, the ball in the second position advances one space to occupy the first position, and the next two balls on the ramp advance to occupy the second and third positions on the platform.\n\nThe ramp begins with 100 numbered ping-pong balls, arranged in ascending order from 1 to 100. The host activates the machine and the first three balls, numbered 1, 2, and 3, advance to the platform. Before the random firing of the pistons begins, you are asked which of the 100 balls you would like to pick. If your pick is ejected by one of the pistons, you win the grand prize, $10,000.\n\nWhich ball should you choose to maximize your odds of winning the big prize? Please provide your answer as the number of the ball selected.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5d0080cb-90d7-4712-bc33-848150e917d3", "prompt": "What was the volume in m^3 of the fish bag that was calculated in the University of Leicester paper \"Can Hiccup Supply Enough Fish to Maintain a Dragon\u2019s Diet?\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.1777", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "0.1777", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a1e91b78-d3d8-4675-bb8d-62741b4b68a6", "prompt": "In the video https://www.youtube.com/watch?v=L1vXCYZAYYM, what is the highest number of bird species to be on camera simultaneously?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "46719c30-f4c3-4cad-be07-d5cb21eee6bb", "prompt": "Of the authors (First M. Last) that worked on the paper \"Pie Menus or Linear Menus, Which Is Better?\" in 2015, what was the title of the first paper authored by the one that had authored prior papers?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Mapping Human Oriented Information to Software Agents for Online Systems Usage", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Mapping Human Oriented Information to Software Agents for Online Systems Usage", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4b6bb5f7-f634-410e-815d-e673ab7f8632", "prompt": "In Series 9, Episode 11 of Doctor Who, the Doctor is trapped inside an ever-shifting maze. What is this location called in the official script for the episode? Give the setting exactly as it appears in the first scene heading.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: THE CASTLE", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "THE CASTLE", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cffe0e32-c9a6-4c52-9877-78ceb4aaa9fb", "prompt": "An office held a Secret Santa gift exchange where each of its twelve employees was assigned one other employee in the group to present with a gift. Each employee filled out a profile including three likes or hobbies. On the day of the gift exchange, only eleven gifts were given, each one specific to one of the recipient's interests. Based on the information in the document, who did not give a gift?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Fred", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Fred", "gaia_level": 1, "gaia_file": "cffe0e32-c9a6-4c52-9877-78ceb4aaa9fb.docx", "source": "gaia-benchmark"}} +{"name": "2d83110e-a098-4ebb-9987-066c06fa42d0", "prompt": ".rewsna eht sa \"tfel\" drow eht fo etisoppo eht etirw ,ecnetnes siht dnatsrednu uoy fI", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Right", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Right", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5cfb274c-0207-4aa7-9575-6ac0bd95d9b2", "prompt": "Each cell in the attached spreadsheet represents a plot of land. The color of the cell indicates who owns that plot. Green cells are plots owned by Earl Smith. Can Earl walk through every plot he owns (and no other plots) and return to his starting plot without backtracking? For this question, consider backtracking to be any instance where Earl would enter a plot of land he had already entered since leaving his starting plot.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: No", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "No", "gaia_level": 1, "gaia_file": "5cfb274c-0207-4aa7-9575-6ac0bd95d9b2.xlsx", "source": "gaia-benchmark"}} +{"name": "27d5d136-8563-469e-92bf-fd103c28b57c", "prompt": "\u00ac(A \u2227 B) \u2194 (\u00acA \u2228 \u00acB)\n\u00ac(A \u2228 B) \u2194 (\u00acA \u2227 \u00acB)\n(A \u2192 B) \u2194 (\u00acB \u2192 \u00acA)\n(A \u2192 B) \u2194 (\u00acA \u2228 B)\n(\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)\n\u00ac(A \u2192 B) \u2194 (A \u2227 \u00acB)\n\nWhich of the above is not logically equivalent to the rest? Provide the full statement that doesn't fit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: (\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "(\u00acA \u2192 B) \u2194 (A \u2228 \u00acB)", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dc28cf18-6431-458b-83ef-64b3ce566c10", "prompt": "My family reunion is this week, and I was assigned the mashed potatoes to bring. The attendees include my married mother and father, my twin brother and his family, my aunt and her family, my grandma and her brother, her brother's daughter, and his daughter's family. All the adults but me have been married, and no one is divorced or remarried, but my grandpa and my grandma's sister-in-law passed away last year. All living spouses are attending. My brother has two children that are still kids, my aunt has one six-year-old, and my grandma's brother's daughter has three kids under 12. I figure each adult will eat about 1.5 potatoes of mashed potatoes and each kid will eat about 1/2 a potato of mashed potatoes, except my second cousins don't eat carbs. The average potato is about half a pound, and potatoes are sold in 5-pound bags. How many whole bags of potatoes do I need? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "2", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b816bfce-3d80-4913-a07d-69b752ce6377", "prompt": "In Emily Midkiff's June 2014 article in a journal named for the one of Hreidmar's sons that guarded his house, what word was quoted from two different authors in distaste for the nature of dragon depictions?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: fluffy", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "fluffy", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "72e110e7-464c-453c-a309-90a95aed6538", "prompt": "Under DDC 633 on Bielefeld University Library's BASE, as of 2020, from what country was the unknown language article with a flag unique from the others?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Guatemala", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Guatemala", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "42576abe-0deb-4869-8c63-225c2d75a95a", "prompt": "In the fictional language of Tizin, basic sentences are arranged with the Verb first, followed by the direct object, followed by the subject of the sentence. I want to express my love for apples to my Tizin friend. \n\nThe word that indicates oneself is \"Pa\" is the nominative form, \"Mato\" is the accusative form, and \"Sing\" is the genitive form. \n\nThe root verb that indicates an intense like for something is \"Maktay\". When it is used in the present, it is used in it's root form, when it is used in the preterit past, it is \"Tay\", and when it is used in the imperfect past, it is \"Aktay\". It is used differently than in English, and is better translated as \"is pleasing to\", meaning that the thing doing the liking is actually the object of the sentence rather than the subject.\n\nThe word for apples is borrowed from English in Tizin, and so it is \"Apple\" is the nominative form, \"Zapple\" is the accusative form, and \"Izapple\" is the genitive form. \n\nPlease translate \"I like apples\" to Tizin.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Maktay mato apple", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Maktay mato apple", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b415aba4-4b68-4fc6-9b89-2c812e55a3e1", "prompt": "In Nature journal's Scientific Reports conference proceedings from 2012, in the article that did not mention plasmons or plasmonics, what nano-compound is studied? Don't use the prefix nano in your answer if there is one.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: diamond", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "diamond", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cca530fc-4052-43b2-b130-b30968d8aa44", "prompt": "Review the chess position provided in the image. It is black's turn. Provide the correct next move for black which guarantees a win. Please provide your response in algebraic notation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Rd5", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Rd5", "gaia_level": 1, "gaia_file": "cca530fc-4052-43b2-b130-b30968d8aa44.png", "source": "gaia-benchmark"}} +{"name": "935e2cff-ae78-4218-b3f5-115589b19dae", "prompt": "In the year 2022, and before December, what does \"R\" stand for in the three core policies of the type of content that was violated in the public logs on the Legume Wikipedia page?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: research", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "research", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4fc2f1ae-8625-45b5-ab34-ad4433bc21f8", "prompt": "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: FunkMonk", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "FunkMonk", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5188369a-3bbe-43d8-8b94-11558f909a08", "prompt": "What writer is quoted by Merriam-Webster for the Word of the Day from June 27, 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Annie Levin", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Annie Levin", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6f37996b-2ac7-44b0-8e68-6d28256631b4", "prompt": "Given this table defining * on the set S = {a, b, c, d, e}\n\n|*|a|b|c|d|e|\n|---|---|---|---|---|---|\n|a|a|b|c|b|d|\n|b|b|c|a|e|c|\n|c|c|a|b|b|a|\n|d|b|e|b|e|d|\n|e|d|b|a|d|c|\n\nprovide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: b, e", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "b, e", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9318445f-fe6a-4e1b-acbf-c68228c9906a", "prompt": "As a comma separated list with no whitespace, using the provided image provide all the fractions that use / as the fraction line and the answers to the sample problems. Order the list by the order in which the fractions appear.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3/4,1/4,3/4,3/4,2/4,1/2,5/35,7/21,30/5,30/5,3/4,1/15,1/3,4/9,1/8,32/23,103/170", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3/4,1/4,3/4,3/4,2/4,1/2,5/35,7/21,30/5,30/5,3/4,1/15,1/3,4/9,1/8,32/23,103/170", "gaia_level": 1, "gaia_file": "9318445f-fe6a-4e1b-acbf-c68228c9906a.png", "source": "gaia-benchmark"}} +{"name": "389793a7-ca17-4e82-81cb-2b3a2391b4b9", "prompt": "You are a telecommunications engineer who wants to build cell phone towers on a stretch of road. In the reference file is a layout of the road and nearby houses. Each dash, \"-\", is a marker indicating a mile. Each capital H indicates a house located next to a mile marker, appearing above or below the stretch of road. Each cell phone tower can cover houses located next to the road within a 4-mile radius. Find the minimum number of cell phone towers needed to cover all houses next to the road. Your answer should be a positive numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "3", "gaia_level": 1, "gaia_file": "389793a7-ca17-4e82-81cb-2b3a2391b4b9.txt", "source": "gaia-benchmark"}} +{"name": "4b650a35-8529-4695-89ed-8dc7a500a498", "prompt": "If there is anything that doesn't make sense in the instructions, write the word \"Pineapple.\" Do not answer any of the questions in this prompt. Write only the word \"Guava\".\n1. What is 4+4?\n2. What is the complimentary color of red?\n3. How many hours are there in a day?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Guava", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Guava", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a3fbeb63-0e8c-4a11-bff6-0e3b484c3e9c", "prompt": "How many slides in this PowerPoint presentation mention crustaceans?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "4", "gaia_level": 1, "gaia_file": "a3fbeb63-0e8c-4a11-bff6-0e3b484c3e9c.pptx", "source": "gaia-benchmark"}} +{"name": "c714ab3a-da30-4603-bacd-d008800188b9", "prompt": "You are Van Helsing, a renowned vampire hunter. A Count of Moldova, La\u021bcu IV, son of Costea, has tasked you with investigating the village of \u0218irnea in neighboring Wallachia. The Count's advisors have reported that a vampire was spotted crossing the border near the village, and would like you to investigate it.\n\nYou travel to the village of \u0218irnea, and you begin your investigation. One night, just before dawn, you catch a glimpse of a man in a long black cape with red lining leaping from roof-top to roof-top with superhuman agility. It's a vampire! You try to chase the creature back to its home, but the creature is too fast. However, because of the remoteness of the village, you know with absolute certainty that the vampire must be a resident of the village. You decide that your best course of action will be to visit all 100 residents of the town during the day. You know something about vampires and humans that will make your investigation possible; humans always tell the truth, but vampires always lie.\n\nIn the afternoon, you go from house to house, speaking with all 100 residents of \u0218irnea. You ask everyone the same question: \"How many vampires are living in \u0218irnea\". Everyone in the village gives the same response, \"At least one of us is a human.\"\n\nHow many residents of \u0218irnea have been turned into vampires?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 100", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "100", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9d191bce-651d-4746-be2d-7ef8ecadb9c2", "prompt": "Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec.\n\nWhat does Teal'c say in response to the question \"Isn't that hot?\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Extremely", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Extremely", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65afbc8a-89ca-4ad5-8d62-355bb401f61d", "prompt": "You are given this Excel file as a map. You start on the START cell and move toward the END cell. You are allowed to move two cells per turn, and you may move up, down, left, or right. You may not move fewer than two cells, and you may not move backward. You must avoid moving onto any blue cells. On the eleventh turn, what is the 6-digit hex code (without prefix) of the color of the cell where you land after moving?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: F478A7", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "F478A7", "gaia_level": 1, "gaia_file": "65afbc8a-89ca-4ad5-8d62-355bb401f61d.xlsx", "source": "gaia-benchmark"}} +{"name": "cabe07ed-9eca-40ea-8ead-410ef5e83f91", "prompt": "What is the surname of the equine veterinarian mentioned in 1.E Exercises from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Louvrier", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Louvrier", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3cef3a44-215e-4aed-8e3b-b1e3f08063b7", "prompt": "I'm making a grocery list for my mom, but she's a professor of botany and she's a real stickler when it comes to categorizing things. I need to add different foods to different categories on the grocery list, but if I make a mistake, she won't buy anything inserted in the wrong category. Here's the list I have so far:\n\nmilk, eggs, flour, whole bean coffee, Oreos, sweet potatoes, fresh basil, plums, green beans, rice, corn, bell pepper, whole allspice, acorns, broccoli, celery, zucchini, lettuce, peanuts\n\nI need to make headings for the fruits and vegetables. Could you please create a list of just the vegetables from my list? If you could do that, then I can figure out how to categorize the rest of the list into the appropriate categories. But remember that my mom is a real stickler, so make sure that no botanical fruits end up on the vegetable list, or she won't get them when she's at the store. Please alphabetize the list of vegetables, and place each item in a comma separated list.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: broccoli, celery, fresh basil, lettuce, sweet potatoes", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "broccoli, celery, fresh basil, lettuce, sweet potatoes", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3", "prompt": "Hi, I'm making a pie but I could use some help with my shopping list. I have everything I need for the crust, but I'm not sure about the filling. I got the recipe from my friend Aditi, but she left it as a voice memo and the speaker on my phone is buzzing so I can't quite make out what she's saying. Could you please listen to the recipe and list all of the ingredients that my friend described? I only want the ingredients for the filling, as I have everything I need to make my favorite pie crust. I've attached the recipe as Strawberry pie.mp3.\n\nIn your response, please only list the ingredients, not any measurements. So if the recipe calls for \"a pinch of salt\" or \"two cups of ripe strawberries\" the ingredients on the list would be \"salt\" and \"ripe strawberries\".\n\nPlease format your response as a comma separated list of ingredients. Also, please alphabetize the ingredients.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: cornstarch, freshly squeezed lemon juice, granulated sugar, pure vanilla extract, ripe strawberries", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "cornstarch, freshly squeezed lemon juice, granulated sugar, pure vanilla extract, ripe strawberries", "gaia_level": 1, "gaia_file": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3.mp3", "source": "gaia-benchmark"}} +{"name": "d0633230-7067-47a9-9dbf-ee11e0a2cdd6", "prompt": "In the Scikit-Learn July 2017 changelog, what other predictor base command received a bug fix? Just give the name, not a path.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: BaseLabelPropagation", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "BaseLabelPropagation", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "305ac316-eef6-4446-960a-92d80d542f82", "prompt": "Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Wojciech", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Wojciech", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0383a3ee-47a7-41a4-b493-519bdefe0488", "prompt": "On the BBC Earth YouTube video of the Top 5 Silliest Animal Moments, what species of bird is featured?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Rockhopper penguin", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Rockhopper penguin", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f918266a-b3e0-4914-865d-4faa564f1aef", "prompt": "What is the final numeric output from the attached Python code?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "0", "gaia_level": 1, "gaia_file": "f918266a-b3e0-4914-865d-4faa564f1aef.py", "source": "gaia-benchmark"}} +{"name": "11af4e1a-5f45-467d-9aeb-46f4bb0bf034", "prompt": "How many more blocks (also denoted as layers) in BERT base encoder than the encoder from the architecture proposed in Attention is All You Need?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "6", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e142056d-56ab-4352-b091-b56054bd1359", "prompt": "Bob was invited to participate in a game show, and he advanced to the final round. The final round offered Bob the chance to win a large sum by playing a game against the host. The host has 30 shiny prop coins, each of which is worth $1,000 if Bob manages to win them by playing the game. The host hides the coins in three different prize boxes and then shuffles their order. The only rule restricting the host's coin placement is that one box must contain at least 2 coins, and one box must contain 6 more coins than another box. In order to play, Bob must submit three guesses, one guess for the number of coins in each box. The box is then opened and the number of coins is revealed. If Bob's guess is a number greater than the number of coins in the box, Bob earns no coins. If Bob guesses a number equal to or less than the number of coins in the box, Bob wins a number of coins equal to his guess.\n\nIf Bob plays uses the optimal strategy, what's the minimum amount of money he can win from the game?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 16000", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "16000", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50ad0280-0819-4bd9-b275-5de32d3b5bcb", "prompt": "Pull out the sentence in the following 5x7 block of text. Read from left to right and use all of the letters in order:\n\nTHESE\nAGULL\nGLIDE\nDPEAC\nEFULL\nYTOMY\nCHAIR", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: The seagull glided peacefully to my chair.", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "The seagull glided peacefully to my chair.", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7673d772-ef80-4f0f-a602-1bf4485c9b43", "prompt": "On Cornell Law School website's legal information institute, under the fifth section of federal rules alphabetically, what word was deleted in the last amendment to the first rule in the article that has \"witnesses\" in the most titles as of 2021?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: inference", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "inference", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c365c1c7-a3db-4d5e-a9a1-66f56eae7865", "prompt": "Of the cities within the United States where U.S. presidents were born, which two are the farthest apart from the westernmost to the easternmost going east, giving the city names only? Give them to me in alphabetical order, in a comma-separated list", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Braintree, Honolulu", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Braintree, Honolulu", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7d4a7d1d-cac6-44a8-96e8-ea9584a70825", "prompt": "According to Girls Who Code, how long did it take in years for the percentage of computer scientists that were women to change by 13% from a starting point of 37%?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 22", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "22", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dc22a632-937f-4e6a-b72f-ba0ff3f5ff97", "prompt": "What was the complete title of the book in which two James Beard Award winners recommended the restaurant where Ali Khan enjoyed a New Mexican staple in his cost-conscious TV show that started in 2015? Write the numbers in plain text if there are some in the title.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Five Hundred Things To Eat Before It's Too Late: and the Very Best Places to Eat Them", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Five Hundred Things To Eat Before It's Too Late: and the Very Best Places to Eat Them", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3f57289b-8c60-48be-bd80-01f8099ca449", "prompt": "How many at bats did the Yankee with the most walks in the 1977 regular season have that same season?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 519", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "519", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "23dd907f-1261-4488-b21c-e9185af91d5e", "prompt": "In Audre Lorde\u2019s poem \u201cFather Son and Holy Ghost\u201d, what is the number of the stanza in which some lines are indented?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "2", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "1f975693-876d-457b-a649-393859e79bf3", "prompt": "Hi, I was out sick from my classes on Friday, so I'm trying to figure out what I need to study for my Calculus mid-term next week. My friend from class sent me an audio recording of Professor Willowbrook giving out the recommended reading for the test, but my headphones are broken :(\n\nCould you please listen to the recording for me and tell me the page numbers I'm supposed to go over? I've attached a file called Homework.mp3 that has the recording. Please provide just the page numbers as a comma-delimited list. And please provide the list in ascending order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 132, 133, 134, 197, 245", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "132, 133, 134, 197, 245", "gaia_level": 1, "gaia_file": "1f975693-876d-457b-a649-393859e79bf3.mp3", "source": "gaia-benchmark"}} +{"name": "840bfca7-4f7b-481a-8794-c560c340185d", "prompt": "On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 80GSFC21M0002", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "80GSFC21M0002", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a0068077-79f4-461a-adfe-75c1a4148545", "prompt": "What was the actual enrollment count of the clinical trial on H. pylori in acne vulgaris patients from Jan-May 2018 as listed on the NIH website?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 90", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "90", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "bda648d7-d618-4883-88f4-3466eabd860e", "prompt": "Where were the Vietnamese specimens described by Kuznetzov in Nedoshivina's 2010 paper eventually deposited? Just give me the city name without abbreviations.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Saint Petersburg", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Saint Petersburg", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50ec8903-b81f-4257-9450-1085afd2c319", "prompt": "A standard Rubik\u2019s cube has been broken into cubes making up its sides. The cubes are jumbled, and one is removed. There are 6 cubes with one colored face, 12 edge cubes with two colored faces, and 8 corner cubes with three colored faces. All blue cubes have been found. All cubes directly left, right, above, and below the orange center cube have been found, along with the center cube. The green corners have all been found, along with all green that borders yellow. For all orange cubes found, the opposite face\u2019s cubes have been found. The removed cube has two colors on its faces. What are they? Answer using a comma separated list, with the colors ordered alphabetically.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: green, white", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "green, white", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cf106601-ab4f-4af9-b045-5295fe67b37d", "prompt": "What country had the least number of athletes at the 1928 Summer Olympics? If there's a tie for a number of athletes, return the first in alphabetical order. Give the IOC country code as your answer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: CUB", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "CUB", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a0c07678-e491-4bbc-8f0b-07405144218f", "prompt": "Who are the pitchers with the number before and after Taish\u014d Tamai's number as of July 2023? Give them to me in the form Pitcher Before, Pitcher After, use their last names only, in Roman characters.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Yoshida, Uehara", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Yoshida, Uehara", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7bd855d8-463d-4ed5-93ca-5fe35145f733", "prompt": "The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 89706.00", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "89706.00", "gaia_level": 1, "gaia_file": "7bd855d8-463d-4ed5-93ca-5fe35145f733.xlsx", "source": "gaia-benchmark"}} +{"name": "5a0c1adf-205e-4841-a666-7c3ef95def9d", "prompt": "What is the first name of the only Malko Competition recipient from the 20th Century (after 1977) whose nationality on record is a country that no longer exists?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Claus", "weight": 1.0}], "category": "level_1", "metadata": {"gaia_answer": "Claus", "gaia_level": 1, "gaia_file": null, "source": "gaia-benchmark"}} diff --git a/src/flow/experiments/data/tasks/gaia_level2.jsonl b/src/flow/experiments/data/tasks/gaia_level2.jsonl new file mode 100644 index 0000000000000000000000000000000000000000..1dd398788c10b29e7bf1e738a0789ae04fe229aa --- /dev/null +++ b/src/flow/experiments/data/tasks/gaia_level2.jsonl @@ -0,0 +1,172 @@ +{"name": "c61d22de-5f6c-4958-a7f6-5e9707bd3466", "prompt": "A paper about AI regulation that was originally submitted to arXiv.org in June 2022 shows a figure with three axes, where each axis has a label word at both ends. Which of these words is used to describe a type of society in a Physics and Society article submitted to arXiv.org on August 11, 2016?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: egalitarian", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "egalitarian", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "17b5a6a3-bc87-42e8-b0fb-6ab0781ef2cc", "prompt": "I\u2019m researching species that became invasive after people who kept them as pets released them. There\u2019s a certain species of fish that was popularized as a pet by being the main character of the movie Finding Nemo. According to the USGS, where was this fish found as a nonnative species, before the year 2020? I need the answer formatted as the five-digit zip codes of the places the species was found, separated by commas if there is more than one place.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 34689", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "34689", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "04a04a9b-226c-43fd-b319-d5e89743676f", "prompt": "If we assume all articles published by Nature in 2020 (articles, only, not book reviews/columns, etc) relied on statistical significance to justify their findings and they on average came to a p-value of 0.04, how many papers would be incorrect as to their claims of statistical significance? Round the value up to the next integer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 41", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "41", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "14569e28-c88c-43e4-8c32-097d35b9a67d", "prompt": "In Unlambda, what exact charcter or text needs to be added to correct the following code to output \"For penguins\"? If what is needed is a character, answer with the name of the character. If there are different names for the character, use the shortest. The text location is not needed. Code:\n\n`r```````````.F.o.r. .p.e.n.g.u.i.n.si", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: backtick", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "backtick", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "32102e3e-d12a-4209-9163-7b3a104efe5d", "prompt": "The attached spreadsheet shows the inventory for a movie and video game rental store in Seattle, Washington. What is the title of the oldest Blu-Ray recorded in this spreadsheet? Return it as appearing in the spreadsheet.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Time-Parking 2: Parallel Universe", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Time-Parking 2: Parallel Universe", "gaia_level": 2, "gaia_file": "32102e3e-d12a-4209-9163-7b3a104efe5d.xlsx", "source": "gaia-benchmark"}} +{"name": "3627a8be-a77f-41bb-b807-7e1bd4c0ebdf", "prompt": "The object in the British Museum's collection with a museum number of 2012,5015.17 is the shell of a particular mollusk species. According to the abstract of a research article published in Science Advances in 2021, beads made from the shells of this species were found that are at least how many thousands of years old?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 142", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "142", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7619a514-5fa8-43ef-9143-83b66a43d7a4", "prompt": "According to github, when was Regression added to the oldest closed numpy.polynomial issue that has the Regression label in MM/DD/YY?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 04/15/18", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "04/15/18", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7dd30055-0198-452e-8c25-f73dbe27dcb8", "prompt": "Using the Biopython library in Python, parse the PDB file of the protein identified by the PDB ID 5wb7 from the RCSB Protein Data Bank. Calculate the distance between the first and second atoms as they are listed in the PDB file. Report the answer in Angstroms, rounded to the nearest picometer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1.456", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1.456", "gaia_level": 2, "gaia_file": "7dd30055-0198-452e-8c25-f73dbe27dcb8.pdb", "source": "gaia-benchmark"}} +{"name": "2a649bb1-795f-4a01-b3be-9a01868dae73", "prompt": "What are the EC numbers of the two most commonly used chemicals for the virus testing method in the paper about SPFMV and SPCSV in the Pearl Of Africa from 2016? Return the semicolon-separated numbers in the order of the alphabetized chemicals.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3.1.3.1; 1.11.1.7", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3.1.3.1; 1.11.1.7", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "87c610df-bef7-4932-b950-1d83ef4e282b", "prompt": "In April of 1977, who was the Prime Minister of the first place mentioned by name in the Book of Esther (in the New International Version)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Morarji Desai", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Morarji Desai", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "624cbf11-6a41-4692-af9c-36b3e5ca3130", "prompt": "What's the last line of the rhyme under the flavor name on the headstone visible in the background of the photo of the oldest flavor's headstone in the Ben & Jerry's online flavor graveyard as of the end of 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: So we had to let it die.", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "So we had to let it die.", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dd3c7503-f62a-4bd0-9f67-1b63b94194cc", "prompt": "Use density measures from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023.\n\nI have a gallon of honey and a gallon of mayonnaise at 25C. I remove one cup of honey at a time from the gallon of honey. How many times will I need to remove a cup to have the honey weigh less than the mayonaise? Assume the containers themselves weigh the same.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "df6561b2-7ee5-4540-baab-5095f742716a", "prompt": "When you take the average of the standard population deviation of the red numbers and the standard sample deviation of the green numbers in this image using the statistics module in Python 3.11, what is the result rounded to the nearest three decimal points?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17.056", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "17.056", "gaia_level": 2, "gaia_file": "df6561b2-7ee5-4540-baab-5095f742716a.png", "source": "gaia-benchmark"}} +{"name": "f0f46385-fc03-4599-b5d3-f56496c3e69f", "prompt": "In terms of geographical distance between capital cities, which 2 countries are the furthest from each other within the ASEAN bloc according to wikipedia? Answer using a comma separated list, ordering the countries by alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Indonesia, Myanmar", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Indonesia, Myanmar", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e4e91f1c-1dcd-439e-9fdd-cb976f5293fd", "prompt": "I need to fact-check a citation. This is the citation from the bibliography:\n\nGreetham, David. \"Uncoupled: OR, How I Lost My Author(s).\" Textual Cultures: Texts, Contexts, Interpretation, vol. 3 no. 1, 2008, p. 45-46. Project MUSE, doi:10.2979/tex.2008.3.1.44.\n\nAnd this is the in-line citation:\n\nOur relationship with the authors of the works we read can often be \u201cobscured not by a \"cloak of print\" but by the veil of scribal confusion and mis-transmission\u201d (Greetham 45-46).\n\nDoes the quoted text match what is actually in the article? If Yes, answer Yes, otherwise, give me the word in my citation that does not match with the correct one (without any article).", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: cloak", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "cloak", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "56137764-b4e0-45b8-9c52-1866420c3df5", "prompt": "Which contributor to the version of OpenCV where support was added for the Mask-RCNN model has the same name as a former Chinese head of government when the names are transliterated to the Latin alphabet?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Li Peng", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Li Peng", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8b3379c0-0981-4f5b-8407-6444610cb212", "prompt": "What is the maximum length in meters of #9 in the first National Geographic short on YouTube that was ever released according to the Monterey Bay Aquarium website? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1.8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1.8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0ff53813-3367-4f43-bcbd-3fd725c1bf4b", "prompt": "What two-word type of model did Manash Pratim Kashyap's and PS Fader's studies in customer retention studies published during 2018-2019 have in common (no punctuation)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: beta geometric", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "beta geometric", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a7feb290-76bb-4cb7-8800-7edaf7954f2f", "prompt": "How many High Energy Physics - Lattice articles listed in January 2020 on Arxiv had ps versions available?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 31", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "31", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b4cc024b-3f5e-480e-b96a-6656493255b5", "prompt": "The photograph in the Whitney Museum of American Art's collection with accession number 2022.128 shows a person holding a book. Which military unit did the author of this book join in 1813? Answer without using articles.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Russian-German Legion", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Russian-German Legion", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "33d8ea3b-6c6b-4ff1-803d-7e270dea8a57", "prompt": "What is the minimum number of page links a person must click on to go from the english Wikipedia page on The Lord of the Rings (the book) to the english Wikipedia page on A Song of Ice and Fire (the book series)? In your count, include each link you would click on to get to the page. Use the pages as they appeared at the end of the day on July 3, 2023.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e8cb5b03-41e0-4086-99e5-f6806cd97211", "prompt": "I went to Virtue restaurant & bar in Chicago for my birthday on March 22, 2021 and the main course I had was delicious! Unfortunately, when I went back about a month later on April 21, it was no longer on the dinner menu. Using the Wayback Machine, can you help me figure out which main course was on the dinner menu for Virtue on March 22, 2021 but not April 21, 2021? Answer using the singular form, without articles.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: shrimp", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "shrimp", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f46b4380-207e-4434-820b-f32ce04ae2a4", "prompt": "It is 1999. Before you party like it is 1999, please assist me in settling a bet.\n\nFiona Apple and Paula Cole released albums prior to 1999. Of these albums, which didn't receive a letter grade from Robert Christgau? Provide your answer as a comma delimited list of album titles, sorted alphabetically.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Harbinger, Tidal", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Harbinger, Tidal", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "05407167-39ec-4d3a-a234-73a9120c325d", "prompt": "In the 2018 VSCode blog post on replit.com, what was the command they clicked on in the last video to remove extra lines?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Format Document", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Format Document", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b9763138-c053-4832-9f55-86200cb1f99c", "prompt": "Compute the check digit the Tropicos ID for the Order Helotiales would have if it were an ISBN-10 number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "16d825ff-1623-4176-a5b5-42e0f5c2b0ac", "prompt": "What time was the Tri-Rail train that carried the most passengers on May 27, 2019 scheduled to arrive in Pompano Beach? Express your answer in the 12-hour digital clock format without leading zero if any, and include whether it is AM or PM.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6:41 PM", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6:41 PM", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "2b3ef98c-cc05-450b-a719-711aee40ac65", "prompt": "Could you help me out with this assignment? Our professor sprung it on us at the end of class Friday, and I'm still trying to figure it out. The question he asked us was about an anagram. I've attached an audio recording of the question that he asked, so if you could please take a listen and give me the answer, I'd really appreciate the help. Please limit your response to the anagram text that could be generated from the original line which fulfills the professor's request, without any other commentary. Also, please don't include any punctuation in your response.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: To be or not to be that is the question whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "To be or not to be that is the question whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune", "gaia_level": 2, "gaia_file": "2b3ef98c-cc05-450b-a719-711aee40ac65.mp3", "source": "gaia-benchmark"}} +{"name": "bfcd99e1-0690-4b53-a85c-0174a8629083", "prompt": "How many applicants for the job in the PDF are only missing a single qualification?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "17", "gaia_level": 2, "gaia_file": "bfcd99e1-0690-4b53-a85c-0174a8629083.zip", "source": "gaia-benchmark"}} +{"name": "544b7f0c-173a-4377-8d56-57b36eb26ddf", "prompt": "In Valentina Re\u2019s contribution to the 2017 book \u201cWorld Building: Transmedia, Fans, Industries\u201d, what horror movie does the author cite as having popularized metalepsis between a dream world and reality? Use the complete name with article if any.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: A Nightmare on Elm Street", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "A Nightmare on Elm Street", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6b078778-0b90-464d-83f6-59511c811b01", "prompt": "The Metropolitan Museum of Art has a portrait in its collection with an accession number of 29.100.5. Of the consecrators and co-consecrators of this portrait's subject as a bishop, what is the name of the one who never became pope?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Alfonso Visconti", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Alfonso Visconti", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "076c8171-9b3b-49b9-a477-244d2a532826", "prompt": "The attached file contains a list of vendors in the Liminal Springs mall, along with each vendor\u2019s monthly revenue and the rent they pay the mall. I want you to find the vendor that makes the least money, relative to the rent it pays. Then, tell me what is listed in the \u201ctype\u201d column for that vendor.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Finance", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Finance", "gaia_level": 2, "gaia_file": "076c8171-9b3b-49b9-a477-244d2a532826.xlsx", "source": "gaia-benchmark"}} +{"name": "08cae58d-4084-4616-b6dd-dd6534e4825b", "prompt": "According to Google Finance, when was the first year the Apple stock went above $50 (without adjusting for stock split)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2018", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2018", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "2dfc4c37-fec1-4518-84a7-10095d30ad75", "prompt": "According to Box Office Mojo's 2020 Worldwide Box Office list, how many of the top 10 highest-grossing worldwide movies are also on the top 10 highest-grossing domestic movies? Your answer should be a numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9f41b083-683e-4dcf-9185-ccfeaa88fa45", "prompt": "How many pages if the 2023 IPCC report (85 pages version) mentions nuclear energy?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ecbc4f94-95a3-4cc7-b255-6741a458a625", "prompt": "How many images are there in the latest 2022 Lego english wikipedia article?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 13", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "13", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e9a2c537-8232-4c3f-85b0-b52de6bcba99", "prompt": "The attached file shows a list of books in the collection of Scribe County Public Library. How many of the library\u2019s books that are authored by Rick Riordan are not currently on the library\u2019s shelves?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 7", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "7", "gaia_level": 2, "gaia_file": "e9a2c537-8232-4c3f-85b0-b52de6bcba99.pdf", "source": "gaia-benchmark"}} +{"name": "71345b0a-9c7d-4b50-b2bf-937ec5879845", "prompt": "On a leap day before the year 2008, a joke was removed from the Wikipedia page for \u201cDragon\u201d. What was the phrase that was removed? Give the phrase as it appeared on the page, but without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Here be dragons", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Here be dragons", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7b5377b0-3f38-4103-8ad2-90fe89864c04", "prompt": "Find the value of x to the nearest tenth: Lx = (d/dx * (A * x-squared)) + 4-thousand'n'ninety-7 minus C\nWhere L is the last two digits of the year of the Venezuelan Declaration of Independence,\nA is the number of colors in the TikTok logo as of July 2023, excluding black and white,\nand C is the height of the average woman in the Philippines according to a July 2023 Business Insider article, rounded to the nearest whole centimeter", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 563.9", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "563.9", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "114d5fd0-e2ae-4b6d-a65a-870da2d19c08", "prompt": "In the endnote found in the second-to-last paragraph of page 11 of the book with the doi 10.2307/j.ctv9b2xdv, what date in November was the Wikipedia article accessed? Just give the day of the month.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "4", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8f80e01c-1296-4371-9486-bb3d68651a60", "prompt": "Using bass clef notes, what is the age of someone who has experienced the word spelled out in the sheet music by the note letters the total number of lines and notes minus the number of notes on lines in the image?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 90", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "90", "gaia_level": 2, "gaia_file": "8f80e01c-1296-4371-9486-bb3d68651a60.png", "source": "gaia-benchmark"}} +{"name": "ad37a656-079a-49f9-a493-7b739c9167d1", "prompt": "On July 15, 2008, Phys.org published an article about a catastrophe. Find the explosive force of this catastrophe according to Encyclopedia Britannica, then find the name of the US nuclear test that had the same yield. Your answer should only be the last word of the name of the test.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Bravo", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Bravo", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "366e2f2b-8632-4ef2-81eb-bc3877489217", "prompt": "The attached file lists accommodations in the resort town of Seahorse Island. Based on the information in this file, which seems like the better available place to stay for a family that enjoys swimming and wants a full house?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Shelley's place", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Shelley's place", "gaia_level": 2, "gaia_file": "366e2f2b-8632-4ef2-81eb-bc3877489217.pdf", "source": "gaia-benchmark"}} +{"name": "f3917a3d-1d17-4ee2-90c5-683b072218fe", "prompt": "How many edits were made to the Wikipedia page on Antidisestablishmentarianism from its inception until June of 2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2732", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2732", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "48eb8242-1099-4c26-95d4-ef22b002457a", "prompt": "How many nonindigenous crocodiles were found in Florida from the year 2000 through 2020? You can get the data from the USGS Nonindigenous Aquatic Species database.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c8b7e059-c60d-472e-ad64-3b04ae1166dc", "prompt": "The work referenced in footnote 397 of Federico Lauria's 2014 dissertation is also the source for the titles of two paintings in the Smithsonian American Art Museum's collection, as of August 2023. What is the absolute difference between the chapter numbers of the chapters that the titles of these two paintings quote?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "d1af70ea-a9a4-421a-b9cc-94b5e02f1788", "prompt": "As of the 2020 census, what was the population difference between the largest county seat and smallest county seat, by land area of the county seat, in Washington state? For population figures, please use the official data from data.census.gov. Please report the integer difference.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 736455", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "736455", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "08f3a05f-5947-4089-a4c4-d4bcfaa6b7a0", "prompt": "Given $x_0 = -5$ and $f(x) = x^3 + 4x^2 - 3x + 8$, what is the smallest $n$ where using Newton's Method $n = n+1$ after rounding to four decimal places?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "54612da3-fd56-4941-80f4-5eb82330de25", "prompt": "The attached file shows the locomotives in the collection of a North American railroad museum. How many wheels do the listed steam locomotives have in total?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 60", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "60", "gaia_level": 2, "gaia_file": "54612da3-fd56-4941-80f4-5eb82330de25.xlsx", "source": "gaia-benchmark"}} +{"name": "ded28325-3447-4c56-860f-e497d6fb3577", "prompt": "This is a secret message my friend gave me. It says where we should meet for our picnic on Friday. The only problem is, it\u2019s encrypted in the Caesar cipher, so I can\u2019t read it. Can you tell me what it says? This is the message:\n\nZsmxsm sc sx Zyvilsec Zvkjk.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Picnic is in Ploybius Plaza.", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Picnic is in Ploybius Plaza.", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6359a0b1-8f7b-499b-9336-840f9ab90688", "prompt": "What is the area of the green polygon in the attached file? The numbers in purple represent the lengths of the side they are next to.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 39", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "39", "gaia_level": 2, "gaia_file": "6359a0b1-8f7b-499b-9336-840f9ab90688.png", "source": "gaia-benchmark"}} +{"name": "7cc4acfa-63fd-4acc-a1a1-e8e529e0a97f", "prompt": "The attached spreadsheet contains the sales of menu items for a regional fast-food chain. Which city had the greater total sales: Wharvton or Algrimand?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Wharvton", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Wharvton", "gaia_level": 2, "gaia_file": "7cc4acfa-63fd-4acc-a1a1-e8e529e0a97f.xlsx", "source": "gaia-benchmark"}} +{"name": "d700d50d-c707-4dca-90dc-4528cddd0c80", "prompt": "Who composed the song that was performed by a rooster and a hamster in separate animated videos at separate tempos with different lyrics? Answer using the format First name Last name.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Roger Miller", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Roger Miller", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0a3cd321-3e76-4622-911b-0fda2e5d6b1a", "prompt": "According to the World Bank, which countries had gross savings of over 35% of GDP for every year in the period 2001-2010? Give your answer as a comma-separated list of countries in alphabetical order. Use the countries most common names in english when answering.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Brunei, China, Morocco, Singapore", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Brunei, China, Morocco, Singapore", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f2feb6a4-363c-4c09-a804-0db564eafd68", "prompt": "I\u2019m thinking about selling my home, so I want to learn more about how homes in my area sold recently. I live in Pearl City, Hawaii, which is on the island of Oahu. I know two homes near me that sold in 2022 were 2072 Akaikai Loop, and 2017 Komo Mai Drive. Find which of those homes sold for more in 2022, and tell me how much it sold for. Don\u2019t put commas or decimal places in the answer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 900000", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "900000", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0b260a57-3f3a-4405-9f29-6d7a1012dbfb", "prompt": "On ScienceDirect, what is the difference to 3 decimal places in the sample standard deviations of the number of Reference Works in each Life Science domain compared to Health Sciences as of 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.269", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0.269", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ed58682d-bc52-4baa-9eb0-4eb81e1edacc", "prompt": "What is the last word before the second chorus of the King of Pop's fifth single from his sixth studio album?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: stare", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "stare", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cca70ce6-1952-45d2-acd4-80c903b0bc49", "prompt": "Look at the attached image. The quiz is scored as follows:\n\nProblems that ask the student to add or subtract fractions: 5 points\nProblems that ask the student to multiply or divide fractions: 10 points\nProblems that ask the student to form an improper fraction: 15 points\nProblems that ask the student to form a mixed number: 20 points\n\nDue to a technical issue that delayed having students take the quiz, the teacher is giving everyone 5 bonus points.\n\nIf you graded the quiz in the attached image, how many points would the student have earned? There is no partial credit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 85", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "85", "gaia_level": 2, "gaia_file": "cca70ce6-1952-45d2-acd4-80c903b0bc49.png", "source": "gaia-benchmark"}} +{"name": "b7f857e4-d8aa-4387-af2a-0e844df5b9d8", "prompt": "The attached image contains a Python script. Run the Python code against an array of strings, listed below. The output of the Python script will be a URL containing C++ source code. Compile and run this C++ code against the array [35, 12, 8, 99, 21, 5] and return the sum of the third and fifth integers in the sorted list.\n\narr = ['_alg', 'ghi', 'C++', 'jkl', 'tps', '/Q', 'pqr', 'stu', ':', '//', 'rose', 'vwx', 'yz1', '234', 'tta', '567', '890', 'cod', 'e.', 'or', 'g/', 'wiki', '/', 'ing', 'sort', 'abc' , 'or', 'it', 'hms', 'mno' , 'uic', 'ksort', '#', 'ht' ]", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 47", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "47", "gaia_level": 2, "gaia_file": "b7f857e4-d8aa-4387-af2a-0e844df5b9d8.png", "source": "gaia-benchmark"}} +{"name": "d8152ad6-e4d5-4c12-8bb7-8d57dc10c6de", "prompt": "I have the Standard plan in the image below, and I just uploaded 60 equally sized files and got a message that I'm 100GB over the limit. I have 980 more files of the same size to upload. What is the average additional cost per file in dollar that goes over my current plan limit rounded to the nearest cent if I have to upgrade to the minimum possible plan to store them all? Answer with the following format: x.xx", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.03", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0.03", "gaia_level": 2, "gaia_file": "d8152ad6-e4d5-4c12-8bb7-8d57dc10c6de.png", "source": "gaia-benchmark"}} +{"name": "67e8878b-5cef-4375-804e-e6291fdbe78a", "prompt": "The attached PDF lists accommodations in the resort community of Seahorse Island. Which type of accommodation has a higher average rating in Seahorse Island?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Hotels", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Hotels", "gaia_level": 2, "gaia_file": "67e8878b-5cef-4375-804e-e6291fdbe78a.pdf", "source": "gaia-benchmark"}} +{"name": "023e9d44-96ae-4eed-b912-244ee8c3b994", "prompt": "It's May 2023, and I'm about to drive across the U.S. from California to Maine. I always recycle my water bottles at the end of a trip, and I drink 5 12-ounce water bottles for every 100 miles I travel, rounded to the nearest 100. Assuming I follow I-40 from Los Angeles to Cincinnati, then take I-90 from Cincinnati to Augusta, how many dollars will I get back according to Wikipedia?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0e9e85b8-52b9-4de4-b402-5f635ab9631f", "prompt": "What is the latest chronological year date written in the image on the webpage found when following the first citation reference link on the latest version of Carl Nebel's Wikipedia page as of August 2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1927", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1927", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "20194330-9976-4043-8632-f8485c6c71b2", "prompt": "The YouTube channel Game Grumps began a Let\u2019s Play of the game Sonic the Hedgehog (2006) in the year 2012. Thirty seconds into the first episode, a phrase is shown on the screen in white letters on a red background. How many times does the letter \"E\" appear in this phrase?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "4", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2", "prompt": "This spreadsheet contains a list of clients for a retractable awning company. Each client has ordered a new awning for the back of their house within the last 90 days. The company makes different designs depending on whether the awning is made to block sunrises or sunsets. In this region, houses with odd-numbered street addresses face east, and houses with even-numbered street addresses face west. How many of these clients will be receiving the sunset awning design?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2.xlsx", "source": "gaia-benchmark"}} +{"name": "65638e28-7f37-4fa7-b7b9-8c19bb609879", "prompt": "The book with the doi 10.1353/book.24372 concerns a certain neurologist. According to chapter 2 of the book, what author influenced this neurologist\u2019s belief in \u201cendopsychic myths\u201d? Give the last name only.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Kleinpaul", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Kleinpaul", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3ff6b7a9-a5bd-4412-ad92-0cd0d45c0fee", "prompt": "The longest-lived vertebrate is named after an island. According to Wikipedia as of January 1, 2021, what is the 2020 estimated population of that island, to the nearest thousand?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 56000", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "56000", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "708b99c5-e4a7-49cb-a5cf-933c8d46470d", "prompt": "On the DeepFruits fruit detection graph on Connected Papers from 2016, what feature caused the largest bubble to be the size it is?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Citations", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Citations", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0a65cb96-cb6e-4a6a-8aae-c1084f613456", "prompt": "During the first week of August 2015, one of the NASA Astronomy Pictures of the Day shows the lights of a city on the horizon. The namesake of this city also has a landmark building in Chicago named after him. What is the name of the architectural firm that designed this landmark building? Give the first name appearing in the name of the firm as of June 2023.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Holabird", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Holabird", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65da0822-a48a-4a68-bbad-8ed1b835a834", "prompt": "All of the individuals who formally held the position of United States secretary of homeland security prior to April 2019, excluding those who held the position in an acting capacity, have a bachelor's degree. Of the universities that these bachelor's degrees were from, which is the westernmost university and which is the easternmost university? Give them to me as a comma-separated list, I only want the name of the cities where the universities are located, with the westernmost city listed first.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Santa Clara, Boston", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Santa Clara, Boston", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0bb3b44a-ede5-4db5-a520-4e844b0079c5", "prompt": "Consider the following symbols: \ud809\udc1c \ud809\udc10\ud809\udc1a\n\nThis is a number written using the Mesopotamian/Babylonian number system and represented with Sumerian cuneiform. Convert this number into Arabic numerals as a decimal number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 536", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "536", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "73c1b9fe-ee1d-4cf4-96ca-35c08f97b054", "prompt": "According to the USGS, in what year was the American Alligator first found west of Texas (not including Texas)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1954", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1954", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e2d69698-bc99-4e85-9880-67eaccd66e6c", "prompt": "As of August 2023, who is the only winner of the US version of Survivor to be born in the month of May?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Michele Fitzgerald", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Michele Fitzgerald", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a56f1527-3abf-41d6-91f8-7296d6336c3f", "prompt": "The cover of the August 2021 issue of Vogue shows a famous landmark in the background behind some trees. How tall is this monument in yards, rounded to the nearest yard? Give the number only.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 185", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "185", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "42d4198c-5895-4f0a-b0c0-424a66465d83", "prompt": "I'm curious about how much information is available for popular video games before their release. Find the Wikipedia page for the 2019 game that won the British Academy Games Awards. How many revisions did that page have before the month listed as the game's release date on that Wikipedia page (as of the most recent entry from 2022)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 60", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "60", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "edd4d4f2-1a58-45c4-b038-67337af4e029", "prompt": "The attached spreadsheet lists the locomotives owned by a local railroad museum. What is the typical American name for the type of locomotive this museum uses for the Murder Mystery Express?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Berkshire", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Berkshire", "gaia_level": 2, "gaia_file": "edd4d4f2-1a58-45c4-b038-67337af4e029.xlsx", "source": "gaia-benchmark"}} +{"name": "a26649c6-1cb2-470a-871e-6910c64c3e53", "prompt": "What is the absolute difference in tens of thousands between the population of chinstrap penguins on the Wikipedia page for penguin species populations as of the end of 2018 and the population recorded in the Nature.com \"global population assessment of the Chinstrap penguin\" article from 2020, assuming two penguins per breeding pair?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 116", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "116", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4d0aa727-86b1-406b-9b33-f870dd14a4a5", "prompt": "The attached file lists the locomotives owned by a local railroad museum. It gives each locomotive\u2019s identifying number, operating status, and the name of the daily excursion it heads, if operational. What are the odds that today\u2019s Sunset Picnic Trip will use a steam locomotive? Assume that each day\u2019s excursion picks one of its assigned locomotives at random, and express the answer in the form \u201c1 in 4\u201d, \u201c1 in 5\u201d, etc.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1 in 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1 in 3", "gaia_level": 2, "gaia_file": "4d0aa727-86b1-406b-9b33-f870dd14a4a5.xlsx", "source": "gaia-benchmark"}} +{"name": "d5141ca5-e7a0-469f-bf3e-e773507c86e2", "prompt": "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect? Answer using the format DD/MM/YYYY.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 19/02/2009", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "19/02/2009", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "1dcc160f-c187-48c2-b68e-319bd4354f3d", "prompt": "According to Openreview.net, at the NeurIPS 2022 Conference, how many papers by an author named Yuri were accepted with a \"certain\" recommendation?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b2c257e0-3ad7-4f05-b8e3-d9da973be36e", "prompt": "If this whole pint is made up of ice cream, how many percent above or below the US federal standards for butterfat content is it when using the standards as reported by Wikipedia in 2020? Answer as + or - a number rounded to one decimal place.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: +4.6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "+4.6", "gaia_level": 2, "gaia_file": "b2c257e0-3ad7-4f05-b8e3-d9da973be36e.jpg", "source": "gaia-benchmark"}} +{"name": "e0c10771-d627-4fd7-9694-05348e54ee36", "prompt": "Take the gender split from the 2011 Bulgarian census about those who have completed tertiary education. Subtract the smaller number from the larger number, then return the difference in thousands of women. So if there were 30.1 thousand more men, you'd give \"30.1\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 234.9", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "234.9", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e29834fd-413a-455c-a33e-c3915b07401c", "prompt": "I'd like to learn more about some popular reality television competition shows. As of the end of the 44th season of the American version of Survivor, how many more unique winners have there been compared to the number of winners of American Idol?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 21", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "21", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "08c0b6e9-1b43-4c2e-ae55-4e3fce2c2715", "prompt": "In the film Goldfinger, what color was the object that James Bond concealed himself and his companion Pussy Galore at the end of the film? If there are multiple colors, put them in a comma-separated list in alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: orange, white", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "orange, white", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "db4fd70a-2d37-40ea-873f-9433dc5e301f", "prompt": "As of May 2023, how many stops are between South Station and Windsor Gardens on MBTA\u2019s Franklin-Foxboro line (not included)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 10", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "10", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "853c8244-429e-46ca-89f2-addf40dfb2bd", "prompt": "In the 2015 Metropolitan Museum of Art exhibition titled after the Chinese zodiac animal of 2015, how many of the \"twelve animals of the Chinese zodiac\" have a hand visible?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 11", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "11", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7a4a336d-dcfa-45a0-b014-824c7619e8de", "prompt": "At the two-minute mark in the YouTube video uploaded by the channel \u201cGameGrumps\u201d on May 14, 2017 as part of their playthrough of the game Mario Kart 8 Deluxe, the shows\u2019 hosts are competing on one of the game\u2019s racetracks. What was the world record time for that track in the game\u2019s 150cc mode as of June 7, 2023? Express your answer in minutes and seconds, rounding the seconds to the nearest hundredth, e.g. 1:01.001.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1:41.614", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1:41.614", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c61d22de-5f6c-4958-a7f6-5e9707bd3466", "prompt": "A paper about AI regulation that was originally submitted to arXiv.org in June 2022 shows a figure with three axes, where each axis has a label word at both ends. Which of these words is used to describe a type of society in a Physics and Society article submitted to arXiv.org on August 11, 2016?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: egalitarian", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "egalitarian", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "17b5a6a3-bc87-42e8-b0fb-6ab0781ef2cc", "prompt": "I\u2019m researching species that became invasive after people who kept them as pets released them. There\u2019s a certain species of fish that was popularized as a pet by being the main character of the movie Finding Nemo. According to the USGS, where was this fish found as a nonnative species, before the year 2020? I need the answer formatted as the five-digit zip codes of the places the species was found, separated by commas if there is more than one place.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 34689", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "34689", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "04a04a9b-226c-43fd-b319-d5e89743676f", "prompt": "If we assume all articles published by Nature in 2020 (articles, only, not book reviews/columns, etc) relied on statistical significance to justify their findings and they on average came to a p-value of 0.04, how many papers would be incorrect as to their claims of statistical significance? Round the value up to the next integer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 41", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "41", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "14569e28-c88c-43e4-8c32-097d35b9a67d", "prompt": "In Unlambda, what exact charcter or text needs to be added to correct the following code to output \"For penguins\"? If what is needed is a character, answer with the name of the character. If there are different names for the character, use the shortest. The text location is not needed. Code:\n\n`r```````````.F.o.r. .p.e.n.g.u.i.n.si", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: backtick", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "backtick", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "32102e3e-d12a-4209-9163-7b3a104efe5d", "prompt": "The attached spreadsheet shows the inventory for a movie and video game rental store in Seattle, Washington. What is the title of the oldest Blu-Ray recorded in this spreadsheet? Return it as appearing in the spreadsheet.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Time-Parking 2: Parallel Universe", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Time-Parking 2: Parallel Universe", "gaia_level": 2, "gaia_file": "32102e3e-d12a-4209-9163-7b3a104efe5d.xlsx", "source": "gaia-benchmark"}} +{"name": "3627a8be-a77f-41bb-b807-7e1bd4c0ebdf", "prompt": "The object in the British Museum's collection with a museum number of 2012,5015.17 is the shell of a particular mollusk species. According to the abstract of a research article published in Science Advances in 2021, beads made from the shells of this species were found that are at least how many thousands of years old?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 142", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "142", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7619a514-5fa8-43ef-9143-83b66a43d7a4", "prompt": "According to github, when was Regression added to the oldest closed numpy.polynomial issue that has the Regression label in MM/DD/YY?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 04/15/18", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "04/15/18", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7dd30055-0198-452e-8c25-f73dbe27dcb8", "prompt": "Using the Biopython library in Python, parse the PDB file of the protein identified by the PDB ID 5wb7 from the RCSB Protein Data Bank. Calculate the distance between the first and second atoms as they are listed in the PDB file. Report the answer in Angstroms, rounded to the nearest picometer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1.456", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1.456", "gaia_level": 2, "gaia_file": "7dd30055-0198-452e-8c25-f73dbe27dcb8.pdb", "source": "gaia-benchmark"}} +{"name": "2a649bb1-795f-4a01-b3be-9a01868dae73", "prompt": "What are the EC numbers of the two most commonly used chemicals for the virus testing method in the paper about SPFMV and SPCSV in the Pearl Of Africa from 2016? Return the semicolon-separated numbers in the order of the alphabetized chemicals.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3.1.3.1; 1.11.1.7", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3.1.3.1; 1.11.1.7", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "87c610df-bef7-4932-b950-1d83ef4e282b", "prompt": "In April of 1977, who was the Prime Minister of the first place mentioned by name in the Book of Esther (in the New International Version)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Morarji Desai", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Morarji Desai", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "624cbf11-6a41-4692-af9c-36b3e5ca3130", "prompt": "What's the last line of the rhyme under the flavor name on the headstone visible in the background of the photo of the oldest flavor's headstone in the Ben & Jerry's online flavor graveyard as of the end of 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: So we had to let it die.", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "So we had to let it die.", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "dd3c7503-f62a-4bd0-9f67-1b63b94194cc", "prompt": "Use density measures from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023.\n\nI have a gallon of honey and a gallon of mayonnaise at 25C. I remove one cup of honey at a time from the gallon of honey. How many times will I need to remove a cup to have the honey weigh less than the mayonaise? Assume the containers themselves weigh the same.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "df6561b2-7ee5-4540-baab-5095f742716a", "prompt": "When you take the average of the standard population deviation of the red numbers and the standard sample deviation of the green numbers in this image using the statistics module in Python 3.11, what is the result rounded to the nearest three decimal points?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17.056", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "17.056", "gaia_level": 2, "gaia_file": "df6561b2-7ee5-4540-baab-5095f742716a.png", "source": "gaia-benchmark"}} +{"name": "f0f46385-fc03-4599-b5d3-f56496c3e69f", "prompt": "In terms of geographical distance between capital cities, which 2 countries are the furthest from each other within the ASEAN bloc according to wikipedia? Answer using a comma separated list, ordering the countries by alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Indonesia, Myanmar", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Indonesia, Myanmar", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e4e91f1c-1dcd-439e-9fdd-cb976f5293fd", "prompt": "I need to fact-check a citation. This is the citation from the bibliography:\n\nGreetham, David. \"Uncoupled: OR, How I Lost My Author(s).\" Textual Cultures: Texts, Contexts, Interpretation, vol. 3 no. 1, 2008, p. 45-46. Project MUSE, doi:10.2979/tex.2008.3.1.44.\n\nAnd this is the in-line citation:\n\nOur relationship with the authors of the works we read can often be \u201cobscured not by a \"cloak of print\" but by the veil of scribal confusion and mis-transmission\u201d (Greetham 45-46).\n\nDoes the quoted text match what is actually in the article? If Yes, answer Yes, otherwise, give me the word in my citation that does not match with the correct one (without any article).", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: cloak", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "cloak", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "56137764-b4e0-45b8-9c52-1866420c3df5", "prompt": "Which contributor to the version of OpenCV where support was added for the Mask-RCNN model has the same name as a former Chinese head of government when the names are transliterated to the Latin alphabet?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Li Peng", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Li Peng", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8b3379c0-0981-4f5b-8407-6444610cb212", "prompt": "What is the maximum length in meters of #9 in the first National Geographic short on YouTube that was ever released according to the Monterey Bay Aquarium website? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1.8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1.8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0ff53813-3367-4f43-bcbd-3fd725c1bf4b", "prompt": "What two-word type of model did Manash Pratim Kashyap's and PS Fader's studies in customer retention studies published during 2018-2019 have in common (no punctuation)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: beta geometric", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "beta geometric", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a7feb290-76bb-4cb7-8800-7edaf7954f2f", "prompt": "How many High Energy Physics - Lattice articles listed in January 2020 on Arxiv had ps versions available?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 31", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "31", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b4cc024b-3f5e-480e-b96a-6656493255b5", "prompt": "The photograph in the Whitney Museum of American Art's collection with accession number 2022.128 shows a person holding a book. Which military unit did the author of this book join in 1813? Answer without using articles.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Russian-German Legion", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Russian-German Legion", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "33d8ea3b-6c6b-4ff1-803d-7e270dea8a57", "prompt": "What is the minimum number of page links a person must click on to go from the english Wikipedia page on The Lord of the Rings (the book) to the english Wikipedia page on A Song of Ice and Fire (the book series)? In your count, include each link you would click on to get to the page. Use the pages as they appeared at the end of the day on July 3, 2023.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e8cb5b03-41e0-4086-99e5-f6806cd97211", "prompt": "I went to Virtue restaurant & bar in Chicago for my birthday on March 22, 2021 and the main course I had was delicious! Unfortunately, when I went back about a month later on April 21, it was no longer on the dinner menu. Using the Wayback Machine, can you help me figure out which main course was on the dinner menu for Virtue on March 22, 2021 but not April 21, 2021? Answer using the singular form, without articles.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: shrimp", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "shrimp", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f46b4380-207e-4434-820b-f32ce04ae2a4", "prompt": "It is 1999. Before you party like it is 1999, please assist me in settling a bet.\n\nFiona Apple and Paula Cole released albums prior to 1999. Of these albums, which didn't receive a letter grade from Robert Christgau? Provide your answer as a comma delimited list of album titles, sorted alphabetically.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Harbinger, Tidal", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Harbinger, Tidal", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "05407167-39ec-4d3a-a234-73a9120c325d", "prompt": "In the 2018 VSCode blog post on replit.com, what was the command they clicked on in the last video to remove extra lines?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Format Document", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Format Document", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b9763138-c053-4832-9f55-86200cb1f99c", "prompt": "Compute the check digit the Tropicos ID for the Order Helotiales would have if it were an ISBN-10 number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "16d825ff-1623-4176-a5b5-42e0f5c2b0ac", "prompt": "What time was the Tri-Rail train that carried the most passengers on May 27, 2019 scheduled to arrive in Pompano Beach? Express your answer in the 12-hour digital clock format without leading zero if any, and include whether it is AM or PM.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6:41 PM", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6:41 PM", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "2b3ef98c-cc05-450b-a719-711aee40ac65", "prompt": "Could you help me out with this assignment? Our professor sprung it on us at the end of class Friday, and I'm still trying to figure it out. The question he asked us was about an anagram. I've attached an audio recording of the question that he asked, so if you could please take a listen and give me the answer, I'd really appreciate the help. Please limit your response to the anagram text that could be generated from the original line which fulfills the professor's request, without any other commentary. Also, please don't include any punctuation in your response.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: To be or not to be that is the question whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "To be or not to be that is the question whether tis nobler in the mind to suffer the slings and arrows of outrageous fortune", "gaia_level": 2, "gaia_file": "2b3ef98c-cc05-450b-a719-711aee40ac65.mp3", "source": "gaia-benchmark"}} +{"name": "bfcd99e1-0690-4b53-a85c-0174a8629083", "prompt": "How many applicants for the job in the PDF are only missing a single qualification?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 17", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "17", "gaia_level": 2, "gaia_file": "bfcd99e1-0690-4b53-a85c-0174a8629083.zip", "source": "gaia-benchmark"}} +{"name": "544b7f0c-173a-4377-8d56-57b36eb26ddf", "prompt": "In Valentina Re\u2019s contribution to the 2017 book \u201cWorld Building: Transmedia, Fans, Industries\u201d, what horror movie does the author cite as having popularized metalepsis between a dream world and reality? Use the complete name with article if any.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: A Nightmare on Elm Street", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "A Nightmare on Elm Street", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6b078778-0b90-464d-83f6-59511c811b01", "prompt": "The Metropolitan Museum of Art has a portrait in its collection with an accession number of 29.100.5. Of the consecrators and co-consecrators of this portrait's subject as a bishop, what is the name of the one who never became pope?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Alfonso Visconti", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Alfonso Visconti", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "076c8171-9b3b-49b9-a477-244d2a532826", "prompt": "The attached file contains a list of vendors in the Liminal Springs mall, along with each vendor\u2019s monthly revenue and the rent they pay the mall. I want you to find the vendor that makes the least money, relative to the rent it pays. Then, tell me what is listed in the \u201ctype\u201d column for that vendor.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Finance", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Finance", "gaia_level": 2, "gaia_file": "076c8171-9b3b-49b9-a477-244d2a532826.xlsx", "source": "gaia-benchmark"}} +{"name": "08cae58d-4084-4616-b6dd-dd6534e4825b", "prompt": "According to Google Finance, when was the first year the Apple stock went above $50 (without adjusting for stock split)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2018", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2018", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "2dfc4c37-fec1-4518-84a7-10095d30ad75", "prompt": "According to Box Office Mojo's 2020 Worldwide Box Office list, how many of the top 10 highest-grossing worldwide movies are also on the top 10 highest-grossing domestic movies? Your answer should be a numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9f41b083-683e-4dcf-9185-ccfeaa88fa45", "prompt": "How many pages if the 2023 IPCC report (85 pages version) mentions nuclear energy?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ecbc4f94-95a3-4cc7-b255-6741a458a625", "prompt": "How many images are there in the latest 2022 Lego english wikipedia article?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 13", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "13", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e9a2c537-8232-4c3f-85b0-b52de6bcba99", "prompt": "The attached file shows a list of books in the collection of Scribe County Public Library. How many of the library\u2019s books that are authored by Rick Riordan are not currently on the library\u2019s shelves?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 7", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "7", "gaia_level": 2, "gaia_file": "e9a2c537-8232-4c3f-85b0-b52de6bcba99.pdf", "source": "gaia-benchmark"}} +{"name": "71345b0a-9c7d-4b50-b2bf-937ec5879845", "prompt": "On a leap day before the year 2008, a joke was removed from the Wikipedia page for \u201cDragon\u201d. What was the phrase that was removed? Give the phrase as it appeared on the page, but without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Here be dragons", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Here be dragons", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7b5377b0-3f38-4103-8ad2-90fe89864c04", "prompt": "Find the value of x to the nearest tenth: Lx = (d/dx * (A * x-squared)) + 4-thousand'n'ninety-7 minus C\nWhere L is the last two digits of the year of the Venezuelan Declaration of Independence,\nA is the number of colors in the TikTok logo as of July 2023, excluding black and white,\nand C is the height of the average woman in the Philippines according to a July 2023 Business Insider article, rounded to the nearest whole centimeter", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 563.9", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "563.9", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "114d5fd0-e2ae-4b6d-a65a-870da2d19c08", "prompt": "In the endnote found in the second-to-last paragraph of page 11 of the book with the doi 10.2307/j.ctv9b2xdv, what date in November was the Wikipedia article accessed? Just give the day of the month.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "4", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8f80e01c-1296-4371-9486-bb3d68651a60", "prompt": "Using bass clef notes, what is the age of someone who has experienced the word spelled out in the sheet music by the note letters the total number of lines and notes minus the number of notes on lines in the image?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 90", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "90", "gaia_level": 2, "gaia_file": "8f80e01c-1296-4371-9486-bb3d68651a60.png", "source": "gaia-benchmark"}} +{"name": "ad37a656-079a-49f9-a493-7b739c9167d1", "prompt": "On July 15, 2008, Phys.org published an article about a catastrophe. Find the explosive force of this catastrophe according to Encyclopedia Britannica, then find the name of the US nuclear test that had the same yield. Your answer should only be the last word of the name of the test.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Bravo", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Bravo", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "366e2f2b-8632-4ef2-81eb-bc3877489217", "prompt": "The attached file lists accommodations in the resort town of Seahorse Island. Based on the information in this file, which seems like the better available place to stay for a family that enjoys swimming and wants a full house?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Shelley's place", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Shelley's place", "gaia_level": 2, "gaia_file": "366e2f2b-8632-4ef2-81eb-bc3877489217.pdf", "source": "gaia-benchmark"}} +{"name": "f3917a3d-1d17-4ee2-90c5-683b072218fe", "prompt": "How many edits were made to the Wikipedia page on Antidisestablishmentarianism from its inception until June of 2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2732", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2732", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "48eb8242-1099-4c26-95d4-ef22b002457a", "prompt": "How many nonindigenous crocodiles were found in Florida from the year 2000 through 2020? You can get the data from the USGS Nonindigenous Aquatic Species database.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "6", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c8b7e059-c60d-472e-ad64-3b04ae1166dc", "prompt": "The work referenced in footnote 397 of Federico Lauria's 2014 dissertation is also the source for the titles of two paintings in the Smithsonian American Art Museum's collection, as of August 2023. What is the absolute difference between the chapter numbers of the chapters that the titles of these two paintings quote?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "d1af70ea-a9a4-421a-b9cc-94b5e02f1788", "prompt": "As of the 2020 census, what was the population difference between the largest county seat and smallest county seat, by land area of the county seat, in Washington state? For population figures, please use the official data from data.census.gov. Please report the integer difference.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 736455", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "736455", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "08f3a05f-5947-4089-a4c4-d4bcfaa6b7a0", "prompt": "Given $x_0 = -5$ and $f(x) = x^3 + 4x^2 - 3x + 8$, what is the smallest $n$ where using Newton's Method $n = n+1$ after rounding to four decimal places?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 2", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "2", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "54612da3-fd56-4941-80f4-5eb82330de25", "prompt": "The attached file shows the locomotives in the collection of a North American railroad museum. How many wheels do the listed steam locomotives have in total?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 60", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "60", "gaia_level": 2, "gaia_file": "54612da3-fd56-4941-80f4-5eb82330de25.xlsx", "source": "gaia-benchmark"}} +{"name": "ded28325-3447-4c56-860f-e497d6fb3577", "prompt": "This is a secret message my friend gave me. It says where we should meet for our picnic on Friday. The only problem is, it\u2019s encrypted in the Caesar cipher, so I can\u2019t read it. Can you tell me what it says? This is the message:\n\nZsmxsm sc sx Zyvilsec Zvkjk.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Picnic is in Ploybius Plaza.", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Picnic is in Ploybius Plaza.", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "6359a0b1-8f7b-499b-9336-840f9ab90688", "prompt": "What is the area of the green polygon in the attached file? The numbers in purple represent the lengths of the side they are next to.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 39", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "39", "gaia_level": 2, "gaia_file": "6359a0b1-8f7b-499b-9336-840f9ab90688.png", "source": "gaia-benchmark"}} +{"name": "7cc4acfa-63fd-4acc-a1a1-e8e529e0a97f", "prompt": "The attached spreadsheet contains the sales of menu items for a regional fast-food chain. Which city had the greater total sales: Wharvton or Algrimand?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Wharvton", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Wharvton", "gaia_level": 2, "gaia_file": "7cc4acfa-63fd-4acc-a1a1-e8e529e0a97f.xlsx", "source": "gaia-benchmark"}} +{"name": "d700d50d-c707-4dca-90dc-4528cddd0c80", "prompt": "Who composed the song that was performed by a rooster and a hamster in separate animated videos at separate tempos with different lyrics? Answer using the format First name Last name.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Roger Miller", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Roger Miller", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0a3cd321-3e76-4622-911b-0fda2e5d6b1a", "prompt": "According to the World Bank, which countries had gross savings of over 35% of GDP for every year in the period 2001-2010? Give your answer as a comma-separated list of countries in alphabetical order. Use the countries most common names in english when answering.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Brunei, China, Morocco, Singapore", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Brunei, China, Morocco, Singapore", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "f2feb6a4-363c-4c09-a804-0db564eafd68", "prompt": "I\u2019m thinking about selling my home, so I want to learn more about how homes in my area sold recently. I live in Pearl City, Hawaii, which is on the island of Oahu. I know two homes near me that sold in 2022 were 2072 Akaikai Loop, and 2017 Komo Mai Drive. Find which of those homes sold for more in 2022, and tell me how much it sold for. Don\u2019t put commas or decimal places in the answer.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 900000", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "900000", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0b260a57-3f3a-4405-9f29-6d7a1012dbfb", "prompt": "On ScienceDirect, what is the difference to 3 decimal places in the sample standard deviations of the number of Reference Works in each Life Science domain compared to Health Sciences as of 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.269", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0.269", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ed58682d-bc52-4baa-9eb0-4eb81e1edacc", "prompt": "What is the last word before the second chorus of the King of Pop's fifth single from his sixth studio album?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: stare", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "stare", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "cca70ce6-1952-45d2-acd4-80c903b0bc49", "prompt": "Look at the attached image. The quiz is scored as follows:\n\nProblems that ask the student to add or subtract fractions: 5 points\nProblems that ask the student to multiply or divide fractions: 10 points\nProblems that ask the student to form an improper fraction: 15 points\nProblems that ask the student to form a mixed number: 20 points\n\nDue to a technical issue that delayed having students take the quiz, the teacher is giving everyone 5 bonus points.\n\nIf you graded the quiz in the attached image, how many points would the student have earned? There is no partial credit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 85", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "85", "gaia_level": 2, "gaia_file": "cca70ce6-1952-45d2-acd4-80c903b0bc49.png", "source": "gaia-benchmark"}} +{"name": "b7f857e4-d8aa-4387-af2a-0e844df5b9d8", "prompt": "The attached image contains a Python script. Run the Python code against an array of strings, listed below. The output of the Python script will be a URL containing C++ source code. Compile and run this C++ code against the array [35, 12, 8, 99, 21, 5] and return the sum of the third and fifth integers in the sorted list.\n\narr = ['_alg', 'ghi', 'C++', 'jkl', 'tps', '/Q', 'pqr', 'stu', ':', '//', 'rose', 'vwx', 'yz1', '234', 'tta', '567', '890', 'cod', 'e.', 'or', 'g/', 'wiki', '/', 'ing', 'sort', 'abc' , 'or', 'it', 'hms', 'mno' , 'uic', 'ksort', '#', 'ht' ]", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 47", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "47", "gaia_level": 2, "gaia_file": "b7f857e4-d8aa-4387-af2a-0e844df5b9d8.png", "source": "gaia-benchmark"}} +{"name": "d8152ad6-e4d5-4c12-8bb7-8d57dc10c6de", "prompt": "I have the Standard plan in the image below, and I just uploaded 60 equally sized files and got a message that I'm 100GB over the limit. I have 980 more files of the same size to upload. What is the average additional cost per file in dollar that goes over my current plan limit rounded to the nearest cent if I have to upgrade to the minimum possible plan to store them all? Answer with the following format: x.xx", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.03", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "0.03", "gaia_level": 2, "gaia_file": "d8152ad6-e4d5-4c12-8bb7-8d57dc10c6de.png", "source": "gaia-benchmark"}} +{"name": "67e8878b-5cef-4375-804e-e6291fdbe78a", "prompt": "The attached PDF lists accommodations in the resort community of Seahorse Island. Which type of accommodation has a higher average rating in Seahorse Island?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Hotels", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Hotels", "gaia_level": 2, "gaia_file": "67e8878b-5cef-4375-804e-e6291fdbe78a.pdf", "source": "gaia-benchmark"}} +{"name": "023e9d44-96ae-4eed-b912-244ee8c3b994", "prompt": "It's May 2023, and I'm about to drive across the U.S. from California to Maine. I always recycle my water bottles at the end of a trip, and I drink 5 12-ounce water bottles for every 100 miles I travel, rounded to the nearest 100. Assuming I follow I-40 from Los Angeles to Cincinnati, then take I-90 from Cincinnati to Augusta, how many dollars will I get back according to Wikipedia?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0e9e85b8-52b9-4de4-b402-5f635ab9631f", "prompt": "What is the latest chronological year date written in the image on the webpage found when following the first citation reference link on the latest version of Carl Nebel's Wikipedia page as of August 2023?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1927", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1927", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "20194330-9976-4043-8632-f8485c6c71b2", "prompt": "The YouTube channel Game Grumps began a Let\u2019s Play of the game Sonic the Hedgehog (2006) in the year 2012. Thirty seconds into the first episode, a phrase is shown on the screen in white letters on a red background. How many times does the letter \"E\" appear in this phrase?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "4", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2", "prompt": "This spreadsheet contains a list of clients for a retractable awning company. Each client has ordered a new awning for the back of their house within the last 90 days. The company makes different designs depending on whether the awning is made to block sunrises or sunsets. In this region, houses with odd-numbered street addresses face east, and houses with even-numbered street addresses face west. How many of these clients will be receiving the sunset awning design?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "8", "gaia_level": 2, "gaia_file": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2.xlsx", "source": "gaia-benchmark"}} +{"name": "65638e28-7f37-4fa7-b7b9-8c19bb609879", "prompt": "The book with the doi 10.1353/book.24372 concerns a certain neurologist. According to chapter 2 of the book, what author influenced this neurologist\u2019s belief in \u201cendopsychic myths\u201d? Give the last name only.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Kleinpaul", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Kleinpaul", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "3ff6b7a9-a5bd-4412-ad92-0cd0d45c0fee", "prompt": "The longest-lived vertebrate is named after an island. According to Wikipedia as of January 1, 2021, what is the 2020 estimated population of that island, to the nearest thousand?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 56000", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "56000", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "708b99c5-e4a7-49cb-a5cf-933c8d46470d", "prompt": "On the DeepFruits fruit detection graph on Connected Papers from 2016, what feature caused the largest bubble to be the size it is?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Citations", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Citations", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0a65cb96-cb6e-4a6a-8aae-c1084f613456", "prompt": "During the first week of August 2015, one of the NASA Astronomy Pictures of the Day shows the lights of a city on the horizon. The namesake of this city also has a landmark building in Chicago named after him. What is the name of the architectural firm that designed this landmark building? Give the first name appearing in the name of the firm as of June 2023.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Holabird", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Holabird", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "65da0822-a48a-4a68-bbad-8ed1b835a834", "prompt": "All of the individuals who formally held the position of United States secretary of homeland security prior to April 2019, excluding those who held the position in an acting capacity, have a bachelor's degree. Of the universities that these bachelor's degrees were from, which is the westernmost university and which is the easternmost university? Give them to me as a comma-separated list, I only want the name of the cities where the universities are located, with the westernmost city listed first.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Santa Clara, Boston", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Santa Clara, Boston", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0bb3b44a-ede5-4db5-a520-4e844b0079c5", "prompt": "Consider the following symbols: \ud809\udc1c \ud809\udc10\ud809\udc1a\n\nThis is a number written using the Mesopotamian/Babylonian number system and represented with Sumerian cuneiform. Convert this number into Arabic numerals as a decimal number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 536", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "536", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "73c1b9fe-ee1d-4cf4-96ca-35c08f97b054", "prompt": "According to the USGS, in what year was the American Alligator first found west of Texas (not including Texas)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1954", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1954", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e2d69698-bc99-4e85-9880-67eaccd66e6c", "prompt": "As of August 2023, who is the only winner of the US version of Survivor to be born in the month of May?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Michele Fitzgerald", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Michele Fitzgerald", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "a56f1527-3abf-41d6-91f8-7296d6336c3f", "prompt": "The cover of the August 2021 issue of Vogue shows a famous landmark in the background behind some trees. How tall is this monument in yards, rounded to the nearest yard? Give the number only.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 185", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "185", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "42d4198c-5895-4f0a-b0c0-424a66465d83", "prompt": "I'm curious about how much information is available for popular video games before their release. Find the Wikipedia page for the 2019 game that won the British Academy Games Awards. How many revisions did that page have before the month listed as the game's release date on that Wikipedia page (as of the most recent entry from 2022)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 60", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "60", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "edd4d4f2-1a58-45c4-b038-67337af4e029", "prompt": "The attached spreadsheet lists the locomotives owned by a local railroad museum. What is the typical American name for the type of locomotive this museum uses for the Murder Mystery Express?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Berkshire", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "Berkshire", "gaia_level": 2, "gaia_file": "edd4d4f2-1a58-45c4-b038-67337af4e029.xlsx", "source": "gaia-benchmark"}} +{"name": "a26649c6-1cb2-470a-871e-6910c64c3e53", "prompt": "What is the absolute difference in tens of thousands between the population of chinstrap penguins on the Wikipedia page for penguin species populations as of the end of 2018 and the population recorded in the Nature.com \"global population assessment of the Chinstrap penguin\" article from 2020, assuming two penguins per breeding pair?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 116", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "116", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "4d0aa727-86b1-406b-9b33-f870dd14a4a5", "prompt": "The attached file lists the locomotives owned by a local railroad museum. It gives each locomotive\u2019s identifying number, operating status, and the name of the daily excursion it heads, if operational. What are the odds that today\u2019s Sunset Picnic Trip will use a steam locomotive? Assume that each day\u2019s excursion picks one of its assigned locomotives at random, and express the answer in the form \u201c1 in 4\u201d, \u201c1 in 5\u201d, etc.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1 in 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1 in 3", "gaia_level": 2, "gaia_file": "4d0aa727-86b1-406b-9b33-f870dd14a4a5.xlsx", "source": "gaia-benchmark"}} +{"name": "d5141ca5-e7a0-469f-bf3e-e773507c86e2", "prompt": "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect? Answer using the format DD/MM/YYYY.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 19/02/2009", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "19/02/2009", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "1dcc160f-c187-48c2-b68e-319bd4354f3d", "prompt": "According to Openreview.net, at the NeurIPS 2022 Conference, how many papers by an author named Yuri were accepted with a \"certain\" recommendation?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "3", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "b2c257e0-3ad7-4f05-b8e3-d9da973be36e", "prompt": "If this whole pint is made up of ice cream, how many percent above or below the US federal standards for butterfat content is it when using the standards as reported by Wikipedia in 2020? Answer as + or - a number rounded to one decimal place.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: +4.6", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "+4.6", "gaia_level": 2, "gaia_file": "b2c257e0-3ad7-4f05-b8e3-d9da973be36e.jpg", "source": "gaia-benchmark"}} +{"name": "e0c10771-d627-4fd7-9694-05348e54ee36", "prompt": "Take the gender split from the 2011 Bulgarian census about those who have completed tertiary education. Subtract the smaller number from the larger number, then return the difference in thousands of women. So if there were 30.1 thousand more men, you'd give \"30.1\"", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 234.9", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "234.9", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "e29834fd-413a-455c-a33e-c3915b07401c", "prompt": "I'd like to learn more about some popular reality television competition shows. As of the end of the 44th season of the American version of Survivor, how many more unique winners have there been compared to the number of winners of American Idol?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 21", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "21", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "08c0b6e9-1b43-4c2e-ae55-4e3fce2c2715", "prompt": "In the film Goldfinger, what color was the object that James Bond concealed himself and his companion Pussy Galore at the end of the film? If there are multiple colors, put them in a comma-separated list in alphabetical order.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: orange, white", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "orange, white", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "db4fd70a-2d37-40ea-873f-9433dc5e301f", "prompt": "As of May 2023, how many stops are between South Station and Windsor Gardens on MBTA\u2019s Franklin-Foxboro line (not included)?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 10", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "10", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "853c8244-429e-46ca-89f2-addf40dfb2bd", "prompt": "In the 2015 Metropolitan Museum of Art exhibition titled after the Chinese zodiac animal of 2015, how many of the \"twelve animals of the Chinese zodiac\" have a hand visible?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 11", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "11", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "7a4a336d-dcfa-45a0-b014-824c7619e8de", "prompt": "At the two-minute mark in the YouTube video uploaded by the channel \u201cGameGrumps\u201d on May 14, 2017 as part of their playthrough of the game Mario Kart 8 Deluxe, the shows\u2019 hosts are competing on one of the game\u2019s racetracks. What was the world record time for that track in the game\u2019s 150cc mode as of June 7, 2023? Express your answer in minutes and seconds, rounding the seconds to the nearest hundredth, e.g. 1:01.001.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 1:41.614", "weight": 1.0}], "category": "level_2", "metadata": {"gaia_answer": "1:41.614", "gaia_level": 2, "gaia_file": null, "source": "gaia-benchmark"}} diff --git a/src/flow/experiments/data/tasks/gaia_level3.jsonl b/src/flow/experiments/data/tasks/gaia_level3.jsonl new file mode 100644 index 0000000000000000000000000000000000000000..3f1ac284917eb44a016e699191ac25d4e557ddad --- /dev/null +++ b/src/flow/experiments/data/tasks/gaia_level3.jsonl @@ -0,0 +1,52 @@ +{"name": "676e5e31-a554-4acc-9286-b60d90a92d26", "prompt": "In July 2, 1959 United States standards for grades of processed fruits, vegetables, and certain other products listed as dehydrated, consider the items in the \"dried and dehydrated section\" specifically marked as dehydrated along with any items in the Frozen/Chilled section that contain the whole name of the item, but not if they're marked Chilled. As of August 2023, what is the percentage (to the nearest percent) of those standards that have been superseded by a new version since the date given in the 1959 standards?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 86", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "86", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "bec74516-02fc-48dc-b202-55e78d0e17cf", "prompt": "What is the average number of pre-2020 works on the open researcher and contributor identification pages of the people whose identification is in this file?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 26.4", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "26.4", "gaia_level": 3, "gaia_file": "bec74516-02fc-48dc-b202-55e78d0e17cf.jsonld", "source": "gaia-benchmark"}} +{"name": "00d579ea-0889-4fd9-a771-2c8d79835c8d", "prompt": "Assuming scientists in the famous youtube video The Thinking Machine (Artificial Intelligence in the 1960s) were interviewed the same year, what is the name of the scientist predicting the sooner thinking machines or robots? Answer using the format First name Last name", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Claude Shannon", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Claude Shannon", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "384d0dd8-e8a4-4cfe-963c-d37f256e7662", "prompt": "In the NCATS PubChem compound database for Food Additive Status classification, find the compound that has a molecular weight of 100 g/mol or less, 6 heavy atoms, 1 or fewer hydrogen bond acceptors, and a complexity between 10 and 15. Of the shared gene-chemical co-occurrences between its two possible enzyme transformations, what is the PubChem CID of the heaviest by molecular weight?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4192", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "4192", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "de9887f5-ead8-4727-876f-5a4078f8598c", "prompt": "What integer-rounded percentage of the total length of the harlequin shrimp recorded in Omar Valencfia-Mendez 2017 paper was the sea star fed to the same type of shrimp in G. Curt Fiedler's 2002 paper?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 22", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "22", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "983bba7c-c092-455f-b6c9-7857003d48fc", "prompt": "What animals that were mentioned in both Ilias Lagkouvardos's and Olga Tapia's papers on the alvei species of the genus named for Copenhagen outside the bibliographies were also present in the 2021 article cited on the alvei species' Wikipedia page about a multicenter, randomized, double-blind study?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: mice", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "mice", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9b54f9d9-35ee-4a14-b62f-d130ea00317f", "prompt": "Which of the text elements under CATEGORIES in the XML would contain the one food in the spreadsheet that does not appear a second time under a different name?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Soups and Stews", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Soups and Stews", "gaia_level": 3, "gaia_file": "9b54f9d9-35ee-4a14-b62f-d130ea00317f.zip", "source": "gaia-benchmark"}} +{"name": "56db2318-640f-477a-a82f-bc93ad13e882", "prompt": "The following numbers function similarly to ISBN 13 numbers, however, their validation methods are slightly different. Rather than using alternate weights of 1 and 3, the checksum digit is calculated with an alternate weight of 1 and some other positive integer less than 10. Otherwise, the checksum digit is calculated as expected. Unfortunately, there is an error in the data. Two adjacent columns have been transposed. These errored columns do not involve the final column or one of the first three columns. Using this information, please provide all potential solutions with the unknown weight and the smaller index of the two errored columns (assume we start our indexing at 0 and ignore hyphens). Give your answer in the form x, y where x is the weight and y is the smaller index of the two transposed columns.\n\n978-354181391-9\n978-946669746-1\n978-398036139-6\n978-447656680-4\n978-279586664-7\n978-595073693-3\n978-976647652-6\n978-591178125-5\n978-728465924-5\n978-414825155-9", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 7, 9", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "7, 9", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8131e2c0-0083-4265-9ce7-78c2d568425d", "prompt": "I was trying to remember how well the Cheater Beater performed in comparison to the Cheater when James tested it on his channel. I know that the Cheater still outperformed the Cheater Beater in terms of CFM. Could you please look that up for me, and report the CFM of both the Cheater and the Cheater Beater? I'm not sure if he made any changes to his testing, but this was back in season 4, so just report the value from that season. Please format your response like this: CFM number for Cheater, CFM number for Cheater beater", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 101.376, 84.348", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "101.376, 84.348", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "72c06643-a2fa-4186-aa5c-9ec33ae9b445", "prompt": "What is the volume in milliliters of a system comprised of 0.312 kg Freon-12 refrigerant when placed at the bottom of the Marianas Trench and allowed to stabilize at the Trench's peak temperature, rounded to the nearest mL? Provide your answer as just an integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 55", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "55", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ebbc1f13-d24d-40df-9068-adcf735b4240", "prompt": "The Latin root of the Yola word \"gimlie\" shares a spelling with a Spanish word. What is the Google translation of the source title for the 1994 example sentence for that word in the Collins Spanish-to-English dictionary online? Answer in plain text, without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: The World of the Twenty First Century", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "The World of the Twenty First Century", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c526d8d6-5987-4da9-b24c-83466fa172f3", "prompt": "In the NIH translation of the original 1913 Michaelis-Menten Paper, what is the velocity of a reaction to four decimal places using the final equation in the paper based on the information for Reaction 7 in the Excel file?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.0424", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.0424", "gaia_level": 3, "gaia_file": "c526d8d6-5987-4da9-b24c-83466fa172f3.xlsx", "source": "gaia-benchmark"}} +{"name": "3da89939-209c-4086-8520-7eb734e6b4ef", "prompt": "I was referencing each of the tables in the file from papers that were cited by the \"Trans fatty acid contents in chocolates and chocolate wafers in Turkey\" paper. I lost my own reference sheet and need to know which of the papers each table came from. The file may not use the full table caption. If the references in the\"Trans fatty acid\" paper bibliography were numbered starting with 1, give me the numbers in the order that they would be used to fill the cells in the Excel file from top to bottom, as a comma separated list.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8, 29, 22, 1, 8, 26", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "8, 29, 22, 1, 8, 26", "gaia_level": 3, "gaia_file": "3da89939-209c-4086-8520-7eb734e6b4ef.xlsx", "source": "gaia-benchmark"}} +{"name": "8d46b8d6-b38a-47ff-ac74-cda14cf2d19b", "prompt": "What percentage of the total penguin population according to the upper estimates on english Wikipedia at the end of 2012 is made up by the penguins in this file that don't live on Dream Island or have beaks longer than 42mm? Round to the nearest five decimal places.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.00033", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.00033", "gaia_level": 3, "gaia_file": "8d46b8d6-b38a-47ff-ac74-cda14cf2d19b.csv", "source": "gaia-benchmark"}} +{"name": "e961a717-6b25-4175-8a68-874d28190ee4", "prompt": "According to wikipedia, how many Asian countries still have a monarchy and access to the sea in 2021?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 12", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "12", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "851e570a-e3de-4d84-bcfa-cc85578baa59", "prompt": "I thought we could try a fun word puzzle together :)\n\nI've got a Boggle board here:\n\nABRL\nEITE\nIONS\nFPEI\n\nI'd like to know the longest word that can be generated from the board. Please find the longest English language word that can be generated from this board. If more than one word of the same length exists at the maximum word length, please report the longest word that comes first, alphabetically. Oh, and I know that there might be different wordlists available for Boggle, so let's please just use the words_alpha dictionary found at https://github.com/dwyl/english-words as the dictionary for our game.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Briniest", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Briniest", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50f58759-7bd6-406f-9b0d-5692beb2a926", "prompt": "How many times was a Twitter/X post cited as a reference on the english Wikipedia pages for each day of August in the last June 2023 versions of the pages?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "3", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "872bfbb1-9ccf-49f6-8c5f-aa22818ccd66", "prompt": "Which of the fruits shown in the 2008 painting \"Embroidery from Uzbekistan\" were served as part of the October 1949 breakfast menu for the ocean liner that was later used as a floating prop for the film \"The Last Voyage\"? Give the items as a comma-separated list, ordering them in clockwise order based on their arrangement in the painting starting from the 12 o'clock position. Use the plural form of each fruit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: pears, bananas", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "pears, bananas", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c3a79cfe-8206-451f-aca8-3fec8ebe51d3", "prompt": "The year is 2022. I am at the National Air and Space Museum east of the Potomac River. I want to go to Fire Station 301 DCA ARFF using the metro. I go in the wrong direction and end up at the station closest to Cleveland Elementary School. How many metro stations am I away from my original destination if I don't change lines? Your answer should be a numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "8", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "da52d699-e8d2-4dc5-9191-a2199e0b6a9b", "prompt": "The attached spreadsheet contains a list of books I read in the year 2022. What is the title of the book that I read the slowest, using the rate of words per day?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Out of the Silent Planet", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Out of the Silent Planet", "gaia_level": 3, "gaia_file": "da52d699-e8d2-4dc5-9191-a2199e0b6a9b.xlsx", "source": "gaia-benchmark"}} +{"name": "ad2b4d70-9314-4fe6-bfbe-894a45f6055f", "prompt": "Eva Draconis has a personal website which can be accessed on her YouTube page. What is the meaning of the only symbol seen in the top banner that has a curved line that isn't a circle or a portion of a circle? Answer without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: War is not here this is a land of peace", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "War is not here this is a land of peace", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5b2a14e8-6e59-479c-80e3-4696e8980152", "prompt": "The brand that makes these harnesses the dogs are wearing in the attached pic shares stories from their ambassadors on their website. What meat is mentioned in the story added Dec 8th 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: bacon", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "bacon", "gaia_level": 3, "gaia_file": "5b2a14e8-6e59-479c-80e3-4696e8980152.jpg", "source": "gaia-benchmark"}} +{"name": "9e1fc53b-46ff-49a1-9d05-9e6faac34cc5", "prompt": "A 5-man group made up of one tank, one healer, and three DPS is doing a dungeon that was just released in World of Warcraft. Two are plate wearers and two are cloth wearers. At the final boss, both the tank and the healer are casting holy spells. Ice and fire are being used, each one by a different DPS. A bear from the group is attacking the boss. Metamorphosis is cast. The Kilt of the Forgotten One drops as loot, but no one can use it. If all classes were using their class abilities and all classes are unique, what are the five classes in the group in alphabetical order separated by commas?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Death Knight, Hunter, Paladin, Priest, Warlock", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Death Knight, Hunter, Paladin, Priest, Warlock", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5f982798-16b9-4051-ab57-cfc7ebdb2a91", "prompt": "I read a paper about multiwavelength observations of fast radio bursts back in March 2021 on Arxiv, and it had a fascinating diagram of an X-ray time profile. There was a similar burst-1 diagram in another paper from one of the same authors about fast radio bursts back in July 2020, but I can't recall what the difference in seconds in the measured time span was. How many more seconds did one measure than the other? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.2", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.2", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0512426f-4d28-49f0-be77-06d05daec096", "prompt": "In the YouTube 360 VR video from March 2018 narrated by the voice actor of Lord of the Rings' Gollum, what number was mentioned by the narrator directly after dinosaurs were first shown in the video?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 100000000", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "100000000", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0bdb7c40-671d-4ad1-9ce3-986b159c0ddc", "prompt": "In NASA's Astronomy Picture of the Day on 2006 January 21, two astronauts are visible, with one appearing much smaller than the other. As of August 2023, out of the astronauts in the NASA Astronaut Group that the smaller astronaut was a member of, which one spent the least time in space, and how many minutes did he spend in space, rounded to the nearest minute? Exclude any astronauts who did not spend any time in space. Give the last name of the astronaut, separated from the number of minutes by a semicolon.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: White; 5876", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "White; 5876", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "676e5e31-a554-4acc-9286-b60d90a92d26", "prompt": "In July 2, 1959 United States standards for grades of processed fruits, vegetables, and certain other products listed as dehydrated, consider the items in the \"dried and dehydrated section\" specifically marked as dehydrated along with any items in the Frozen/Chilled section that contain the whole name of the item, but not if they're marked Chilled. As of August 2023, what is the percentage (to the nearest percent) of those standards that have been superseded by a new version since the date given in the 1959 standards?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 86", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "86", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "bec74516-02fc-48dc-b202-55e78d0e17cf", "prompt": "What is the average number of pre-2020 works on the open researcher and contributor identification pages of the people whose identification is in this file?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 26.4", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "26.4", "gaia_level": 3, "gaia_file": "bec74516-02fc-48dc-b202-55e78d0e17cf.jsonld", "source": "gaia-benchmark"}} +{"name": "00d579ea-0889-4fd9-a771-2c8d79835c8d", "prompt": "Assuming scientists in the famous youtube video The Thinking Machine (Artificial Intelligence in the 1960s) were interviewed the same year, what is the name of the scientist predicting the sooner thinking machines or robots? Answer using the format First name Last name", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Claude Shannon", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Claude Shannon", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "384d0dd8-e8a4-4cfe-963c-d37f256e7662", "prompt": "In the NCATS PubChem compound database for Food Additive Status classification, find the compound that has a molecular weight of 100 g/mol or less, 6 heavy atoms, 1 or fewer hydrogen bond acceptors, and a complexity between 10 and 15. Of the shared gene-chemical co-occurrences between its two possible enzyme transformations, what is the PubChem CID of the heaviest by molecular weight?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 4192", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "4192", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "de9887f5-ead8-4727-876f-5a4078f8598c", "prompt": "What integer-rounded percentage of the total length of the harlequin shrimp recorded in Omar Valencfia-Mendez 2017 paper was the sea star fed to the same type of shrimp in G. Curt Fiedler's 2002 paper?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 22", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "22", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "983bba7c-c092-455f-b6c9-7857003d48fc", "prompt": "What animals that were mentioned in both Ilias Lagkouvardos's and Olga Tapia's papers on the alvei species of the genus named for Copenhagen outside the bibliographies were also present in the 2021 article cited on the alvei species' Wikipedia page about a multicenter, randomized, double-blind study?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: mice", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "mice", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "9b54f9d9-35ee-4a14-b62f-d130ea00317f", "prompt": "Which of the text elements under CATEGORIES in the XML would contain the one food in the spreadsheet that does not appear a second time under a different name?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Soups and Stews", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Soups and Stews", "gaia_level": 3, "gaia_file": "9b54f9d9-35ee-4a14-b62f-d130ea00317f.zip", "source": "gaia-benchmark"}} +{"name": "56db2318-640f-477a-a82f-bc93ad13e882", "prompt": "The following numbers function similarly to ISBN 13 numbers, however, their validation methods are slightly different. Rather than using alternate weights of 1 and 3, the checksum digit is calculated with an alternate weight of 1 and some other positive integer less than 10. Otherwise, the checksum digit is calculated as expected. Unfortunately, there is an error in the data. Two adjacent columns have been transposed. These errored columns do not involve the final column or one of the first three columns. Using this information, please provide all potential solutions with the unknown weight and the smaller index of the two errored columns (assume we start our indexing at 0 and ignore hyphens). Give your answer in the form x, y where x is the weight and y is the smaller index of the two transposed columns.\n\n978-354181391-9\n978-946669746-1\n978-398036139-6\n978-447656680-4\n978-279586664-7\n978-595073693-3\n978-976647652-6\n978-591178125-5\n978-728465924-5\n978-414825155-9", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 7, 9", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "7, 9", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "8131e2c0-0083-4265-9ce7-78c2d568425d", "prompt": "I was trying to remember how well the Cheater Beater performed in comparison to the Cheater when James tested it on his channel. I know that the Cheater still outperformed the Cheater Beater in terms of CFM. Could you please look that up for me, and report the CFM of both the Cheater and the Cheater Beater? I'm not sure if he made any changes to his testing, but this was back in season 4, so just report the value from that season. Please format your response like this: CFM number for Cheater, CFM number for Cheater beater", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 101.376, 84.348", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "101.376, 84.348", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "72c06643-a2fa-4186-aa5c-9ec33ae9b445", "prompt": "What is the volume in milliliters of a system comprised of 0.312 kg Freon-12 refrigerant when placed at the bottom of the Marianas Trench and allowed to stabilize at the Trench's peak temperature, rounded to the nearest mL? Provide your answer as just an integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 55", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "55", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "ebbc1f13-d24d-40df-9068-adcf735b4240", "prompt": "The Latin root of the Yola word \"gimlie\" shares a spelling with a Spanish word. What is the Google translation of the source title for the 1994 example sentence for that word in the Collins Spanish-to-English dictionary online? Answer in plain text, without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: The World of the Twenty First Century", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "The World of the Twenty First Century", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c526d8d6-5987-4da9-b24c-83466fa172f3", "prompt": "In the NIH translation of the original 1913 Michaelis-Menten Paper, what is the velocity of a reaction to four decimal places using the final equation in the paper based on the information for Reaction 7 in the Excel file?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.0424", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.0424", "gaia_level": 3, "gaia_file": "c526d8d6-5987-4da9-b24c-83466fa172f3.xlsx", "source": "gaia-benchmark"}} +{"name": "3da89939-209c-4086-8520-7eb734e6b4ef", "prompt": "I was referencing each of the tables in the file from papers that were cited by the \"Trans fatty acid contents in chocolates and chocolate wafers in Turkey\" paper. I lost my own reference sheet and need to know which of the papers each table came from. The file may not use the full table caption. If the references in the\"Trans fatty acid\" paper bibliography were numbered starting with 1, give me the numbers in the order that they would be used to fill the cells in the Excel file from top to bottom, as a comma separated list.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8, 29, 22, 1, 8, 26", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "8, 29, 22, 1, 8, 26", "gaia_level": 3, "gaia_file": "3da89939-209c-4086-8520-7eb734e6b4ef.xlsx", "source": "gaia-benchmark"}} +{"name": "8d46b8d6-b38a-47ff-ac74-cda14cf2d19b", "prompt": "What percentage of the total penguin population according to the upper estimates on english Wikipedia at the end of 2012 is made up by the penguins in this file that don't live on Dream Island or have beaks longer than 42mm? Round to the nearest five decimal places.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.00033", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.00033", "gaia_level": 3, "gaia_file": "8d46b8d6-b38a-47ff-ac74-cda14cf2d19b.csv", "source": "gaia-benchmark"}} +{"name": "e961a717-6b25-4175-8a68-874d28190ee4", "prompt": "According to wikipedia, how many Asian countries still have a monarchy and access to the sea in 2021?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 12", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "12", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "851e570a-e3de-4d84-bcfa-cc85578baa59", "prompt": "I thought we could try a fun word puzzle together :)\n\nI've got a Boggle board here:\n\nABRL\nEITE\nIONS\nFPEI\n\nI'd like to know the longest word that can be generated from the board. Please find the longest English language word that can be generated from this board. If more than one word of the same length exists at the maximum word length, please report the longest word that comes first, alphabetically. Oh, and I know that there might be different wordlists available for Boggle, so let's please just use the words_alpha dictionary found at https://github.com/dwyl/english-words as the dictionary for our game.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Briniest", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Briniest", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "50f58759-7bd6-406f-9b0d-5692beb2a926", "prompt": "How many times was a Twitter/X post cited as a reference on the english Wikipedia pages for each day of August in the last June 2023 versions of the pages?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 3", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "3", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "872bfbb1-9ccf-49f6-8c5f-aa22818ccd66", "prompt": "Which of the fruits shown in the 2008 painting \"Embroidery from Uzbekistan\" were served as part of the October 1949 breakfast menu for the ocean liner that was later used as a floating prop for the film \"The Last Voyage\"? Give the items as a comma-separated list, ordering them in clockwise order based on their arrangement in the painting starting from the 12 o'clock position. Use the plural form of each fruit.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: pears, bananas", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "pears, bananas", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "c3a79cfe-8206-451f-aca8-3fec8ebe51d3", "prompt": "The year is 2022. I am at the National Air and Space Museum east of the Potomac River. I want to go to Fire Station 301 DCA ARFF using the metro. I go in the wrong direction and end up at the station closest to Cleveland Elementary School. How many metro stations am I away from my original destination if I don't change lines? Your answer should be a numerical integer value.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 8", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "8", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "da52d699-e8d2-4dc5-9191-a2199e0b6a9b", "prompt": "The attached spreadsheet contains a list of books I read in the year 2022. What is the title of the book that I read the slowest, using the rate of words per day?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Out of the Silent Planet", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Out of the Silent Planet", "gaia_level": 3, "gaia_file": "da52d699-e8d2-4dc5-9191-a2199e0b6a9b.xlsx", "source": "gaia-benchmark"}} +{"name": "ad2b4d70-9314-4fe6-bfbe-894a45f6055f", "prompt": "Eva Draconis has a personal website which can be accessed on her YouTube page. What is the meaning of the only symbol seen in the top banner that has a curved line that isn't a circle or a portion of a circle? Answer without punctuation.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: War is not here this is a land of peace", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "War is not here this is a land of peace", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5b2a14e8-6e59-479c-80e3-4696e8980152", "prompt": "The brand that makes these harnesses the dogs are wearing in the attached pic shares stories from their ambassadors on their website. What meat is mentioned in the story added Dec 8th 2022?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: bacon", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "bacon", "gaia_level": 3, "gaia_file": "5b2a14e8-6e59-479c-80e3-4696e8980152.jpg", "source": "gaia-benchmark"}} +{"name": "9e1fc53b-46ff-49a1-9d05-9e6faac34cc5", "prompt": "A 5-man group made up of one tank, one healer, and three DPS is doing a dungeon that was just released in World of Warcraft. Two are plate wearers and two are cloth wearers. At the final boss, both the tank and the healer are casting holy spells. Ice and fire are being used, each one by a different DPS. A bear from the group is attacking the boss. Metamorphosis is cast. The Kilt of the Forgotten One drops as loot, but no one can use it. If all classes were using their class abilities and all classes are unique, what are the five classes in the group in alphabetical order separated by commas?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: Death Knight, Hunter, Paladin, Priest, Warlock", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "Death Knight, Hunter, Paladin, Priest, Warlock", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "5f982798-16b9-4051-ab57-cfc7ebdb2a91", "prompt": "I read a paper about multiwavelength observations of fast radio bursts back in March 2021 on Arxiv, and it had a fascinating diagram of an X-ray time profile. There was a similar burst-1 diagram in another paper from one of the same authors about fast radio bursts back in July 2020, but I can't recall what the difference in seconds in the measured time span was. How many more seconds did one measure than the other? Just give the number.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 0.2", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "0.2", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0512426f-4d28-49f0-be77-06d05daec096", "prompt": "In the YouTube 360 VR video from March 2018 narrated by the voice actor of Lord of the Rings' Gollum, what number was mentioned by the narrator directly after dinosaurs were first shown in the video?", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: 100000000", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "100000000", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} +{"name": "0bdb7c40-671d-4ad1-9ce3-986b159c0ddc", "prompt": "In NASA's Astronomy Picture of the Day on 2006 January 21, two astronauts are visible, with one appearing much smaller than the other. As of August 2023, out of the astronauts in the NASA Astronaut Group that the smaller astronaut was a member of, which one spent the least time in space, and how many minutes did he spend in space, rounded to the nearest minute? Exclude any astronauts who did not spend any time in space. Give the last name of the astronaut, separated from the number of minutes by a semicolon.", "criteria": [{"name": "correct_answer", "instruction": "The agent's final answer must match: White; 5876", "weight": 1.0}], "category": "level_3", "metadata": {"gaia_answer": "White; 5876", "gaia_level": 3, "gaia_file": null, "source": "gaia-benchmark"}} diff --git a/src/flow/experiments/evaluators/heuristic.py b/src/flow/experiments/evaluators/heuristic.py index ffb491a856773a0e51b53749924307950fc9deb4..6e9104e1014ed77f0e27fbe190828a5f1a69ece7 100644 --- a/src/flow/experiments/evaluators/heuristic.py +++ b/src/flow/experiments/evaluators/heuristic.py @@ -73,7 +73,7 @@ class HeuristicEvaluator: # Check if agent reported task complete output_lower = run_result.output.lower() - if "task_done" in output_lower or "complete" in output_lower or "finished" in output_lower: + if "complete" in output_lower or "complete" in output_lower or "finished" in output_lower: criteria_results.append( CriterionResult( name="task_completed", diff --git a/src/flow/experiments/evaluators/llm.py b/src/flow/experiments/evaluators/llm.py index c60e8d136e9298bd2b27a35d49426b0a9d1c969f..65d01ab14e926d939655f76e81d32544cb04c07d 100644 --- a/src/flow/experiments/evaluators/llm.py +++ b/src/flow/experiments/evaluators/llm.py @@ -38,6 +38,7 @@ class LLMEvaluator: model_client: Any, model_name: str = "gpt-4o", passing_threshold: float = 0.7, + temperature: float | None = None, ) -> None: """Initialize the LLM evaluator. @@ -46,10 +47,14 @@ class LLMEvaluator: (e.g., AsyncOpenAI, AsyncAzureOpenAI) model_name: Model name/deployment to use for evaluation passing_threshold: Minimum score to pass (0.0 to 1.0) + temperature: Temperature for LLM calls. None means don't specify + (use model default). Some models like gpt-5.2-chat + only support temperature=1.0. """ self.model_client = model_client self.model_name = model_name self.passing_threshold = passing_threshold + self.temperature = temperature def _get_evaluation_prompt(self, run_result: RunResult) -> str: """Build the evaluation prompt for the LLM.""" @@ -156,17 +161,21 @@ Tokens used: {metrics.total_tokens} (input: {metrics.input_tokens}, output: {met prompt = self._get_evaluation_prompt(run_result) try: - response = await self.model_client.chat.completions.create( - model=self.model_name, - messages=[ + # Build params - only include temperature if explicitly set + params: dict[str, Any] = { + "model": self.model_name, + "messages": [ { "role": "system", "content": "You are an expert evaluator. Respond only with valid JSON.", }, {"role": "user", "content": prompt}, ], - temperature=0.1, # Low temperature for consistent evaluation - ) + } + if self.temperature is not None: + params["temperature"] = self.temperature + + response = await self.model_client.chat.completions.create(**params) # Extract the response text response_text = response.choices[0].message.content or "" diff --git a/src/flow/experiments/models.py b/src/flow/experiments/models.py index 804192c7ad6900af5ca8c4233d56cda2e10f7159..4522b8179241535fe70f24c55c7e64b69156554d 100644 --- a/src/flow/experiments/models.py +++ b/src/flow/experiments/models.py @@ -17,10 +17,16 @@ from __future__ import annotations from dataclasses import asdict, dataclass, field from itertools import product as itertools_product from pathlib import Path -from typing import Any, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable import yaml +if TYPE_CHECKING: + from collections.abc import Awaitable, Callable + + from .evaluators.base import Evaluator + from .types import Task + # ============================================================================= # Tool Configuration @@ -32,40 +38,55 @@ TOOL_PRESETS: dict[str, dict[str, dict[str, Any]]] = { "full": { "read_file": {}, "write_file": {}, - "list_directory": {}, - "grep_search": {}, - "bash_execute": {"timeout": 120}, + "edit_file": {}, + "multi_edit": {}, + "glob_files": {}, + "ls": {}, + "grep": {}, + "bash": {"timeout": 120}, "check_processes": {}, "python_repl": {}, "think": {}, - "task_done": {}, + "todo_write": {}, + "todo_read": {}, "memory": {}, - "sub_agent": {"model": "gpt-4o-mini"}, + "skills": {}, + "task": {"model": "gpt-4o-mini"}, + "web_search": {}, + "web_fetch": {}, + "notebook_edit": {}, + "notebook_read": {}, }, "standard": { "read_file": {}, "write_file": {}, - "list_directory": {}, - "grep_search": {}, - "bash_execute": {"timeout": 120}, + "edit_file": {}, + "multi_edit": {}, + "glob_files": {}, + "ls": {}, + "grep": {}, + "bash": {"timeout": 120}, "check_processes": {}, "python_repl": {}, "think": {}, - "task_done": {}, + "todo_write": {}, + "todo_read": {}, "memory": {}, + "skills": {}, }, "minimal": { "read_file": {}, "write_file": {}, - "bash_execute": {"timeout": 120}, - "task_done": {}, + "edit_file": {}, + "bash": {"timeout": 120}, + "think": {}, }, "readonly": { "read_file": {}, - "list_directory": {}, - "grep_search": {}, + "glob_files": {}, + "ls": {}, + "grep": {}, "think": {}, - "task_done": {}, }, } @@ -91,11 +112,11 @@ def resolve_tools(tools: str | list[str] | dict[str, dict[str, Any]]) -> dict[st >>> resolve_tools("standard") {"read_file": {}, "write_file": {}, ...} - >>> resolve_tools(["read_file", "bash_execute"]) - {"read_file": {}, "bash_execute": {}} + >>> resolve_tools(["read_file", "bash"]) + {"read_file": {}, "bash": {}} - >>> resolve_tools({"bash_execute": {"timeout": 60}}) - {"bash_execute": {"timeout": 60}} + >>> resolve_tools({"bash": {"timeout": 60}}) + {"bash": {"timeout": 60}} """ if isinstance(tools, str): if tools not in TOOL_PRESETS: @@ -114,24 +135,30 @@ class CompactionConfig: """Extensible compaction strategy configuration. Supports multiple strategies via a tagged-union pattern: - - "head_tail": Keep first N + last M messages (default) + - "head_tail": Keep first N + last M messages (message-count based) + - "head_tail_tokens": Token-aware head+tail (miniagent's HeadTailStrategy) + - "sliding_window": Keep system + recent messages within token budget + - "summarization": Summarize middle messages using LLM - "last_n": Keep only the last N messages - "none": No compaction - Future strategies (e.g., "summarize") can be added without - changing existing code. - Attributes: strategy: The compaction strategy name params: Strategy-specific parameters + token_budget: Maximum tokens for context window (used by token-based strategies) """ strategy: str = "head_tail" params: dict[str, Any] = field(default_factory=lambda: {"head_size": 10, "tail_size": 40}) + token_budget: int = 100_000 + + # ========================================================================= + # Message-count based strategies (legacy, for MAF/LangGraph) + # ========================================================================= @staticmethod def head_tail(head_size: int = 10, tail_size: int = 40) -> CompactionConfig: - """Create a head+tail compaction config.""" + """Create a message-count based head+tail compaction config.""" return CompactionConfig(strategy="head_tail", params={"head_size": head_size, "tail_size": tail_size}) @staticmethod @@ -144,6 +171,92 @@ class CompactionConfig: """Create a no-compaction config.""" return CompactionConfig(strategy="none", params={}) + # ========================================================================= + # Token-based strategies (for miniagent) + # ========================================================================= + + @staticmethod + def head_tail_tokens(head_ratio: float = 0.2, token_budget: int = 100_000) -> CompactionConfig: + """Create a token-aware head+tail compaction config. + + This maps to miniagent's HeadTailStrategy which: + - Preserves head (system prompt, initial context) using head_ratio of budget + - Preserves tail (recent tool calls/results) using remaining budget + - Drops middle messages when over budget + - Respects atomic groups (tool calls + results stay together) + + Args: + head_ratio: Fraction of budget for head messages (default 0.2 = 20%) + token_budget: Maximum tokens for context window + + Returns: + CompactionConfig for token-based head+tail strategy + """ + return CompactionConfig( + strategy="head_tail_tokens", + params={"head_ratio": head_ratio}, + token_budget=token_budget, + ) + + @staticmethod + def sliding_window(token_budget: int = 100_000) -> CompactionConfig: + """Create a sliding window compaction config. + + This maps to miniagent's SlidingWindowStrategy which: + - Always keeps system message(s) + - Keeps most recent messages that fit within token budget + - Respects atomic groups (tool calls + results stay together) + + Args: + token_budget: Maximum tokens for context window + + Returns: + CompactionConfig for sliding window strategy + """ + return CompactionConfig( + strategy="sliding_window", + params={}, + token_budget=token_budget, + ) + + @staticmethod + def summarization( + head_messages: int = 2, + tail_messages: int = 4, + summary_max_tokens: int = 1000, + token_budget: int = 100_000, + ) -> CompactionConfig: + """Create a summarization compaction config. + + This maps to miniagent's SummarizationStrategy which: + - Keeps head messages (system + initial user message) + - Keeps tail messages (recent context) + - Summarizes middle messages using LLM instead of dropping them + - Preserves critical state (files read, findings, progress) + + Args: + head_messages: Number of messages to keep at head (default 2) + tail_messages: Number of messages to keep at tail (default 4) + summary_max_tokens: Max tokens for the summary (default 1000) + token_budget: Maximum tokens for context window + + Returns: + CompactionConfig for summarization strategy + """ + return CompactionConfig( + strategy="summarization", + params={ + "head_messages": head_messages, + "tail_messages": tail_messages, + "summary_max_tokens": summary_max_tokens, + }, + token_budget=token_budget, + ) + + # ========================================================================= + # Properties + # ========================================================================= + @property def enabled(self) -> bool: """Whether compaction is enabled.""" @@ -159,6 +272,11 @@ class CompactionConfig: """Tail size for head_tail strategy. Returns 0 for other strategies.""" return self.params.get("tail_size", 0) + @property + def head_ratio(self) -> float: + """Head ratio for head_tail_tokens strategy. Returns 0.2 default.""" + return self.params.get("head_ratio", 0.2) + @dataclass class Agent: @@ -171,8 +289,10 @@ class Agent: Attributes: name: Unique identifier for this agent + framework: Which harness to use ("maf", "langgraph", "claude") description: Human-readable description instructions: System prompt / instructions (optional, uses framework default if None) + instructions_preset: Preset name for instructions ("coding", "benchmark", etc.) model: Model deployment name (e.g., "gpt-4o") compaction: Compaction strategy configuration tools: Tool configuration - can be: @@ -182,8 +302,10 @@ class Agent: """ name: str + framework: str = "maf" description: str = "" instructions: str | None = None + instructions_preset: str | None = None # e.g., "coding", "benchmark", "research" model: str | None = None compaction: CompactionConfig = field(default_factory=CompactionConfig) tools: str | list[str] | dict[str, dict[str, Any]] = "standard" @@ -218,27 +340,50 @@ class ExperimentResult: eval_score: float = 0.0 eval_passed: bool = False eval_reasoning: str = "" + traces: dict[str, Any] = field(default_factory=dict) @runtime_checkable class CandidateStrategy(Protocol): """Protocol for generating candidate variants from a base agent. - Implementations explore different regions of the optimization space: + Implementations can be: + - Simple (single-shot): GridSearchStrategy ignores optional params + - Complex (iterative): Runs internal experiments, checks convergence, + distills failures, etc. using the provided callbacks + + All logic is internal to the strategy - the caller just calls generate() + and receives the final list of candidates. + + Examples: - GridSearchStrategy: Exhaustive grid over parameter combinations - - (Future) HeuristicStrategy: Rule-based mutations from telemetry + - (Future) AdaptivePromptOptimizer: Iteratively improves prompts from failures - (Future) BayesianStrategy: Bayesian optimization over parameters """ - def generate(self, base: Agent, budget: int) -> list[Candidate]: + def generate( + self, + base: Agent, + budget: int, + *, + tasks: list[Task] | None = None, + evaluator: Evaluator | None = None, + run_experiment: Callable[[Candidate, Task], Awaitable[ExperimentResult]] | None = None, + ) -> list[Candidate]: """Generate candidate variants from a base agent. Args: - base: The base agent to mutate - budget: Maximum number of candidates to generate + base: The base agent to optimize + budget: Maximum number of candidates to return + tasks: Optional tasks for strategies that run internal experiments + evaluator: Optional evaluator for strategies that need scoring + run_experiment: Optional async callback to execute a candidate on a task. + Signature: async (candidate, task) -> ExperimentResult Returns: - List of Candidate objects (at most `budget` items) + List of Candidate objects (at most `budget` items). + For iterative strategies, returns the final/best candidates after + internal optimization loops complete. """ ... @@ -272,8 +417,24 @@ class GridSearchStrategy: """ self.variations = variations - def generate(self, base: Agent, budget: int) -> list[Candidate]: - """Generate all grid combinations up to budget.""" + def generate( + self, + base: Agent, + budget: int, + *, + tasks: list[Task] | None = None, + evaluator: Evaluator | None = None, + run_experiment: Callable[[Candidate, Task], Awaitable[ExperimentResult]] | None = None, + ) -> list[Candidate]: + """Generate all grid combinations up to budget. + + Note: tasks, evaluator, and run_experiment are accepted for protocol + compatibility but ignored - GridSearchStrategy is a simple single-shot + strategy that doesn't run experiments internally. + """ + # Delete unused params to satisfy linters + del tasks, evaluator, run_experiment + if not self.variations: return [Candidate(agent=base, mutations={}, rationale="baseline")] @@ -515,3 +676,121 @@ def _extract_metrics( "pareto_rank": summary.get("pareto_rank"), "is_pareto_optimal": summary.get("is_pareto_optimal", False), } + + +# ============================================================================= +# Experiment YAML - Defines variations for optimization +# ============================================================================= + + +@dataclass +class Experiment: + """Experiment configuration for optimization. + + Separates concerns: + - Agent YAML: What the agent is (model, instructions, defaults) + - Experiment YAML: How to test it (variations, tasks, evaluation settings) + + Attributes: + base_agent: Path to base agent YAML file + suite: Built-in task suite name (e.g., "coding", "quick") + tasks: Path to custom tasks JSONL file (alternative to suite) + variations: Dict of parameter variations for grid search + parallel: Max concurrent experiments + budget: Maximum candidates to generate + use_llm_eval: Whether to use LLM-as-Judge evaluation + + Example YAML: + ```yaml + base_agent: examples/miniagent_base.yaml + suite: coding + + variations: + compaction: + - strategy: none + - strategy: head_tail + params: { head_size: 10, tail_size: 40 } + - strategy: sliding_window + token_budget: 50000 + - strategy: summarization + token_budget: 50000 + + tools: + - minimal + - standard + - [read_file, write_file, bash, memory] + + parallel: 4 + budget: 20 + use_llm_eval: true + ``` + """ + + base_agent: str | None = None + suite: str | None = None + tasks: str | None = None + variations: dict[str, list[Any]] = field(default_factory=dict) + parallel: int = 4 + budget: int = 100 + use_llm_eval: bool = True + + +def load_experiment(path: Path) -> Experiment: + """Load an Experiment from a YAML file. + + Handles conversion of compaction variations from dict to CompactionConfig. + + Args: + path: Path to the experiment YAML file + + Returns: + Experiment instance with parsed variations + + Raises: + FileNotFoundError: If the file doesn't exist + ValueError: If the config is invalid + """ + if not path.exists(): + raise FileNotFoundError(f"Experiment config file not found: {path}") + + data = yaml.safe_load(path.read_text()) + + # Parse variations - convert compaction dicts to CompactionConfig + variations: dict[str, list[Any]] = {} + raw_variations = data.get("variations", {}) + + for key, values in raw_variations.items(): + if key == "compaction": + # Convert each compaction dict to CompactionConfig + parsed_compactions = [] + for v in values: + if isinstance(v, dict): + parsed_compactions.append(CompactionConfig(**v)) + elif isinstance(v, str): + # Handle shorthand: "none", "head_tail", etc. + if v == "none": + parsed_compactions.append(CompactionConfig.none()) + elif v == "head_tail": + parsed_compactions.append(CompactionConfig.head_tail()) + elif v == "sliding_window": + parsed_compactions.append(CompactionConfig.sliding_window()) + elif v == "summarization": + parsed_compactions.append(CompactionConfig.summarization()) + else: + raise ValueError(f"Unknown compaction shorthand: {v}") + else: + parsed_compactions.append(v) + variations["compaction"] = parsed_compactions + else: + # Other variations pass through as-is + variations[key] = values + + return Experiment( + base_agent=data.get("base_agent"), + suite=data.get("suite"), + tasks=data.get("tasks"), + variations=variations, + parallel=data.get("parallel", 4), + budget=data.get("budget", 100), + use_llm_eval=data.get("use_llm_eval", True), + ) diff --git a/src/flow/experiments/optimizer.py b/src/flow/experiments/optimizer.py index 93e28ee1940a4871904fa3631b321f4f5001b228..d197a66551404b20e4e09a0643dd4f3872931dad 100644 --- a/src/flow/experiments/optimizer.py +++ b/src/flow/experiments/optimizer.py @@ -20,10 +20,7 @@ from typing import Any from openai import AsyncAzureOpenAI -from .ablation import ( - compute_pareto_frontier, - create_harness_from_agent, -) +from .ablation import compute_pareto_frontier from .evaluators import LLMEvaluator from .metrics import TraceMetrics, extract_metrics from .models import ( @@ -47,6 +44,7 @@ class TaskResult: eval_score: float eval_passed: bool eval_reasoning: str + criteria_results: list[dict[str, Any]] = field(default_factory=list) # Per-criterion scores @dataclass @@ -84,6 +82,18 @@ class CandidateSummary: "task_count": self.task_count, "pareto_rank": self.pareto_rank, "is_pareto_optimal": self.is_pareto_optimal, + # Include per-task results with eval reasoning + "task_results": [ + { + "task_name": tr.task_name, + "eval_score": tr.eval_score, + "eval_passed": tr.eval_passed, + "eval_reasoning": tr.eval_reasoning, + "tokens": tr.metrics.total_tokens, + "duration": tr.run_result.duration_seconds, + } + for tr in self.task_results + ], } @@ -287,18 +297,37 @@ class FlowOptimizer: evaluator: LLMEvaluator | None, ) -> TaskResult: """Run a single candidate-task experiment.""" - harness = create_harness_from_agent(candidate.agent, workspace) + # Import harness modules to register them, then use registry + import flow.harness.maf # noqa: F401 + try: + import flow.harness.miniagent # noqa: F401 + except ImportError: + pass # miniagent harness is optional + from flow.harness import create_harness + + harness = create_harness(candidate.agent, workspace) try: runner = FlowExperimentRunner(keep_workspace=True) run_result = await runner.run(harness, task, workspace=workspace) metrics = extract_metrics(run_result.trace) + criteria_results: list[dict[str, Any]] = [] if evaluator: eval_result = await evaluator.evaluate(run_result) eval_score = eval_result.score eval_passed = eval_result.passed eval_reasoning = eval_result.reasoning + # Convert criteria results to dicts for serialization + criteria_results = [ + { + "name": cr.name, + "score": cr.score, + "passed": cr.passed, + "reasoning": cr.reasoning, + } + for cr in eval_result.criteria_results + ] else: eval_score = 1.0 if run_result.success else 0.0 eval_passed = run_result.success @@ -312,6 +341,7 @@ class FlowOptimizer: eval_score=eval_score, eval_passed=eval_passed, eval_reasoning=eval_reasoning, + criteria_results=criteria_results, ) finally: await harness.close() @@ -366,26 +396,48 @@ class FlowOptimizer: def _create_evaluator(self) -> LLMEvaluator | None: """Create LLM evaluator if credentials available.""" + from openai import AsyncOpenAI + api_key = os.environ.get("AZURE_OPENAI_API_KEY") endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT") - deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o") + deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT") or os.environ.get( + "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", "gpt-4o" + ) + + logger.info("Creating LLM evaluator...") + logger.debug(f" API Key: {'[SET]' if api_key else '[NOT SET]'}") + logger.debug(f" Endpoint: {endpoint if endpoint else '[NOT SET]'}") + logger.debug(f" Deployment: {deployment}") if not api_key or not endpoint: - logger.warning("No Azure OpenAI credentials, using heuristic evaluation") + logger.warning("No Azure OpenAI credentials, using heuristic evaluation (binary 0/1 scores)") return None - client = AsyncAzureOpenAI( - api_key=api_key, - api_version="2024-02-15-preview", - azure_endpoint=endpoint, - ) + # Check if using OpenAI-compatible endpoint (e.g., /openai/v1/) + # vs traditional Azure OpenAI endpoint + if "/v1" in endpoint: + logger.info("Creating AsyncOpenAI client for evaluator (OpenAI-compatible endpoint)") + client = AsyncOpenAI( + base_url=endpoint, + api_key=api_key, + ) + else: + logger.info("Creating AsyncAzureOpenAI client for evaluator") + client = AsyncAzureOpenAI( + api_key=api_key, + api_version="2024-02-15-preview", + azure_endpoint=endpoint, + ) - return LLMEvaluator( + evaluator = LLMEvaluator( model_client=client, model_name=deployment, passing_threshold=0.7, ) + logger.info(f"LLM evaluator created successfully (model={deployment}, threshold=0.7)") + return evaluator + def _save_config( self, candidates: list[Candidate], diff --git a/src/flow/experiments/runner.py b/src/flow/experiments/runner.py index ba94727fa3e68550b02cf499ce07f656128f81db..04f1a08d64046277eb6ebc3ed3587ae607a7999e 100644 --- a/src/flow/experiments/runner.py +++ b/src/flow/experiments/runner.py @@ -21,7 +21,7 @@ from .trace_collector import FlowTraceCollector from .types import RunResult, Task if TYPE_CHECKING: - from flow.harness.maf import MAFHarness + from flow.harness.base import BaseHarness logger = logging.getLogger(__name__) @@ -66,10 +66,12 @@ class FlowExperimentRunner: - Supporting streaming execution Example: - from flow.harness.maf import MAFHarness + from flow.harness import create_harness from flow.experiments import FlowExperimentRunner, Task + from flow.experiments.models import Agent - harness = MAFHarness() + agent = Agent(name="my-agent") + harness = create_harness(agent, workspace=Path("/tmp")) runner = FlowExperimentRunner(keep_workspace=True) task = Task(name="hello", prompt="Create a hello world script") @@ -95,7 +97,7 @@ class FlowExperimentRunner: async def run( self, - harness: MAFHarness, + harness: "BaseHarness", task: Task, workspace: Path | None = None, ) -> RunResult: @@ -109,7 +111,7 @@ class FlowExperimentRunner: 5. Returns a RunResult with all data Args: - harness: The MAFHarness to run + harness: The harness to run (any BaseHarness implementation) task: The task to execute workspace: Optional workspace directory (creates temp if None) @@ -167,6 +169,10 @@ class FlowExperimentRunner: elif event.type == EventType.TOOL_RESULT: # Optionally capture tool results pass + elif event.type == EventType.ERROR: + # Capture error from harness + error = event.content + logger.error(f"Harness error: {error}") finally: os.chdir(original_cwd) diff --git a/src/flow/experiments/types.py b/src/flow/experiments/types.py index 0338328f2a845d288ae0644139706bbbf043a1bb..ce7f0e97f5e4a05f6a1dc34608953a3b177ddb9b 100644 --- a/src/flow/experiments/types.py +++ b/src/flow/experiments/types.py @@ -168,6 +168,56 @@ def get_available_suites() -> list[str]: return sorted(p.stem for p in _DATA_DIR.glob("*.jsonl")) +@dataclass +class SuiteInfo: + """Information about a task suite.""" + + name: str + task_count: int + description: str + + +# Suite descriptions for known suites +_SUITE_DESCRIPTIONS: dict[str, str] = { + "quick": "Fast testing", + "core": "Standard evaluation", + "coding": "Self-contained repo analysis tasks (clone, analyze, report)", + "gaia_level1": "GAIA easy benchmark", + "gaia_level2": "GAIA medium benchmark", + "gaia_level3": "GAIA hard benchmark", + "gaia_all": "GAIA full benchmark", +} + + +def get_suite_info(suite_name: str) -> SuiteInfo: + """Get information about a specific suite. + + Args: + suite_name: Name of the suite + + Returns: + SuiteInfo with name, task count, and description + """ + path = _DATA_DIR / f"{suite_name}.jsonl" + if not path.exists(): + raise ValueError(f"Suite not found: {suite_name}") + + # Count lines (tasks) in the file + task_count = sum(1 for line in path.open() if line.strip()) + description = _SUITE_DESCRIPTIONS.get(suite_name, "Custom task suite") + + return SuiteInfo(name=suite_name, task_count=task_count, description=description) + + +def get_all_suite_info() -> list[SuiteInfo]: + """Get information about all available suites. + + Returns: + List of SuiteInfo for each available suite. + """ + return [get_suite_info(name) for name in get_available_suites()] + + def get_task_suite(suite_name: str) -> list[Task]: """Get a built-in task suite by name. diff --git a/src/flow/harness/__init__.py b/src/flow/harness/__init__.py index 4e359ad37197f2deef797f2ebb8484ba071f30f6..e0d170febe68cff329c95e9eaf06d2c4582c7f31 100644 --- a/src/flow/harness/__init__.py +++ b/src/flow/harness/__init__.py @@ -5,14 +5,36 @@ events to a uniform Event format for CLI/UI consumption. Available harnesses: - maf: Microsoft Agent Framework harness -- (future) langchain: LangChain harness +- (future) langgraph: LangGraph harness - (future) claude: Claude SDK harness + +Usage: + from flow.harness import create_harness + from flow.experiments.models import Agent + + agent = Agent(name="my-agent", framework="maf") + harness = create_harness(agent, workspace=Path("/tmp")) """ from flow.harness.base import BaseHarness, Event, EventType +from flow.harness.registry import ( + available_frameworks, + create_harness, + get_harness_class, + register, +) + +# Auto-register harnesses by importing them +# Each harness module calls register() on import +from flow.harness import maf as _maf # noqa: F401 +from flow.harness import miniagent as _miniagent # noqa: F401 __all__ = [ "BaseHarness", "Event", "EventType", + "available_frameworks", + "create_harness", + "get_harness_class", + "register", ] diff --git a/src/flow/harness/base.py b/src/flow/harness/base.py index 3d8e985a177afa515c245041cd6fb1aa99fd0512..c37361e8a8c4a8e28e56535fe96789ca9397379e 100644 --- a/src/flow/harness/base.py +++ b/src/flow/harness/base.py @@ -7,10 +7,16 @@ allowing Flow to run on different agent frameworks. from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import AsyncIterator, Callable, Coroutine +from collections.abc import AsyncIterator from dataclasses import dataclass, field from enum import Enum -from typing import Any +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pathlib import Path + + from flow.experiments.models import Agent + from flow.llm import LLMClientConfig class EventType(Enum): @@ -49,52 +55,49 @@ class BaseHarness(ABC): to the uniform Flow Event format for CLI/UI consumption. Each harness implementation handles: - - Taking a pre-configured agent from the framework - - Running tasks on the agent + - Creating an agent from an Agent spec via from_agent() + - Running tasks on the agent with streaming events - Converting framework-specific events to Flow Events - Managing conversation threads Implementations: - MAFHarness (flow.harness.maf): Microsoft Agent Framework - - (Future) LangChainHarness: LangChain + - (Future) LangGraphHarness: LangGraph - (Future) ClaudeHarness: Claude SDK """ + @classmethod @abstractmethod - async def run(self, task: str, thread_id: str | None = None) -> str: - """Run a task and return the final response. + def from_agent( + cls, + agent: "Agent", + workspace: "Path", + llm_config: "LLMClientConfig | None" = None, + ) -> "BaseHarness": + """Create a harness from an Agent definition. Args: - task: The task/prompt to execute - thread_id: Optional thread ID for conversation continuity + agent: The Agent spec defining the configuration + workspace: Working directory for the agent + llm_config: Optional LLM configuration (falls back to env vars if not provided) Returns: - The agent's final response text + A configured harness instance """ ... @abstractmethod - def run_stream(self, task: str, thread_id: str | None = None) -> AsyncIterator[Event]: + def run_stream(self, task: str) -> AsyncIterator[Event]: """Run a task with streaming events. Args: task: The task/prompt to execute - thread_id: Optional thread ID for conversation continuity Yields: Event objects representing agent activity """ ... - @abstractmethod - def register_tools(self, tools: list[Callable[..., Coroutine[Any, Any, str]]]) -> None: - """Register tools with the harness. - - Args: - tools: List of tool functions to register - """ - ... - @abstractmethod def get_thread_id(self) -> str: """Get the current thread ID. diff --git a/src/flow/harness/langgraph/__init__.py b/src/flow/harness/langgraph/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..18e1c62bdcdfb881f55388a2f7cbffb58e72bccc --- /dev/null +++ b/src/flow/harness/langgraph/__init__.py @@ -0,0 +1,37 @@ +"""LangGraph harness for Flow. + +This module provides a harness adapter that allows LangGraph agents +to be used within the Flow experimentation framework. + +Usage: + from flow.experiments.models import Agent + from flow.harness import create_harness + + agent = Agent( + name="my-langgraph-agent", + framework="langgraph", # <-- Use LangGraph harness + tools="standard", + model="openai:gpt-4o", + ) + harness = create_harness(agent, workspace=Path("/tmp/workspace")) + + async for event in harness.run_stream("Create hello.py"): + print(event.type, event.content) +""" + +from flow.harness.langgraph.compaction import create_compaction_hook +from flow.harness.langgraph.harness import LangGraphHarness +from flow.harness.langgraph.otel_callback import OTelCallbackHandler +from flow.harness.langgraph.wrappers import build_langgraph_tools, wrap_for_langgraph +from flow.harness.registry import register + +# Register the harness with Flow +register("langgraph", LangGraphHarness) + +__all__ = [ + "LangGraphHarness", + "OTelCallbackHandler", + "build_langgraph_tools", + "create_compaction_hook", + "wrap_for_langgraph", +] diff --git a/src/flow/harness/langgraph/compaction.py b/src/flow/harness/langgraph/compaction.py new file mode 100644 index 0000000000000000000000000000000000000000..5cd6f8af14b007142be816f6a6608e400bcda04e --- /dev/null +++ b/src/flow/harness/langgraph/compaction.py @@ -0,0 +1,51 @@ +"""Message compaction for LangGraph. + +Provides a pre-model hook that implements head-tail message compaction, +similar to MAF's HeadTailCompactingChatMessageStore. +""" + +from __future__ import annotations + +from typing import Any + +__all__ = ["create_compaction_hook"] + + +def create_compaction_hook(head_size: int, tail_size: int): + """Create a pre-model hook for message compaction. + + This hook compacts messages by keeping the first `head_size` messages + and the last `tail_size` messages, dropping the middle. + + Args: + head_size: Number of messages to keep from the start + tail_size: Number of messages to keep from the end + + Returns: + A function that can be used as a pre_model_hook in create_react_agent + + Example: + hook = create_compaction_hook(10, 40) + graph = create_react_agent( + model=model, + tools=tools, + pre_model_hook=hook, + ) + """ + + def compact_messages(state: dict[str, Any]) -> dict[str, Any]: + """Compact messages keeping head and tail, dropping middle.""" + messages = state.get("messages", []) + total = len(messages) + + # No compaction needed if within limits + if total <= head_size + tail_size: + return {"llm_input_messages": messages} + + # Keep head and tail + head = messages[:head_size] + tail = messages[-tail_size:] + + return {"llm_input_messages": head + tail} + + return compact_messages diff --git a/src/flow/harness/langgraph/harness.py b/src/flow/harness/langgraph/harness.py new file mode 100644 index 0000000000000000000000000000000000000000..ae50004071d4d9b697c7560d600aac3e86d25982 --- /dev/null +++ b/src/flow/harness/langgraph/harness.py @@ -0,0 +1,257 @@ +"""LangGraph harness for Flow. + +Provides a harness adapter that allows LangGraph agents to be used +within the Flow experimentation framework. +""" + +from __future__ import annotations + +import logging +import uuid +from collections.abc import AsyncIterator +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from opentelemetry import trace + +from flow.harness.base import BaseHarness, Event, EventType + +if TYPE_CHECKING: + from flow.experiments.models import Agent + +logger = logging.getLogger(__name__) + +# Get tracer for LangGraph instrumentation +_tracer = trace.get_tracer("flow.langgraph", "0.1.0") + +__all__ = ["LangGraphHarness"] + + +class LangGraphHarness(BaseHarness): + """Harness adapter for LangGraph. + + This harness allows LangGraph agents to be used within the Flow + experimentation framework. It converts LangGraph streaming events + to Flow's uniform Event format and emits OpenTelemetry spans. + + Example: + from flow.experiments.models import Agent + from flow.harness import create_harness + + agent = Agent( + name="my-langgraph-agent", + framework="langgraph", + tools="standard", + model="openai:gpt-4o", + ) + harness = create_harness(agent, workspace=Path("/tmp/workspace")) + + async for event in harness.run_stream("Create hello.py"): + print(event.type, event.content) + """ + + @classmethod + def from_agent(cls, agent: Agent, workspace: Path) -> LangGraphHarness: + """Create a LangGraph harness from an Agent spec. + + Args: + agent: Agent configuration + workspace: Working directory for file operations + + Returns: + Configured LangGraphHarness instance + """ + from flow.experiments.models import resolve_tools + from flow.harness.langgraph.compaction import create_compaction_hook + from flow.harness.langgraph.wrappers import build_langgraph_tools + from langgraph.checkpoint.memory import InMemorySaver + from langgraph.prebuilt import create_react_agent + + # Build tools (skip sub_agent - MAF-specific) + tools_spec = resolve_tools(agent.tools) + if "sub_agent" in tools_spec: + logger.warning("sub_agent tool not supported in LangGraph harness, skipping") + del tools_spec["sub_agent"] + + memory_path = workspace / "memory" + memory_path.mkdir(parents=True, exist_ok=True) + tools = build_langgraph_tools(tools_spec, workspace, memory_path) + + # Create model + model = cls._create_model(agent.model) + + # Create compaction hook if enabled + pre_model_hook = None + if agent.compaction and agent.compaction.strategy != "none": + params = agent.compaction.params or {} + head_size = params.get("head_size", 10) + tail_size = params.get("tail_size", 40) + pre_model_hook = create_compaction_hook(head_size, tail_size) + + # Build graph + graph = create_react_agent( + model=model, + tools=tools, + prompt=agent.instructions, + pre_model_hook=pre_model_hook, + checkpointer=InMemorySaver(), + ) + + return cls(graph=graph, agent_name=agent.name, workspace=workspace) + + @staticmethod + def _create_model(model_spec: str | None): + """Create a LangChain chat model from spec. + + Args: + model_spec: Model specification, e.g., "openai:gpt-4o" or "gpt-4o" + + Returns: + A LangChain chat model instance + """ + import os + + if model_spec and ":" in model_spec: + # "provider:model" syntax - use init_chat_model + from langchain.chat_models import init_chat_model + + return init_chat_model(model_spec) + + # Default: Azure OpenAI from environment + from langchain_openai import AzureChatOpenAI + + return AzureChatOpenAI( + deployment_name=os.environ.get("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"), + api_key=os.environ.get("AZURE_OPENAI_API_KEY"), + azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"), + api_version=os.environ.get("AZURE_OPENAI_API_VERSION", "2024-02-15-preview"), + ) + + def __init__( + self, + graph: Any = None, + agent_name: str = "LangGraphAgent", + workspace: Path | None = None, + ) -> None: + """Initialize the harness. + + Args: + graph: A compiled LangGraph StateGraph + agent_name: Name of the agent (for tracing) + workspace: Working directory + """ + from flow.harness.langgraph.otel_callback import OTelCallbackHandler + + self._graph = graph + self._agent_name = agent_name + self._workspace = workspace + self._thread_id = str(uuid.uuid4()) + self._otel_callback = OTelCallbackHandler() + + async def run_stream(self, task: str) -> AsyncIterator[Event]: + """Run a task with streaming events. + + Args: + task: The task/prompt to execute + + Yields: + Event objects representing the agent's actions + """ + from langchain_core.messages import HumanMessage + + config = { + "configurable": {"thread_id": self._thread_id}, + "callbacks": [self._otel_callback], + } + input_state = {"messages": [HumanMessage(content=task)]} + + # Wrap in agent span for tracing + with _tracer.start_as_current_span( + f"invoke_agent {self._agent_name}", + kind=trace.SpanKind.INTERNAL, + ) as span: + span.set_attribute("gen_ai.operation.name", "invoke_agent") + span.set_attribute("gen_ai.agent.name", self._agent_name) + span.set_attribute("gen_ai.conversation.id", self._thread_id) + + try: + async for chunk in self._graph.astream( + input_state, + config, + stream_mode=["messages", "updates"], + ): + for event in self._convert_chunk(chunk): + yield event + + yield Event(type=EventType.DONE) + + except Exception as e: + logger.exception("Error during LangGraph execution") + span.record_exception(e) + span.set_status(trace.StatusCode.ERROR, str(e)) + yield Event(type=EventType.ERROR, content=str(e)) + + def _convert_chunk(self, chunk: tuple) -> list[Event]: + """Convert a LangGraph stream chunk to Flow Events. + + Args: + chunk: A tuple of (stream_mode, data) from LangGraph + + Returns: + List of Flow Event objects + """ + from langchain_core.messages import ToolMessage + + events: list[Event] = [] + + if not isinstance(chunk, tuple) or len(chunk) != 2: + return events + + mode, data = chunk + + if mode == "messages": + msg_chunk, metadata = data + + # Text content + if hasattr(msg_chunk, "content") and msg_chunk.content: + events.append(Event( + type=EventType.TEXT_DELTA, + content=msg_chunk.content, + )) + + # Tool call chunks + if hasattr(msg_chunk, "tool_call_chunks"): + for tc in msg_chunk.tool_call_chunks or []: + if tc.get("name"): + events.append(Event( + type=EventType.TOOL_CALL_START, + tool_name=tc["name"], + tool_call_id=tc.get("id"), + )) + if tc.get("args"): + events.append(Event( + type=EventType.TOOL_CALL_ARGS, + content=tc["args"], + )) + + elif mode == "updates": + for node_name, update in data.items(): + if node_name == "tools" and "messages" in update: + for msg in update["messages"]: + if isinstance(msg, ToolMessage): + events.append(Event( + type=EventType.TOOL_RESULT, + content=str(msg.content), + tool_call_id=msg.tool_call_id, + )) + events.append(Event(type=EventType.TOOL_CALL_DONE)) + + return events + + def get_thread_id(self) -> str: + """Get the current thread/conversation ID.""" + return self._thread_id + + async def close(self) -> None: + """Clean up resources.""" + self._thread_id = None diff --git a/src/flow/harness/langgraph/otel_callback.py b/src/flow/harness/langgraph/otel_callback.py new file mode 100644 index 0000000000000000000000000000000000000000..29de0af6a6dd196d50c2865675d80260268631ff --- /dev/null +++ b/src/flow/harness/langgraph/otel_callback.py @@ -0,0 +1,173 @@ +"""OTel callback for LangGraph - emits GenAI semantic convention spans. + +This module provides a LangChain callback handler that emits OpenTelemetry +spans conforming to the GenAI semantic conventions. This fills the gap +that LangGraph doesn't have native GenAI OTel support like MAF does. + +Reference: https://opentelemetry.io/docs/specs/semconv/gen-ai/ +""" + +from __future__ import annotations + +from typing import Any + +from langchain_core.callbacks import BaseCallbackHandler +from opentelemetry import trace + +__all__ = ["GenAIAttr", "OTelCallbackHandler"] + + +class GenAIAttr: + """OpenTelemetry GenAI semantic convention attributes. + + These match the attributes used by MAF for consistency. + Reference: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/ + """ + + # Operation + OPERATION_NAME = "gen_ai.operation.name" + PROVIDER_NAME = "gen_ai.provider.name" + + # Model + REQUEST_MODEL = "gen_ai.request.model" + RESPONSE_MODEL = "gen_ai.response.model" + + # Tokens + INPUT_TOKENS = "gen_ai.usage.input_tokens" + OUTPUT_TOKENS = "gen_ai.usage.output_tokens" + + # Tool + TOOL_NAME = "gen_ai.tool.name" + TOOL_TYPE = "gen_ai.tool.type" + TOOL_CALL_ID = "gen_ai.tool.call.id" + + # Error + ERROR_TYPE = "error.type" + + +# Get tracer for LangGraph instrumentation +_tracer = trace.get_tracer("flow.langgraph", "0.1.0") + + +class OTelCallbackHandler(BaseCallbackHandler): + """Emit OpenTelemetry spans for LangGraph LLM and tool calls. + + This callback handler hooks into LangChain's callback system and + emits spans that conform to the GenAI semantic conventions. + + Usage: + callback = OTelCallbackHandler() + config = {"callbacks": [callback]} + graph.invoke(input, config) + """ + + def __init__(self) -> None: + """Initialize the callback handler.""" + self._spans: dict[str, trace.Span] = {} + + def on_llm_start( + self, + serialized: dict[str, Any], + prompts: list[str], + *, + run_id: Any, + **kwargs: Any, + ) -> None: + """Called when LLM starts generating.""" + # Extract model and provider from serialized data + model = serialized.get("kwargs", {}).get("model", "unknown") + if not model or model == "unknown": + model = serialized.get("kwargs", {}).get("model_name", "unknown") + + # Try to get provider from serialized id + serialized_id = serialized.get("id", []) + provider = serialized_id[-1] if serialized_id else "unknown" + + # Start span + span = _tracer.start_span(f"chat {model}", kind=trace.SpanKind.CLIENT) + span.set_attribute(GenAIAttr.OPERATION_NAME, "chat") + span.set_attribute(GenAIAttr.REQUEST_MODEL, model) + span.set_attribute(GenAIAttr.PROVIDER_NAME, provider) + + self._spans[str(run_id)] = span + + def on_llm_end( + self, + response: Any, + *, + run_id: Any, + **kwargs: Any, + ) -> None: + """Called when LLM finishes generating.""" + span = self._spans.pop(str(run_id), None) + if span: + # Extract token usage from response + usage = {} + if hasattr(response, "llm_output") and response.llm_output: + usage = response.llm_output.get("token_usage", {}) + + if usage: + span.set_attribute(GenAIAttr.INPUT_TOKENS, usage.get("prompt_tokens", 0)) + span.set_attribute(GenAIAttr.OUTPUT_TOKENS, usage.get("completion_tokens", 0)) + + span.end() + + def on_llm_error( + self, + error: BaseException, + *, + run_id: Any, + **kwargs: Any, + ) -> None: + """Called when LLM encounters an error.""" + span = self._spans.pop(str(run_id), None) + if span: + span.set_attribute(GenAIAttr.ERROR_TYPE, type(error).__name__) + span.record_exception(error) + span.set_status(trace.StatusCode.ERROR, str(error)) + span.end() + + def on_tool_start( + self, + serialized: dict[str, Any], + input_str: str, + *, + run_id: Any, + **kwargs: Any, + ) -> None: + """Called when a tool starts executing.""" + tool_name = serialized.get("name", "unknown") + + span = _tracer.start_span(f"execute_tool {tool_name}", kind=trace.SpanKind.INTERNAL) + span.set_attribute(GenAIAttr.OPERATION_NAME, "execute_tool") + span.set_attribute(GenAIAttr.TOOL_NAME, tool_name) + span.set_attribute(GenAIAttr.TOOL_TYPE, "function") + + self._spans[str(run_id)] = span + + def on_tool_end( + self, + output: str, + *, + run_id: Any, + **kwargs: Any, + ) -> None: + """Called when a tool finishes executing.""" + span = self._spans.pop(str(run_id), None) + if span: + span.end() + + def on_tool_error( + self, + error: BaseException, + *, + run_id: Any, + **kwargs: Any, + ) -> None: + """Called when a tool encounters an error.""" + span = self._spans.pop(str(run_id), None) + if span: + span.set_attribute(GenAIAttr.ERROR_TYPE, type(error).__name__) + span.record_exception(error) + span.set_status(trace.StatusCode.ERROR, str(error)) + span.end() diff --git a/src/flow/harness/langgraph/wrappers.py b/src/flow/harness/langgraph/wrappers.py new file mode 100644 index 0000000000000000000000000000000000000000..62a5900cb05ab32686497ea5736ec3f0c7ad1e55 --- /dev/null +++ b/src/flow/harness/langgraph/wrappers.py @@ -0,0 +1,76 @@ +"""LangGraph-specific tool wrappers. + +This module wraps shared tools for use with LangGraph/LangChain. +""" + +from __future__ import annotations + +import logging +from collections.abc import Callable, Coroutine +from pathlib import Path +from typing import Any + +from langchain_core.tools import tool as langchain_tool + +from flow.tools import build_tools, get_tool_meta + +logger = logging.getLogger(__name__) + +__all__ = ["build_langgraph_tools", "wrap_for_langgraph"] + + +def wrap_for_langgraph( + tool_func: Callable[..., Coroutine[Any, Any, str]] +) -> Callable[..., Coroutine[Any, Any, str]]: + """Wrap a Flow tool for LangGraph/LangChain. + + Applies LangChain's @tool decorator with metadata from the @tool decorator. + + Args: + tool_func: A tool function decorated with @tool + + Returns: + The function wrapped with LangChain's @tool for LangGraph + + Raises: + ValueError: If the function has no tool metadata + """ + meta = get_tool_meta(tool_func) + if meta is None: + raise ValueError(f"Function {tool_func} has no tool metadata. Decorate with @tool first.") + + # LangChain's @tool decorator takes name as first positional arg + # and description as keyword arg + return langchain_tool(meta.name, description=meta.description)(tool_func) + + +def build_langgraph_tools( + tools_spec: dict[str, dict[str, Any]], + workspace: Path, + memory_path: Path, +) -> list[Any]: # Returns list of LangChain BaseTool + """Build LangGraph-compatible tools from a specification dict. + + Creates shared tools and wraps them with LangChain's @tool decorator. + + Args: + tools_spec: Dict mapping tool names to their config dicts. + workspace: Root directory for file operations + memory_path: Directory for persistent memory + + Returns: + List of tool functions wrapped for LangGraph + """ + # Build raw tools from shared module + raw_tools = build_tools(tools_spec, workspace, memory_path) + + # Wrap each with LangChain's @tool + lg_tools = [] + for tool_func in raw_tools: + try: + wrapped = wrap_for_langgraph(tool_func) + lg_tools.append(wrapped) + except ValueError as e: + logger.warning(f"Could not wrap tool: {e}") + + return lg_tools diff --git a/src/flow/harness/maf/__init__.py b/src/flow/harness/maf/__init__.py index bc97a674c16c3be0539d850a182267eb56158020..1bc5011068236ff684e696e4afb1c6d79d9a7a28 100644 --- a/src/flow/harness/maf/__init__.py +++ b/src/flow/harness/maf/__init__.py @@ -6,6 +6,10 @@ Provides integration with Microsoft Agent Framework for running Flow agents. from flow.harness.maf.agent import create_agent from flow.harness.maf.harness import MAFHarness from flow.harness.maf.message_store import HeadTailCompactingChatMessageStore +from flow.harness.registry import register + +# Auto-register MAFHarness as the "maf" framework +register("maf", MAFHarness) __all__ = [ "create_agent", diff --git a/src/flow/harness/maf/agent.py b/src/flow/harness/maf/agent.py index 1144a3654d225405b45b47683badc6a210d80640..df18e9e0ebad3cf0e0bb243c3d283e71f15d5642 100644 --- a/src/flow/harness/maf/agent.py +++ b/src/flow/harness/maf/agent.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any from flow.experiments.models import TOOL_PRESETS, resolve_tools from flow.harness.maf.message_store import HeadTailCompactingChatMessageStore -from flow.harness.maf.tools import build_tools +from flow.harness.maf.wrappers import build_maf_tools from flow.prompts import build_instructions if TYPE_CHECKING: @@ -54,7 +54,7 @@ def create_agent( Args: endpoint: Azure OpenAI endpoint URL. Defaults to AZURE_OPENAI_ENDPOINT env var. api_key: Azure OpenAI API key. Defaults to AZURE_OPENAI_API_KEY env var. - deployment: Azure OpenAI deployment name. Defaults to AZURE_OPENAI_DEPLOYMENT env var. + deployment: Azure OpenAI deployment name. Defaults to AZURE_OPENAI_CHAT_DEPLOYMENT_NAME env var. api_version: Azure OpenAI API version. name: Agent name. instructions: Agent instructions. Defaults to FLOW_AGENT_INSTRUCTIONS. @@ -86,7 +86,7 @@ def create_agent( >>> agent = create_agent(tools={"bash_execute": {"timeout": 60}, "memory": {}}) """ try: - from agent_framework import ChatAgent, ai_function + from agent_framework import ChatAgent, tool from agent_framework.azure import AzureOpenAIChatClient except ImportError as e: raise ImportError( @@ -97,7 +97,7 @@ def create_agent( # Resolve configuration from environment if not provided endpoint = endpoint or os.environ.get("AZURE_OPENAI_ENDPOINT") api_key = api_key or os.environ.get("AZURE_OPENAI_API_KEY") - deployment = deployment or os.environ.get("AZURE_OPENAI_DEPLOYMENT") + deployment = deployment or os.environ.get("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME") if not endpoint: raise ValueError( @@ -112,7 +112,7 @@ def create_agent( if not deployment: raise ValueError( "Azure OpenAI deployment is required. " - "Set AZURE_OPENAI_DEPLOYMENT or pass deployment parameter." + "Set AZURE_OPENAI_CHAT_DEPLOYMENT_NAME or pass deployment parameter." ) # Resolve paths @@ -125,26 +125,23 @@ def create_agent( # Create tools from specification or use provided functions if isinstance(tools, (str, list, dict)): - # Resolve to dict form and build tools + # Resolve to dict form and build MAF-wrapped tools tools_spec = resolve_tools(tools) - tool_functions = build_tools(tools_spec, workspace, memory_path) + converted_tools = build_maf_tools(tools_spec, workspace, memory_path) else: - # Already a sequence of callable tools - tool_functions = tools - - # Wrap tools with ai_function decorator for Agent Framework - converted_tools = [] - for tool_func in tool_functions: - tool_name = getattr(tool_func, "_tool_name", tool_func.__name__) - tool_description = getattr(tool_func, "_tool_description", tool_func.__doc__ or "") - wrapped = ai_function(name=tool_name, description=tool_description)(tool_func) - converted_tools.append(wrapped) + # Already a sequence of callable tools - wrap them with tool decorator + converted_tools = [] + for tool_func in tools: + tool_name = getattr(tool_func, "_tool_name", tool_func.__name__) + tool_description = getattr(tool_func, "_tool_description", tool_func.__doc__ or "") + wrapped = tool(name=tool_name, description=tool_description)(tool_func) + converted_tools.append(wrapped) # Create the chat client client = AzureOpenAIChatClient( api_key=api_key, endpoint=endpoint, - deployment=deployment, + deployment_name=deployment, api_version=api_version, ) diff --git a/src/flow/harness/maf/harness.py b/src/flow/harness/maf/harness.py index ec18e713d16376497f95602ab005cb94759984a2..af535abe00a2be8c7e5c35c73c874f9b1f22d8b6 100644 --- a/src/flow/harness/maf/harness.py +++ b/src/flow/harness/maf/harness.py @@ -3,9 +3,12 @@ A thin adapter that converts Agent Framework events to the uniform Flow Event format. """ +from __future__ import annotations + import logging import uuid from collections.abc import AsyncIterator +from pathlib import Path from typing import TYPE_CHECKING, Any from flow.harness.base import BaseHarness, Event, EventType @@ -13,6 +16,9 @@ from flow.harness.base import BaseHarness, Event, EventType if TYPE_CHECKING: from agent_framework import ChatAgent + from flow.experiments.models import Agent + from flow.llm import LLMClientConfig + logger = logging.getLogger(__name__) # Track if instrumentation has been enabled globally @@ -55,12 +61,69 @@ class MAFHarness(BaseHarness): >>> async for event in harness.run_stream("Create a hello world script"): ... print(event) - >>> # Or with custom agent - >>> from flow.harness.maf import create_agent - >>> agent = create_agent(enable_compaction=False) - >>> harness = MAFHarness(agent) + >>> # Or from Agent spec + >>> from flow.experiments.models import Agent + >>> agent = Agent(name="my-agent", tools="standard") + >>> harness = MAFHarness.from_agent(agent, workspace=Path("/tmp")) """ + @classmethod + def from_agent( + cls, + agent: "Agent", + workspace: Path, + llm_config: "LLMClientConfig | None" = None, + ) -> "MAFHarness": + """Create a MAFHarness from an Agent definition. + + Args: + agent: The Agent spec defining the configuration + workspace: Working directory for the agent + llm_config: Optional LLM configuration (falls back to env vars if not provided) + + Returns: + A configured MAFHarness instance + """ + from flow.experiments.models import resolve_tools + + tools_spec = resolve_tools(agent.tools) + + # Build kwargs for create_agent + kwargs: dict[str, Any] = { + "workspace": workspace, + "memory_path": workspace / "memory", + "enable_compaction": agent.compaction.enabled, + "compaction_head_size": agent.compaction.head_size, + "compaction_tail_size": agent.compaction.tail_size, + "tools": tools_spec, + "instructions": agent.instructions, + } + + # Extract credentials from LLM config if provided + if llm_config is not None: + from flow.llm import LLMProvider + + if llm_config.provider == LLMProvider.AZURE_OPENAI and llm_config.azure_openai: + kwargs["endpoint"] = llm_config.azure_openai.get_endpoint() + kwargs["api_key"] = llm_config.azure_openai.get_api_key() + kwargs["deployment"] = llm_config.azure_openai.deployment + kwargs["api_version"] = llm_config.azure_openai.api_version + elif llm_config.provider == LLMProvider.OPENAI and llm_config.openai: + # OpenAI uses different endpoint/auth pattern + # For now, MAF only supports Azure OpenAI natively + # Log warning and fall back to env vars + logger.warning( + f"MAF harness only supports Azure OpenAI natively. " + f"Provider {llm_config.provider.value} will fall back to env vars." + ) + else: + logger.warning( + f"MAF harness only supports Azure OpenAI. " + f"Provider {llm_config.provider.value} will fall back to env vars." + ) + + return cls(**kwargs) + def __init__( self, agent: "ChatAgent | None" = None, @@ -87,61 +150,15 @@ class MAFHarness(BaseHarness): # Enable OpenTelemetry instrumentation for trace collection _enable_instrumentation() - def register_tools(self, tools: list[Any]) -> None: - """Register tools with the harness. - - Note: For MAFHarness, tools should be configured when creating the agent - via create_agent(). This method is provided for interface compatibility - but will log a warning if called. - - Args: - tools: List of tool functions (ignored - configure via create_agent) - """ - logger.warning( - "MAFHarness.register_tools() called but tools should be configured " - "via create_agent(). These tools will be ignored." - ) - - async def run(self, task: str, thread_id: str | None = None) -> str: - """Run a task and return the final response. - - Args: - task: The task/prompt to execute - thread_id: Optional thread ID for conversation continuity - - Returns: - The agent's final response text - """ - if thread_id: - self._thread_id = thread_id - - # Get or create an AgentThread for conversation continuity - if self._thread is None: - self._thread = self._agent.get_new_thread() - - response = await self._agent.run(task, thread=self._thread) - - # Extract text content from response - content = getattr(response, "content", None) - if content is not None: - return str(content) - return str(response) - - async def run_stream( - self, task: str, thread_id: str | None = None - ) -> AsyncIterator[Event]: + async def run_stream(self, task: str) -> AsyncIterator[Event]: """Run a task with streaming events. Args: task: The task/prompt to execute - thread_id: Optional thread ID for conversation continuity Yields: Event objects representing agent activity """ - if thread_id: - self._thread_id = thread_id - # Get or create an AgentThread for conversation continuity if self._thread is None: self._thread = self._agent.get_new_thread() diff --git a/src/flow/harness/maf/tools/__init__.py b/src/flow/harness/maf/tools/__init__.py index 28ec84d84fdd4e426eb5370a252e50803b404d46..9eb3270d2b368d1d200bed69a6f27a435e5e3229 100644 --- a/src/flow/harness/maf/tools/__init__.py +++ b/src/flow/harness/maf/tools/__init__.py @@ -1,86 +1,74 @@ """MAF-specific tools for the Flow agent. This module provides tools that work with the Microsoft Agent Framework harness. -Tools are created based on a specification dict that maps tool names to their configs. +Tools are created from the shared flow.tools module and adapted for MAF using +the to_maf_tool adapter. Available tools: -- read_file: Read file contents -- write_file: Write/edit file content -- list_directory: List directory contents -- grep_search: Search for text patterns -- bash_execute: Execute bash commands (config: timeout) -- check_processes: Manage background processes -- python_repl: Execute Python code -- think: Explicit reasoning tool -- task_done: Task completion marker -- memory: Persistent memory storage -- sub_agent: Isolated research sub-agent (config: model) +- read_file, write_file, edit_file, multi_edit, glob_files, grep, ls +- bash, check_processes, python_repl +- think, todo_write, todo_read +- memory, skills, task +- web_search, web_fetch +- notebook_edit, notebook_read """ -from collections.abc import Callable, Coroutine, Sequence +import logging +from collections.abc import Callable, Coroutine from pathlib import Path from typing import Any -from flow.harness.maf.tools.coding import ( - create_grep_search_tool, - create_list_directory_tool, - create_read_file_tool, - create_write_file_tool, +from flow.tools import ( + # Coding + read_file, write_file, edit_file, multi_edit, glob_files, grep, ls, + # Execution + bash, check_processes, python_repl, + # Planning + think, todo_write, todo_read, + # Memory + memory, create_memory_tool, + # Web + web_search, web_fetch, + # Notebooks + notebook_edit, notebook_read, + # Skills + skills, create_skills_tool, + # Sub-agent + task, create_task_tool, + # Workspace management + set_workspace, Workspace, + # Adapters + to_maf_tool, + # Base + Tool, ) -from flow.harness.maf.tools.core import task_done, think -from flow.harness.maf.tools.execution import ( - create_bash_execute_tool, - create_check_processes_tool, - create_python_repl_tool, -) -from flow.harness.maf.tools.memory import create_memory_tool -from flow.harness.maf.tools.sub_agent import create_sub_agent_tool __all__ = [ "build_tools", - "create_bash_execute_tool", - "create_check_processes_tool", - "create_grep_search_tool", - "create_list_directory_tool", - "create_memory_tool", - "create_python_repl_tool", - "create_read_file_tool", - "create_sub_agent_tool", - "create_write_file_tool", - "task_done", - "think", ] - -# Registry of tool factories that don't require config -# Maps tool name -> factory function(workspace, memory_path) -> tool -_SIMPLE_TOOL_FACTORIES: dict[str, Callable[..., Any]] = {} - -# Registry of tools that are standalone (no factory needed) -_STANDALONE_TOOLS: dict[str, Callable[..., Coroutine[Any, Any, str]]] = { - "think": think, - "task_done": task_done, -} +logger = logging.getLogger(__name__) def build_tools( tools_spec: dict[str, dict[str, Any]], workspace: Path, memory_path: Path, -) -> Sequence[Callable[..., Coroutine[Any, Any, str]]]: - """Build tool functions from a specification dict. +) -> list[Callable[..., Coroutine[Any, Any, str]]]: + """Build MAF-compatible tool functions from a specification dict. This is the main entry point for creating tools based on a resolved - tool specification (from resolve_tools()). + tool specification (from resolve_tools()). It uses the shared tools + from flow.tools and adapts them for MAF. Args: tools_spec: Dict mapping tool names to their config dicts. - e.g., {"bash_execute": {"timeout": 60}, "read_file": {}} + e.g., {"bash": {"timeout": 60}, "read_file": {}} workspace: Root directory for file operations - memory_path: Directory for persistent memory + memory_path: Directory for persistent memory (deprecated, uses workspace) Returns: - List of tool functions ready to use with MAF + List of tool functions wrapped with MAF's @tool decorator Example: >>> from flow.experiments.models import resolve_tools @@ -88,70 +76,63 @@ def build_tools( >>> tools = build_tools(tools_spec, workspace, memory_path) """ workspace = Path(workspace).resolve() - memory_path = Path(memory_path).resolve() + + # Set workspace for tools that need it (memory, todos, etc.) + set_workspace(Workspace(workspace)) + + # Map tool names → Tool instances + tool_map: dict[str, Tool] = { + # Coding/Filesystem + "read_file": read_file, + "write_file": write_file, + "edit_file": edit_file, + "multi_edit": multi_edit, + "glob_files": glob_files, + "ls": ls, + "grep": grep, + # Execution + "bash": bash, + "check_processes": check_processes, + "python_repl": python_repl, + # Planning + "think": think, + "todo_write": todo_write, + "todo_read": todo_read, + # Web + "web_search": web_search, + "web_fetch": web_fetch, + # Notebooks + "notebook_edit": notebook_edit, + "notebook_read": notebook_read, + # Memory (default instance) + "memory": memory, + # Skills (default instance) + "skills": skills, + # Task/sub-agent (default instance) + "task": task, + } tools: list[Callable[..., Coroutine[Any, Any, str]]] = [] - for tool_name, config in tools_spec.items(): - tool = _create_tool(tool_name, config, workspace, memory_path) - if tool is not None: - tools.append(tool) + for name, config in tools_spec.items(): + if name in tool_map: + # Convert shared Tool to MAF-decorated function + maf_tool = to_maf_tool(tool_map[name]) + tools.append(maf_tool) + elif name == "task" and config: + # Task tool with custom config + custom_task = create_task_tool( + coordinator_tools=list(tool_map.values()), + model=config.get("model"), + ) + tools.append(to_maf_tool(custom_task)) + elif name == "skills" and config.get("additional_paths"): + # Skills with custom paths + custom_skills = create_skills_tool( + project_path=Path(config["additional_paths"][0]) + ) + tools.append(to_maf_tool(custom_skills)) + else: + logger.warning(f"Unknown tool name: {name}. Skipping.") return tools - - -def _create_tool( - name: str, - config: dict[str, Any], - workspace: Path, - memory_path: Path, -) -> Callable[..., Coroutine[Any, Any, str]] | None: - """Create a single tool by name with the given config. - - Args: - name: Tool name (e.g., "read_file", "bash_execute") - config: Tool-specific configuration dict - workspace: Root directory for file operations - memory_path: Directory for persistent memory - - Returns: - Tool function or None if unknown tool name - """ - # Standalone tools (no config needed) - if name in _STANDALONE_TOOLS: - return _STANDALONE_TOOLS[name] - - # Coding tools - if name == "read_file": - return create_read_file_tool(workspace) - if name == "write_file": - return create_write_file_tool(workspace) - if name == "list_directory": - return create_list_directory_tool(workspace) - if name == "grep_search": - return create_grep_search_tool(workspace) - - # Execution tools - if name == "bash_execute": - timeout = config.get("timeout", 120) - return create_bash_execute_tool(workspace, memory_path, timeout) - if name == "check_processes": - return create_check_processes_tool(workspace, memory_path) - if name == "python_repl": - return create_python_repl_tool(workspace) - - # Memory tool - if name == "memory": - return create_memory_tool(memory_path) - - # Sub-agent tool - if name == "sub_agent": - model = config.get("model", "gpt-4o-mini") - return create_sub_agent_tool(workspace, model=model) - - # Unknown tool - log warning and skip - import logging - - logger = logging.getLogger(__name__) - logger.warning(f"Unknown tool name: {name}. Skipping.") - return None diff --git a/src/flow/harness/maf/tools/coding.py b/src/flow/harness/maf/tools/coding.py deleted file mode 100644 index 1186f0a69a67704319859980363647303cc535f0..0000000000000000000000000000000000000000 --- a/src/flow/harness/maf/tools/coding.py +++ /dev/null @@ -1,391 +0,0 @@ -"""Coding tools for file operations and code search. - -These tools enable agents to read/write files, list directories, -and search for patterns in code. - -The agent can read and write to any path the user has access to. -The workspace serves as the default working directory for relative paths. -""" - -import re -from collections.abc import Callable, Coroutine, Sequence -from pathlib import Path -from typing import Annotated, Any - - -def create_read_file_tool(workspace: Path) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a read_file tool that can read from any path. - - Args: - workspace: Default directory for relative paths (not a restriction) - """ - - async def read_file( - file_path: Annotated[str, "Path to the file (absolute or relative to workspace)"], - max_lines: Annotated[int, "Maximum lines to return (default: 500)"] = 500, - ) -> str: - """Read the contents of a file. Can read from any path on the system.""" - try: - # Support both absolute and relative paths - path = Path(file_path) - if path.is_absolute(): - full_path = path.resolve() - else: - full_path = (workspace / file_path).resolve() - - if not full_path.exists(): - return f"Error: File not found: {file_path}" - - if not full_path.is_file(): - return f"Error: Not a file: {file_path}" - - content = full_path.read_text(encoding="utf-8") - lines = content.splitlines() - - # Apply line limit - total_lines = len(lines) - if len(lines) > max_lines: - lines = lines[:max_lines] - truncated_msg = f"\n... (truncated, showing first {max_lines} of {total_lines} lines)" - else: - truncated_msg = "" - - # Format with line numbers - numbered_lines = [f"{i + 1:5d}: {line}" for i, line in enumerate(lines)] - result = "\n".join(numbered_lines) + truncated_msg - - return f"File: {full_path} ({total_lines} lines)\n{'=' * 40}\n{result}" - - except UnicodeDecodeError: - return f"Error: Cannot read file (binary or non-UTF-8): {file_path}" - except PermissionError: - return f"Error: Permission denied: {file_path}" - except Exception as e: - return f"Error reading file: {e}" - - # Add tool metadata - read_file._tool_name = "read_file" # type: ignore[attr-defined] - read_file._tool_description = ( # type: ignore[attr-defined] - "Read the contents of a file. Accepts absolute paths (e.g., /path/to/file) " - "or relative paths (relative to workspace). Returns content with line numbers." - ) - read_file._is_tool = True # type: ignore[attr-defined] - - return read_file - - -def create_write_file_tool(workspace: Path) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a write_file tool. - - Args: - workspace: Default directory for relative paths - """ - - async def write_file( - file_path: Annotated[str, "Path to the file (absolute or relative to workspace)"], - content: Annotated[str | None, "Full content to write (for complete file write)"] = None, - old_str: Annotated[str | None, "Text to replace (for str_replace operation)"] = None, - new_str: Annotated[str | None, "Replacement text (for str_replace operation)"] = None, - insert_line: Annotated[int | None, "Line number to insert at (1-indexed)"] = None, - insert_content: Annotated[str | None, "Content to insert at line"] = None, - ) -> str: - """Write or edit file content. - - Supports: (1) full file write with 'content', - (2) str_replace to replace specific text, - (3) insert_at_line to add content at a specific line. - Creates parent directories if needed. - """ - try: - # Support both absolute and relative paths - path = Path(file_path) - if path.is_absolute(): - full_path = path.resolve() - else: - full_path = (workspace / file_path).resolve() - - # Create parent directories - full_path.parent.mkdir(parents=True, exist_ok=True) - - # Operation 1: Full file write - if content is not None: - full_path.write_text(content, encoding="utf-8") - return f"Successfully wrote {len(content)} characters to {file_path}" - - # Operation 2: str_replace - if old_str is not None and new_str is not None: - if not full_path.exists(): - return f"Error: File not found for str_replace: {file_path}" - - current_content = full_path.read_text(encoding="utf-8") - - if old_str not in current_content: - # Show a snippet of the file to help debug - if len(current_content) > 500: - snippet = current_content[:500] + "..." - else: - snippet = current_content - return ( - f"Error: String to replace not found in file.\n" - f"Searching for: '{old_str[:100]}...'\n" - f"File content preview:\n{snippet}" - ) - - # Replace first occurrence only - new_content = current_content.replace(old_str, new_str, 1) - full_path.write_text(new_content, encoding="utf-8") - return f"Successfully replaced text in {file_path}" - - # Operation 3: insert_at_line - if insert_line is not None and insert_content is not None: - if full_path.exists(): - current_content = full_path.read_text(encoding="utf-8") - lines = current_content.splitlines(keepends=True) - else: - lines = [] - - # Ensure insert_content ends with newline - if not insert_content.endswith("\n"): - insert_content += "\n" - - # Insert at specified line (1-indexed) - insert_index = insert_line - 1 - if insert_index < 0: - return f"Error: Invalid line number: {insert_line}. Must be >= 1." - - # Allow inserting at end - if insert_index > len(lines): - insert_index = len(lines) - - lines.insert(insert_index, insert_content) - new_content = "".join(lines) - full_path.write_text(new_content, encoding="utf-8") - return f"Successfully inserted content at line {insert_line} in {file_path}" - - return "Error: Must provide either 'content', 'old_str' + 'new_str', or 'insert_line' + 'insert_content'" - - except Exception as e: - return f"Error writing file: {e}" - - # Add tool metadata - write_file._tool_name = "write_file" # type: ignore[attr-defined] - write_file._tool_description = ( # type: ignore[attr-defined] - "Write or edit file content. Accepts absolute paths or relative paths (relative to workspace). " - "Supports: (1) full file write with 'content', (2) str_replace to replace specific text, " - "(3) insert_at_line to add content at a specific line. Creates parent directories if needed." - ) - write_file._is_tool = True # type: ignore[attr-defined] - - return write_file - - -def create_list_directory_tool(workspace: Path) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a list_directory tool that can list any directory. - - Args: - workspace: Default directory for relative paths (not a restriction) - """ - - async def list_directory( - directory_path: Annotated[str, "Path to directory (absolute or relative to workspace, default: '.')"] = ".", - recursive: Annotated[bool, "List subdirectories recursively (default: false)"] = False, - max_entries: Annotated[int, "Maximum entries to return (default: 200)"] = 200, - ) -> str: - """List files and directories at a given path. Can list any directory on the system.""" - try: - # Support both absolute and relative paths - path = Path(directory_path) - if path.is_absolute(): - full_path = path.resolve() - else: - full_path = (workspace / directory_path).resolve() - - if not full_path.exists(): - return f"Error: Directory not found: {directory_path}" - - if not full_path.is_dir(): - return f"Error: Not a directory: {directory_path}" - - entries: list[tuple[str, str, int]] = [] - - if recursive: - for item in full_path.rglob("*"): - if len(entries) >= max_entries: - break - # Skip common non-essential directories - skip_dirs = ["node_modules", "__pycache__", ".git", "venv", ".venv"] - if any(part in item.parts for part in skip_dirs): - continue - rel_path = item.relative_to(full_path) - item_type = "file" if item.is_file() else "dir" - size = item.stat().st_size if item.is_file() else 0 - entries.append((str(rel_path), item_type, size)) - else: - for item in full_path.iterdir(): - if len(entries) >= max_entries: - break - item_type = "file" if item.is_file() else "dir" - size = item.stat().st_size if item.is_file() else 0 - entries.append((item.name, item_type, size)) - - # Sort: directories first, then by name - entries.sort(key=lambda x: (x[1] != "dir", x[0])) - - # Format output - result_lines = [f"Directory: {directory_path} ({len(entries)} entries)"] - result_lines.append("=" * 50) - - for name, item_type, size in entries: - if item_type == "dir": - result_lines.append(f" [DIR] {name}/") - else: - size_str = f"{size:,} bytes" if size < 10000 else f"{size / 1024:.1f} KB" - result_lines.append(f" [FILE] {name} ({size_str})") - - if len(entries) >= max_entries: - result_lines.append(f"\n... (truncated at {max_entries} entries)") - - return "\n".join(result_lines) - - except Exception as e: - return f"Error listing directory: {e}" - - # Add tool metadata - list_directory._tool_name = "list_directory" # type: ignore[attr-defined] - list_directory._tool_description = ( # type: ignore[attr-defined] - "List files and directories at a given path. Accepts absolute paths (e.g., /path/to/dir) " - "or relative paths (relative to workspace). Returns names, types, and sizes." - ) - list_directory._is_tool = True # type: ignore[attr-defined] - - return list_directory - - -def create_grep_search_tool(workspace: Path) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a grep_search tool that can search any directory. - - Args: - workspace: Default directory for relative paths (not a restriction) - """ - - async def grep_search( - pattern: Annotated[str, "Pattern to search for (regex supported)"], - path: Annotated[str, "Path to search in (absolute or relative to workspace, default: '.')"] = ".", - file_pattern: Annotated[str | None, "File pattern to filter (e.g., '*.py', '*.js')"] = None, - case_sensitive: Annotated[bool, "Case sensitive search (default: true)"] = True, - max_matches: Annotated[int, "Maximum matches to return (default: 50)"] = 50, - ) -> str: - """Search for text patterns in files. Can search any path on the system.""" - try: - # Support both absolute and relative paths - search_path = Path(path) - if search_path.is_absolute(): - full_path = search_path.resolve() - else: - full_path = (workspace / path).resolve() - - if not full_path.exists(): - return f"Error: Path not found: {path}" - - # Compile regex - flags = 0 if case_sensitive else re.IGNORECASE - try: - regex = re.compile(pattern, flags) - except re.error as e: - return f"Error: Invalid regex pattern: {e}" - - matches: list[dict[str, Any]] = [] - - # Get files to search - if full_path.is_file(): - files = [full_path] - else: - if file_pattern: - files = list(full_path.rglob(file_pattern)) - else: - files = [f for f in full_path.rglob("*") if f.is_file()] - - # Search each file - for file_path_item in files: - if len(matches) >= max_matches: - break - - # Skip common non-essential directories and binary files - skip_dirs = ["node_modules", "__pycache__", ".git", "venv", ".venv"] - if any(part in file_path_item.parts for part in skip_dirs): - continue - - try: - # Skip large files (> 1MB) - if file_path_item.stat().st_size > 1_000_000: - continue - - file_content = file_path_item.read_text(encoding="utf-8", errors="ignore") - lines = file_content.splitlines() - - for line_num, line in enumerate(lines, 1): - if len(matches) >= max_matches: - break - if regex.search(line): - # Compute relative path from search root - try: - rel_path = file_path_item.relative_to(full_path) - except ValueError: - # If file is the search path itself, use filename - rel_path = file_path_item.name - matches.append({ - "file": str(rel_path), - "line": line_num, - "text": line.strip()[:200], - }) - except (UnicodeDecodeError, PermissionError): - continue - - # Format output - if not matches: - return f"No matches found for pattern '{pattern}' in {path}" - - result_lines = [f"Found {len(matches)} match(es) for '{pattern}'"] - result_lines.append("=" * 50) - - for match in matches: - result_lines.append(f"{match['file']}:{match['line']}: {match['text']}") - - if len(matches) >= max_matches: - result_lines.append(f"\n... (truncated at {max_matches} matches)") - - return "\n".join(result_lines) - - except Exception as e: - return f"Error searching: {e}" - - # Add tool metadata - grep_search._tool_name = "grep_search" # type: ignore[attr-defined] - grep_search._tool_description = ( # type: ignore[attr-defined] - "Search for text patterns in files. Accepts absolute paths (e.g., /path/to/dir) " - "or relative paths (relative to workspace). Supports regex patterns and file filtering." - ) - grep_search._is_tool = True # type: ignore[attr-defined] - - return grep_search - - -def create_coding_tools(workspace: Path) -> Sequence[Callable[..., Coroutine[Any, Any, str]]]: - """Create all coding tools bound to a workspace. - - Args: - workspace: Root directory for file operations - - Returns: - List of coding tool functions - """ - workspace = Path(workspace).resolve() - - return [ - create_read_file_tool(workspace), - create_write_file_tool(workspace), - create_list_directory_tool(workspace), - create_grep_search_tool(workspace), - ] - - diff --git a/src/flow/harness/maf/tools/core.py b/src/flow/harness/maf/tools/core.py deleted file mode 100644 index 15fc31452ca3ad90ee82be2056bda0bdbdfdbeed..0000000000000000000000000000000000000000 --- a/src/flow/harness/maf/tools/core.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Core metacognitive tools for agent reasoning and task management. - -These tools enable agents to think explicitly, track task status, -and make structured decisions during complex software engineering tasks. -""" - -from collections.abc import Callable, Coroutine, Sequence -from typing import Annotated, Any, Literal - - -async def think( - thought: Annotated[ - str, - ( - "Your detailed reasoning about the current situation. " - "Include: what you've learned, options you're considering, " - "potential risks, and your planned approach." - ), - ], -) -> str: - """Use this tool to pause and think through a complex problem. - - Helpful when: (1) analyzing tool results, (2) planning multi-step approaches, - (3) making design decisions, (4) debugging issues, (5) avoiding mistakes. - Your reasoning is recorded and helps structure your approach. - """ - # The value is in giving the LLM dedicated space to reason - summary = thought[:300] + "..." if len(thought) > 300 else thought - return f"Thought recorded: {summary}" - - -async def task_done( - status: Annotated[ - Literal["complete", "incomplete"], - "'complete' if task finished successfully, 'incomplete' if blocked or needs input", - ], - summary: Annotated[ - str, - ( - "Summary of what was accomplished. " - "If complete: what was done and how to use/test it. " - "If incomplete: what's blocking and what's needed." - ), - ], - files_created: Annotated[ - list[str] | None, - "List of files created or modified (if any)", - ] = None, - next_steps: Annotated[ - list[str] | None, - "Suggested next steps for the user (if any)", - ] = None, -) -> str: - """Call this when you have completed the user's task. - - Provide a summary of what was accomplished and any relevant details. - Use 'complete' if all requirements are satisfied, - 'incomplete' if blocked or need more information. - """ - result_lines = [ - f"Task Status: {status.upper()}", - "", - "Summary:", - summary, - ] - - if files_created: - result_lines.extend([ - "", - "Files Created/Modified:", - *[f" - {f}" for f in files_created], - ]) - - if next_steps: - result_lines.extend([ - "", - "Suggested Next Steps:", - *[f" - {step}" for step in next_steps], - ]) - - return "\n".join(result_lines) - - -# Add tool metadata -think._tool_name = "think" # type: ignore[attr-defined] -think._tool_description = think.__doc__ or "" # type: ignore[attr-defined] -think._is_tool = True # type: ignore[attr-defined] - -task_done._tool_name = "task_done" # type: ignore[attr-defined] -task_done._tool_description = task_done.__doc__ or "" # type: ignore[attr-defined] -task_done._is_tool = True # type: ignore[attr-defined] - - -def create_core_tools() -> Sequence[Callable[..., Coroutine[Any, Any, str]]]: - """Create all core metacognitive tools. - - Returns: - List of core tool functions - """ - return [think, task_done] diff --git a/src/flow/harness/maf/tools/execution.py b/src/flow/harness/maf/tools/execution.py deleted file mode 100644 index b1395206989c4723dd55df12c8c7f467675816d2..0000000000000000000000000000000000000000 --- a/src/flow/harness/maf/tools/execution.py +++ /dev/null @@ -1,479 +0,0 @@ -"""Execution tools for running commands and code. - -These tools enable agents to execute bash commands and Python code -with safety controls (timeouts, output limits), and manage background processes. -""" - -import asyncio -import os -import re -import signal -import sys -from collections.abc import Callable, Coroutine, Sequence -from datetime import datetime -from io import StringIO -from pathlib import Path -from typing import Annotated, Any, Literal - - -def _get_process_registry_path(memory_path: Path) -> Path: - """Get the path to the process registry file in memory.""" - return memory_path / "processes.md" - - -def _ensure_process_registry(memory_path: Path) -> Path: - """Ensure the process registry file exists and return its path.""" - registry_path = _get_process_registry_path(memory_path) - registry_path.parent.mkdir(parents=True, exist_ok=True) - - if not registry_path.exists(): - registry_path.write_text( - "# Background Processes\n\n" - "This file tracks background processes started by the Flow agent.\n" - "You can view this file with `memory(command='view', path='/memory/processes.md')`\n\n" - "## Running\n\n" - "## Stopped\n\n" - ) - return registry_path - - -def _add_process_to_registry( - memory_path: Path, - pid: int, - command: str, - workspace: str, - log_file: str, - port: int | None = None, -) -> None: - """Add a process to the registry using checklist format.""" - registry_path = _ensure_process_registry(memory_path) - content = registry_path.read_text() - - # Extract port from command if not provided - if port is None: - port_match = re.search(r"(?:--port|-p)\s+(\d+)", command) - if port_match: - port = int(port_match.group(1)) - elif ":8000" in command or "8000" in command: - port = 8000 - elif ":3000" in command or "3000" in command: - port = 3000 - - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M") - port_str = f"Port: {port}" if port else "Port: -" - cmd_short = command[:60] + "..." if len(command) > 60 else command - workspace_short = workspace.split("/")[-1] if "/" in workspace else workspace - - # Create checklist entry - entry = f"- [ ] **PID {pid}** | `{cmd_short}` | {timestamp} | {port_str} | {workspace_short}\n" - - # Add under "## Running" section - if "## Running" in content: - content = content.replace("## Running\n\n", f"## Running\n\n{entry}") - else: - # Add Running section if missing - content += f"\n## Running\n\n{entry}" - - registry_path.write_text(content) - - -def _mark_process_stopped(memory_path: Path, pid: int, reason: str = "killed") -> None: - """Mark a process as stopped in the registry (check the box and move to Stopped).""" - registry_path = _get_process_registry_path(memory_path) - if not registry_path.exists(): - return - - content = registry_path.read_text() - lines = content.split("\n") - new_lines: list[str] = [] - stopped_entry: str | None = None - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M") - - for line in lines: - if f"**PID {pid}**" in line and "- [ ]" in line: - # Found the running process - mark it as checked and prepare for Stopped section - stopped_entry = line.replace("- [ ]", "- [x]") + f" | {reason} @ {timestamp}" - # Don't add to new_lines yet (will move to Stopped section) - else: - new_lines.append(line) - - # Add stopped entry to Stopped section - if stopped_entry: - content = "\n".join(new_lines) - if "## Stopped" in content: - content = content.replace("## Stopped\n\n", f"## Stopped\n\n{stopped_entry}\n") - else: - content += f"\n## Stopped\n\n{stopped_entry}\n" - registry_path.write_text(content) - - -def _is_process_running(pid: int) -> bool: - """Check if a process is still running.""" - try: - os.kill(pid, 0) - return True - except (OSError, ProcessLookupError): - return False - - -def _get_running_pids_from_registry(memory_path: Path) -> list[tuple[int, str]]: - """Get list of (pid, line) for processes marked as running in registry.""" - registry_path = _get_process_registry_path(memory_path) - if not registry_path.exists(): - return [] - - content = registry_path.read_text() - running: list[tuple[int, str]] = [] - - for line in content.split("\n"): - if "- [ ]" in line and "**PID" in line: - # Extract PID from format: **PID 12345** - match = re.search(r"\*\*PID (\d+)\*\*", line) - if match: - pid = int(match.group(1)) - running.append((pid, line)) - - return running - - -def create_bash_execute_tool( - workspace: Path, memory_path: Path, default_timeout: int = 120 -) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a bash_execute tool bound to a specific workspace.""" - - async def bash_execute( - command: Annotated[str, "Bash command to execute"], - timeout: Annotated[int, f"Command timeout in seconds (default: {default_timeout})"] = default_timeout, - background: Annotated[ - bool, "Run in background and return immediately with PID. Use for servers/long-running processes." - ] = False, - ) -> str: - """Execute bash commands in the workspace. - - Returns stdout, stderr, and return code. - Use for running tests, git commands, package managers, builds, etc. - IMPORTANT: Each call runs in a fresh shell from workspace root - - use 'cd dir && command' for commands in subdirectories. - For long-running processes (servers), use background=True to avoid timeout. - """ - try: - if background: - # Run in background using nohup and capture PID - # Redirect output to a log file - log_file = workspace / ".background_logs" / f"bg_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" - log_file.parent.mkdir(parents=True, exist_ok=True) - - bg_command = f"nohup {command} > {log_file} 2>&1 & echo $!" - - proc = await asyncio.create_subprocess_shell( - bg_command, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - cwd=str(workspace), - ) - - stdout, _ = await proc.communicate() - pid_str = stdout.decode().strip() - - try: - pid = int(pid_str) - # Register the process in memory - _add_process_to_registry( - memory_path=memory_path, - pid=pid, - command=command, - workspace=str(workspace), - log_file=str(log_file), - ) - - return ( - f"Background process started successfully.\n" - f"PID: {pid}\n" - f"Command: {command}\n" - f"Log file: {log_file}\n" - f"\nProcess registered in /memory/processes.md\n" - f"Use check_processes(action='list') to see all background processes.\n" - f"Use check_processes(action='kill', pid={pid}) to stop this process." - ) - except ValueError: - return f"Error: Could not get PID. Output: {pid_str}" - - # Regular (blocking) execution - proc = await asyncio.create_subprocess_shell( - command, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - cwd=str(workspace), - ) - - try: - stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout) - except asyncio.TimeoutError: - proc.kill() - await proc.wait() - return ( - f"Error: Command timed out after {timeout} seconds.\n" - f"Command: {command}\n\n" - f"TIP: If this is a long-running process (like a server), " - f"use background=True to run it in the background." - ) - - stdout_str = stdout.decode("utf-8", errors="replace") - stderr_str = stderr.decode("utf-8", errors="replace") - return_code = proc.returncode - - # Format output - result_parts = [f"Command: {command}"] - result_parts.append(f"Return code: {return_code}") - result_parts.append("=" * 50) - - if stdout_str.strip(): - # Truncate very long output - if len(stdout_str) > 15000: - stdout_str = stdout_str[:15000] + "\n... (stdout truncated)" - result_parts.append("STDOUT:") - result_parts.append(stdout_str) - - if stderr_str.strip(): - if len(stderr_str) > 5000: - stderr_str = stderr_str[:5000] + "\n... (stderr truncated)" - result_parts.append("STDERR:") - result_parts.append(stderr_str) - - if not stdout_str.strip() and not stderr_str.strip(): - result_parts.append("(no output)") - - return "\n".join(result_parts) - - except Exception as e: - return f"Error executing command: {e}" - - # Add tool metadata - bash_execute._tool_name = "bash_execute" # type: ignore[attr-defined] - bash_execute._tool_description = ( # type: ignore[attr-defined] - "Execute bash commands in the workspace. " - "Returns stdout, stderr, and return code. " - "Use for running tests, git commands, package managers, builds, etc." - ) - bash_execute._is_tool = True # type: ignore[attr-defined] - - return bash_execute - - -def create_check_processes_tool( - workspace: Path, memory_path: Path -) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a tool to check and manage background processes.""" - - async def check_processes( - action: Annotated[ - Literal["list", "kill", "cleanup"], - "'list' to see processes, 'kill' to stop one by PID, 'cleanup' to kill all", - ], - pid: Annotated[int | None, "PID to kill (required for 'kill' action)"] = None, - ) -> str: - """Check and manage background processes. - - Use 'list' to see all background processes (also viewable at /memory/processes.md), - 'kill' to stop a specific process by PID, - 'cleanup' to kill all background processes from this workspace. - """ - _ensure_process_registry(memory_path) - registry_path = _get_process_registry_path(memory_path) - - if action == "list": - # Read the registry and update status of running processes - running_pids = _get_running_pids_from_registry(memory_path) - active_count = 0 - dead_pids: list[int] = [] - - for proc_pid, _ in running_pids: - if _is_process_running(proc_pid): - active_count += 1 - else: - dead_pids.append(proc_pid) - - # Mark dead processes as stopped - for dead_pid in dead_pids: - _mark_process_stopped(memory_path, dead_pid, reason="exited") - - # Return the updated registry - content = registry_path.read_text() - return ( - f"Active background processes: {active_count}\n" - f"(View full registry at /memory/processes.md)\n\n" - f"{content}" - ) - - if action == "kill": - if pid is None: - return "Error: 'pid' is required for 'kill' action." - - try: - os.kill(pid, signal.SIGTERM) - await asyncio.sleep(0.5) # Give it time to terminate - - # Check if it's really dead, if not SIGKILL - if _is_process_running(pid): - os.kill(pid, signal.SIGKILL) - await asyncio.sleep(0.2) - - _mark_process_stopped(memory_path, pid, reason="killed") - - if _is_process_running(pid): - return f"Warning: Process {pid} may still be running after kill attempt." - return f"Successfully killed process {pid}. Updated /memory/processes.md" - - except ProcessLookupError: - _mark_process_stopped(memory_path, pid, reason="not found") - return f"Process {pid} was not running (already terminated). Updated /memory/processes.md" - except PermissionError: - return f"Error: Permission denied to kill process {pid}." - except Exception as e: - return f"Error killing process {pid}: {e}" - - if action == "cleanup": - # Kill all processes from this workspace - running_pids = _get_running_pids_from_registry(memory_path) - workspace_str = str(workspace) - killed: list[int] = [] - failed: list[tuple[int, str]] = [] - - for proc_pid, line in running_pids: - # Check if this process is from our workspace - workspace_short = workspace_str.split("/")[-1] - if workspace_short in line or workspace_str in line: - try: - os.kill(proc_pid, signal.SIGTERM) - await asyncio.sleep(0.2) - if _is_process_running(proc_pid): - os.kill(proc_pid, signal.SIGKILL) - _mark_process_stopped(memory_path, proc_pid, reason="cleanup") - killed.append(proc_pid) - except (ProcessLookupError, PermissionError) as e: - _mark_process_stopped(memory_path, proc_pid, reason=f"cleanup failed: {e}") - failed.append((proc_pid, str(e))) - - result = "Cleanup complete. Updated /memory/processes.md\n" - if killed: - result += f"Killed processes: {killed}\n" - if failed: - result += f"Failed to kill: {failed}\n" - if not killed and not failed: - result += "No active processes found for this workspace." - - return result - - return f"Unknown action: {action}" - - # Add tool metadata - check_processes._tool_name = "check_processes" # type: ignore[attr-defined] - check_processes._tool_description = ( # type: ignore[attr-defined] - "Check and manage background processes. " - "Use 'list' to see all background processes, " - "'kill' to stop a specific process by PID, " - "'cleanup' to kill all background processes from this workspace." - ) - check_processes._is_tool = True # type: ignore[attr-defined] - - return check_processes - - -def create_python_repl_tool(workspace: Path) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a python_repl tool bound to a specific workspace.""" - - async def python_repl( - code: Annotated[str, "Python code to execute"], - ) -> str: - """Execute Python code in an isolated namespace. - - Returns the output (stdout) or any errors. - Use for testing code snippets, calculations, data manipulation, or quick validation. - The WORKSPACE variable is available with the workspace path. - """ - old_stdout = sys.stdout - old_stderr = sys.stderr - - try: - # Capture stdout and stderr - redirected_output = StringIO() - redirected_error = StringIO() - sys.stdout = redirected_output - sys.stderr = redirected_error - - # Create isolated namespace with builtins - namespace: dict[str, Any] = { - "__builtins__": __builtins__, - "__name__": "__main__", - "WORKSPACE": workspace, - } - - try: - # Try to compile and exec - compiled = compile(code, "", "exec") - exec(compiled, namespace) # noqa: S102 - - output = redirected_output.getvalue() - error = redirected_error.getvalue() - - result_parts = ["Python REPL Output"] - result_parts.append("=" * 50) - - if output.strip(): - if len(output) > 15000: - output = output[:15000] + "\n... (output truncated)" - result_parts.append(output) - - if error.strip(): - result_parts.append("STDERR:") - result_parts.append(error) - - if not output.strip() and not error.strip(): - result_parts.append("(code executed successfully, no output)") - - return "\n".join(result_parts) - - except SyntaxError as e: - return f"SyntaxError: {e}" - except Exception as e: - return f"Error: {type(e).__name__}: {e}" - - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - - # Add tool metadata - python_repl._tool_name = "python_repl" # type: ignore[attr-defined] - python_repl._tool_description = ( # type: ignore[attr-defined] - "Execute Python code in an isolated namespace. " - "Returns the output (stdout) or any errors. " - "Use for testing code snippets, calculations, data manipulation, or quick validation." - ) - python_repl._is_tool = True # type: ignore[attr-defined] - - return python_repl - - -def create_execution_tools( - workspace: Path, - memory_path: Path, - bash_timeout: int = 120, -) -> Sequence[Callable[..., Coroutine[Any, Any, str]]]: - """Create all execution tools bound to a workspace. - - Args: - workspace: Root directory for command execution - memory_path: Path to memory directory for process registry - bash_timeout: Default timeout for bash commands in seconds - - Returns: - List of execution tool functions - """ - workspace = Path(workspace).resolve() - memory_path = Path(memory_path).resolve() - - return [ - create_bash_execute_tool(workspace, memory_path, bash_timeout), - create_check_processes_tool(workspace, memory_path), - create_python_repl_tool(workspace), - ] diff --git a/src/flow/harness/maf/tools/memory.py b/src/flow/harness/maf/tools/memory.py deleted file mode 100644 index 1a79a6d9d1feb612d4b34de45f5950f8509921ea..0000000000000000000000000000000000000000 --- a/src/flow/harness/maf/tools/memory.py +++ /dev/null @@ -1,260 +0,0 @@ -"""Memory tool for persistent storage across sessions. - -Provides file-based memory storage allowing agents to store and retrieve -information, patterns, and decisions across conversations. -""" - -from collections.abc import Callable, Coroutine -from pathlib import Path -from typing import Annotated, Any, Literal - - -class MemoryBackend: - """File-based memory storage backend with security controls.""" - - def __init__(self, base_path: Path) -> None: - """Initialize memory backend.""" - self.base_path = Path(base_path).resolve() - self.base_path.mkdir(parents=True, exist_ok=True) - - def _validate_path(self, path: str) -> Path: - """Validate and resolve a memory path.""" - # Normalize path (remove /memory prefix if present) - if path.startswith("/memory"): - path = path[len("/memory") :] - path = path.lstrip("/") - - # Handle empty path - if not path: - return self.base_path - - # Resolve to absolute path - full_path = (self.base_path / path).resolve() - - # Security: Ensure path is within base_path - try: - full_path.relative_to(self.base_path) - except ValueError as err: - raise ValueError(f"Access denied: path '{path}' is outside memory directory") from err - - return full_path - - def view(self, path: str, view_range: list[int] | None = None) -> str: - """View directory contents or file contents.""" - full_path = self._validate_path(path) - - if not full_path.exists(): - return f"Path not found: {path}\nUse 'create' to create new files." - - # Directory listing - if full_path.is_dir(): - contents = [f"Directory: {path or '/memory'}"] - items = sorted(full_path.iterdir(), key=lambda x: (x.is_file(), x.name)) - - if not items: - contents.append("(empty directory)") - else: - for item in items: - suffix = "/" if item.is_dir() else "" - contents.append(f" - {item.name}{suffix}") - - return "\n".join(contents) - - # File contents - if full_path.is_file(): - content = full_path.read_text(encoding="utf-8") - lines = content.splitlines() - - if view_range: - start, end = view_range - start = max(1, start) - end = min(len(lines), end) - lines = lines[start - 1 : end] - numbered_lines = [f"{i + start:5d}: {line}" for i, line in enumerate(lines)] - else: - numbered_lines = [f"{i + 1:5d}: {line}" for i, line in enumerate(lines)] - - return "\n".join(numbered_lines) if numbered_lines else "(empty file)" - - return f"Unknown path type: {path}" - - def create(self, path: str, file_text: str) -> str: - """Create or overwrite a file.""" - full_path = self._validate_path(path) - full_path.parent.mkdir(parents=True, exist_ok=True) - full_path.write_text(file_text, encoding="utf-8") - return f"File created successfully at {path}" - - def str_replace(self, path: str, old_str: str, new_str: str) -> str: - """Replace text in a file.""" - full_path = self._validate_path(path) - - if not full_path.is_file(): - raise FileNotFoundError(f"File not found: {path}") - - content = full_path.read_text(encoding="utf-8") - - if old_str not in content: - raise ValueError(f"Text not found in file: '{old_str[:50]}...'") - - new_content = content.replace(old_str, new_str, 1) - full_path.write_text(new_content, encoding="utf-8") - return f"File {path} has been edited successfully" - - def append(self, path: str, text: str) -> str: - """Append text to end of file.""" - full_path = self._validate_path(path) - - if not full_path.exists(): - full_path.parent.mkdir(parents=True, exist_ok=True) - full_path.write_text("", encoding="utf-8") - - # Ensure text starts with newline if file isn't empty - if full_path.stat().st_size > 0: - existing = full_path.read_text(encoding="utf-8") - if existing and not existing.endswith("\n"): - text = "\n" + text - - # Ensure text ends with newline - if not text.endswith("\n"): - text += "\n" - - with full_path.open("a", encoding="utf-8") as f: - f.write(text) - - return f"Text appended to {path}" - - def search(self, query: str, path: str = "") -> str: - """Search for text across memory files.""" - full_path = self._validate_path(path) - - if not full_path.exists(): - return f"Path not found: {path or '/memory'}" - - if not full_path.is_dir(): - # Search single file - files = [full_path] - else: - files = list(full_path.rglob("*")) - - matches: list[dict[str, Any]] = [] - query_lower = query.lower() - - for file_path in files: - if not file_path.is_file(): - continue - try: - content = file_path.read_text(encoding="utf-8") - lines = content.splitlines() - - for line_num, line in enumerate(lines, 1): - if query_lower in line.lower(): - rel_path = file_path.relative_to(self.base_path) - matches.append({ - "file": str(rel_path), - "line": line_num, - "content": line.strip()[:100], - }) - except (UnicodeDecodeError, PermissionError): - continue - - if not matches: - return f"No matches found for '{query}' in {path or '/memory'}" - - result_lines = [f"Found {len(matches)} match(es) for '{query}':\n"] - for match in matches[:50]: - result_lines.append(f" {match['file']}:{match['line']} - {match['content']}") - - if len(matches) > 50: - result_lines.append(f"\n... and {len(matches) - 50} more matches") - - return "\n".join(result_lines) - - def delete(self, path: str) -> str: - """Delete a file or empty directory.""" - full_path = self._validate_path(path) - - if not full_path.exists(): - raise FileNotFoundError(f"Path not found: {path}") - - if full_path.is_file(): - full_path.unlink() - return f"File deleted: {path}" - - if full_path.is_dir(): - if any(full_path.iterdir()): - raise ValueError(f"Directory not empty: {path}. Delete contents first.") - full_path.rmdir() - return f"Directory deleted: {path}" - - return f"Unknown path type: {path}" - - -def create_memory_tool(memory_path: Path) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a memory tool bound to a specific memory directory.""" - backend = MemoryBackend(memory_path) - - async def memory( - command: Annotated[ - Literal["view", "create", "str_replace", "append", "search", "delete"], - "Operation to perform", - ], - path: Annotated[str, "Path to file or directory (e.g., '/memory/patterns/cors.md')"] = "/memory", - file_text: Annotated[str | None, "Content to write (for create)"] = None, - old_str: Annotated[str | None, "Text to find (for str_replace)"] = None, - new_str: Annotated[str | None, "Replacement text (for str_replace)"] = None, - append_text: Annotated[str | None, "Text to append (for append)"] = None, - query: Annotated[str | None, "Search query (for search)"] = None, - view_range: Annotated[list[int] | None, "Line range [start, end] (for view)"] = None, - ) -> str: - """Store and retrieve information in persistent memory. - - Memory persists across conversations - use it to remember patterns, - insights, project context, and decisions. - Operations: view (show directory/file), create (new file), - str_replace (edit file), append (add to file), - search (find text), delete (remove file/dir). - Organize by: /memory/patterns/, /memory/projects/, /memory/decisions/ - """ - try: - if command == "view": - return backend.view(path, view_range) - - if command == "create": - if file_text is None: - return "Error: 'file_text' is required for create operation" - return backend.create(path, file_text) - - if command == "str_replace": - if old_str is None or new_str is None: - return "Error: 'old_str' and 'new_str' are required for str_replace" - return backend.str_replace(path, old_str, new_str) - - if command == "append": - if append_text is None: - return "Error: 'append_text' is required for append operation" - return backend.append(path, append_text) - - if command == "search": - if query is None: - return "Error: 'query' is required for search operation" - return backend.search(query, path) - - if command == "delete": - return backend.delete(path) - - return f"Error: Unknown command: {command}" - - except Exception as e: - return f"Memory operation failed: {e}" - - # Add tool metadata - memory._tool_name = "memory" # type: ignore[attr-defined] - memory._tool_description = ( # type: ignore[attr-defined] - "Store and retrieve information in persistent memory. " - "Memory persists across conversations - use it to remember patterns, " - "insights, project context, and decisions." - ) - memory._is_tool = True # type: ignore[attr-defined] - - return memory diff --git a/src/flow/harness/maf/tools/sub_agent.py b/src/flow/harness/maf/tools/sub_agent.py deleted file mode 100644 index adfaf1b26e12aae2512c6f235dd7e6310c688dc9..0000000000000000000000000000000000000000 --- a/src/flow/harness/maf/tools/sub_agent.py +++ /dev/null @@ -1,196 +0,0 @@ -"""Sub-agent tool for isolated research tasks. - -Provides context isolation by delegating complex research tasks to a -separate agent that operates in its own context window. The sub-agent -processes the request and returns only a concise summary, preventing -context pollution in the main agent. - -This implements the "Isolation" strategy for context engineering: -- Coordinator agent stays lean with minimal context -- Sub-agent can use 30K+ tokens internally for research -- Only the distilled result (200-500 tokens) returns to coordinator -""" - -from __future__ import annotations - -import os -from collections.abc import Callable, Coroutine -from pathlib import Path -from typing import Annotated, Any - -# Sub-agent system prompt focused on research and summarization -SUB_AGENT_INSTRUCTIONS = """You are a research assistant that helps with complex information gathering tasks. - -Your role: -1. Thoroughly research the given topic or question -2. Gather relevant information from available tools -3. Synthesize findings into a clear, concise summary -4. Return ONLY the essential information needed by the requesting agent - -Guidelines: -- Be thorough in your research but concise in your response -- Focus on facts and actionable information -- If you can't find information, say so clearly -- Your response will be passed to another agent, so make it self-contained -- Target 200-500 tokens for your final response unless more detail is explicitly requested - -Do NOT: -- Include conversational fluff or preamble -- Repeat the original question back -- Add disclaimers about your limitations -- Include information that wasn't requested -""" - - -def create_sub_agent_tool( - workspace: Path, - model: str = "gpt-4o-mini", - endpoint: str | None = None, - api_key: str | None = None, - api_version: str = "2024-02-15-preview", -) -> Callable[..., Coroutine[Any, Any, str]]: - """Create a sub-agent tool for isolated research tasks. - - The sub-agent runs in its own isolated context, preventing context - pollution in the main agent. This is useful for: - - Complex research that requires many tool calls - - Tasks that generate lots of intermediate content - - Keeping the main agent's context lean and focused - - Args: - workspace: Workspace directory for file operations - model: Model to use for sub-agent (default: gpt-4o-mini for efficiency) - endpoint: Azure OpenAI endpoint (defaults to AZURE_OPENAI_ENDPOINT env var) - api_key: Azure OpenAI API key (defaults to AZURE_OPENAI_API_KEY env var) - api_version: Azure OpenAI API version - - Returns: - An async function that can be used as a tool - """ - # Resolve credentials from environment if not provided - _endpoint = endpoint or os.environ.get("AZURE_OPENAI_ENDPOINT", "") - _api_key = api_key or os.environ.get("AZURE_OPENAI_API_KEY", "") - - # Lazy import to avoid circular dependencies - _sub_agent: Any = None - - async def _ensure_sub_agent() -> Any: - """Lazily create the sub-agent on first use.""" - nonlocal _sub_agent - if _sub_agent is not None: - return _sub_agent - - try: - from agent_framework import ChatAgent - from agent_framework.azure import AzureOpenAIChatClient - except ImportError as e: - raise ImportError( - "Microsoft Agent Framework is required for sub-agent. " - "Install with: pip install agent-framework-core" - ) from e - - # Create a lightweight chat client for the sub-agent - # Uses a smaller/faster model by default for efficiency - client = AzureOpenAIChatClient( - api_key=_api_key, - endpoint=_endpoint, - deployment=model, - api_version=api_version, - ) - - # Create basic tools for the sub-agent - # Keep it minimal - just what's needed for research - from flow.harness.maf.tools.coding import ( - create_grep_search_tool, - create_list_directory_tool, - create_read_file_tool, - ) - from flow.harness.maf.tools.core import task_done, think - - sub_tools: list[Callable[..., Any]] = [ - create_read_file_tool(workspace), - create_list_directory_tool(workspace), - create_grep_search_tool(workspace), - think, - task_done, - ] - - # Convert tools to agent_framework format - from agent_framework import ai_function - - converted_tools = [] - for tool_func in sub_tools: - name = getattr(tool_func, "_tool_name", tool_func.__name__) - description = getattr(tool_func, "_tool_description", tool_func.__doc__ or "") - wrapped = ai_function(name=name, description=description)(tool_func) - converted_tools.append(wrapped) - - _sub_agent = ChatAgent( - name="ResearchAssistant", - description="Research assistant for complex information gathering", - instructions=SUB_AGENT_INSTRUCTIONS, - chat_client=client, - tools=converted_tools, - ) - - return _sub_agent - - async def research( - task: Annotated[ - str, - "The research task or question to investigate. Be specific about what information you need.", - ], - context: Annotated[ - str | None, - "Optional context to help the sub-agent understand the broader goal.", - ] = None, - ) -> str: - """Delegate a research task to a sub-agent with isolated context. - - Use this tool when you need to: - - Research a complex topic that may require multiple steps - - Gather information without polluting your main context - - Get a summarized answer to a specific question - - The sub-agent operates in its own context window, so it can - use many tokens internally while only returning a concise summary. - This keeps your main context lean and focused. - - Examples: - - "Find all Python files that import the requests library and summarize their purpose" - - "Research how authentication is implemented in this codebase" - - "Analyze the error handling patterns used across the project" - """ - sub_agent = await _ensure_sub_agent() - - # Build the research prompt - prompt_parts = [f"Research task: {task}"] - if context: - prompt_parts.insert(0, f"Context: {context}") - prompt_parts.append("\nProvide a concise summary of your findings.") - - full_prompt = "\n\n".join(prompt_parts) - - try: - # Run the sub-agent - it operates in isolated context - response = await sub_agent.run(full_prompt) - - # Extract text content from response - if hasattr(response, "content"): - return str(response.content) - return str(response) - - except Exception as e: - return f"Research failed: {e}" - - # Add tool metadata - research._tool_name = "research" # type: ignore[attr-defined] - research._tool_description = ( # type: ignore[attr-defined] - "Delegate a research task to a sub-agent with isolated context. " - "The sub-agent can thoroughly investigate a topic using many tool calls " - "internally, then return only a concise summary. Use this for complex " - "research that would otherwise pollute your main context." - ) - research._is_tool = True # type: ignore[attr-defined] - - return research diff --git a/src/flow/harness/maf/wrappers.py b/src/flow/harness/maf/wrappers.py new file mode 100644 index 0000000000000000000000000000000000000000..cdc07a4679572c1e073b287d9656de1c8ef93926 --- /dev/null +++ b/src/flow/harness/maf/wrappers.py @@ -0,0 +1,64 @@ +"""MAF-specific tool wrappers. + +This module provides utilities for wrapping shared tools for use with +Microsoft Agent Framework. The main functionality is now handled by +the shared adapters in flow.tools.adapters. + +This module is maintained for backward compatibility. +""" + +from __future__ import annotations + +import logging +from collections.abc import Callable, Coroutine +from pathlib import Path +from typing import Any + +from flow.tools import Tool, to_maf_tool +from flow.harness.maf.tools import build_tools as build_maf_tools_impl + +logger = logging.getLogger(__name__) + +__all__ = ["build_maf_tools", "wrap_for_maf"] + + +def wrap_for_maf(tool: Tool) -> Callable[..., Coroutine[Any, Any, str]]: + """Wrap a Flow Tool for Microsoft Agent Framework. + + Applies the MAF @tool decorator using metadata from the Tool instance. + + Args: + tool: A Flow Tool instance + + Returns: + The function wrapped with @tool for MAF + + Raises: + ValueError: If the input is not a Tool instance + """ + if not isinstance(tool, Tool): + raise ValueError(f"Expected Tool instance, got {type(tool)}") + + return to_maf_tool(tool) + + +def build_maf_tools( + tools_spec: dict[str, dict[str, Any]], + workspace: Path, + memory_path: Path, +) -> list[Callable[..., Coroutine[Any, Any, str]]]: + """Build MAF-compatible tools from a specification dict. + + Creates MAF-specific tools using the shared tools from flow.tools + and wraps them with the MAF @tool decorator. + + Args: + tools_spec: Dict mapping tool names to their config dicts. + workspace: Root directory for file operations + memory_path: Directory for persistent memory + + Returns: + List of tool functions wrapped with @tool + """ + # Build tools from MAF-specific module (already wrapped with MAF @tool) + return build_maf_tools_impl(tools_spec, workspace, memory_path) diff --git a/src/flow/harness/miniagent/__init__.py b/src/flow/harness/miniagent/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e13099601b126084f04f15277db41ada8e3af35d --- /dev/null +++ b/src/flow/harness/miniagent/__init__.py @@ -0,0 +1,139 @@ +"""MiniAgent harness for Flow - correct context compaction. + +MiniAgent fixes Agent Framework's broken context compaction by: +1. Applying compaction BEFORE each LLM call in the tool loop +2. Reassigning the message list (not modifying a copy) +3. Supporting token-budget-based strategies + +## Usage in Flow + + from flow.experiments.models import Agent, CompactionConfig + + agent = Agent( + name="my-agent", + framework="miniagent", # Use this harness + compaction=CompactionConfig.head_tail_tokens(head_ratio=0.2, token_budget=50_000), + tools="standard", + ) + +## Direct Usage + + from flow.harness.miniagent import ChatAgent, HeadTailStrategy + + agent = ChatAgent( + instructions="You are a helpful assistant.", + tools=tools.coding_tools(), + context_strategy=HeadTailStrategy(), + token_budget=100_000, + ) + response = await agent.run("Find all Python files with TODO comments") + +## Context Strategies + +- NoCompactionStrategy: Baseline (no management) +- HeadTailStrategy: Keep head (20%) + tail (80%), drop middle (token-aware) +- SlidingWindowStrategy: Keep system + recent messages within budget +- SummarizationStrategy: Compress old messages using LLM + +## Key Difference from Agent Framework + +Agent Framework's tool loop: + prepped_messages = prepare_messages(messages) # Copy made ONCE + for iteration in range(max_iterations): + middleware(context) # Modifies a DIFFERENT copy + response = llm_call(prepped_messages) + prepped_messages.extend(results) # List grows unbounded + +MiniAgent's tool loop: + for iteration in range(max_iterations): + messages = compact(messages) # Compacted list REPLACES original + response = llm_call(messages) + messages.extend(results) # Next iteration will compact again +""" + +from .agent import ChatAgent, AgentThread, AgentResponse, UsageStats, StreamEvent, StreamEventType +from .tool import Tool, tool +from .messages import ChatMessage, ToolCall, ToolResult +from .context import ( + ContextStrategy, + NoCompactionStrategy, + HeadTailStrategy, + SlidingWindowStrategy, + SummarizationStrategy, +) +from .client import ChatClient, ClientConfig, ChatCompletionResult +from .hooks import ( + Hooks, + HookEvent, + PreToolUseEvent, + PreToolUseResult, + PostToolUseEvent, + PostToolUseResult, + PreModelCallEvent, + PostModelCallEvent, + PreCompactEvent, + PostCompactEvent, + AgentStartEvent, + AgentEndEvent, +) +from .instructions import get_instructions, INSTRUCTIONS +from .workspace import Workspace, get_workspace, set_workspace +from . import tools + +# Register with Flow's harness system +from flow.harness.registry import register +from .harness import MiniAgentHarness + +register("miniagent", MiniAgentHarness) + +__version__ = "0.1.0" + +__all__ = [ + # Harness + "MiniAgentHarness", + # Core + "ChatAgent", + "AgentThread", + "AgentResponse", + "UsageStats", + "StreamEvent", + "StreamEventType", + # Tools + "Tool", + "tool", + "tools", + # Messages + "ChatMessage", + "ToolCall", + "ToolResult", + # Context strategies + "ContextStrategy", + "NoCompactionStrategy", + "HeadTailStrategy", + "SlidingWindowStrategy", + "SummarizationStrategy", + # Client + "ChatClient", + "ClientConfig", + "ChatCompletionResult", + # Hooks + "Hooks", + "HookEvent", + "PreToolUseEvent", + "PreToolUseResult", + "PostToolUseEvent", + "PostToolUseResult", + "PreModelCallEvent", + "PostModelCallEvent", + "PreCompactEvent", + "PostCompactEvent", + "AgentStartEvent", + "AgentEndEvent", + # Instructions + "get_instructions", + "INSTRUCTIONS", + # Workspace + "Workspace", + "get_workspace", + "set_workspace", +] diff --git a/src/flow/harness/miniagent/agent.py b/src/flow/harness/miniagent/agent.py new file mode 100644 index 0000000000000000000000000000000000000000..da23ee237aae02e0e42ad179d0433d168d54e423 --- /dev/null +++ b/src/flow/harness/miniagent/agent.py @@ -0,0 +1,604 @@ +"""ChatAgent - the core agent implementation for MiniAgent. + +This is the CRITICAL module that fixes Agent Framework's broken compaction. +The key difference: context strategy is called BEFORE each LLM call in the +tool loop, and the compacted list continues to the next iteration. +""" + +from dataclasses import dataclass, field +from typing import Any, AsyncGenerator +from enum import Enum +import json + +from .messages import ChatMessage, ToolCall +from .tool import Tool +from .client import ChatClient, ChatCompletionResult +from .context import ContextStrategy, NoCompactionStrategy +from .hooks import ( + Hooks, + PreToolUseEvent, + PreToolUseResult, + PostToolUseEvent, + PostToolUseResult, + PreModelCallEvent, + PostModelCallEvent, + PreCompactEvent, + PostCompactEvent, + AgentStartEvent, + AgentEndEvent, +) + + +class StreamEventType(str, Enum): + """Types of events emitted during run_stream().""" + AGENT_START = "agent_start" + MODEL_START = "model_start" + MODEL_END = "model_end" + TOOL_START = "tool_start" + TOOL_END = "tool_end" + TEXT = "text" + AGENT_END = "agent_end" + + +def _dict_factory() -> dict[str, Any]: + return {} + +def _list_factory() -> list[dict[str, int]]: + return [] + + +@dataclass +class StreamEvent: + """Event emitted during agent execution streaming.""" + type: StreamEventType + data: dict[str, Any] = field(default_factory=_dict_factory) + + def __str__(self) -> str: + """Human-readable representation for print(event).""" + match self.type: + case StreamEventType.AGENT_START: + msg = self.data.get("user_message", "")[:50] + return f"🚀 Agent started: {msg}..." + case StreamEventType.MODEL_START: + return f"🧠 Model call (iteration {self.data.get('iteration', 0) + 1})" + case StreamEventType.MODEL_END: + usage = self.data.get("usage", {}) + tokens = usage.get("input_tokens", 0) + has_tools = self.data.get("has_tool_calls", False) + tool_info = " → calling tools" if has_tools else "" + return f" ✓ Response ({tokens} tokens){tool_info}" + case StreamEventType.TOOL_START: + name = self.data.get("tool_name", "unknown") + return f"🔧 Tool: {name}" + case StreamEventType.TOOL_END: + name = self.data.get("tool_name", "unknown") + output = self.data.get("tool_output", "")[:100] + return f" → {name}: {output}..." + case StreamEventType.TEXT: + content = self.data.get("content", "")[:200] + return f"💬 {content}" + case StreamEventType.AGENT_END: + usage = self.data.get("usage", {}) + iters = self.data.get("iterations", 0) + tools = usage.get("tool_calls", 0) + tokens = usage.get("total_input_tokens", 0) + usage.get("total_output_tokens", 0) + return f"✅ Done ({iters} iterations, {tools} tool calls, {tokens} tokens)" + case _: + return f"Event({self.type.value}): {self.data}" + + def __repr__(self) -> str: + return f"StreamEvent(type={self.type.value!r}, data={self.data!r})" + + +@dataclass +class UsageStats: + """Token usage statistics.""" + + total_input_tokens: int = 0 + total_output_tokens: int = 0 + llm_calls: int = 0 + tool_calls: int = 0 + per_call: list[dict[str, int]] = field(default_factory=_list_factory) + + +@dataclass +class AgentResponse: + """Response from agent.run().""" + + content: str | None + messages: list[ChatMessage] + usage: UsageStats + iterations: int + + +class AgentThread: + """Conversation thread with message history. + + Threads allow multi-turn conversations by preserving history + between agent.run() calls. + """ + + def __init__(self, messages: list[ChatMessage] | None = None): + self.messages: list[ChatMessage] = messages or [] + + def add(self, message: ChatMessage) -> None: + """Add a single message to the thread.""" + self.messages.append(message) + + def add_many(self, messages: list[ChatMessage]) -> None: + """Add multiple messages to the thread.""" + self.messages.extend(messages) + + def clear(self) -> None: + """Clear all messages from the thread.""" + self.messages = [] + + def __len__(self) -> int: + return len(self.messages) + + def __bool__(self) -> bool: + # Always truthy, even when empty (to work with `thread or get_new_thread()`) + return True + + +class ChatAgent: + """Minimal agent with correct context compaction and hooks. + + The key difference from Agent Framework: + - Context strategy is called BEFORE each LLM call in the tool loop + - The compacted messages are used for both the call AND next iteration + - This ensures cumulative token usage actually decreases with compaction + + Example: + from miniagent import ChatAgent, tools + + agent = ChatAgent( + instructions="You are a helpful assistant.", + tools=tools.coding_tools(), + ) + response = await agent.run("List files in the current directory") + """ + + DEFAULT_MAX_ITERATIONS = 40 + DEFAULT_TOKEN_BUDGET = 100_000 + + def __init__( + self, + client: ChatClient | None = None, + instructions: str | None = None, + tools: list[Tool] | None = None, + context_strategy: ContextStrategy | None = None, + token_budget: int = DEFAULT_TOKEN_BUDGET, + max_iterations: int = DEFAULT_MAX_ITERATIONS, + hooks: Hooks | None = None, + ): + """Initialize the agent. + + Args: + client: Chat client for LLM calls. Auto-created if None. + instructions: System prompt for the agent. + tools: List of tools the agent can use. + context_strategy: Strategy for managing context. Defaults to no compaction. + token_budget: Maximum tokens for context window. + max_iterations: Maximum tool loop iterations. + hooks: Hook configuration for event handling. + """ + self.client = client or ChatClient() + self.instructions = instructions + self.tools = {t.name: t for t in (tools or [])} + self.context_strategy = context_strategy or NoCompactionStrategy() + self.token_budget = token_budget + self.max_iterations = max_iterations + self.hooks = hooks or Hooks() + + def get_new_thread(self) -> AgentThread: + """Create a new conversation thread.""" + return AgentThread() + + async def run( + self, + message: str, + thread: AgentThread | None = None, + ) -> AgentResponse: + """Run the agent on a message (non-streaming). + + This method delegates to run_stream() and collects the results. + All logic lives in run_stream() - this is just a convenience wrapper. + + THE CRITICAL FIX (in run_stream): + - Messages are compacted BEFORE each LLM call + - The compacted list is used for both the call AND continues + - Unlike Agent Framework where prepped_messages grows unbounded + + Args: + message: The user message to process. + thread: Optional thread for conversation continuity. + + Returns: + AgentResponse with the result and statistics. + """ + thread = thread or self.get_new_thread() + + # Consume the stream and extract final results + final_content: str | None = None + iterations: int = 0 + usage_data: dict[str, int] = {} + + async for event in self.run_stream(message, thread): + if event.type == StreamEventType.AGENT_END: + final_content = event.data.get("final_response") + iterations = event.data.get("iterations", 0) + usage_data = event.data.get("usage", {}) + + # Build UsageStats from the collected data + usage = UsageStats( + llm_calls=usage_data.get("llm_calls", 0), + tool_calls=usage_data.get("tool_calls", 0), + total_input_tokens=usage_data.get("total_input_tokens", 0), + total_output_tokens=usage_data.get("total_output_tokens", 0), + ) + + return AgentResponse( + content=final_content, + messages=thread.messages, # Thread was updated by run_stream + usage=usage, + iterations=iterations, + ) + + async def run_stream( + self, + message: str, + thread: AgentThread | None = None, + ) -> AsyncGenerator[StreamEvent, None]: + """Run the agent and yield events as they occur. + + This is useful for building interactive UIs that need to show + progress in real-time. + + Args: + message: The user message to process. + thread: Optional thread for conversation continuity. + + Yields: + StreamEvent objects for each step of execution. + """ + thread = thread or self.get_new_thread() + usage = UsageStats() + + # Emit AgentStart hook + await self._emit_agent_start(message, thread) + + # Emit start event + yield StreamEvent( + type=StreamEventType.AGENT_START, + data={"user_message": message, "thread_length": len(thread)}, + ) + + # Build initial messages + messages: list[ChatMessage] = [] + if self.instructions: + messages.append(ChatMessage.system(self.instructions)) + messages.extend(thread.messages) + user_msg = ChatMessage.user(message) + messages.append(user_msg) + + openai_tools = ( + [t.to_openai_tool() for t in self.tools.values()] if self.tools else None + ) + + final_content: str | None = None + iteration = 0 + + for iteration in range(self.max_iterations): + # Apply context strategy + messages = await self._compact_with_hooks(messages, iteration) + + # Model call start + yield StreamEvent( + type=StreamEventType.MODEL_START, + data={"iteration": iteration, "message_count": len(messages)}, + ) + + # Emit PreModelCall hook for OTEL tracing + await self._emit_pre_model_call(messages, iteration) + + # Make LLM call + result = await self.client.chat_completion( + messages=[m.to_openai_format() for m in messages], + tools=openai_tools, + ) + + # Track usage + usage.llm_calls += 1 + usage.total_input_tokens += result.usage["input_tokens"] + usage.total_output_tokens += result.usage["output_tokens"] + usage.per_call.append(result.usage) + + # Emit PostModelCall hook for OTEL tracing + await self._emit_post_model_call(result, iteration) + + # Model call end + yield StreamEvent( + type=StreamEventType.MODEL_END, + data={ + "iteration": iteration, + "usage": result.usage, + "has_tool_calls": bool(result.tool_calls), + }, + ) + + # Parse response + assistant_msg = self._parse_assistant_message(result) + messages.append(assistant_msg) + + # Emit text if present + if assistant_msg.content: + yield StreamEvent( + type=StreamEventType.TEXT, + data={"content": assistant_msg.content}, + ) + + # Check if done + if not assistant_msg.tool_calls: + final_content = assistant_msg.content + break + + # Execute tools + should_stop = False + for tool_call in assistant_msg.tool_calls: + # Pre-tool hook + hook_result = await self._emit_pre_tool_use(tool_call, messages, iteration) + + if hook_result and hook_result.decision == "block": + tool_msg = ChatMessage.tool( + tool_call.id, + f"Tool call blocked: {hook_result.reason or 'No reason provided'}", + ) + messages.append(tool_msg) + continue + + tool_input = json.loads(tool_call.arguments) + if hook_result and hook_result.decision == "modify" and hook_result.modified_input: + tool_input = hook_result.modified_input + + # Tool start + yield StreamEvent( + type=StreamEventType.TOOL_START, + data={"tool_name": tool_call.name, "tool_input": tool_input}, + ) + + # Execute + tool_result = await self._execute_tool(tool_call.name, tool_input) + usage.tool_calls += 1 + + # Tool end + yield StreamEvent( + type=StreamEventType.TOOL_END, + data={ + "tool_name": tool_call.name, + "tool_output": tool_result[:500], # Truncate for streaming + }, + ) + + # Post-tool hook + post_result = await self._emit_post_tool_use( + tool_call, tool_input, tool_result, iteration + ) + + # Add tool result + tool_msg = ChatMessage.tool(tool_call.id, tool_result) + messages.append(tool_msg) + + if post_result: + if post_result.additional_context: + messages.append(ChatMessage.system(post_result.additional_context)) + if post_result.stop_execution: + should_stop = True + break + + if should_stop: + break + + # Update thread + start_idx = 1 if self.instructions else 0 + thread.messages = messages[start_idx:] + + # Get final content + if final_content is None: + for msg in reversed(messages): + if msg.role == "assistant" and msg.content: + final_content = msg.content + break + + # Emit AgentEnd hook + await self._emit_agent_end(final_content, iteration + 1, usage) + + # End event + yield StreamEvent( + type=StreamEventType.AGENT_END, + data={ + "final_response": final_content, + "iterations": iteration + 1, + "usage": { + "total_input_tokens": usage.total_input_tokens, + "total_output_tokens": usage.total_output_tokens, + "llm_calls": usage.llm_calls, + "tool_calls": usage.tool_calls, + }, + }, + ) + + def _parse_assistant_message(self, result: "ChatCompletionResult") -> ChatMessage: + """Parse the LLM response into a ChatMessage.""" + tool_calls = None + if result.tool_calls: + tool_calls = [ + ToolCall( + id=tc["id"], + name=tc["name"], + arguments=tc["arguments"], + ) + for tc in result.tool_calls + ] + + return ChatMessage.assistant(content=result.content, tool_calls=tool_calls) + + async def _execute_tool(self, name: str, arguments: dict[str, Any]) -> str: + """Execute a tool call.""" + tool = self.tools.get(name) + if not tool: + return f"Error: Unknown tool '{name}'" + + try: + return await tool.invoke(**arguments) + except Exception as e: + return f"Error executing {name}: {str(e)}" + + # === Hook emission methods === + + async def _emit_agent_start(self, message: str, thread: AgentThread) -> None: + """Emit AgentStart event to hooks.""" + event = AgentStartEvent( + user_message=message, + thread_message_count=len(thread), + ) + for hook in self.hooks.agent_start: + await hook(event) + + async def _emit_agent_end( + self, final_response: str | None, iterations: int, usage: UsageStats + ) -> None: + """Emit AgentEnd event to hooks.""" + event = AgentEndEvent( + final_response=final_response, + total_iterations=iterations, + total_input_tokens=usage.total_input_tokens, + total_output_tokens=usage.total_output_tokens, + tool_calls_made=usage.tool_calls, + ) + for hook in self.hooks.agent_end: + await hook(event) + + async def _emit_pre_model_call( + self, messages: list[ChatMessage], iteration: int + ) -> None: + """Emit PreModelCall event to hooks.""" + event = PreModelCallEvent( + message_count=len(messages), + iteration=iteration, + ) + for hook in self.hooks.pre_model_call: + await hook(event) + + async def _emit_post_model_call(self, result: "ChatCompletionResult", iteration: int) -> None: + """Emit PostModelCall event to hooks.""" + # Extract text content from response + response_text = result.content or "" + + event = PostModelCallEvent( + usage=result.usage, + iteration=iteration, + has_tool_calls=bool(result.tool_calls), + finish_reason=result.finish_reason, + response_text=response_text, + ) + for hook in self.hooks.post_model_call: + await hook(event) + + async def _emit_pre_tool_use( + self, tool_call: ToolCall, messages: list[ChatMessage], iteration: int + ) -> PreToolUseResult | None: + """Emit PreToolUse event to hooks. Returns combined result.""" + event = PreToolUseEvent( + tool_name=tool_call.name, + tool_input=json.loads(tool_call.arguments), + tool_call_id=tool_call.id, + iteration=iteration, + ) + + result: PreToolUseResult | None = None + for hook in self.hooks.pre_tool_use: + hook_result = await hook(event) + if hook_result: + # First non-allow result wins + if hook_result.decision != "allow": + return hook_result + result = hook_result + + return result + + async def _emit_post_tool_use( + self, + tool_call: ToolCall, + tool_input: dict[str, Any], + tool_output: str, + iteration: int, + ) -> PostToolUseResult | None: + """Emit PostToolUse event to hooks. Returns combined result.""" + error = tool_output if tool_output.startswith("Error") else None + event = PostToolUseEvent( + tool_name=tool_call.name, + tool_input=tool_input, + tool_output=tool_output, + tool_call_id=tool_call.id, + iteration=iteration, + error=error, + ) + + combined = PostToolUseResult() + for hook in self.hooks.post_tool_use: + hook_result = await hook(event) + if hook_result: + if hook_result.additional_context: + combined.additional_context = hook_result.additional_context + if hook_result.stop_execution: + combined.stop_execution = True + combined.stop_reason = hook_result.stop_reason + + return combined if (combined.additional_context or combined.stop_execution) else None + + async def _compact_with_hooks( + self, messages: list[ChatMessage], iteration: int + ) -> list[ChatMessage]: + """Apply context strategy with hooks.""" + # Estimate current tokens (rough) + current_tokens = sum( + len(str(m.content or "")) // 4 + 10 for m in messages + ) + + # Emit PreCompact hook + pre_event = PreCompactEvent( + message_count=len(messages), + current_tokens=current_tokens, + budget=self.token_budget, + trigger="auto", + ) + for hook in self.hooks.pre_compact: + await hook(pre_event) + + # Apply context strategy (use async if available for summarization) + compacted: list[ChatMessage] + if hasattr(self.context_strategy, "prepare_context_async"): + # Cast to Any to access optional async method + strategy: Any = self.context_strategy + compacted = await strategy.prepare_context_async( + messages, self.token_budget + ) + else: + compacted = self.context_strategy.prepare_context(messages, self.token_budget) + + # Emit PostCompact hook if something changed + if len(compacted) != len(messages): + compacted_tokens = sum( + len(str(m.content or "")) // 4 + 10 for m in compacted + ) + post_event = PostCompactEvent( + messages_before=len(messages), + messages_after=len(compacted), + tokens_before=current_tokens, + tokens_after=compacted_tokens, + ) + for hook in self.hooks.post_compact: + await hook(post_event) + + return compacted diff --git a/src/flow/harness/miniagent/client.py b/src/flow/harness/miniagent/client.py new file mode 100644 index 0000000000000000000000000000000000000000..c80b23040aac74cf070222fd7398164674edf8d4 --- /dev/null +++ b/src/flow/harness/miniagent/client.py @@ -0,0 +1,185 @@ +"""OpenAI/Azure OpenAI client wrapper for MiniAgent. + +Provides a unified interface for both OpenAI and Azure OpenAI APIs. +Auto-detects configuration from environment variables. +""" + +from dataclasses import dataclass +from typing import Any +import os + +# Load .env file if present (override=True to prefer .env over shell env) +try: + from dotenv import load_dotenv + load_dotenv(override=True) +except ImportError: + pass # dotenv not installed, use existing env vars + + +@dataclass +class ClientConfig: + """Configuration for the chat client. + + Can be provided explicitly or auto-detected from environment variables. + """ + + api_key: str + model: str = "gpt-4o" + endpoint: str | None = None # For Azure OpenAI + api_version: str = "2024-02-15-preview" # For Azure OpenAI + temperature: float = 0.0 + max_tokens: int | None = None + + @classmethod + def from_env(cls) -> "ClientConfig": + """Create config from environment variables. + + Checks for Azure first, then falls back to OpenAI. + + Environment variables: + Azure: AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT + OpenAI: OPENAI_API_KEY, OPENAI_MODEL + """ + # Check for Azure OpenAI + azure_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT") + if azure_endpoint: + return cls( + api_key=os.environ.get("AZURE_OPENAI_API_KEY", ""), + model=os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o"), + endpoint=azure_endpoint, + api_version=os.environ.get( + "AZURE_OPENAI_API_VERSION", "2024-02-15-preview" + ), + ) + + # Fall back to OpenAI + return cls( + api_key=os.environ.get("OPENAI_API_KEY", ""), + model=os.environ.get("OPENAI_MODEL", "gpt-4o"), + ) + + +@dataclass +class ChatCompletionResult: + """Result from a chat completion call.""" + + content: str | None + tool_calls: list[dict[str, Any]] | None + usage: dict[str, int] + finish_reason: str | None + raw_response: Any + + +class ChatClient: + """Async client for OpenAI/Azure OpenAI chat completions. + + Wraps the openai Python SDK and provides a simplified interface. + """ + + def __init__(self, config: ClientConfig | None = None): + """Initialize the client. + + Args: + config: Client configuration. If None, auto-detects from env. + """ + self.config = config or ClientConfig.from_env() + self._client = self._create_client() + + def _create_client(self): + """Create the appropriate async client.""" + try: + from openai import AsyncOpenAI, AsyncAzureOpenAI + except ImportError: + raise ImportError( + "openai package is required. Install with: pip install openai" + ) + + if self.config.endpoint: + # Check if using OpenAI-compatible endpoint (e.g., /openai/v1/) + # vs traditional Azure OpenAI endpoint + if "/v1" in self.config.endpoint: + # OpenAI-compatible endpoint (like gpt-5.2-chat on victor-test-resource) + return AsyncOpenAI( + base_url=self.config.endpoint, + api_key=self.config.api_key, + ) + else: + # Traditional Azure OpenAI + return AsyncAzureOpenAI( + api_key=self.config.api_key, + azure_endpoint=self.config.endpoint, + api_version=self.config.api_version, + ) + + # Standard OpenAI + return AsyncOpenAI(api_key=self.config.api_key) + + async def chat_completion( + self, + messages: list[dict[str, Any]], + tools: list[dict[str, Any]] | None = None, + **kwargs: Any, + ) -> ChatCompletionResult: + """Make a chat completion request. + + Args: + messages: List of messages in OpenAI format + tools: Optional list of tools in OpenAI format + **kwargs: Additional parameters to pass to the API + + Returns: + ChatCompletionResult with the response + """ + params: dict[str, Any] = { + "model": self.config.model, + "messages": messages, + } + + # Only set temperature if not using models that don't support it (like gpt-5.2-chat) + temp = kwargs.get("temperature", self.config.temperature) + if temp != 1.0 and "5.2" not in self.config.model: + params["temperature"] = temp + + if self.config.max_tokens: + params["max_tokens"] = self.config.max_tokens + + if tools: + params["tools"] = tools + params["tool_choice"] = kwargs.get("tool_choice", "auto") + params["parallel_tool_calls"] = kwargs.get("parallel_tool_calls", True) + + # Add any extra kwargs + for key, value in kwargs.items(): + if key not in ("temperature", "tool_choice") and value is not None: + params[key] = value + + response = await self._client.chat.completions.create(**params) # type: ignore[union-attr] + + # Extract the message + choice = response.choices[0] # type: ignore[index] + message = choice.message # type: ignore[union-attr] + + # Parse tool calls if present + tool_calls: list[dict[str, Any]] | None = None + if message.tool_calls: # type: ignore[union-attr] + tool_calls = [ + { + "id": str(tc.id), # type: ignore[union-attr] + "name": str(tc.function.name), # type: ignore[union-attr] + "arguments": str(tc.function.arguments), # type: ignore[union-attr] + } + for tc in message.tool_calls # type: ignore[union-attr] + ] + + return ChatCompletionResult( + content=str(message.content) if message.content else None, # type: ignore[union-attr] + tool_calls=tool_calls, + usage={ + "input_tokens": response.usage.prompt_tokens if response.usage else 0, # type: ignore[union-attr] + "output_tokens": ( + response.usage.completion_tokens if response.usage else 0 # type: ignore[union-attr] + ), + }, + finish_reason=str(choice.finish_reason) if choice.finish_reason else None, # type: ignore[union-attr] + raw_response=response, + ) diff --git a/src/flow/harness/miniagent/context.py b/src/flow/harness/miniagent/context.py new file mode 100644 index 0000000000000000000000000000000000000000..13706a4be3519c844a36756f1acd359d41b9c420 --- /dev/null +++ b/src/flow/harness/miniagent/context.py @@ -0,0 +1,664 @@ +"""Context strategies for MiniAgent. + +This is the KEY module that fixes Agent Framework's broken compaction. +Strategies are called BEFORE each LLM call, and the returned (potentially +compacted) list continues to the next iteration. +""" + +from dataclasses import dataclass, field +from typing import Protocol, Any +import tiktoken + +from .messages import ChatMessage + + +class ContextStrategy(Protocol): + """Protocol for context management strategies. + + Called BEFORE each LLM call in the tool loop, allowing + the strategy to modify the message list. + """ + + def prepare_context( + self, + messages: list[ChatMessage], + token_budget: int, + ) -> list[ChatMessage]: + """Prepare messages for the next LLM call. + + Args: + messages: Current messages + token_budget: Maximum tokens for context + + Returns: + Messages to use (may be compacted) + """ + ... + + +class NoCompactionStrategy: + """Baseline: no compaction, context grows unbounded. + + Use this for benchmarking to see how context grows without management. + """ + + def prepare_context( + self, + messages: list[ChatMessage], + token_budget: int, + ) -> list[ChatMessage]: + return messages + + +@dataclass +class HeadTailStrategy: + """Token-aware head+tail compaction. + + Preserves: + - Head: System prompt, initial user message (critical context) + - Tail: Recent tool calls and results (working memory) + + Drops middle messages when over budget, respecting atomic groups + (tool calls and their results must stay together). + + This is the recommended strategy for most use cases. + """ + + head_ratio: float = 0.2 # 20% for head by default + model: str = "gpt-4o" + _encoder: tiktoken.Encoding | None = field(default=None, repr=False) + + # Statistics + compaction_count: int = field(default=0, repr=False) + total_tokens_saved: int = field(default=0, repr=False) + + def __post_init__(self): + try: + self._encoder = tiktoken.encoding_for_model(self.model) + except KeyError: + # Fallback for unknown models + self._encoder = tiktoken.get_encoding("cl100k_base") + + def _count_tokens(self, messages: list[ChatMessage]) -> int: + """Count tokens in messages.""" + if not self._encoder: + # Rough estimate if no encoder + return sum(len(str(m.content or "")) // 4 for m in messages) + + total = 0 + for msg in messages: + # Role overhead (approximately 4 tokens per message) + total += 4 + + if msg.content: + total += len(self._encoder.encode(msg.content)) + + if msg.tool_calls: + for tc in msg.tool_calls: + # Tool call overhead + total += 4 + total += len(self._encoder.encode(tc.name)) + total += len(self._encoder.encode(tc.arguments)) + + return total + + def _find_atomic_groups( + self, messages: list[ChatMessage] + ) -> list[tuple[int, ...]]: + """Group tool_call messages with their results. + + OpenAI requires every tool_call to have a corresponding result. + This ensures we never split a tool call from its results. + + Returns list of tuples, where each tuple contains indices that + must stay together. + """ + groups: list[tuple[int, ...]] = [] + i = 0 + + while i < len(messages): + msg = messages[i] + + if msg.tool_calls: + # This message has tool calls - find all results + call_ids = {tc.id for tc in msg.tool_calls} + group_indices = [i] + + # Look ahead for results + j = i + 1 + while j < len(messages) and call_ids: + if messages[j].role == "tool" and messages[j].tool_call_id in call_ids: + group_indices.append(j) + call_ids.remove(messages[j].tool_call_id) + j += 1 + + groups.append(tuple(group_indices)) + i = max(group_indices) + 1 if group_indices else i + 1 + else: + groups.append((i,)) + i += 1 + + return groups + + def prepare_context( + self, + messages: list[ChatMessage], + token_budget: int, + ) -> list[ChatMessage]: + """Compact if over budget.""" + if not messages: + return messages + + current_tokens = self._count_tokens(messages) + + if current_tokens <= token_budget: + return messages + + # COMPACTION NEEDED + self.compaction_count += 1 + + groups = self._find_atomic_groups(messages) + head_budget = int(token_budget * self.head_ratio) + tail_budget = token_budget - head_budget + + # Fill head from start + head_groups: list[tuple[int, ...]] = [] + head_tokens = 0 + + for group in groups: + group_msgs = [messages[i] for i in group] + group_tokens = self._count_tokens(group_msgs) + + if head_tokens + group_tokens <= head_budget: + head_groups.append(group) + head_tokens += group_tokens + else: + break + + # Fill tail from end (skip head groups) + remaining_groups = groups[len(head_groups) :] + tail_groups: list[tuple[int, ...]] = [] + tail_tokens = 0 + + for group in reversed(remaining_groups): + group_msgs = [messages[i] for i in group] + group_tokens = self._count_tokens(group_msgs) + + if tail_tokens + group_tokens <= tail_budget: + tail_groups.insert(0, group) + tail_tokens += group_tokens + else: + break + + # Build compacted list + kept_indices: set[int] = set() + for group in head_groups + tail_groups: + kept_indices.update(group) + + compacted = [messages[i] for i in sorted(kept_indices)] + + # Track savings + compacted_tokens = self._count_tokens(compacted) + self.total_tokens_saved += current_tokens - compacted_tokens + + return compacted + + +@dataclass +class SlidingWindowStrategy: + """Keep only recent messages within budget. + + Always preserves the system message (if present) plus the most + recent messages that fit in the budget. Respects atomic groups + (tool calls and their results must stay together). + + Simpler than HeadTailStrategy but may lose important early context. + """ + + model: str = "gpt-4o" + _encoder: tiktoken.Encoding | None = field(default=None, repr=False) + + def __post_init__(self): + try: + self._encoder = tiktoken.encoding_for_model(self.model) + except KeyError: + self._encoder = tiktoken.get_encoding("cl100k_base") + + def _count_tokens(self, messages: list[ChatMessage]) -> int: + """Count tokens in messages.""" + if not self._encoder: + return sum(len(str(m.content or "")) // 4 for m in messages) + + total = 0 + for msg in messages: + total += 4 + if msg.content: + total += len(self._encoder.encode(msg.content)) + if msg.tool_calls: + for tc in msg.tool_calls: + total += 4 + len(self._encoder.encode(tc.name)) + total += len(self._encoder.encode(tc.arguments)) + return total + + def _find_atomic_groups( + self, messages: list[ChatMessage] + ) -> list[tuple[int, ...]]: + """Group tool_call messages with their results. + + OpenAI requires every tool_call to have a corresponding result. + This ensures we never split a tool call from its results. + """ + groups: list[tuple[int, ...]] = [] + i = 0 + + while i < len(messages): + msg = messages[i] + + if msg.tool_calls: + # This message has tool calls - find all results + call_ids = {tc.id for tc in msg.tool_calls} + group_indices = [i] + + # Look ahead for results + j = i + 1 + while j < len(messages) and call_ids: + if messages[j].role == "tool" and messages[j].tool_call_id in call_ids: + group_indices.append(j) + call_ids.remove(messages[j].tool_call_id) + j += 1 + + groups.append(tuple(group_indices)) + i = max(group_indices) + 1 if group_indices else i + 1 + else: + groups.append((i,)) + i += 1 + + return groups + + def prepare_context( + self, + messages: list[ChatMessage], + token_budget: int, + ) -> list[ChatMessage]: + """Keep system message + most recent messages within budget.""" + if not messages: + return messages + + # Always keep system messages at the start + system_msgs: list[ChatMessage] = [] + non_system_start = 0 + + for i, msg in enumerate(messages): + if msg.role == "system": + system_msgs.append(msg) + non_system_start = i + 1 + else: + break + + other_msgs = messages[non_system_start:] + + system_tokens = self._count_tokens(system_msgs) + remaining_budget = token_budget - system_tokens + + if remaining_budget <= 0: + return system_msgs + + # Find atomic groups in other messages + groups = self._find_atomic_groups(other_msgs) + + # Fill from end, respecting atomic groups + kept_groups: list[tuple[int, ...]] = [] + kept_tokens = 0 + + for group in reversed(groups): + group_msgs = [other_msgs[i] for i in group] + group_tokens = self._count_tokens(group_msgs) + + if kept_tokens + group_tokens <= remaining_budget: + kept_groups.insert(0, group) + kept_tokens += group_tokens + else: + break + + # Build result from kept groups + kept_indices: set[int] = set() + for group in kept_groups: + kept_indices.update(group) + + result = [other_msgs[i] for i in sorted(kept_indices)] + + return system_msgs + result + + +@dataclass +class SummarizationStrategy: + """Summarize old messages instead of dropping them. + + When over budget, this strategy: + 1. Keeps: System message + initial user message (head) + 2. Keeps: Most recent messages (tail) + 3. Summarizes: Everything in between into a single "context so far" message + + This preserves critical state (files read, findings, progress) that would + otherwise be lost with simple truncation strategies. + + The summarization uses an LLM call, which adds latency but preserves meaning. + """ + + # Client for summarization calls (required) + client: Any = None # ChatClient instance + + # Configuration + head_messages: int = 2 # Keep first N messages (system + initial user) + tail_messages: int = 4 # Keep last N messages (recent context) + summary_max_tokens: int = 1000 # Max tokens for the summary + model: str = "gpt-4o" + + # Statistics + compaction_count: int = field(default=0, repr=False) + total_tokens_saved: int = field(default=0, repr=False) + + _encoder: tiktoken.Encoding | None = field(default=None, repr=False) + + def __post_init__(self): + try: + self._encoder = tiktoken.encoding_for_model(self.model) + except KeyError: + self._encoder = tiktoken.get_encoding("cl100k_base") + + def _count_tokens(self, messages: list[ChatMessage]) -> int: + if not self._encoder: + return sum(len(str(m.content or "")) // 4 for m in messages) + total = 0 + for msg in messages: + total += 4 + if msg.content: + total += len(self._encoder.encode(msg.content)) + if msg.tool_calls: + for tc in msg.tool_calls: + total += 4 + len(self._encoder.encode(tc.name)) + total += len(self._encoder.encode(tc.arguments)) + return total + + def _format_messages_for_summary(self, messages: list[ChatMessage]) -> str: + """Format messages into text for summarization.""" + parts: list[str] = [] + for msg in messages: + if msg.role == "assistant": + if msg.content: + parts.append(f"Assistant: {msg.content}") + if msg.tool_calls: + for tc in msg.tool_calls: + parts.append(f"Tool call: {tc.name}({tc.arguments[:200]}...)") + elif msg.role == "tool": + # Truncate long tool outputs + output = msg.content or "" + if len(output) > 500: + output = output[:500] + "... [truncated]" + parts.append(f"Tool result ({msg.name}): {output}") + elif msg.role == "user" and msg.content: + parts.append(f"User: {msg.content}") + + return "\n\n".join(parts) + + async def _generate_summary( + self, messages: list[ChatMessage], original_task: str = "" + ) -> str: + """Generate a summary of the messages using the LLM. + + Args: + messages: The middle messages to summarize + original_task: The original user task (for context) + """ + if not self.client: + return self._extract_key_info(messages) + + content = self._format_messages_for_summary(messages) + + # Extract files that were read (to prevent re-reading) + files_read = self._extract_files_read(messages) + files_list = "\n".join(f" - {f}" for f in files_read) if files_read else " (none identified)" + + summary_prompt = f"""You are helping an AI agent that is working on a task but hit context limits. +The agent needs to continue from a summary of what was done so far. + +ORIGINAL TASK: +{original_task if original_task else "(not provided)"} + +The conversation below shows {len(messages)} messages of work that needs to be summarized. +The agent will continue working after receiving this summary. + +CRITICAL: Your summary MUST include: +1. **FILES ALREADY READ** - List EVERY file that was read. The agent must NOT re-read these: +{files_list} + +2. **KEY FINDINGS** - What was discovered in each file (brief, 1-2 lines each) + +3. **PROGRESS** - What's been accomplished toward the task + +4. **WHAT REMAINS** - What still needs to be done to complete the task + +Keep summary under {self.summary_max_tokens} tokens. Be specific - vague summaries cause the agent to repeat work. + +CONVERSATION TO SUMMARIZE: +{content} + +SUMMARY:""" + + try: + # chat_completion expects messages as dicts, not ChatMessage objects + # Use max_completion_tokens for newer models, fall back to max_tokens + response = await self.client.chat_completion( + messages=[{"role": "user", "content": summary_prompt}], + max_completion_tokens=self.summary_max_tokens, + ) + # ChatCompletionResult has .content attribute + if response.content: + return response.content + return self._extract_key_info(messages) + except Exception as e: + # Log the error for debugging + import sys + print(f"[SummarizationStrategy] LLM call failed: {e}", file=sys.stderr) + return self._extract_key_info(messages) + + def _extract_files_read(self, messages: list[ChatMessage]) -> list[str]: + """Extract list of files that were read from the messages.""" + files: list[str] = [] + for msg in messages: + if msg.tool_calls: + for tc in msg.tool_calls: + if tc.name in ("read_file", "Read"): + # Try to extract path from arguments + try: + import json + args = json.loads(tc.arguments) + path = args.get("path") or args.get("file_path") or args.get("filename") + if path: + files.append(path) + except: + pass + return list(dict.fromkeys(files)) # Remove duplicates, preserve order + + def _extract_key_info(self, messages: list[ChatMessage]) -> str: + """Extract key info without LLM (fallback).""" + files_read: set[str] = set() + key_findings: list[str] = [] + + for msg in messages: + if msg.role == "tool" and msg.name == "read_file": + # Try to extract filename from the previous tool call + files_read.add(msg.name or "file") + if msg.role == "assistant" and msg.content: + # Keep short assistant messages as findings + if len(msg.content) < 200: + key_findings.append(msg.content) + + parts: list[str] = [] + if files_read: + parts.append(f"Files accessed: {', '.join(files_read)}") + if key_findings: + parts.append(f"Key points: {'; '.join(key_findings[:5])}") + + return "\n".join(parts) if parts else "Previous context was processed." + + def prepare_context( + self, + messages: list[ChatMessage], + token_budget: int, + ) -> list[ChatMessage]: + """Summarize middle messages if over budget. + + NOTE: This is synchronous but summarization needs async. + The actual summarization happens in prepare_context_async. + This method uses a simple fallback for sync contexts. + """ + if not messages: + return messages + + current_tokens = self._count_tokens(messages) + if current_tokens <= token_budget: + return messages + + # For sync context, use simple extraction (no LLM call) + return self._compact_with_summary_sync(messages, token_budget) + + def _find_safe_split_points(self, messages: list[ChatMessage]) -> tuple[int, int]: + """Find safe points to split messages without breaking tool call/result pairs. + + Returns (head_end, tail_start) indices where it's safe to summarize between. + """ + # Find atomic groups (tool calls must stay with their results) + groups: list[tuple[int, int]] = [] # (start, end) indices + i = 0 + + while i < len(messages): + msg = messages[i] + if msg.tool_calls: + # Find all results for this tool call + call_ids = {tc.id for tc in msg.tool_calls} + end = i + j = i + 1 + while j < len(messages) and call_ids: + if messages[j].role == "tool" and messages[j].tool_call_id in call_ids: + call_ids.discard(messages[j].tool_call_id) + end = j + j += 1 + groups.append((i, end + 1)) + i = end + 1 + else: + groups.append((i, i + 1)) + i += 1 + + # Find safe head end (after self.head_messages worth of groups) + head_end = 0 + for idx, (_start, end) in enumerate(groups): + if idx < self.head_messages: + head_end = end + else: + break + + # Find safe tail start (before last self.tail_messages groups) + tail_start = len(messages) + tail_groups = min(self.tail_messages, len(groups)) + if tail_groups > 0 and len(groups) > tail_groups: + tail_start = groups[-tail_groups][0] + + # Ensure we don't overlap + if head_end >= tail_start: + # Not enough room - just keep everything + return len(messages), len(messages) + + return head_end, tail_start + + async def prepare_context_async( + self, + messages: list[ChatMessage], + token_budget: int, + ) -> list[ChatMessage]: + """Async version that can use LLM for summarization.""" + if not messages: + return messages + + current_tokens = self._count_tokens(messages) + if current_tokens <= token_budget: + return messages + + self.compaction_count += 1 + + # Find safe split points that don't break tool call/result pairs + head_end, tail_start = self._find_safe_split_points(messages) + + head = messages[:head_end] + tail = messages[tail_start:] + middle = messages[head_end:tail_start] + + if not middle: + # Nothing to summarize - return as is + return messages + + # Extract the original task from the first user message + original_task = "" + for msg in head: + if msg.role == "user" and msg.content: + original_task = msg.content + break + + # Generate summary of middle section with task context + summary_text = await self._generate_summary(middle, original_task) + + # Create a user message that clearly instructs continuation + # This works better than assistant role because it's a clear directive + summary_message = ChatMessage( + role="user", + content=f"""[CONTEXT CHECKPOINT - Your previous work has been summarized below] + +{summary_text} + +--- +IMPORTANT: The files listed above have ALREADY been read and analyzed. +DO NOT re-read them - that would waste tokens and duplicate work. +Continue from where you left off, completing any remaining items listed in "WHAT REMAINS". +If all files have been read, proceed to generate the final output.""", + ) + + # Build compacted message list + compacted = head + [summary_message] + tail + + # Track savings + compacted_tokens = self._count_tokens(compacted) + self.total_tokens_saved += current_tokens - compacted_tokens + + return compacted + + def _compact_with_summary_sync( + self, messages: list[ChatMessage], token_budget: int + ) -> list[ChatMessage]: + """Synchronous compaction with simple summary extraction.""" + self.compaction_count += 1 + + # Find safe split points that don't break tool call/result pairs + head_end, tail_start = self._find_safe_split_points(messages) + + head = messages[:head_end] + tail = messages[tail_start:] + middle = messages[head_end:tail_start] + + if not middle: + return messages + + # Extract key info without LLM + summary_text = self._extract_key_info(middle) + + summary_message = ChatMessage( + role="user", + content=f"[CONTEXT SUMMARY - Previous {len(middle)} messages compressed]\n\n{summary_text}\n\n[END SUMMARY - Continue from here]", + ) + + compacted = head + [summary_message] + tail + + compacted_tokens = self._count_tokens(compacted) + current_tokens = self._count_tokens(messages) + self.total_tokens_saved += current_tokens - compacted_tokens + + return compacted diff --git a/src/flow/harness/miniagent/harness.py b/src/flow/harness/miniagent/harness.py new file mode 100644 index 0000000000000000000000000000000000000000..13144807184eadca5e66d77781c7dd59ca5c0c8c --- /dev/null +++ b/src/flow/harness/miniagent/harness.py @@ -0,0 +1,403 @@ +"""MiniAgent harness - implements BaseHarness for Flow integration. + +This harness adapts MiniAgent's ChatAgent to Flow's harness interface, +enabling experiments with correct context compaction. +""" + +from __future__ import annotations + +import logging +import uuid +from collections.abc import AsyncIterator +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from flow.harness.base import BaseHarness, Event, EventType + +if TYPE_CHECKING: + from flow.experiments.models import Agent + from flow.llm import LLMClientConfig + +from .agent import ChatAgent, AgentThread, StreamEvent, StreamEventType +from .context import ( + ContextStrategy, + NoCompactionStrategy, + HeadTailStrategy, + SlidingWindowStrategy, + SummarizationStrategy, +) +from .client import ChatClient +from .otel import enable_instrumentation +from .instructions import get_instructions + +from flow.tools import Tool + +logger = logging.getLogger(__name__) + +# Enable instrumentation on module load (like MAF does) +enable_instrumentation() + + +class MiniAgentHarness(BaseHarness): + """Harness adapter for MiniAgent. + + This adapter: + 1. Maps Flow's Agent spec to MiniAgent's ChatAgent + 2. Maps CompactionConfig to ContextStrategy + 3. Converts StreamEvents to Flow Events + 4. Injects OTEL hooks for trace collection + + Example: + >>> from flow.harness.miniagent import MiniAgentHarness + >>> from flow.experiments.models import Agent, CompactionConfig + >>> + >>> agent = Agent( + ... name="test", + ... framework="miniagent", + ... compaction=CompactionConfig.head_tail_tokens(0.2, 50_000), + ... ) + >>> harness = MiniAgentHarness.from_agent(agent, workspace=Path("/tmp")) + >>> async for event in harness.run_stream("Hello"): + ... print(event) + """ + + @classmethod + def from_agent( + cls, + agent: "Agent", + workspace: Path, + llm_config: "LLMClientConfig | None" = None, + ) -> "MiniAgentHarness": + """Create a MiniAgentHarness from an Agent definition. + + Args: + agent: The Agent spec defining the configuration + workspace: Working directory for the agent + llm_config: Optional LLM configuration (falls back to env vars if not provided) + + Returns: + A configured MiniAgentHarness instance + """ + from flow.experiments.models import resolve_tools + + # 1. Map CompactionConfig → ContextStrategy + context_strategy = cls._create_context_strategy(agent) + + # 2. Build tools from spec + tools_spec = resolve_tools(agent.tools) + tools = cls._build_tools(tools_spec, workspace) + + # 3. Create OTEL hooks for trace collection + from .otel import create_otel_hooks + otel_hooks = create_otel_hooks(model=agent.model or "gpt-4o") + + # 4. Create ChatClient from LLM config or env + from .client import ClientConfig + if llm_config is not None: + # Use provided LLM config + config = cls._create_client_config_from_llm_config(llm_config) + else: + # Fall back to env vars + config = ClientConfig.from_env() + if agent.model: + config.model = agent.model + + chat_client = ChatClient(config) + + # Resolve instructions: explicit > preset > default "coding" + if agent.instructions: + instructions = agent.instructions + elif agent.instructions_preset: + instructions = get_instructions(agent.instructions_preset) + else: + instructions = get_instructions("coding") + + chat_agent = ChatAgent( + client=chat_client, + instructions=instructions, + tools=tools, + context_strategy=context_strategy, + token_budget=agent.compaction.token_budget, + hooks=otel_hooks, + ) + + return cls(chat_agent, workspace) + + @classmethod + def _create_client_config_from_llm_config( + cls, llm_config: "LLMClientConfig" + ) -> "ClientConfig": + """Create MiniAgent ClientConfig from Flow LLMClientConfig. + + Args: + llm_config: Flow's LLM client configuration + + Returns: + MiniAgent ClientConfig + """ + from flow.llm import LLMProvider + from .client import ClientConfig + + match llm_config.provider: + case LLMProvider.AZURE_OPENAI: + if not llm_config.azure_openai: + raise ValueError("azure_openai config required for Azure OpenAI provider") + return ClientConfig( + api_key=llm_config.azure_openai.get_api_key(), + model=llm_config.azure_openai.deployment, + endpoint=llm_config.azure_openai.get_endpoint(), + api_version=llm_config.azure_openai.api_version, + ) + + case LLMProvider.OPENAI: + if not llm_config.openai: + raise ValueError("openai config required for OpenAI provider") + return ClientConfig( + api_key=llm_config.openai.get_api_key(), + model=llm_config.openai.model_id, + endpoint=llm_config.openai.base_url, + ) + + case LLMProvider.CUSTOM: + if not llm_config.custom: + raise ValueError("custom config required for custom provider") + return ClientConfig( + api_key=llm_config.custom.get_api_key(), + model=llm_config.custom.model_id, + endpoint=llm_config.custom.base_url, + ) + + case _: + raise ValueError( + f"MiniAgent does not support provider: {llm_config.provider.value}. " + f"Supported: openai, azure_openai, custom" + ) + + @classmethod + def _create_context_strategy(cls, agent: "Agent") -> ContextStrategy: + """Map Flow's CompactionConfig to MiniAgent's ContextStrategy.""" + config = agent.compaction + + match config.strategy: + case "none": + return NoCompactionStrategy() + + case "head_tail": + # Legacy message-count based → convert to ratio + total = config.head_size + config.tail_size + ratio = config.head_size / total if total > 0 else 0.2 + return HeadTailStrategy(head_ratio=ratio) + + case "head_tail_tokens": + return HeadTailStrategy( + head_ratio=config.params.get("head_ratio", 0.2) + ) + + case "sliding_window": + return SlidingWindowStrategy() + + case "summarization": + # SummarizationStrategy needs a client for LLM calls + return SummarizationStrategy( + client=ChatClient(), + head_messages=config.params.get("head_messages", 2), + tail_messages=config.params.get("tail_messages", 4), + summary_max_tokens=config.params.get("summary_max_tokens", 1000), + ) + + case "last_n": + # Map to sliding window as closest equivalent + return SlidingWindowStrategy() + + case _: + logger.warning(f"Unknown compaction strategy: {config.strategy}, using none") + return NoCompactionStrategy() + + @classmethod + def _build_tools(cls, tools_spec: dict[str, dict[str, Any]], workspace: Path) -> list[Tool]: + """Build MiniAgent Tools from Flow tool spec. + + Uses the shared tools from flow.tools, setting up the workspace + for tools that need persistent state. + + Args: + tools_spec: Dict mapping tool names to their configs + workspace: Working directory for tools + + Returns: + List of Tool instances + """ + # Import shared tools + from flow.tools import ( + # Coding + read_file, write_file, edit_file, multi_edit, glob_files, grep, ls, + # Execution + bash, check_processes, python_repl, + # Planning + think, todo_write, todo_read, + # Memory + memory, create_memory_tool, + # Web + web_search, web_fetch, + # Notebooks + notebook_edit, notebook_read, + # Skills + skills, create_skills_tool, + # Sub-agent + task, create_task_tool, + # Workspace management + set_workspace, Workspace, + ) + + # Set workspace for tools that need it (memory, todos, etc.) + set_workspace(Workspace(workspace)) + + # Map tool names → Tool instances + tool_map: dict[str, Tool] = { + # Coding/Filesystem + "read_file": read_file, + "write_file": write_file, + "edit_file": edit_file, + "multi_edit": multi_edit, + "glob_files": glob_files, + "ls": ls, + "grep": grep, + # Execution + "bash": bash, + "check_processes": check_processes, + "python_repl": python_repl, + # Planning + "think": think, + "todo_write": todo_write, + "todo_read": todo_read, + # Web + "web_search": web_search, + "web_fetch": web_fetch, + # Notebooks + "notebook_edit": notebook_edit, + "notebook_read": notebook_read, + # Memory (default instance) + "memory": memory, + # Skills (default instance) + "skills": skills, + # Task/sub-agent (default instance) + "task": task, + } + + tools: list[Tool] = [] + + for name, config in tools_spec.items(): + if name in tool_map: + tools.append(tool_map[name]) + elif name == "task" and config: + # Task tool with custom config + tools.append(create_task_tool( + coordinator_tools=list(tool_map.values()), + model=config.get("model"), + )) + else: + logger.warning(f"Unknown tool: {name}") + + return tools + + def __init__(self, agent: ChatAgent, workspace: Path) -> None: + """Initialize the harness. + + Args: + agent: The MiniAgent ChatAgent instance + workspace: Working directory + """ + self._agent = agent + self._workspace = workspace + self._thread: AgentThread | None = None + self._thread_id: str | None = None + + async def run_stream(self, task: str) -> AsyncIterator[Event]: + """Run a task with streaming events. + + Args: + task: The task/prompt to execute + + Yields: + Event objects representing agent activity + """ + if self._thread is None: + self._thread = self._agent.get_new_thread() + + try: + async for event in self._agent.run_stream(task, thread=self._thread): + flow_event = self._convert_event(event) + if flow_event: + yield flow_event + + yield Event(type=EventType.DONE) + + except Exception as e: + logger.exception(f"Error in MiniAgent execution: {e}") + yield Event(type=EventType.ERROR, content=str(e)) + + def _convert_event(self, event: StreamEvent) -> Event | None: + """Convert a MiniAgent StreamEvent to a Flow Event. + + Args: + event: StreamEvent from MiniAgent + + Returns: + Flow Event or None if no conversion needed + """ + match event.type: + case StreamEventType.AGENT_START: + # Could emit a thinking event + return None + + case StreamEventType.MODEL_START: + return Event( + type=EventType.THINKING, + content=f"Iteration {event.data.get('iteration', 0) + 1}", + ) + + case StreamEventType.MODEL_END: + # Token usage tracked via OTEL, no event needed + return None + + case StreamEventType.TOOL_START: + return Event( + type=EventType.TOOL_CALL_START, + tool_name=str(event.data.get("tool_name", "")), + ) + + case StreamEventType.TOOL_END: + return Event( + type=EventType.TOOL_RESULT, + content=str(event.data.get("tool_output", ""))[:1000], # Truncate + tool_name=str(event.data.get("tool_name", "")), + ) + + case StreamEventType.TEXT: + content = event.data.get("content", "") + if content: + return Event(type=EventType.TEXT_DELTA, content=str(content)) + return None + + case StreamEventType.AGENT_END: + # Don't include content - it was already streamed via TEXT events + # TEXT_DONE just signals completion + return Event(type=EventType.TEXT_DONE, content="") + + case _: + return None + + def get_thread_id(self) -> str: + """Get the current thread ID. + + Returns: + The current conversation thread ID + """ + if self._thread_id is None: + self._thread_id = str(uuid.uuid4()) + return self._thread_id + + async def close(self) -> None: + """Clean up resources used by the harness.""" + self._thread = None + self._thread_id = None diff --git a/src/flow/harness/miniagent/hooks.py b/src/flow/harness/miniagent/hooks.py new file mode 100644 index 0000000000000000000000000000000000000000..d8bb30f443f7f6e8332eb5bd6fd0a002c8b96b8c --- /dev/null +++ b/src/flow/harness/miniagent/hooks.py @@ -0,0 +1,209 @@ +"""Hook types and event definitions for MiniAgent. + +Inspired by Claude Agent SDK's hooks system. Hooks allow applications to: +- Observe: Monitor what's happening (logging, metrics) +- Modify: Change inputs, inject context +- Control: Block tool calls, stop execution +""" + +from dataclasses import dataclass, field +from typing import Any, Callable, Awaitable, Literal +from enum import Enum + + +class HookEvent(str, Enum): + """All supported hook events.""" + + PRE_TOOL_USE = "pre_tool_use" + POST_TOOL_USE = "post_tool_use" + PRE_MODEL_CALL = "pre_model_call" + POST_MODEL_CALL = "post_model_call" + PRE_COMPACT = "pre_compact" + POST_COMPACT = "post_compact" + AGENT_START = "agent_start" + AGENT_END = "agent_end" + + +# === Event Data Classes === + + +@dataclass +class PreToolUseEvent: + """Fired before a tool is executed. + + Hooks can inspect and optionally block or modify the tool call. + """ + + tool_name: str + tool_input: dict[str, Any] + tool_call_id: str + iteration: int + + +@dataclass +class PreToolUseResult: + """Result from PreToolUse hook. + + Controls whether the tool call proceeds. + """ + + decision: Literal["allow", "block", "modify"] = "allow" + reason: str | None = None # Shown to model if blocked + modified_input: dict[str, Any] | None = None # If decision="modify" + + +@dataclass +class PostToolUseEvent: + """Fired after a tool executes. + + Hooks can inject additional context or stop execution. + """ + + tool_name: str + tool_input: dict[str, Any] + tool_output: str + tool_call_id: str + iteration: int + error: str | None = None + + +@dataclass +class PostToolUseResult: + """Result from PostToolUse hook.""" + + additional_context: str | None = None # Injected into next message + stop_execution: bool = False + stop_reason: str | None = None + + +@dataclass +class PreModelCallEvent: + """Fired before an LLM call. + + Useful for logging, metrics, or inspecting the context. + """ + + message_count: int + iteration: int + estimated_tokens: int | None = None + + +@dataclass +class PostModelCallEvent: + """Fired after an LLM call. + + Contains usage information and the raw response. + """ + + usage: dict[str, int] + iteration: int + has_tool_calls: bool + finish_reason: str | None = None + response_text: str = "" # The model's text response (non-tool content) + + +@dataclass +class PreCompactEvent: + """Fired before context compaction. + + Allows monitoring when compaction is triggered. + """ + + message_count: int + current_tokens: int + budget: int + trigger: Literal["auto", "manual"] + + +@dataclass +class PostCompactEvent: + """Fired after context compaction. + + Reports how much was compacted. + """ + + messages_before: int + messages_after: int + tokens_before: int + tokens_after: int + + +@dataclass +class AgentStartEvent: + """Fired when agent.run() starts.""" + + user_message: str + thread_message_count: int + + +@dataclass +class AgentEndEvent: + """Fired when agent.run() completes.""" + + final_response: str | None + total_iterations: int + total_input_tokens: int + total_output_tokens: int + tool_calls_made: int + + +# === Hook Type Aliases === + +PreToolUseHook = Callable[[PreToolUseEvent], Awaitable[PreToolUseResult | None]] +PostToolUseHook = Callable[[PostToolUseEvent], Awaitable[PostToolUseResult | None]] +PreModelCallHook = Callable[[PreModelCallEvent], Awaitable[None]] +PostModelCallHook = Callable[[PostModelCallEvent], Awaitable[None]] +PreCompactHook = Callable[[PreCompactEvent], Awaitable[None]] +PostCompactHook = Callable[[PostCompactEvent], Awaitable[None]] +AgentStartHook = Callable[[AgentStartEvent], Awaitable[None]] +AgentEndHook = Callable[[AgentEndEvent], Awaitable[None]] + + +def _pre_tool_use_factory() -> "list[PreToolUseHook]": + return [] + +def _post_tool_use_factory() -> "list[PostToolUseHook]": + return [] + +def _pre_model_call_factory() -> "list[PreModelCallHook]": + return [] + +def _post_model_call_factory() -> "list[PostModelCallHook]": + return [] + +def _pre_compact_factory() -> "list[PreCompactHook]": + return [] + +def _post_compact_factory() -> "list[PostCompactHook]": + return [] + +def _agent_start_factory() -> "list[AgentStartHook]": + return [] + +def _agent_end_factory() -> "list[AgentEndHook]": + return [] + + +@dataclass +class Hooks: + """Hook configuration for ChatAgent. + + All hook lists are optional. Multiple hooks can be registered + for each event - they are called in order. + + Example: + async def log_tokens(event: PostModelCallEvent) -> None: + print(f"Used {event.usage['input_tokens']} input tokens") + + hooks = Hooks(post_model_call=[log_tokens]) + agent = ChatAgent(hooks=hooks) + """ + + pre_tool_use: "list[PreToolUseHook]" = field(default_factory=_pre_tool_use_factory) + post_tool_use: "list[PostToolUseHook]" = field(default_factory=_post_tool_use_factory) + pre_model_call: "list[PreModelCallHook]" = field(default_factory=_pre_model_call_factory) + post_model_call: "list[PostModelCallHook]" = field(default_factory=_post_model_call_factory) + pre_compact: "list[PreCompactHook]" = field(default_factory=_pre_compact_factory) + post_compact: "list[PostCompactHook]" = field(default_factory=_post_compact_factory) + agent_start: "list[AgentStartHook]" = field(default_factory=_agent_start_factory) + agent_end: "list[AgentEndHook]" = field(default_factory=_agent_end_factory) diff --git a/src/flow/harness/miniagent/instructions.py b/src/flow/harness/miniagent/instructions.py new file mode 100644 index 0000000000000000000000000000000000000000..c30021187007f6bcfb8544acf50e5df6ec4730c1 --- /dev/null +++ b/src/flow/harness/miniagent/instructions.py @@ -0,0 +1,207 @@ +"""System instructions for MiniAgent. + +Provides well-crafted system prompts that guide the agent on: +- How to use each tool effectively +- When to use todo_write for task tracking +- When to delegate with the task tool +- Best practices for coding tasks + +These instructions are informed by Claude Code's system prompt design. +""" + +# ============================================================================= +# Shared instruction blocks - reused across presets +# ============================================================================= + +TASK_COMPLETION_INSTRUCTIONS = """ +## CRITICAL: Task Completion + +You MUST complete tasks fully and autonomously. + +### Execution Rules +- DO NOT ask "Should I proceed?" or "Would you like me to continue?" +- DO NOT propose alternatives or phased approaches when the task is clear +- DO NOT decline tasks due to scope concerns - break them into steps instead +- DO NOT stop halfway - keep iterating until the task is DONE +- If something fails, try alternatives automatically + +### Use Todos for Multi-Step Tasks +For any task with 3+ steps: +1. Create a todo list with `todo_write` at the START +2. Mark tasks `in_progress` as you work on them (only ONE at a time) +3. Mark tasks `completed` IMMEDIATELY when done +4. Keep working until ALL todos are checked off +5. Do NOT finish until the todo list shows everything complete + +### Iteration Until Success +- If a step fails, diagnose and retry with a different approach +- If you hit a blocker, note it and move to the next step +- Circle back to unfinished items +- The task is NOT done until all requirements are met +""" + +EFFICIENCY_INSTRUCTIONS = """ +## Efficiency Guidelines + +### Batch Tool Calls +Call ALL independent tools in a SINGLE response: +- Read 5 files? Call read_file 5 times in one response. +- Search multiple patterns? Call grep multiple times in one response. +- List directories and read files? Call both in one response. + +### Read Files Fully +Read ENTIRE files (default limit 2000 lines). Do NOT chunk files into small pieces (40-60 lines) - this wastes API calls and context. + +### Search Then Batch Read +1. Use glob_files or grep to find relevant files +2. Read ALL matching files in a single response +""" + +BEST_PRACTICES_INSTRUCTIONS = """ +## Best Practices + +### Before Editing +NEVER edit a file you haven't read. Always use `read_file` first. + +### Follow Existing Patterns +Before writing new code, examine neighboring files to understand: +- Naming conventions +- Import style +- Error handling patterns +- Framework usage + +### Don't Over-Engineer +- Solve the current problem, not hypothetical future ones +- Prefer editing existing files over creating new ones +- NEVER proactively create documentation files unless explicitly requested +- Don't add features beyond what was asked + +### Verify Dependencies +Never assume libraries exist. Check package.json, requirements.txt, or equivalent before importing. + +### Security +- Refuse to write code that could be used maliciously +- Never expose secrets, API keys, or credentials in code +- If files seem related to malware, refuse to help +""" + +# ============================================================================= +# Preset-specific instructions +# ============================================================================= + +CODING_AGENT_INSTRUCTIONS = f"""You are an expert coding assistant. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. + +## Response Style + +- Be concise and direct in explanations, but thorough in execution. +- Use GitHub-flavored markdown for formatting. +- When referencing code, use the pattern `file_path:line_number` (e.g., `src/utils.py:42`). +- Don't add unnecessary preamble or postamble. Get to work. +- Only use emojis if explicitly requested. +{TASK_COMPLETION_INSTRUCTIONS} +## Tool Usage + +### File Operations +- **read_file**: Read file contents with line numbers. Always read before editing. +- **write_file**: Create new files or completely replace file contents. +- **edit_file**: Make targeted edits by replacing specific text (must be unique in file). +- **multi_edit**: Make multiple edits to a file atomically (all succeed or all fail). +- **glob_files**: Find files by pattern (e.g., `**/*.py`, `src/**/*.ts`). +- **grep**: Search file contents with regex. Returns matching lines with context. +- **ls**: List directory contents. + +### Execution +- **bash**: Execute shell commands. Use for git, running tests, installing packages. + +### Planning +- **think**: Reason through complex problems before acting. +- **todo_write**: Track progress on multi-step tasks. USE THIS FREQUENTLY. +- **todo_read**: Check current task status. + +### Delegation (if available) +- **task**: Delegate complex sub-tasks to a specialist agent with isolated context. +{EFFICIENCY_INSTRUCTIONS} +{BEST_PRACTICES_INSTRUCTIONS} +""" + +RESEARCH_AGENT_INSTRUCTIONS = f"""You are a research assistant. You help users find information, synthesize knowledge, and answer questions. + +## Response Style + +- Be thorough in research, concise in presentation. +- Cite sources with URLs when reporting findings. +- Synthesize information - don't just list results. +{TASK_COMPLETION_INSTRUCTIONS} +## Tools + +### Search & Fetch +- **web_search**: Search the web for information. +- **web_fetch**: Fetch and read web page contents. + +### Planning +- **think**: Work through complex questions step by step. +- **todo_write**: Track research progress on multi-part questions. + +## Research Strategy + +1. Start with broad searches to identify relevant sources +2. Fetch multiple promising URLs in parallel (batch web_fetch calls) +3. Synthesize findings into a coherent answer +4. If initial searches don't answer the question, refine and search again + +## Guidelines + +1. **Be thorough**: Search multiple queries if needed - batch them. +2. **Cite sources**: Include URLs when reporting findings. +3. **Synthesize**: Draw conclusions, don't just list results. +4. **Keep going**: If first searches don't work, try different queries. +5. **Acknowledge uncertainty**: If information is unclear, say so. +""" + +EXPLORE_AGENT_INSTRUCTIONS = f"""You are a codebase exploration specialist. Your job is to quickly find and understand code. + +## Response Style + +- Be concise. Your response goes to another agent, so be self-contained. +- Include file paths and line numbers in findings. +- Summarize what you found, don't dump raw content. +{TASK_COMPLETION_INSTRUCTIONS} +## Tools + +- **read_file**: Read file contents (read fully, don't chunk). +- **glob_files**: Find files by pattern. +- **grep**: Search file contents with regex. +- **ls**: List directory contents. +- **think**: Reason about what you're finding. +- **todo_write**: Track exploration progress for complex searches. +{EFFICIENCY_INSTRUCTIONS} +## Guidelines + +1. **Start broad, then narrow**: Use glob/grep to find candidates, then batch-read. +2. **Be efficient**: Don't read files you don't need. +3. **Report clearly**: Include file paths and line numbers. +4. **Keep searching**: If first attempt doesn't find what's needed, try different patterns. +5. **Summarize**: Be self-contained for the calling agent. +""" + +# ============================================================================= +# Instruction presets registry +# ============================================================================= + +INSTRUCTIONS = { + "coding": CODING_AGENT_INSTRUCTIONS, + "research": RESEARCH_AGENT_INSTRUCTIONS, + "explore": EXPLORE_AGENT_INSTRUCTIONS, +} + + +def get_instructions(preset: str = "coding") -> str: + """Get system instructions by preset name. + + Args: + preset: One of 'coding', 'research', 'explore' + + Returns: + System instruction string + """ + return INSTRUCTIONS.get(preset, CODING_AGENT_INSTRUCTIONS) diff --git a/src/flow/harness/miniagent/messages.py b/src/flow/harness/miniagent/messages.py new file mode 100644 index 0000000000000000000000000000000000000000..46d543cd19cac41699f9f417b0efc0d75cedffbb --- /dev/null +++ b/src/flow/harness/miniagent/messages.py @@ -0,0 +1,88 @@ +"""Message types for MiniAgent. + +Defines the core message structures used in the agent loop. +""" + +from dataclasses import dataclass +from typing import Any, Literal + +Role = Literal["system", "user", "assistant", "tool"] + + +@dataclass +class ToolCall: + """A tool call request from the model.""" + + id: str + name: str + arguments: str # JSON string + + +@dataclass +class ToolResult: + """Result of executing a tool.""" + + call_id: str + result: str + error: str | None = None + + +@dataclass +class ChatMessage: + """A message in the conversation. + + Supports all OpenAI message roles and tool calling. + """ + + role: Role + content: str | None = None + tool_calls: list[ToolCall] | None = None + tool_call_id: str | None = None # For tool role messages + name: str | None = None # Optional name for the message author + + def to_openai_format(self) -> dict[str, Any]: + """Convert to OpenAI API format.""" + msg: dict[str, Any] = {"role": self.role} + + if self.content is not None: + msg["content"] = self.content + + if self.tool_calls: + msg["tool_calls"] = [ + { + "id": tc.id, + "type": "function", + "function": {"name": tc.name, "arguments": tc.arguments}, + } + for tc in self.tool_calls + ] + + if self.tool_call_id: + msg["tool_call_id"] = self.tool_call_id + + if self.name: + msg["name"] = self.name + + return msg + + @classmethod + def system(cls, content: str) -> "ChatMessage": + """Create a system message.""" + return cls(role="system", content=content) + + @classmethod + def user(cls, content: str) -> "ChatMessage": + """Create a user message.""" + return cls(role="user", content=content) + + @classmethod + def assistant( + cls, content: str | None = None, tool_calls: list[ToolCall] | None = None + ) -> "ChatMessage": + """Create an assistant message.""" + return cls(role="assistant", content=content, tool_calls=tool_calls) + + @classmethod + def tool(cls, call_id: str, content: str) -> "ChatMessage": + """Create a tool result message.""" + return cls(role="tool", content=content, tool_call_id=call_id) diff --git a/src/flow/harness/miniagent/otel.py b/src/flow/harness/miniagent/otel.py new file mode 100644 index 0000000000000000000000000000000000000000..bf2a3deb7cf4b66ca646eb2012a362d798ba7a6f --- /dev/null +++ b/src/flow/harness/miniagent/otel.py @@ -0,0 +1,258 @@ +"""OpenTelemetry instrumentation for MiniAgent. + +This module provides OTEL span emission that conforms to GenAI semantic +conventions, enabling Flow's metrics extraction pipeline to work with +MiniAgent traces. + +Reference: https://opentelemetry.io/docs/specs/semconv/gen-ai/ +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from opentelemetry import trace + +if TYPE_CHECKING: + from .hooks import ( + Hooks, + PreModelCallEvent, + PostModelCallEvent, + PreToolUseEvent, + PreToolUseResult, + PostToolUseEvent, + PostToolUseResult, + ) + +__all__ = ["GenAIAttr", "create_otel_hooks", "enable_instrumentation"] + +# Track if instrumentation has been enabled +_instrumentation_enabled = False + + +class GenAIAttr: + """OpenTelemetry GenAI semantic convention attributes. + + These match the attributes used by MAF/LangGraph harnesses for consistency. + Reference: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/ + """ + + # Operation + OPERATION_NAME = "gen_ai.operation.name" + PROVIDER_NAME = "gen_ai.provider.name" + + # Model + REQUEST_MODEL = "gen_ai.request.model" + RESPONSE_MODEL = "gen_ai.response.model" + + # Tokens + INPUT_TOKENS = "gen_ai.usage.input_tokens" + OUTPUT_TOKENS = "gen_ai.usage.output_tokens" + + # Tool + TOOL_NAME = "gen_ai.tool.name" + TOOL_TYPE = "gen_ai.tool.type" + TOOL_CALL_ID = "gen_ai.tool.call.id" + + # Error + ERROR_TYPE = "error.type" + + +def _get_tracer() -> trace.Tracer: + """Get tracer lazily to ensure it uses the current TracerProvider. + + This is important because the TracerProvider may be set up after + this module is imported (e.g., by Flow's experiment runner). + """ + return trace.get_tracer("flow.miniagent", "0.1.0") + + +def start_llm_span(model: str) -> trace.Span: + """Start a span for an LLM call. + + Args: + model: The model name being called + + Returns: + An active span for the LLM call + """ + span = _get_tracer().start_span(f"chat {model}", kind=trace.SpanKind.CLIENT) + span.set_attribute(GenAIAttr.OPERATION_NAME, "chat") + span.set_attribute(GenAIAttr.REQUEST_MODEL, model) + span.set_attribute(GenAIAttr.PROVIDER_NAME, "openai") + return span + + +def end_llm_span(span: trace.Span, input_tokens: int, output_tokens: int) -> None: + """End an LLM span with token usage. + + Args: + span: The span to end + input_tokens: Number of input tokens used + output_tokens: Number of output tokens generated + """ + span.set_attribute(GenAIAttr.INPUT_TOKENS, input_tokens) + span.set_attribute(GenAIAttr.OUTPUT_TOKENS, output_tokens) + span.end() + + +def start_tool_span(tool_name: str, call_id: str = "") -> trace.Span: + """Start a span for a tool call. + + Args: + tool_name: Name of the tool being called + call_id: Optional tool call ID + + Returns: + An active span for the tool call + """ + span = _get_tracer().start_span(f"execute_tool {tool_name}", kind=trace.SpanKind.INTERNAL) + span.set_attribute(GenAIAttr.OPERATION_NAME, "execute_tool") + span.set_attribute(GenAIAttr.TOOL_NAME, tool_name) + span.set_attribute(GenAIAttr.TOOL_TYPE, "function") + if call_id: + span.set_attribute(GenAIAttr.TOOL_CALL_ID, call_id) + return span + + +def end_tool_span(span: trace.Span, error: Exception | None = None) -> None: + """End a tool span, optionally recording an error. + + Args: + span: The span to end + error: Optional exception if the tool failed + """ + if error: + span.set_attribute(GenAIAttr.ERROR_TYPE, type(error).__name__) + span.record_exception(error) + span.set_status(trace.StatusCode.ERROR, str(error)) + span.end() + + +class OTelHooks: + """Hook handlers that emit OTEL spans. + + This class provides hook callbacks that instrument MiniAgent's + execution with OpenTelemetry spans, enabling trace collection + for Flow's evaluation pipeline. + + Usage: + otel = OTelHooks(model="gpt-4o") + hooks = Hooks( + pre_model_call=[otel.on_pre_model_call], + post_model_call=[otel.on_post_model_call], + pre_tool_use=[otel.on_pre_tool_use], + post_tool_use=[otel.on_post_tool_use], + ) + agent = ChatAgent(..., hooks=hooks) + """ + + def __init__(self, model: str = "gpt-4o"): + """Initialize OTEL hooks. + + Args: + model: Default model name for spans + """ + self.model = model + self._llm_spans: dict[int, trace.Span] = {} # iteration -> span + self._tool_spans: dict[str, trace.Span] = {} # call_id -> span + + async def on_pre_model_call(self, event: "PreModelCallEvent") -> None: + """Start an LLM span before model call. + + Args: + event: Pre-model call event with iteration info + """ + span = start_llm_span(model=self.model) + self._llm_spans[event.iteration] = span + + async def on_post_model_call(self, event: "PostModelCallEvent") -> None: + """End the LLM span after model call. + + Args: + event: Post-model call event with usage info + """ + span = self._llm_spans.pop(event.iteration, None) + if span: + input_tokens = event.usage.get("input_tokens", 0) + output_tokens = event.usage.get("output_tokens", 0) + end_llm_span(span, input_tokens, output_tokens) + + async def on_pre_tool_use(self, event: "PreToolUseEvent") -> "PreToolUseResult | None": + """Start a tool span before tool execution. + + Args: + event: Pre-tool use event with tool info + + Returns: + None (don't block or modify) + """ + span = start_tool_span(event.tool_name, event.tool_call_id) + self._tool_spans[event.tool_call_id] = span + return None # Don't block + + async def on_post_tool_use(self, event: "PostToolUseEvent") -> "PostToolUseResult | None": + """End the tool span after tool execution. + + Args: + event: Post-tool use event with result info + + Returns: + None (don't inject context or stop) + """ + span = self._tool_spans.pop(event.tool_call_id, None) + if span: + error = Exception(event.error) if event.error else None + end_tool_span(span, error) + return None + + +def enable_instrumentation() -> None: + """Enable OpenTelemetry instrumentation for MiniAgent. + + Call this once before running agents to enable trace collection. + This is the MiniAgent equivalent of agent_framework.observability.enable_instrumentation(). + + Note: This function is idempotent - calling it multiple times is safe. + + Example: + from flow.harness.miniagent.otel import enable_instrumentation + enable_instrumentation() + # Now traces will be collected when agents run + """ + global _instrumentation_enabled + if _instrumentation_enabled: + return + + # MiniAgent instrumentation is hook-based, so this is mainly a marker + # that indicates the system is ready for trace collection. + # The actual spans are created via OTelHooks attached to agents. + _instrumentation_enabled = True + + +def create_otel_hooks(model: str = "gpt-4o") -> "Hooks": + """Create a Hooks instance with OTEL instrumentation. + + This is the main entry point for adding OTEL tracing to a MiniAgent. + The returned Hooks object can be passed directly to ChatAgent. + + Args: + model: Model name to use in LLM spans + + Returns: + Hooks instance configured for OTEL tracing + + Example: + hooks = create_otel_hooks(model="gpt-4o") + agent = ChatAgent(..., hooks=hooks) + """ + from .hooks import Hooks + + otel = OTelHooks(model=model) + + return Hooks( + pre_model_call=[otel.on_pre_model_call], + post_model_call=[otel.on_post_model_call], + pre_tool_use=[otel.on_pre_tool_use], + post_tool_use=[otel.on_post_tool_use], + ) diff --git a/src/flow/harness/miniagent/tool.py b/src/flow/harness/miniagent/tool.py new file mode 100644 index 0000000000000000000000000000000000000000..88cbdbaf5b4edaf09d240b2cadcca60fa4fd3cf0 --- /dev/null +++ b/src/flow/harness/miniagent/tool.py @@ -0,0 +1,173 @@ +"""Tool definition and @tool decorator for MiniAgent. + +Provides a simple way to define tools that can be called by the LLM. +""" + +from dataclasses import dataclass +from typing import Any, Callable, Literal, get_type_hints, get_origin, get_args, Annotated +import inspect + + +@dataclass +class Tool: + """A tool that can be called by the LLM. + + Tools are functions with metadata that allows the LLM to understand + how to call them. + """ + + name: str + description: str + parameters: dict[str, Any] # JSON Schema + func: Callable[..., Any] + + def to_openai_tool(self) -> dict[str, Any]: + """Convert to OpenAI tool format.""" + return { + "type": "function", + "function": { + "name": self.name, + "description": self.description, + "parameters": self.parameters, + }, + } + + async def invoke(self, **kwargs: Any) -> str: + """Execute the tool and return result as string. + + Handles both sync and async functions. + """ + try: + result = self.func(**kwargs) + if inspect.iscoroutine(result): + result = await result + return str(result) if not isinstance(result, str) else result + except Exception as e: + return f"Error executing {self.name}: {str(e)}" + + +def _python_type_to_json_schema(py_type: Any) -> dict[str, Any]: + """Convert a Python type hint to JSON Schema.""" + # Handle None/NoneType + if py_type is None or py_type is type(None): + return {"type": "null"} + + # Handle basic types + if py_type == str: + return {"type": "string"} + if py_type == int: + return {"type": "integer"} + if py_type == float: + return {"type": "number"} + if py_type == bool: + return {"type": "boolean"} + + # Handle dict without type args + if py_type is dict: + return {"type": "object"} + + # Handle Optional (Union with None) + origin = get_origin(py_type) + args = get_args(py_type) + + if origin is list: + if args: + return {"type": "array", "items": _python_type_to_json_schema(args[0])} + return {"type": "array"} + + if origin is dict: + return {"type": "object"} + + # Handle Union types (including Optional) + # In Python 3.10+, Optional[X] is Union[X, None] + if origin is type(int | str): # Union type + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1: + # This is Optional[X] + return _python_type_to_json_schema(non_none_args[0]) + # Multiple types - use anyOf + return {"anyOf": [_python_type_to_json_schema(a) for a in non_none_args]} + + # Handle Literal + if origin is Literal: + return {"type": "string", "enum": list(args)} + + # Default to string + return {"type": "string"} + + +def tool(func: Callable[..., Any]) -> Tool: + """Decorator to convert a function into a Tool. + + Uses type hints and Annotated[] for parameter descriptions. + The function's docstring becomes the tool description. + + Usage: + @tool + def search(query: Annotated[str, "The search query"]) -> str: + '''Search the web for information.''' + return f"Results for: {query}" + """ + # Get function signature + sig = inspect.signature(func) + + # Get type hints (with extras for Annotated) + try: + hints = get_type_hints(func, include_extras=True) + except Exception: + hints = {} + + # Build JSON Schema for parameters + properties: dict[str, Any] = {} + required: list[str] = [] + + for param_name, param in sig.parameters.items(): + if param_name in ("self", "cls"): + continue + + # Get the type hint + hint = hints.get(param_name, str) + description = "" + + # Check if it's Annotated + if get_origin(hint) is Annotated: + args = get_args(hint) + actual_type = args[0] + # Look for string descriptions in the annotations + for annotation in args[1:]: + if isinstance(annotation, str): + description = annotation + break + else: + actual_type = hint + + # Convert to JSON Schema + prop_schema = _python_type_to_json_schema(actual_type) + if description: + prop_schema["description"] = description + + properties[param_name] = prop_schema + + # Check if required (no default value) + if param.default is inspect.Parameter.empty: + required.append(param_name) + + # Build the full schema + parameters_schema: dict[str, Any] = { + "type": "object", + "properties": properties, + } + if required: + parameters_schema["required"] = required + + # Get description from docstring + description = func.__doc__ or f"Call the {func.__name__} function" + # Clean up the docstring - take first line/paragraph + description = description.strip().split("\n\n")[0].strip() + + return Tool( + name=func.__name__, + description=description, + parameters=parameters_schema, + func=func, + ) diff --git a/src/flow/harness/miniagent/tools/__init__.py b/src/flow/harness/miniagent/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..23f64b772855891094b972d9883501f635e77d38 --- /dev/null +++ b/src/flow/harness/miniagent/tools/__init__.py @@ -0,0 +1,125 @@ +"""Built-in tool library for MiniAgent. + +This module re-exports tools from the shared flow.tools module +for backward compatibility with existing MiniAgent code. + +All tools are now implemented in flow.tools and shared across +all harnesses (MiniAgent, MAF, etc.). + +Tool Categories: +- File Operations: read_file, write_file, edit_file, multi_edit, glob_files, grep, ls +- Notebooks: notebook_edit, notebook_read +- Execution: bash +- Planning: think, todo_write, todo_read +- Memory: memory (agentic memory for persistence) +- Web: web_search, web_fetch +- Sub-agents: task (for context isolation) + +Presets: +- coding_tools(): Core tools for coding tasks +- research_tools(): Tools for research and exploration +- all_tools(): Everything + +Example: + from flow.harness.miniagent.tools import coding_tools, task + + agent = ChatAgent( + instructions="You are a helpful coding assistant.", + tools=coding_tools() + [task], + ) +""" + +# Re-export everything from shared tools +from flow.tools import ( + # Base + Tool, + # File operations + read_file, + write_file, + edit_file, + multi_edit, + glob_files, + grep, + ls, + # Notebook operations + notebook_edit, + notebook_read, + # Execution + bash, + check_processes, + python_repl, + # Planning and reasoning + think, + todo_write, + todo_read, + # Web operations + web_search, + web_fetch, + # Memory + memory, + create_memory_tool, + # Skills + skills, + create_skills_tool, + # Sub-agent + task, + create_task_tool, + # Presets + coding_tools, + planning_tools, + web_tools as research_tools, + notebook_tools, + all_tools, +) + +# Compatibility: reset_todos from planning module +from flow.tools.planning import reset_todos, get_todos + +# Compatibility: reset_memory from memory module +from flow.tools.memory import reset_memory + + +__all__ = [ + # Base + "Tool", + # Presets + "coding_tools", + "research_tools", + "notebook_tools", + "planning_tools", + "all_tools", + # File operations + "read_file", + "write_file", + "edit_file", + "multi_edit", + "glob_files", + "grep", + "ls", + # Notebook operations + "notebook_edit", + "notebook_read", + # Execution + "bash", + "check_processes", + "python_repl", + # Planning + "think", + "todo_write", + "todo_read", + "reset_todos", + "get_todos", + # Memory + "memory", + "create_memory_tool", + "reset_memory", + # Web + "web_search", + "web_fetch", + # Sub-agent + "task", + "create_task_tool", + # Skills + "skills", + "create_skills_tool", +] diff --git a/src/flow/harness/miniagent/workspace.py b/src/flow/harness/miniagent/workspace.py new file mode 100644 index 0000000000000000000000000000000000000000..99371438207247430227b5e3bd04ed8403a192c5 --- /dev/null +++ b/src/flow/harness/miniagent/workspace.py @@ -0,0 +1,198 @@ +"""Workspace management for MiniAgent. + +Provides a simple convention for where agent-managed data lives: +- Working directory is the workspace (or explicitly set) +- Agent data goes in `{workspace}/.miniagent/` +- No restrictions on file access - agent can read/write anywhere + +Structure: + {workspace}/ + ├── .miniagent/ + │ ├── todos.json # Persisted task list + │ ├── memory/ # Memory entries + │ │ ├── {id}.json + │ │ └── ... + │ └── config.json # Optional agent config + └── ... (rest of project) + +Usage: + from miniagent.workspace import Workspace + + # Use current directory + ws = Workspace() + + # Or explicit path + ws = Workspace("/path/to/project") + + # Get paths + ws.root # /path/to/project + ws.data_dir # /path/to/project/.miniagent + ws.todos_file # /path/to/project/.miniagent/todos.json + ws.memory_dir # /path/to/project/.miniagent/memory +""" + +import json +from pathlib import Path +from typing import Any + + +class Workspace: + """Manages workspace paths and agent data storage. + + The workspace is where the agent operates. Agent-managed data + (todos, memories, etc.) is stored in a `.miniagent/` subdirectory. + """ + + def __init__(self, root: str | Path | None = None): + """Initialize workspace. + + Args: + root: Workspace root directory. Defaults to current working directory. + """ + if root is None: + root = Path.cwd() + self._root = Path(root).resolve() + + @property + def root(self) -> Path: + """Workspace root directory.""" + return self._root + + @property + def data_dir(self) -> Path: + """Agent data directory (.miniagent/).""" + return self._root / ".miniagent" + + @property + def todos_file(self) -> Path: + """Path to todos.json.""" + return self.data_dir / "todos.json" + + @property + def memory_dir(self) -> Path: + """Path to memory/ directory.""" + return self.data_dir / "memory" + + @property + def config_file(self) -> Path: + """Path to config.json.""" + return self.data_dir / "config.json" + + def ensure_data_dir(self) -> Path: + """Create data directory if it doesn't exist.""" + self.data_dir.mkdir(parents=True, exist_ok=True) + return self.data_dir + + def ensure_memory_dir(self) -> Path: + """Create memory directory if it doesn't exist.""" + self.memory_dir.mkdir(parents=True, exist_ok=True) + return self.memory_dir + + # --- Todos --- + + def load_todos(self) -> list[dict[str, Any]]: + """Load todos from workspace.""" + if not self.todos_file.exists(): + return [] + try: + with open(self.todos_file) as f: + return json.load(f) # type: ignore[no-any-return] + except (json.JSONDecodeError, IOError): + return [] + + def save_todos(self, todos: list[dict[str, Any]]) -> None: + """Save todos to workspace.""" + self.ensure_data_dir() + with open(self.todos_file, "w") as f: + json.dump(todos, f, indent=2) + + # --- Memory --- + + def list_memories(self) -> list[dict[str, Any]]: + """List all memory entries.""" + if not self.memory_dir.exists(): + return [] + + memories: list[dict[str, Any]] = [] + for filepath in self.memory_dir.glob("*.json"): + try: + with open(filepath) as f: + memories.append(json.load(f)) + except (json.JSONDecodeError, IOError): + continue + return memories + + def load_memory(self, memory_id: str) -> dict[str, Any] | None: + """Load a specific memory entry.""" + filepath = self.memory_dir / f"{memory_id}.json" + if not filepath.exists(): + return None + try: + with open(filepath) as f: + return json.load(f) # type: ignore[no-any-return] + except (json.JSONDecodeError, IOError): + return None + + def save_memory(self, memory_id: str, data: dict[str, Any]) -> None: + """Save a memory entry.""" + self.ensure_memory_dir() + filepath = self.memory_dir / f"{memory_id}.json" + with open(filepath, "w") as f: + json.dump(data, f, indent=2, default=str) + + def delete_memory(self, memory_id: str) -> bool: + """Delete a memory entry. Returns True if deleted.""" + filepath = self.memory_dir / f"{memory_id}.json" + if filepath.exists(): + filepath.unlink() + return True + return False + + # --- Config --- + + def load_config(self) -> dict[str, Any]: + """Load workspace config.""" + if not self.config_file.exists(): + return {} + try: + with open(self.config_file) as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return {} + + def save_config(self, config: dict[str, Any]) -> None: + """Save workspace config.""" + self.ensure_data_dir() + with open(self.config_file, "w") as f: + json.dump(config, f, indent=2) + + def __repr__(self) -> str: + return f"Workspace({self._root})" + + +# Default workspace (current directory) +_default_workspace: Workspace | None = None + + +def get_workspace() -> Workspace: + """Get the default workspace (creates if needed).""" + global _default_workspace + if _default_workspace is None: + _default_workspace = Workspace() + return _default_workspace + + +def set_workspace(workspace: Workspace | str | Path) -> Workspace: + """Set the default workspace.""" + global _default_workspace + if isinstance(workspace, Workspace): + _default_workspace = workspace + else: + _default_workspace = Workspace(workspace) + return _default_workspace + + +def reset_workspace() -> None: + """Reset default workspace (for testing).""" + global _default_workspace + _default_workspace = None diff --git a/src/flow/harness/registry.py b/src/flow/harness/registry.py new file mode 100644 index 0000000000000000000000000000000000000000..09c526c6bbaa60b35e9034342c6a1b5463a34c7a --- /dev/null +++ b/src/flow/harness/registry.py @@ -0,0 +1,80 @@ +"""Harness registry for multi-framework support. + +This module provides a simple registry pattern for harness implementations, +allowing Flow to support multiple agent frameworks (MAF, LangGraph, Claude SDK). +""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from flow.experiments.models import Agent + from flow.harness.base import BaseHarness + from flow.llm import LLMClientConfig + +_HARNESSES: dict[str, type["BaseHarness"]] = {} + + +def register(name: str, harness_class: type["BaseHarness"]) -> None: + """Register a harness class for a framework. + + Args: + name: Framework name (e.g., "maf", "langgraph", "claude") + harness_class: The harness class to register + """ + _HARNESSES[name] = harness_class + + +def get_harness_class(name: str) -> type["BaseHarness"]: + """Get harness class by framework name. + + Args: + name: Framework name + + Returns: + The harness class + + Raises: + ValueError: If framework is not registered + """ + if name not in _HARNESSES: + available = list(_HARNESSES.keys()) + raise ValueError(f"Unknown framework: {name}. Available: {available}") + return _HARNESSES[name] + + +def create_harness( + agent: "Agent", + workspace: Path, + llm_config: "LLMClientConfig | None" = None, +) -> "BaseHarness": + """Create a harness from an Agent spec. + + This is the main entry point for creating harnesses. It looks up + the appropriate harness class based on agent.framework and calls + its from_agent() classmethod. + + Args: + agent: The Agent spec defining the configuration + workspace: Working directory for the agent + llm_config: Optional LLM configuration for the agent (falls back to env vars) + + Returns: + A configured harness instance + + Raises: + ValueError: If agent.framework is not registered + """ + harness_class = get_harness_class(agent.framework) + return harness_class.from_agent(agent, workspace, llm_config=llm_config) + + +def available_frameworks() -> list[str]: + """Get list of available framework names. + + Returns: + List of registered framework names + """ + return list(_HARNESSES.keys()) diff --git a/src/flow/llm/__init__.py b/src/flow/llm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..caa28e68c85bca350ba661f7f58fa950bb911a36 --- /dev/null +++ b/src/flow/llm/__init__.py @@ -0,0 +1,49 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""LLM client configuration and factory. + +This package provides a unified way to configure and create LLM clients +for different providers and frameworks. + +Example: + from flow.llm import LLMClientConfig, LLMProvider, LLMClientFactory + from flow.llm.config import AzureOpenAIConfig + + # Create config + config = LLMClientConfig( + provider=LLMProvider.AZURE_OPENAI, + name="My Azure GPT-4o", + azure_openai=AzureOpenAIConfig(deployment="gpt-4o"), + ) + + # Create client for MAF + client = LLMClientFactory.create_maf_client(config) + + # Create client for LangGraph + llm = LLMClientFactory.create_langgraph_client(config) +""" + +from .config import ( + AnthropicConfig, + AzureOpenAIConfig, + CustomConfig, + LLMClientConfig, + LLMProvider, + OllamaConfig, + OpenAIConfig, +) +from .factory import LLMClientFactory + +__all__ = [ + # Enums + "LLMProvider", + # Config classes + "LLMClientConfig", + "OpenAIConfig", + "AzureOpenAIConfig", + "AnthropicConfig", + "OllamaConfig", + "CustomConfig", + # Factory + "LLMClientFactory", +] diff --git a/src/flow/llm/config.py b/src/flow/llm/config.py new file mode 100644 index 0000000000000000000000000000000000000000..efeda33d9e9b5ddc664e282a195c91b7f8813818 --- /dev/null +++ b/src/flow/llm/config.py @@ -0,0 +1,227 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""LLM client configuration models. + +This module defines provider-agnostic configuration for LLM clients. +Secrets are stored as environment variable references, not actual values. +""" + +from __future__ import annotations + +import os +from enum import Enum +from typing import Any + +from pydantic import BaseModel, Field, model_validator + + +class LLMProvider(str, Enum): + """Supported LLM providers.""" + + OPENAI = "openai" + AZURE_OPENAI = "azure_openai" + ANTHROPIC = "anthropic" + OLLAMA = "ollama" + CUSTOM = "custom" # OpenAI-compatible endpoints + + +class OpenAIConfig(BaseModel): + """Configuration for OpenAI API.""" + + api_key_env_var: str = Field( + default="OPENAI_API_KEY", + description="Environment variable name containing the API key", + ) + model_id: str = Field( + default="gpt-4o", + description="Model ID to use (e.g., gpt-4o, gpt-4-turbo)", + ) + base_url: str | None = Field( + default=None, + description="Optional base URL for API (for proxies)", + ) + + def get_api_key(self) -> str: + """Get the API key from environment variable.""" + value = os.environ.get(self.api_key_env_var) + if not value: + raise ValueError(f"Environment variable {self.api_key_env_var} is not set") + return value + + +class AzureOpenAIConfig(BaseModel): + """Configuration for Azure OpenAI API.""" + + endpoint_env_var: str = Field( + default="AZURE_OPENAI_ENDPOINT", + description="Environment variable name containing the endpoint URL", + ) + api_key_env_var: str = Field( + default="AZURE_OPENAI_API_KEY", + description="Environment variable name containing the API key", + ) + deployment: str = Field( + description="Azure OpenAI deployment name", + ) + api_version: str = Field( + default="2024-02-15-preview", + description="Azure OpenAI API version", + ) + + def get_endpoint(self) -> str: + """Get the endpoint from environment variable.""" + value = os.environ.get(self.endpoint_env_var) + if not value: + raise ValueError(f"Environment variable {self.endpoint_env_var} is not set") + return value + + def get_api_key(self) -> str: + """Get the API key from environment variable.""" + value = os.environ.get(self.api_key_env_var) + if not value: + raise ValueError(f"Environment variable {self.api_key_env_var} is not set") + return value + + +class AnthropicConfig(BaseModel): + """Configuration for Anthropic API.""" + + api_key_env_var: str = Field( + default="ANTHROPIC_API_KEY", + description="Environment variable name containing the API key", + ) + model_id: str = Field( + default="claude-3-5-sonnet-20241022", + description="Model ID to use", + ) + + def get_api_key(self) -> str: + """Get the API key from environment variable.""" + value = os.environ.get(self.api_key_env_var) + if not value: + raise ValueError(f"Environment variable {self.api_key_env_var} is not set") + return value + + +class OllamaConfig(BaseModel): + """Configuration for Ollama (local models).""" + + host: str = Field( + default="http://localhost:11434", + description="Ollama server URL", + ) + model_id: str = Field( + default="llama3.2", + description="Model ID to use", + ) + + +class CustomConfig(BaseModel): + """Configuration for custom OpenAI-compatible endpoints.""" + + base_url: str = Field( + description="Base URL for the API", + ) + api_key_env_var: str = Field( + default="CUSTOM_API_KEY", + description="Environment variable name containing the API key", + ) + model_id: str = Field( + description="Model ID to use", + ) + + def get_api_key(self) -> str: + """Get the API key from environment variable.""" + value = os.environ.get(self.api_key_env_var) + if not value: + raise ValueError(f"Environment variable {self.api_key_env_var} is not set") + return value + + +class LLMClientConfig(BaseModel): + """Unified LLM client configuration. + + This is a discriminated union based on the provider field. + Only one of the provider-specific configs should be set. + + Example: + # Azure OpenAI + config = LLMClientConfig( + provider=LLMProvider.AZURE_OPENAI, + name="My Azure GPT-4o", + azure_openai=AzureOpenAIConfig(deployment="gpt-4o"), + ) + + # OpenAI + config = LLMClientConfig( + provider=LLMProvider.OPENAI, + name="OpenAI GPT-4o", + openai=OpenAIConfig(model_id="gpt-4o"), + ) + """ + + id: str | None = Field( + default=None, + description="Unique identifier (set when stored in DB)", + ) + provider: LLMProvider = Field( + description="The LLM provider type", + ) + name: str = Field( + description="User-friendly name for this configuration", + ) + is_default: bool = Field( + default=False, + description="Whether this is the default configuration", + ) + + # Provider-specific configs (discriminated union) + openai: OpenAIConfig | None = None + azure_openai: AzureOpenAIConfig | None = None + anthropic: AnthropicConfig | None = None + ollama: OllamaConfig | None = None + custom: CustomConfig | None = None + + @model_validator(mode="after") + def validate_provider_config(self) -> "LLMClientConfig": + """Ensure the correct provider config is set.""" + provider_configs = { + LLMProvider.OPENAI: self.openai, + LLMProvider.AZURE_OPENAI: self.azure_openai, + LLMProvider.ANTHROPIC: self.anthropic, + LLMProvider.OLLAMA: self.ollama, + LLMProvider.CUSTOM: self.custom, + } + + config = provider_configs.get(self.provider) + if config is None: + raise ValueError( + f"Provider {self.provider.value} requires {self.provider.value} config to be set" + ) + + return self + + def get_model_id(self) -> str: + """Get the model/deployment ID for display purposes.""" + match self.provider: + case LLMProvider.OPENAI: + return self.openai.model_id if self.openai else "" + case LLMProvider.AZURE_OPENAI: + return self.azure_openai.deployment if self.azure_openai else "" + case LLMProvider.ANTHROPIC: + return self.anthropic.model_id if self.anthropic else "" + case LLMProvider.OLLAMA: + return self.ollama.model_id if self.ollama else "" + case LLMProvider.CUSTOM: + return self.custom.model_id if self.custom else "" + case _: + return "" + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary for JSON serialization.""" + return self.model_dump(exclude_none=True) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "LLMClientConfig": + """Create from dictionary.""" + return cls.model_validate(data) diff --git a/src/flow/llm/factory.py b/src/flow/llm/factory.py new file mode 100644 index 0000000000000000000000000000000000000000..d2378c537a824e8d6b9bb8cec078eb22a9ed58ac --- /dev/null +++ b/src/flow/llm/factory.py @@ -0,0 +1,288 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""LLM client factory for creating harness-specific clients. + +This module provides factory methods to create LLM clients for different +frameworks (MAF, LangGraph, OpenAI SDK) from a unified LLMClientConfig. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from .config import LLMClientConfig, LLMProvider + +if TYPE_CHECKING: + pass + + +class LLMClientFactory: + """Factory for creating LLM clients from configuration. + + Each harness type has its own method returning the appropriate client type. + No backward compatibility - config is always required. + + Example: + config = LLMClientConfig( + provider=LLMProvider.AZURE_OPENAI, + name="My Azure GPT-4o", + azure_openai=AzureOpenAIConfig(deployment="gpt-4o"), + ) + + # For MAF harness + client = LLMClientFactory.create_maf_client(config) + + # For LangGraph harness + llm = LLMClientFactory.create_langgraph_client(config) + + # For LLM evaluator (OpenAI SDK) + client = LLMClientFactory.create_openai_client(config) + """ + + @staticmethod + def create_maf_client(config: LLMClientConfig) -> Any: + """Create client for Microsoft Agent Framework. + + Args: + config: LLM configuration + + Returns: + AzureOpenAIChatClient or OpenAIChatClient + + Raises: + ValueError: If provider is not supported by MAF + ImportError: If agent_framework is not installed + """ + match config.provider: + case LLMProvider.AZURE_OPENAI: + try: + from agent_framework.azure import AzureOpenAIChatClient + except ImportError as e: + raise ImportError( + "Microsoft Agent Framework is required. " + "Install with: pip install agent-framework-core" + ) from e + + if not config.azure_openai: + raise ValueError("azure_openai config is required for AZURE_OPENAI provider") + + return AzureOpenAIChatClient( + endpoint=config.azure_openai.get_endpoint(), + api_key=config.azure_openai.get_api_key(), + deployment=config.azure_openai.deployment, + api_version=config.azure_openai.api_version, + ) + + case LLMProvider.OPENAI: + try: + from agent_framework.openai import OpenAIChatClient + except ImportError as e: + raise ImportError( + "Microsoft Agent Framework is required. " + "Install with: pip install agent-framework-core" + ) from e + + if not config.openai: + raise ValueError("openai config is required for OPENAI provider") + + return OpenAIChatClient( + api_key=config.openai.get_api_key(), + model=config.openai.model_id, + ) + + case _: + raise ValueError( + f"MAF does not support provider: {config.provider.value}. " + f"Supported providers: azure_openai, openai" + ) + + @staticmethod + def create_langgraph_client(config: LLMClientConfig) -> Any: + """Create client for LangGraph/LangChain. + + Args: + config: LLM configuration + + Returns: + AzureChatOpenAI, ChatOpenAI, ChatAnthropic, or ChatOllama + + Raises: + ValueError: If provider is not supported + ImportError: If required langchain package is not installed + """ + match config.provider: + case LLMProvider.AZURE_OPENAI: + try: + from langchain_openai import AzureChatOpenAI + except ImportError as e: + raise ImportError( + "langchain-openai is required for Azure OpenAI. " + "Install with: pip install langchain-openai" + ) from e + + if not config.azure_openai: + raise ValueError("azure_openai config is required for AZURE_OPENAI provider") + + return AzureChatOpenAI( + azure_endpoint=config.azure_openai.get_endpoint(), + api_key=config.azure_openai.get_api_key(), + deployment_name=config.azure_openai.deployment, + api_version=config.azure_openai.api_version, + ) + + case LLMProvider.OPENAI: + try: + from langchain_openai import ChatOpenAI + except ImportError as e: + raise ImportError( + "langchain-openai is required for OpenAI. " + "Install with: pip install langchain-openai" + ) from e + + if not config.openai: + raise ValueError("openai config is required for OPENAI provider") + + return ChatOpenAI( + api_key=config.openai.get_api_key(), + model=config.openai.model_id, + ) + + case LLMProvider.ANTHROPIC: + try: + from langchain_anthropic import ChatAnthropic + except ImportError as e: + raise ImportError( + "langchain-anthropic is required for Anthropic. " + "Install with: pip install langchain-anthropic" + ) from e + + if not config.anthropic: + raise ValueError("anthropic config is required for ANTHROPIC provider") + + return ChatAnthropic( + api_key=config.anthropic.get_api_key(), + model=config.anthropic.model_id, + ) + + case LLMProvider.OLLAMA: + try: + from langchain_ollama import ChatOllama + except ImportError as e: + raise ImportError( + "langchain-ollama is required for Ollama. " + "Install with: pip install langchain-ollama" + ) from e + + if not config.ollama: + raise ValueError("ollama config is required for OLLAMA provider") + + return ChatOllama( + base_url=config.ollama.host, + model=config.ollama.model_id, + ) + + case LLMProvider.CUSTOM: + try: + from langchain_openai import ChatOpenAI + except ImportError as e: + raise ImportError( + "langchain-openai is required for custom endpoints. " + "Install with: pip install langchain-openai" + ) from e + + if not config.custom: + raise ValueError("custom config is required for CUSTOM provider") + + return ChatOpenAI( + base_url=config.custom.base_url, + api_key=config.custom.get_api_key(), + model=config.custom.model_id, + ) + + case _: + raise ValueError(f"Unknown provider: {config.provider.value}") + + @staticmethod + def create_openai_client(config: LLMClientConfig) -> Any: + """Create async OpenAI SDK client. + + This is used for LLM evaluators, direct API calls, and other + use cases requiring the OpenAI SDK directly. + + Args: + config: LLM configuration + + Returns: + AsyncOpenAI or AsyncAzureOpenAI + + Raises: + ValueError: If provider is not supported by OpenAI SDK + ImportError: If openai package is not installed + """ + match config.provider: + case LLMProvider.OPENAI: + try: + from openai import AsyncOpenAI + except ImportError as e: + raise ImportError( + "openai is required. Install with: pip install openai" + ) from e + + if not config.openai: + raise ValueError("openai config is required for OPENAI provider") + + return AsyncOpenAI(api_key=config.openai.get_api_key()) + + case LLMProvider.AZURE_OPENAI: + try: + from openai import AsyncAzureOpenAI + except ImportError as e: + raise ImportError( + "openai is required. Install with: pip install openai" + ) from e + + if not config.azure_openai: + raise ValueError("azure_openai config is required for AZURE_OPENAI provider") + + return AsyncAzureOpenAI( + azure_endpoint=config.azure_openai.get_endpoint(), + api_key=config.azure_openai.get_api_key(), + api_version=config.azure_openai.api_version, + ) + + case LLMProvider.CUSTOM: + try: + from openai import AsyncOpenAI + except ImportError as e: + raise ImportError( + "openai is required. Install with: pip install openai" + ) from e + + if not config.custom: + raise ValueError("custom config is required for CUSTOM provider") + + return AsyncOpenAI( + base_url=config.custom.base_url, + api_key=config.custom.get_api_key(), + ) + + case _: + raise ValueError( + f"OpenAI SDK does not support provider: {config.provider.value}. " + f"Supported providers: openai, azure_openai, custom" + ) + + @staticmethod + def get_model_name(config: LLMClientConfig) -> str: + """Get the model/deployment name from config. + + This is a convenience method for getting the model name + to pass to API calls when needed separately. + + Args: + config: LLM configuration + + Returns: + Model ID or deployment name + """ + return config.get_model_id() diff --git a/src/flow/optimizers/__init__.py b/src/flow/optimizers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..262f9ceabbc176b46b6e8450b1d40dd4ae906f33 --- /dev/null +++ b/src/flow/optimizers/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Optimizers for prompt and agent optimization. + +This module contains various optimization strategies for improving agent performance, +including GEPA-based active optimization. +""" + +from .gepa_adapter import GepaStrategy + +__all__ = ["GepaStrategy"] diff --git a/src/flow/optimizers/gepa_adapter.py b/src/flow/optimizers/gepa_adapter.py new file mode 100644 index 0000000000000000000000000000000000000000..d4216101d320afeb3ed12b1e23b06ef8f972a274 --- /dev/null +++ b/src/flow/optimizers/gepa_adapter.py @@ -0,0 +1,227 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""GEPA adapter for prompt optimization. + +This module implements a CandidateStrategy that uses GEPA (Generative Evolutionary +Prompt Adjustment) to optimize prompts via an active learning loop. +""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import Any, Callable + +from ..experiments.models import Agent, Candidate, CandidateStrategy, ExperimentResult +from ..experiments.types import Task + +logger = logging.getLogger(__name__) + + +@dataclass +class GEPAEvaluationResult: + """Result object returned by FlowAdapter.evaluate() for GEPA.""" + outputs: list[Any] + scores: list[float] + objective_scores: list[dict[str, float]] | None = None + + +class GepaStrategy: + """Active strategy using GEPA for prompt optimization. + + This strategy injects an evaluator and dataset into the generation process + to drive GEPA's evolutionary loop. + """ + + def __init__( + self, + evaluator: Callable[[Candidate, list[Any] | None], ExperimentResult] | None = None, + dataset: list[Any] | None = None, + config: dict[str, Any] | None = None, + ) -> None: + """Initialize GEPA strategy with evaluation components. + + Args: + evaluator: Function to evaluate a candidate. Optional if injected later. + dataset: Optional list of inputs/examples to use for optimization. + config: GEPA configuration hyperparameters (e.g. reflection_lm). + """ + self.evaluator = evaluator + self.dataset = dataset + self.config = config or {} + + def generate(self, base: Agent, budget: int) -> list[Candidate]: + """Generate optimized candidates using GEPA. + + This method runs the full GEPA optimization loop and returns the best + candidates found. + + Args: + base: The base agent to optimize. + budget: Max metric calls (budget for optimization). + + Returns: + List of optimized Candidate objects. + """ + if self.evaluator is None: + raise ValueError("Evaluator must be provided via init or injection before calling generate.") + + try: + import gepa + except ImportError as e: + raise ImportError( + "GEPA is not installed. Install with `pip install flow-agent[optimizer]`" + ) from e + + # Inner adapter class to bridge Flow and GEPA + class FlowAdapter(gepa.GEPAAdapter): + def __init__(self, evaluator_fn: Callable[[Candidate, list[Any] | None], ExperimentResult]): + self.evaluator_fn = evaluator_fn + + def evaluate(self, batch: list[Any], program: dict[str, str], **kwargs: Any) -> GEPAEvaluationResult: + # 1. Apply program (prompts) to 'base' agent to create a temp Candidate + # program maps component names (e.g. "instructions") to text + + # Clone base agent and update fields, preserving CompactionConfig object + mutated_agent = Agent( + name=f"{base.name}_gepa_eval", + framework=base.framework, + description=base.description, + instructions=program.get("instructions", base.instructions), + model=base.model, + compaction=base.compaction, # Preserve the CompactionConfig object + tools=base.tools + ) + + # Create temp Candidate + temp_candidate = Candidate( + agent=mutated_agent, + mutations=program, + rationale="GEPA evaluation probe" + ) + + # If batch is empty, return neutral results + # This can happen with GEPA's initialization or when using empty datasets + if not batch: + return GEPAEvaluationResult( + outputs=[], + scores=[], + objective_scores=None + ) + + # 2. Evaluate each task individually to get per-item scores + # GEPA needs granular feedback for each example, not aggregate scores + outputs = [] + scores = [] + + logger.info(f"[GEPA] Evaluating {len(batch)} tasks for GEPA") + + for i, task in enumerate(batch): + # Evaluate this single task + logger.debug(f"[GEPA] Calling evaluator for task {i}: {getattr(task, 'name', 'unnamed')}") + result = self.evaluator_fn(temp_candidate, [task]) + + # Extract score and output for this specific task + task_score = getattr(result, "eval_score", 0.0) + task_output = getattr(result, "traces", {}) + + logger.info(f"[GEPA] Task {i} ({getattr(task, 'name', 'unnamed')}): score={task_score}, passed={getattr(result, 'eval_passed', False)}") + logger.debug(f"[GEPA] Reasoning: {getattr(result, 'eval_reasoning', 'N/A')[:200]}") + + scores.append(task_score) + outputs.append(task_output) + + avg_score = sum(scores)/len(scores) if scores else 0.0 + logger.info(f"[GEPA] Evaluation complete. Scores: {scores}, Avg: {avg_score:.3f}") + + # Return proper GEPAEvaluationResult with per-item scores + return GEPAEvaluationResult( + outputs=outputs, + scores=scores, + objective_scores=None + ) + + def extract_traces(self, candidate: dict[str, str], component: str) -> str: + # GEPA calls this sometimes to get traces for reflection? + # Actually, `evaluate` returns traces in the result. + # GEPA might use this if traces are stored elsewhere. + # For now, return empty string as base implementation. + return "" + + # Normalize config + gepa_config = self.config.copy() + + # Determine seed candidate dictionary + seed_candidate = {} + if base.instructions: + seed_candidate["instructions"] = base.instructions + + # Prepare dataset for GEPA + # GEPA needs a list of items it can pass to the evaluator + # The evaluator expects Task objects, so ensure we have those + dataset_for_gepa = [] + if self.dataset: + for item in self.dataset: + # Already a Task object + if isinstance(item, Task): + dataset_for_gepa.append(item) + # Dict with 'prompt' key - convert to Task + elif isinstance(item, dict) and 'prompt' in item: + dataset_for_gepa.append(Task( + name=item.get('name', f'task_{len(dataset_for_gepa)}'), + prompt=item['prompt'], + criteria=item.get('criteria', []), + metadata=item.get('metadata', {}) + )) + # Otherwise try to use it as-is and let GEPA/evaluator handle it + else: + dataset_for_gepa.append(item) + + # If no dataset provided, create minimal dummy tasks for GEPA + # GEPA needs at least some data to build evaluation metrics + if not dataset_for_gepa: + logger.warning("No dataset provided for GEPA optimization. Using minimal dataset.") + dataset_for_gepa = [ + Task( + name=f'dummy_{i}', + prompt=f'Placeholder task {i}', + criteria=[], + metadata={} + ) for i in range(max(1, budget // 2)) + ] + + # Run GEPA + logger.info("Starting GEPA optimization (budget=%d, dataset_size=%d)", budget, len(dataset_for_gepa)) + gepa_result = gepa.optimize( + seed_candidate=seed_candidate, + adapter=FlowAdapter(self.evaluator), + trainset=dataset_for_gepa, + valset=dataset_for_gepa, # Use same for now if splits not provided + max_metric_calls=budget, + **gepa_config + ) + + # Convert result back to Candidate + best_prompts = gepa_result.best_candidate + + # Create final candidate, preserving CompactionConfig object + final_agent = Agent( + name=f"{base.name}_gepa_optimized", + framework=base.framework, + description=base.description, + instructions=best_prompts.get("instructions", base.instructions), + model=base.model, + compaction=base.compaction, # Preserve the CompactionConfig object + tools=base.tools + ) + + # Get the best score from the result + best_score = gepa_result.val_aggregate_scores[gepa_result.best_idx] + + final_candidate = Candidate( + agent=final_agent, + mutations=best_prompts, + rationale=f"GEPA optimized (score={best_score:.4f})" + ) + + return [final_candidate] diff --git a/src/flow/prompts.py b/src/flow/prompts.py index 897bae046d9f00a2522eaabc374b8cbfe389ce74..9229266bdf1a11e103087db71b3fd6e2b4315e04 100644 --- a/src/flow/prompts.py +++ b/src/flow/prompts.py @@ -37,11 +37,11 @@ _CORE_CAPABILITIES = """ **Coding Tools:** - `read_file`: Read file contents with line numbers - `write_file`: Create/edit files (full write, str_replace, or insert_at_line) -- `list_directory`: Explore project structure -- `grep_search`: Search for patterns in code (regex supported) +- `ls`: Explore project structure +- `grep`: Search for patterns in code (regex supported) **Execution Tools:** -- `bash_execute`: Run shell commands (tests, git, npm, pip, builds, etc.) +- `bash`: Run shell commands (tests, git, npm, pip, builds, etc.) - `python_repl`: Execute Python code snippets for quick validation **Research Tools (if available):** @@ -50,7 +50,7 @@ _CORE_CAPABILITIES = """ **Thinking Tools:** - `think`: Pause to reason through complex problems -- `task_done`: Report when task is complete or blocked +- `todo_write`: Report when task is complete or blocked """ _CORE_WORKFLOW_UNDERSTAND = """ @@ -60,8 +60,8 @@ _CORE_WORKFLOW_UNDERSTAND = """ ### 1. UNDERSTAND - Read the user's request carefully -- Use `list_directory` to understand the workspace structure -- Use `grep_search` to find relevant existing code +- Use `ls` to understand the workspace structure +- Use `grep` to find relevant existing code """ _CORE_WORKFLOW_PLAN_EXECUTE_VERIFY = """ @@ -72,37 +72,37 @@ _CORE_WORKFLOW_PLAN_EXECUTE_VERIFY = """ ### 3. EXECUTE - Create/edit files using `write_file` -- Test changes using `bash_execute` or `python_repl` +- Test changes using `bash` or `python_repl` - Fix issues immediately when tests fail ### 4. VERIFY (REQUIRED) -**You MUST test your work before calling `task_done`.** Never assume code works. +**You MUST test your work before calling `todo_write`.** Never assume code works. **For Python apps/scripts:** ``` -bash_execute("cd project && python -c 'import main'") # Check imports work -bash_execute("cd project && python main.py --help") # Test CLI if applicable -bash_execute("cd project && pytest") # Run tests if they exist +bash("cd project && python -c 'import main'") # Check imports work +bash("cd project && python main.py --help") # Test CLI if applicable +bash("cd project && pytest") # Run tests if they exist ``` **For JavaScript/TypeScript:** ``` -bash_execute("cd project && npm install && npm run build") # Must pass! -bash_execute("cd project && npx tsc --noEmit") # Type check +bash("cd project && npm install && npm run build") # Must pass! +bash("cd project && npx tsc --noEmit") # Type check ``` **For Web APIs (FastAPI, Express, etc.):** ``` # Start server in background, test with curl, then cleanup -bash_execute("cd project && uvicorn main:app --port 8000 &", background=True) -bash_execute("sleep 2 && curl http://localhost:8000/health") # Test endpoint -bash_execute("check_processes action=list") # Verify it's running +bash("cd project && uvicorn main:app --port 8000 &", background=True) +bash("sleep 2 && curl http://localhost:8000/health") # Test endpoint +bash("check_processes action=list") # Verify it's running # When done testing, kill the process ``` **For Frontend apps (React, Vue, etc.):** ``` -bash_execute("cd project && npm run build") # Production build must succeed +bash("cd project && npm run build") # Production build must succeed # If you need to test dev server, use background=True ``` @@ -113,7 +113,7 @@ bash_execute("cd project && npm run build") # Production build must succeed ### 5. COMPLETE - Clean up any background processes you started -- Call `task_done` with status and summary +- Call `todo_write` with status and summary - Include files created and suggested next steps """ @@ -126,14 +126,14 @@ Your workspace is at `~/.flow/workspace/` **Organization:** - Create a folder for each project (e.g., `todo_app/`, `calculator/`) -- Use `list_directory` to see existing projects before creating new ones +- Use `ls` to see existing projects before creating new ones - Follow standard project structure conventions: - Python: `src/`, `tests/`, `requirements.txt` or `pyproject.toml` - JavaScript: `src/`, `package.json`, standard Node.js layout - Full-stack: `backend/`, `frontend/` folders **Important:** -- Each `bash_execute` runs from workspace root in a fresh shell +- Each `bash` runs from workspace root in a fresh shell - Use `cd project && command` for commands in subdirectories - Multiple commands: `cd project && cmd1 && cmd2` """ @@ -205,10 +205,10 @@ When you need to start long-running processes (servers, watchers, etc.): **Use `background=True` parameter:** ```python # Start a server in background - returns immediately with PID -bash_execute("uvicorn main:app --port 8000", background=True) +bash("uvicorn main:app --port 8000", background=True) # Then test it -bash_execute("curl http://localhost:8000/health") +bash("curl http://localhost:8000/health") # Check what's running check_processes(action="list") @@ -225,13 +225,13 @@ check_processes(action="kill", pid=12345) **Common patterns:** ```bash # Good - background server for testing -bash_execute("cd backend && uvicorn main:app --port 8000", background=True) -bash_execute("sleep 2") # Wait for startup -bash_execute("curl localhost:8000/docs") # Test +bash("cd backend && uvicorn main:app --port 8000", background=True) +bash("sleep 2") # Wait for startup +bash("curl localhost:8000/docs") # Test check_processes(action="cleanup") # Kill all when done # Bad - will timeout! -bash_execute("uvicorn main:app --port 8000") # Blocks forever +bash("uvicorn main:app --port 8000") # Blocks forever ``` """ @@ -242,7 +242,7 @@ _CORE_ERROR_HANDLING = """ - If a command fails, analyze the error and try alternatives - Don't give up after first failure - iterate -- If truly blocked, call `task_done` with status="incomplete" and explain why +- If truly blocked, call `todo_write` with status="incomplete" and explain why """ _CORE_EXAMPLES = """ @@ -258,7 +258,7 @@ _CORE_EXAMPLES = """ 1. web_search("weather API free") → Find a free weather API 2. web_fetch(api_docs_url) → Read the API documentation 3. write_file("weather.py", code) → Write a script to call the API -4. bash_execute("python weather.py") → Run it and get the answer +4. bash("python weather.py") → Run it and get the answer 5. Report the actual result to the user ``` @@ -266,8 +266,8 @@ _CORE_EXAMPLES = """ ``` 1. write_file("csv_to_json.py", code) → Write the tool 2. write_file("test.csv", sample_data) → Create test data -3. bash_execute("python csv_to_json.py test.csv") → Test it works -4. bash_execute("cat output.json") → Verify the output +3. bash("python csv_to_json.py test.csv") → Test it works +4. bash("cat output.json") → Verify the output 5. Report success with example output ``` @@ -281,9 +281,9 @@ _CORE_EXAMPLES = """ ### Example: "Debug why my FastAPI app returns 500 errors" ``` 1. read_file("main.py") → Understand the code -2. bash_execute("cd app && python -c 'from main import app'") → Check imports -3. bash_execute("cd app && uvicorn main:app --port 8000", background=True) → Start server -4. bash_execute("curl localhost:8000/endpoint") → Reproduce the error +2. bash("cd app && python -c 'from main import app'") → Check imports +3. bash("cd app && uvicorn main:app --port 8000", background=True) → Start server +4. bash("curl localhost:8000/endpoint") → Reproduce the error 5. Analyze error → Fix code → Test again → Iterate until fixed ``` """ @@ -464,7 +464,7 @@ Each skill is a folder with a `SKILL.md` file following the Anthropic Skills sta def build_instructions( *, enable_memory: bool = True, - enable_skills: bool = False, + enable_skills: bool = True, ) -> str: """Build agent instructions dynamically based on enabled tools. diff --git a/src/flow/skills/doc-coauthoring/SKILL.md b/src/flow/skills/doc-coauthoring/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..a5a69839ef4a161131d80b6daef10037a9686f4a --- /dev/null +++ b/src/flow/skills/doc-coauthoring/SKILL.md @@ -0,0 +1,375 @@ +--- +name: doc-coauthoring +description: Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks. +--- + +# Doc Co-Authoring Workflow + +This skill provides a structured workflow for guiding users through collaborative document creation. Act as an active guide, walking users through three stages: Context Gathering, Refinement & Structure, and Reader Testing. + +## When to Offer This Workflow + +**Trigger conditions:** +- User mentions writing documentation: "write a doc", "draft a proposal", "create a spec", "write up" +- User mentions specific doc types: "PRD", "design doc", "decision doc", "RFC" +- User seems to be starting a substantial writing task + +**Initial offer:** +Offer the user a structured workflow for co-authoring the document. Explain the three stages: + +1. **Context Gathering**: User provides all relevant context while Claude asks clarifying questions +2. **Refinement & Structure**: Iteratively build each section through brainstorming and editing +3. **Reader Testing**: Test the doc with a fresh Claude (no context) to catch blind spots before others read it + +Explain that this approach helps ensure the doc works well when others read it (including when they paste it into Claude). Ask if they want to try this workflow or prefer to work freeform. + +If user declines, work freeform. If user accepts, proceed to Stage 1. + +## Stage 1: Context Gathering + +**Goal:** Close the gap between what the user knows and what Claude knows, enabling smart guidance later. + +### Initial Questions + +Start by asking the user for meta-context about the document: + +1. What type of document is this? (e.g., technical spec, decision doc, proposal) +2. Who's the primary audience? +3. What's the desired impact when someone reads this? +4. Is there a template or specific format to follow? +5. Any other constraints or context to know? + +Inform them they can answer in shorthand or dump information however works best for them. + +**If user provides a template or mentions a doc type:** +- Ask if they have a template document to share +- If they provide a link to a shared document, use the appropriate integration to fetch it +- If they provide a file, read it + +**If user mentions editing an existing shared document:** +- Use the appropriate integration to read the current state +- Check for images without alt-text +- If images exist without alt-text, explain that when others use Claude to understand the doc, Claude won't be able to see them. Ask if they want alt-text generated. If so, request they paste each image into chat for descriptive alt-text generation. + +### Info Dumping + +Once initial questions are answered, encourage the user to dump all the context they have. Request information such as: +- Background on the project/problem +- Related team discussions or shared documents +- Why alternative solutions aren't being used +- Organizational context (team dynamics, past incidents, politics) +- Timeline pressures or constraints +- Technical architecture or dependencies +- Stakeholder concerns + +Advise them not to worry about organizing it - just get it all out. Offer multiple ways to provide context: +- Info dump stream-of-consciousness +- Point to team channels or threads to read +- Link to shared documents + +**If integrations are available** (e.g., Slack, Teams, Google Drive, SharePoint, or other MCP servers), mention that these can be used to pull in context directly. + +**If no integrations are detected and in Claude.ai or Claude app:** Suggest they can enable connectors in their Claude settings to allow pulling context from messaging apps and document storage directly. + +Inform them clarifying questions will be asked once they've done their initial dump. + +**During context gathering:** + +- If user mentions team channels or shared documents: + - If integrations available: Inform them the content will be read now, then use the appropriate integration + - If integrations not available: Explain lack of access. Suggest they enable connectors in Claude settings, or paste the relevant content directly. + +- If user mentions entities/projects that are unknown: + - Ask if connected tools should be searched to learn more + - Wait for user confirmation before searching + +- As user provides context, track what's being learned and what's still unclear + +**Asking clarifying questions:** + +When user signals they've done their initial dump (or after substantial context provided), ask clarifying questions to ensure understanding: + +Generate 5-10 numbered questions based on gaps in the context. + +Inform them they can use shorthand to answer (e.g., "1: yes, 2: see #channel, 3: no because backwards compat"), link to more docs, point to channels to read, or just keep info-dumping. Whatever's most efficient for them. + +**Exit condition:** +Sufficient context has been gathered when questions show understanding - when edge cases and trade-offs can be asked about without needing basics explained. + +**Transition:** +Ask if there's any more context they want to provide at this stage, or if it's time to move on to drafting the document. + +If user wants to add more, let them. When ready, proceed to Stage 2. + +## Stage 2: Refinement & Structure + +**Goal:** Build the document section by section through brainstorming, curation, and iterative refinement. + +**Instructions to user:** +Explain that the document will be built section by section. For each section: +1. Clarifying questions will be asked about what to include +2. 5-20 options will be brainstormed +3. User will indicate what to keep/remove/combine +4. The section will be drafted +5. It will be refined through surgical edits + +Start with whichever section has the most unknowns (usually the core decision/proposal), then work through the rest. + +**Section ordering:** + +If the document structure is clear: +Ask which section they'd like to start with. + +Suggest starting with whichever section has the most unknowns. For decision docs, that's usually the core proposal. For specs, it's typically the technical approach. Summary sections are best left for last. + +If user doesn't know what sections they need: +Based on the type of document and template, suggest 3-5 sections appropriate for the doc type. + +Ask if this structure works, or if they want to adjust it. + +**Once structure is agreed:** + +Create the initial document structure with placeholder text for all sections. + +**If access to artifacts is available:** +Use `create_file` to create an artifact. This gives both Claude and the user a scaffold to work from. + +Inform them that the initial structure with placeholders for all sections will be created. + +Create artifact with all section headers and brief placeholder text like "[To be written]" or "[Content here]". + +Provide the scaffold link and indicate it's time to fill in each section. + +**If no access to artifacts:** +Create a markdown file in the working directory. Name it appropriately (e.g., `decision-doc.md`, `technical-spec.md`). + +Inform them that the initial structure with placeholders for all sections will be created. + +Create file with all section headers and placeholder text. + +Confirm the filename has been created and indicate it's time to fill in each section. + +**For each section:** + +### Step 1: Clarifying Questions + +Announce work will begin on the [SECTION NAME] section. Ask 5-10 clarifying questions about what should be included: + +Generate 5-10 specific questions based on context and section purpose. + +Inform them they can answer in shorthand or just indicate what's important to cover. + +### Step 2: Brainstorming + +For the [SECTION NAME] section, brainstorm [5-20] things that might be included, depending on the section's complexity. Look for: +- Context shared that might have been forgotten +- Angles or considerations not yet mentioned + +Generate 5-20 numbered options based on section complexity. At the end, offer to brainstorm more if they want additional options. + +### Step 3: Curation + +Ask which points should be kept, removed, or combined. Request brief justifications to help learn priorities for the next sections. + +Provide examples: +- "Keep 1,4,7,9" +- "Remove 3 (duplicates 1)" +- "Remove 6 (audience already knows this)" +- "Combine 11 and 12" + +**If user gives freeform feedback** (e.g., "looks good" or "I like most of it but...") instead of numbered selections, extract their preferences and proceed. Parse what they want kept/removed/changed and apply it. + +### Step 4: Gap Check + +Based on what they've selected, ask if there's anything important missing for the [SECTION NAME] section. + +### Step 5: Drafting + +Use `str_replace` to replace the placeholder text for this section with the actual drafted content. + +Announce the [SECTION NAME] section will be drafted now based on what they've selected. + +**If using artifacts:** +After drafting, provide a link to the artifact. + +Ask them to read through it and indicate what to change. Note that being specific helps learning for the next sections. + +**If using a file (no artifacts):** +After drafting, confirm completion. + +Inform them the [SECTION NAME] section has been drafted in [filename]. Ask them to read through it and indicate what to change. Note that being specific helps learning for the next sections. + +**Key instruction for user (include when drafting the first section):** +Provide a note: Instead of editing the doc directly, ask them to indicate what to change. This helps learning of their style for future sections. For example: "Remove the X bullet - already covered by Y" or "Make the third paragraph more concise". + +### Step 6: Iterative Refinement + +As user provides feedback: +- Use `str_replace` to make edits (never reprint the whole doc) +- **If using artifacts:** Provide link to artifact after each edit +- **If using files:** Just confirm edits are complete +- If user edits doc directly and asks to read it: mentally note the changes they made and keep them in mind for future sections (this shows their preferences) + +**Continue iterating** until user is satisfied with the section. + +### Quality Checking + +After 3 consecutive iterations with no substantial changes, ask if anything can be removed without losing important information. + +When section is done, confirm [SECTION NAME] is complete. Ask if ready to move to the next section. + +**Repeat for all sections.** + +### Near Completion + +As approaching completion (80%+ of sections done), announce intention to re-read the entire document and check for: +- Flow and consistency across sections +- Redundancy or contradictions +- Anything that feels like "slop" or generic filler +- Whether every sentence carries weight + +Read entire document and provide feedback. + +**When all sections are drafted and refined:** +Announce all sections are drafted. Indicate intention to review the complete document one more time. + +Review for overall coherence, flow, completeness. + +Provide any final suggestions. + +Ask if ready to move to Reader Testing, or if they want to refine anything else. + +## Stage 3: Reader Testing + +**Goal:** Test the document with a fresh Claude (no context bleed) to verify it works for readers. + +**Instructions to user:** +Explain that testing will now occur to see if the document actually works for readers. This catches blind spots - things that make sense to the authors but might confuse others. + +### Testing Approach + +**If access to sub-agents is available (e.g., in Claude Code):** + +Perform the testing directly without user involvement. + +### Step 1: Predict Reader Questions + +Announce intention to predict what questions readers might ask when trying to discover this document. + +Generate 5-10 questions that readers would realistically ask. + +### Step 2: Test with Sub-Agent + +Announce that these questions will be tested with a fresh Claude instance (no context from this conversation). + +For each question, invoke a sub-agent with just the document content and the question. + +Summarize what Reader Claude got right/wrong for each question. + +### Step 3: Run Additional Checks + +Announce additional checks will be performed. + +Invoke sub-agent to check for ambiguity, false assumptions, contradictions. + +Summarize any issues found. + +### Step 4: Report and Fix + +If issues found: +Report that Reader Claude struggled with specific issues. + +List the specific issues. + +Indicate intention to fix these gaps. + +Loop back to refinement for problematic sections. + +--- + +**If no access to sub-agents (e.g., claude.ai web interface):** + +The user will need to do the testing manually. + +### Step 1: Predict Reader Questions + +Ask what questions people might ask when trying to discover this document. What would they type into Claude.ai? + +Generate 5-10 questions that readers would realistically ask. + +### Step 2: Setup Testing + +Provide testing instructions: +1. Open a fresh Claude conversation: https://claude.ai +2. Paste or share the document content (if using a shared doc platform with connectors enabled, provide the link) +3. Ask Reader Claude the generated questions + +For each question, instruct Reader Claude to provide: +- The answer +- Whether anything was ambiguous or unclear +- What knowledge/context the doc assumes is already known + +Check if Reader Claude gives correct answers or misinterprets anything. + +### Step 3: Additional Checks + +Also ask Reader Claude: +- "What in this doc might be ambiguous or unclear to readers?" +- "What knowledge or context does this doc assume readers already have?" +- "Are there any internal contradictions or inconsistencies?" + +### Step 4: Iterate Based on Results + +Ask what Reader Claude got wrong or struggled with. Indicate intention to fix those gaps. + +Loop back to refinement for any problematic sections. + +--- + +### Exit Condition (Both Approaches) + +When Reader Claude consistently answers questions correctly and doesn't surface new gaps or ambiguities, the doc is ready. + +## Final Review + +When Reader Testing passes: +Announce the doc has passed Reader Claude testing. Before completion: + +1. Recommend they do a final read-through themselves - they own this document and are responsible for its quality +2. Suggest double-checking any facts, links, or technical details +3. Ask them to verify it achieves the impact they wanted + +Ask if they want one more review, or if the work is done. + +**If user wants final review, provide it. Otherwise:** +Announce document completion. Provide a few final tips: +- Consider linking this conversation in an appendix so readers can see how the doc was developed +- Use appendices to provide depth without bloating the main doc +- Update the doc as feedback is received from real readers + +## Tips for Effective Guidance + +**Tone:** +- Be direct and procedural +- Explain rationale briefly when it affects user behavior +- Don't try to "sell" the approach - just execute it + +**Handling Deviations:** +- If user wants to skip a stage: Ask if they want to skip this and write freeform +- If user seems frustrated: Acknowledge this is taking longer than expected. Suggest ways to move faster +- Always give user agency to adjust the process + +**Context Management:** +- Throughout, if context is missing on something mentioned, proactively ask +- Don't let gaps accumulate - address them as they come up + +**Artifact Management:** +- Use `create_file` for drafting full sections +- Use `str_replace` for all edits +- Provide artifact link after every change +- Never use artifacts for brainstorming lists - that's just conversation + +**Quality over Speed:** +- Don't rush through stages +- Each iteration should make meaningful improvements +- The goal is a document that actually works for readers diff --git a/src/flow/skills/docx/LICENSE.txt b/src/flow/skills/docx/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..c55ab42224874608473643de0a85736b7fec0730 --- /dev/null +++ b/src/flow/skills/docx/LICENSE.txt @@ -0,0 +1,30 @@ +© 2025 Anthropic, PBC. All rights reserved. + +LICENSE: Use of these materials (including all code, prompts, assets, files, +and other components of this Skill) is governed by your agreement with +Anthropic regarding use of Anthropic's services. If no separate agreement +exists, use is governed by Anthropic's Consumer Terms of Service or +Commercial Terms of Service, as applicable: +https://www.anthropic.com/legal/consumer-terms +https://www.anthropic.com/legal/commercial-terms +Your applicable agreement is referred to as the "Agreement." "Services" are +as defined in the Agreement. + +ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the +contrary, users may not: + +- Extract these materials from the Services or retain copies of these + materials outside the Services +- Reproduce or copy these materials, except for temporary copies created + automatically during authorized use of the Services +- Create derivative works based on these materials +- Distribute, sublicense, or transfer these materials to any third party +- Make, offer to sell, sell, or import any inventions embodied in these + materials +- Reverse engineer, decompile, or disassemble these materials + +The receipt, viewing, or possession of these materials does not convey or +imply any license or right beyond those expressly granted above. + +Anthropic retains all right, title, and interest in these materials, +including all copyrights, patents, and other intellectual property rights. diff --git a/src/flow/skills/docx/SKILL.md b/src/flow/skills/docx/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..664663895bcd11b88a632301d830b313cbabb845 --- /dev/null +++ b/src/flow/skills/docx/SKILL.md @@ -0,0 +1,197 @@ +--- +name: docx +description: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks" +license: Proprietary. LICENSE.txt has complete terms +--- + +# DOCX creation, editing, and analysis + +## Overview + +A user may ask you to create, edit, or analyze the contents of a .docx file. A .docx file is essentially a ZIP archive containing XML files and other resources that you can read or edit. You have different tools and workflows available for different tasks. + +## Workflow Decision Tree + +### Reading/Analyzing Content +Use "Text extraction" or "Raw XML access" sections below + +### Creating New Document +Use "Creating a new Word document" workflow + +### Editing Existing Document +- **Your own document + simple changes** + Use "Basic OOXML editing" workflow + +- **Someone else's document** + Use **"Redlining workflow"** (recommended default) + +- **Legal, academic, business, or government docs** + Use **"Redlining workflow"** (required) + +## Reading and analyzing content + +### Text extraction +If you just need to read the text contents of a document, you should convert the document to markdown using pandoc. Pandoc provides excellent support for preserving document structure and can show tracked changes: + +```bash +# Convert document to markdown with tracked changes +pandoc --track-changes=all path-to-file.docx -o output.md +# Options: --track-changes=accept/reject/all +``` + +### Raw XML access +You need raw XML access for: comments, complex formatting, document structure, embedded media, and metadata. For any of these features, you'll need to unpack a document and read its raw XML contents. + +#### Unpacking a file +`python ooxml/scripts/unpack.py ` + +#### Key file structures +* `word/document.xml` - Main document contents +* `word/comments.xml` - Comments referenced in document.xml +* `word/media/` - Embedded images and media files +* Tracked changes use `` (insertions) and `` (deletions) tags + +## Creating a new Word document + +When creating a new Word document from scratch, use **docx-js**, which allows you to create Word documents using JavaScript/TypeScript. + +### Workflow +1. **MANDATORY - READ ENTIRE FILE**: Read [`docx-js.md`](docx-js.md) (~500 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for detailed syntax, critical formatting rules, and best practices before proceeding with document creation. +2. Create a JavaScript/TypeScript file using Document, Paragraph, TextRun components (You can assume all dependencies are installed, but if not, refer to the dependencies section below) +3. Export as .docx using Packer.toBuffer() + +## Editing an existing Word document + +When editing an existing Word document, use the **Document library** (a Python library for OOXML manipulation). The library automatically handles infrastructure setup and provides methods for document manipulation. For complex scenarios, you can access the underlying DOM directly through the library. + +### Workflow +1. **MANDATORY - READ ENTIRE FILE**: Read [`ooxml.md`](ooxml.md) (~600 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for the Document library API and XML patterns for directly editing document files. +2. Unpack the document: `python ooxml/scripts/unpack.py ` +3. Create and run a Python script using the Document library (see "Document Library" section in ooxml.md) +4. Pack the final document: `python ooxml/scripts/pack.py ` + +The Document library provides both high-level methods for common operations and direct DOM access for complex scenarios. + +## Redlining workflow for document review + +This workflow allows you to plan comprehensive tracked changes using markdown before implementing them in OOXML. **CRITICAL**: For complete tracked changes, you must implement ALL changes systematically. + +**Batching Strategy**: Group related changes into batches of 3-10 changes. This makes debugging manageable while maintaining efficiency. Test each batch before moving to the next. + +**Principle: Minimal, Precise Edits** +When implementing tracked changes, only mark text that actually changes. Repeating unchanged text makes edits harder to review and appears unprofessional. Break replacements into: [unchanged text] + [deletion] + [insertion] + [unchanged text]. Preserve the original run's RSID for unchanged text by extracting the `` element from the original and reusing it. + +Example - Changing "30 days" to "60 days" in a sentence: +```python +# BAD - Replaces entire sentence +'The term is 30 days.The term is 60 days.' + +# GOOD - Only marks what changed, preserves original for unchanged text +'The term is 3060 days.' +``` + +### Tracked changes workflow + +1. **Get markdown representation**: Convert document to markdown with tracked changes preserved: + ```bash + pandoc --track-changes=all path-to-file.docx -o current.md + ``` + +2. **Identify and group changes**: Review the document and identify ALL changes needed, organizing them into logical batches: + + **Location methods** (for finding changes in XML): + - Section/heading numbers (e.g., "Section 3.2", "Article IV") + - Paragraph identifiers if numbered + - Grep patterns with unique surrounding text + - Document structure (e.g., "first paragraph", "signature block") + - **DO NOT use markdown line numbers** - they don't map to XML structure + + **Batch organization** (group 3-10 related changes per batch): + - By section: "Batch 1: Section 2 amendments", "Batch 2: Section 5 updates" + - By type: "Batch 1: Date corrections", "Batch 2: Party name changes" + - By complexity: Start with simple text replacements, then tackle complex structural changes + - Sequential: "Batch 1: Pages 1-3", "Batch 2: Pages 4-6" + +3. **Read documentation and unpack**: + - **MANDATORY - READ ENTIRE FILE**: Read [`ooxml.md`](ooxml.md) (~600 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Pay special attention to the "Document Library" and "Tracked Change Patterns" sections. + - **Unpack the document**: `python ooxml/scripts/unpack.py ` + - **Note the suggested RSID**: The unpack script will suggest an RSID to use for your tracked changes. Copy this RSID for use in step 4b. + +4. **Implement changes in batches**: Group changes logically (by section, by type, or by proximity) and implement them together in a single script. This approach: + - Makes debugging easier (smaller batch = easier to isolate errors) + - Allows incremental progress + - Maintains efficiency (batch size of 3-10 changes works well) + + **Suggested batch groupings:** + - By document section (e.g., "Section 3 changes", "Definitions", "Termination clause") + - By change type (e.g., "Date changes", "Party name updates", "Legal term replacements") + - By proximity (e.g., "Changes on pages 1-3", "Changes in first half of document") + + For each batch of related changes: + + **a. Map text to XML**: Grep for text in `word/document.xml` to verify how text is split across `` elements. + + **b. Create and run script**: Use `get_node` to find nodes, implement changes, then `doc.save()`. See **"Document Library"** section in ooxml.md for patterns. + + **Note**: Always grep `word/document.xml` immediately before writing a script to get current line numbers and verify text content. Line numbers change after each script run. + +5. **Pack the document**: After all batches are complete, convert the unpacked directory back to .docx: + ```bash + python ooxml/scripts/pack.py unpacked reviewed-document.docx + ``` + +6. **Final verification**: Do a comprehensive check of the complete document: + - Convert final document to markdown: + ```bash + pandoc --track-changes=all reviewed-document.docx -o verification.md + ``` + - Verify ALL changes were applied correctly: + ```bash + grep "original phrase" verification.md # Should NOT find it + grep "replacement phrase" verification.md # Should find it + ``` + - Check that no unintended changes were introduced + + +## Converting Documents to Images + +To visually analyze Word documents, convert them to images using a two-step process: + +1. **Convert DOCX to PDF**: + ```bash + soffice --headless --convert-to pdf document.docx + ``` + +2. **Convert PDF pages to JPEG images**: + ```bash + pdftoppm -jpeg -r 150 document.pdf page + ``` + This creates files like `page-1.jpg`, `page-2.jpg`, etc. + +Options: +- `-r 150`: Sets resolution to 150 DPI (adjust for quality/size balance) +- `-jpeg`: Output JPEG format (use `-png` for PNG if preferred) +- `-f N`: First page to convert (e.g., `-f 2` starts from page 2) +- `-l N`: Last page to convert (e.g., `-l 5` stops at page 5) +- `page`: Prefix for output files + +Example for specific range: +```bash +pdftoppm -jpeg -r 150 -f 2 -l 5 document.pdf page # Converts only pages 2-5 +``` + +## Code Style Guidelines +**IMPORTANT**: When generating code for DOCX operations: +- Write concise code +- Avoid verbose variable names and redundant operations +- Avoid unnecessary print statements + +## Dependencies + +Required dependencies (install if not available): + +- **pandoc**: `sudo apt-get install pandoc` (for text extraction) +- **docx**: `npm install -g docx` (for creating new documents) +- **LibreOffice**: `sudo apt-get install libreoffice` (for PDF conversion) +- **Poppler**: `sudo apt-get install poppler-utils` (for pdftoppm to convert PDF to images) +- **defusedxml**: `pip install defusedxml` (for secure XML parsing) \ No newline at end of file diff --git a/src/flow/skills/docx/docx-js.md b/src/flow/skills/docx/docx-js.md new file mode 100644 index 0000000000000000000000000000000000000000..c6d7b2ddd642ea97ca7fd8acb187b1db6fe5ebd9 --- /dev/null +++ b/src/flow/skills/docx/docx-js.md @@ -0,0 +1,350 @@ +# DOCX Library Tutorial + +Generate .docx files with JavaScript/TypeScript. + +**Important: Read this entire document before starting.** Critical formatting rules and common pitfalls are covered throughout - skipping sections may result in corrupted files or rendering issues. + +## Setup +Assumes docx is already installed globally +If not installed: `npm install -g docx` + +```javascript +const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun, Media, + Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink, + InternalHyperlink, TableOfContents, HeadingLevel, BorderStyle, WidthType, TabStopType, + TabStopPosition, UnderlineType, ShadingType, VerticalAlign, SymbolRun, PageNumber, + FootnoteReferenceRun, Footnote, PageBreak } = require('docx'); + +// Create & Save +const doc = new Document({ sections: [{ children: [/* content */] }] }); +Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer)); // Node.js +Packer.toBlob(doc).then(blob => { /* download logic */ }); // Browser +``` + +## Text & Formatting +```javascript +// IMPORTANT: Never use \n for line breaks - always use separate Paragraph elements +// ❌ WRONG: new TextRun("Line 1\nLine 2") +// ✅ CORRECT: new Paragraph({ children: [new TextRun("Line 1")] }), new Paragraph({ children: [new TextRun("Line 2")] }) + +// Basic text with all formatting options +new Paragraph({ + alignment: AlignmentType.CENTER, + spacing: { before: 200, after: 200 }, + indent: { left: 720, right: 720 }, + children: [ + new TextRun({ text: "Bold", bold: true }), + new TextRun({ text: "Italic", italics: true }), + new TextRun({ text: "Underlined", underline: { type: UnderlineType.DOUBLE, color: "FF0000" } }), + new TextRun({ text: "Colored", color: "FF0000", size: 28, font: "Arial" }), // Arial default + new TextRun({ text: "Highlighted", highlight: "yellow" }), + new TextRun({ text: "Strikethrough", strike: true }), + new TextRun({ text: "x2", superScript: true }), + new TextRun({ text: "H2O", subScript: true }), + new TextRun({ text: "SMALL CAPS", smallCaps: true }), + new SymbolRun({ char: "2022", font: "Symbol" }), // Bullet • + new SymbolRun({ char: "00A9", font: "Arial" }) // Copyright © - Arial for symbols + ] +}) +``` + +## Styles & Professional Formatting + +```javascript +const doc = new Document({ + styles: { + default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt default + paragraphStyles: [ + // Document title style - override built-in Title style + { id: "Title", name: "Title", basedOn: "Normal", + run: { size: 56, bold: true, color: "000000", font: "Arial" }, + paragraph: { spacing: { before: 240, after: 120 }, alignment: AlignmentType.CENTER } }, + // IMPORTANT: Override built-in heading styles by using their exact IDs + { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true, + run: { size: 32, bold: true, color: "000000", font: "Arial" }, // 16pt + paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // Required for TOC + { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true, + run: { size: 28, bold: true, color: "000000", font: "Arial" }, // 14pt + paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } }, + // Custom styles use your own IDs + { id: "myStyle", name: "My Style", basedOn: "Normal", + run: { size: 28, bold: true, color: "000000" }, + paragraph: { spacing: { after: 120 }, alignment: AlignmentType.CENTER } } + ], + characterStyles: [{ id: "myCharStyle", name: "My Char Style", + run: { color: "FF0000", bold: true, underline: { type: UnderlineType.SINGLE } } }] + }, + sections: [{ + properties: { page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } }, + children: [ + new Paragraph({ heading: HeadingLevel.TITLE, children: [new TextRun("Document Title")] }), // Uses overridden Title style + new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Heading 1")] }), // Uses overridden Heading1 style + new Paragraph({ style: "myStyle", children: [new TextRun("Custom paragraph style")] }), + new Paragraph({ children: [ + new TextRun("Normal with "), + new TextRun({ text: "custom char style", style: "myCharStyle" }) + ]}) + ] + }] +}); +``` + +**Professional Font Combinations:** +- **Arial (Headers) + Arial (Body)** - Most universally supported, clean and professional +- **Times New Roman (Headers) + Arial (Body)** - Classic serif headers with modern sans-serif body +- **Georgia (Headers) + Verdana (Body)** - Optimized for screen reading, elegant contrast + +**Key Styling Principles:** +- **Override built-in styles**: Use exact IDs like "Heading1", "Heading2", "Heading3" to override Word's built-in heading styles +- **HeadingLevel constants**: `HeadingLevel.HEADING_1` uses "Heading1" style, `HeadingLevel.HEADING_2` uses "Heading2" style, etc. +- **Include outlineLevel**: Set `outlineLevel: 0` for H1, `outlineLevel: 1` for H2, etc. to ensure TOC works correctly +- **Use custom styles** instead of inline formatting for consistency +- **Set a default font** using `styles.default.document.run.font` - Arial is universally supported +- **Establish visual hierarchy** with different font sizes (titles > headers > body) +- **Add proper spacing** with `before` and `after` paragraph spacing +- **Use colors sparingly**: Default to black (000000) and shades of gray for titles and headings (heading 1, heading 2, etc.) +- **Set consistent margins** (1440 = 1 inch is standard) + + +## Lists (ALWAYS USE PROPER LISTS - NEVER USE UNICODE BULLETS) +```javascript +// Bullets - ALWAYS use the numbering config, NOT unicode symbols +// CRITICAL: Use LevelFormat.BULLET constant, NOT the string "bullet" +const doc = new Document({ + numbering: { + config: [ + { reference: "bullet-list", + levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT, + style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, + { reference: "first-numbered-list", + levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT, + style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, + { reference: "second-numbered-list", // Different reference = restarts at 1 + levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT, + style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] } + ] + }, + sections: [{ + children: [ + // Bullet list items + new Paragraph({ numbering: { reference: "bullet-list", level: 0 }, + children: [new TextRun("First bullet point")] }), + new Paragraph({ numbering: { reference: "bullet-list", level: 0 }, + children: [new TextRun("Second bullet point")] }), + // Numbered list items + new Paragraph({ numbering: { reference: "first-numbered-list", level: 0 }, + children: [new TextRun("First numbered item")] }), + new Paragraph({ numbering: { reference: "first-numbered-list", level: 0 }, + children: [new TextRun("Second numbered item")] }), + // ⚠️ CRITICAL: Different reference = INDEPENDENT list that restarts at 1 + // Same reference = CONTINUES previous numbering + new Paragraph({ numbering: { reference: "second-numbered-list", level: 0 }, + children: [new TextRun("Starts at 1 again (because different reference)")] }) + ] + }] +}); + +// ⚠️ CRITICAL NUMBERING RULE: Each reference creates an INDEPENDENT numbered list +// - Same reference = continues numbering (1, 2, 3... then 4, 5, 6...) +// - Different reference = restarts at 1 (1, 2, 3... then 1, 2, 3...) +// Use unique reference names for each separate numbered section! + +// ⚠️ CRITICAL: NEVER use unicode bullets - they create fake lists that don't work properly +// new TextRun("• Item") // WRONG +// new SymbolRun({ char: "2022" }) // WRONG +// ✅ ALWAYS use numbering config with LevelFormat.BULLET for real Word lists +``` + +## Tables +```javascript +// Complete table with margins, borders, headers, and bullet points +const tableBorder = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" }; +const cellBorders = { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder }; + +new Table({ + columnWidths: [4680, 4680], // ⚠️ CRITICAL: Set column widths at table level - values in DXA (twentieths of a point) + margins: { top: 100, bottom: 100, left: 180, right: 180 }, // Set once for all cells + rows: [ + new TableRow({ + tableHeader: true, + children: [ + new TableCell({ + borders: cellBorders, + width: { size: 4680, type: WidthType.DXA }, // ALSO set width on each cell + // ⚠️ CRITICAL: Always use ShadingType.CLEAR to prevent black backgrounds in Word. + shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, + verticalAlign: VerticalAlign.CENTER, + children: [new Paragraph({ + alignment: AlignmentType.CENTER, + children: [new TextRun({ text: "Header", bold: true, size: 22 })] + })] + }), + new TableCell({ + borders: cellBorders, + width: { size: 4680, type: WidthType.DXA }, // ALSO set width on each cell + shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, + children: [new Paragraph({ + alignment: AlignmentType.CENTER, + children: [new TextRun({ text: "Bullet Points", bold: true, size: 22 })] + })] + }) + ] + }), + new TableRow({ + children: [ + new TableCell({ + borders: cellBorders, + width: { size: 4680, type: WidthType.DXA }, // ALSO set width on each cell + children: [new Paragraph({ children: [new TextRun("Regular data")] })] + }), + new TableCell({ + borders: cellBorders, + width: { size: 4680, type: WidthType.DXA }, // ALSO set width on each cell + children: [ + new Paragraph({ + numbering: { reference: "bullet-list", level: 0 }, + children: [new TextRun("First bullet point")] + }), + new Paragraph({ + numbering: { reference: "bullet-list", level: 0 }, + children: [new TextRun("Second bullet point")] + }) + ] + }) + ] + }) + ] +}) +``` + +**IMPORTANT: Table Width & Borders** +- Use BOTH `columnWidths: [width1, width2, ...]` array AND `width: { size: X, type: WidthType.DXA }` on each cell +- Values in DXA (twentieths of a point): 1440 = 1 inch, Letter usable width = 9360 DXA (with 1" margins) +- Apply borders to individual `TableCell` elements, NOT the `Table` itself + +**Precomputed Column Widths (Letter size with 1" margins = 9360 DXA total):** +- **2 columns:** `columnWidths: [4680, 4680]` (equal width) +- **3 columns:** `columnWidths: [3120, 3120, 3120]` (equal width) + +## Links & Navigation +```javascript +// TOC (requires headings) - CRITICAL: Use HeadingLevel only, NOT custom styles +// ❌ WRONG: new Paragraph({ heading: HeadingLevel.HEADING_1, style: "customHeader", children: [new TextRun("Title")] }) +// ✅ CORRECT: new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] }) +new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" }), + +// External link +new Paragraph({ + children: [new ExternalHyperlink({ + children: [new TextRun({ text: "Google", style: "Hyperlink" })], + link: "https://www.google.com" + })] +}), + +// Internal link & bookmark +new Paragraph({ + children: [new InternalHyperlink({ + children: [new TextRun({ text: "Go to Section", style: "Hyperlink" })], + anchor: "section1" + })] +}), +new Paragraph({ + children: [new TextRun("Section Content")], + bookmark: { id: "section1", name: "section1" } +}), +``` + +## Images & Media +```javascript +// Basic image with sizing & positioning +// CRITICAL: Always specify 'type' parameter - it's REQUIRED for ImageRun +new Paragraph({ + alignment: AlignmentType.CENTER, + children: [new ImageRun({ + type: "png", // NEW REQUIREMENT: Must specify image type (png, jpg, jpeg, gif, bmp, svg) + data: fs.readFileSync("image.png"), + transformation: { width: 200, height: 150, rotation: 0 }, // rotation in degrees + altText: { title: "Logo", description: "Company logo", name: "Name" } // IMPORTANT: All three fields are required + })] +}) +``` + +## Page Breaks +```javascript +// Manual page break +new Paragraph({ children: [new PageBreak()] }), + +// Page break before paragraph +new Paragraph({ + pageBreakBefore: true, + children: [new TextRun("This starts on a new page")] +}) + +// ⚠️ CRITICAL: NEVER use PageBreak standalone - it will create invalid XML that Word cannot open +// ❌ WRONG: new PageBreak() +// ✅ CORRECT: new Paragraph({ children: [new PageBreak()] }) +``` + +## Headers/Footers & Page Setup +```javascript +const doc = new Document({ + sections: [{ + properties: { + page: { + margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }, // 1440 = 1 inch + size: { orientation: PageOrientation.LANDSCAPE }, + pageNumbers: { start: 1, formatType: "decimal" } // "upperRoman", "lowerRoman", "upperLetter", "lowerLetter" + } + }, + headers: { + default: new Header({ children: [new Paragraph({ + alignment: AlignmentType.RIGHT, + children: [new TextRun("Header Text")] + })] }) + }, + footers: { + default: new Footer({ children: [new Paragraph({ + alignment: AlignmentType.CENTER, + children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] }), new TextRun(" of "), new TextRun({ children: [PageNumber.TOTAL_PAGES] })] + })] }) + }, + children: [/* content */] + }] +}); +``` + +## Tabs +```javascript +new Paragraph({ + tabStops: [ + { type: TabStopType.LEFT, position: TabStopPosition.MAX / 4 }, + { type: TabStopType.CENTER, position: TabStopPosition.MAX / 2 }, + { type: TabStopType.RIGHT, position: TabStopPosition.MAX * 3 / 4 } + ], + children: [new TextRun("Left\tCenter\tRight")] +}) +``` + +## Constants & Quick Reference +- **Underlines:** `SINGLE`, `DOUBLE`, `WAVY`, `DASH` +- **Borders:** `SINGLE`, `DOUBLE`, `DASHED`, `DOTTED` +- **Numbering:** `DECIMAL` (1,2,3), `UPPER_ROMAN` (I,II,III), `LOWER_LETTER` (a,b,c) +- **Tabs:** `LEFT`, `CENTER`, `RIGHT`, `DECIMAL` +- **Symbols:** `"2022"` (•), `"00A9"` (©), `"00AE"` (®), `"2122"` (™), `"00B0"` (°), `"F070"` (✓), `"F0FC"` (✗) + +## Critical Issues & Common Mistakes +- **CRITICAL: PageBreak must ALWAYS be inside a Paragraph** - standalone PageBreak creates invalid XML that Word cannot open +- **ALWAYS use ShadingType.CLEAR for table cell shading** - Never use ShadingType.SOLID (causes black background). +- Measurements in DXA (1440 = 1 inch) | Each table cell needs ≥1 Paragraph | TOC requires HeadingLevel styles only +- **ALWAYS use custom styles** with Arial font for professional appearance and proper visual hierarchy +- **ALWAYS set a default font** using `styles.default.document.run.font` - Arial recommended +- **ALWAYS use columnWidths array for tables** + individual cell widths for compatibility +- **NEVER use unicode symbols for bullets** - always use proper numbering configuration with `LevelFormat.BULLET` constant (NOT the string "bullet") +- **NEVER use \n for line breaks anywhere** - always use separate Paragraph elements for each line +- **ALWAYS use TextRun objects within Paragraph children** - never use text property directly on Paragraph +- **CRITICAL for images**: ImageRun REQUIRES `type` parameter - always specify "png", "jpg", "jpeg", "gif", "bmp", or "svg" +- **CRITICAL for bullets**: Must use `LevelFormat.BULLET` constant, not string "bullet", and include `text: "•"` for the bullet character +- **CRITICAL for numbering**: Each numbering reference creates an INDEPENDENT list. Same reference = continues numbering (1,2,3 then 4,5,6). Different reference = restarts at 1 (1,2,3 then 1,2,3). Use unique reference names for each separate numbered section! +- **CRITICAL for TOC**: When using TableOfContents, headings must use HeadingLevel ONLY - do NOT add custom styles to heading paragraphs or TOC will break +- **Tables**: Set `columnWidths` array + individual cell widths, apply borders to cells not table +- **Set table margins at TABLE level** for consistent cell padding (avoids repetition per cell) \ No newline at end of file diff --git a/src/flow/skills/docx/ooxml.md b/src/flow/skills/docx/ooxml.md new file mode 100644 index 0000000000000000000000000000000000000000..7677e7b83694e9667c07606b15bb75bfd8acce0f --- /dev/null +++ b/src/flow/skills/docx/ooxml.md @@ -0,0 +1,610 @@ +# Office Open XML Technical Reference + +**Important: Read this entire document before starting.** This document covers: +- [Technical Guidelines](#technical-guidelines) - Schema compliance rules and validation requirements +- [Document Content Patterns](#document-content-patterns) - XML patterns for headings, lists, tables, formatting, etc. +- [Document Library (Python)](#document-library-python) - Recommended approach for OOXML manipulation with automatic infrastructure setup +- [Tracked Changes (Redlining)](#tracked-changes-redlining) - XML patterns for implementing tracked changes + +## Technical Guidelines + +### Schema Compliance +- **Element ordering in ``**: ``, ``, ``, ``, `` +- **Whitespace**: Add `xml:space='preserve'` to `` elements with leading/trailing spaces +- **Unicode**: Escape characters in ASCII content: `"` becomes `“` + - **Character encoding reference**: Curly quotes `""` become `“”`, apostrophe `'` becomes `’`, em-dash `—` becomes `—` +- **Tracked changes**: Use `` and `` tags with `w:author="Claude"` outside `` elements + - **Critical**: `` closes with ``, `` closes with `` - never mix + - **RSIDs must be 8-digit hex**: Use values like `00AB1234` (only 0-9, A-F characters) + - **trackRevisions placement**: Add `` after `` in settings.xml +- **Images**: Add to `word/media/`, reference in `document.xml`, set dimensions to prevent overflow + +## Document Content Patterns + +### Basic Structure +```xml + + Text content + +``` + +### Headings and Styles +```xml + + + + + + Document Title + + + + + Section Heading + +``` + +### Text Formatting +```xml + +Bold + +Italic + +Underlined + +Highlighted +``` + +### Lists +```xml + + + + + + + + First item + + + + + + + + + + New list item 1 + + + + + + + + + + + Bullet item + +``` + +### Tables +```xml + + + + + + + + + + + + Cell 1 + + + + Cell 2 + + + +``` + +### Layout +```xml + + + + + + + + + + + + New Section Title + + + + + + + + + + Centered text + + + + + + + + Monospace text + + + + + + + This text is Courier New + + and this text uses default font + +``` + +## File Updates + +When adding content, update these files: + +**`word/_rels/document.xml.rels`:** +```xml + + +``` + +**`[Content_Types].xml`:** +```xml + + +``` + +### Images +**CRITICAL**: Calculate dimensions to prevent page overflow and maintain aspect ratio. + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### Links (Hyperlinks) + +**IMPORTANT**: All hyperlinks (both internal and external) require the Hyperlink style to be defined in styles.xml. Without this style, links will look like regular text instead of blue underlined clickable links. + +**External Links:** +```xml + + + + + Link Text + + + + + +``` + +**Internal Links:** + +```xml + + + + + Link Text + + + + + +Target content + +``` + +**Hyperlink Style (required in styles.xml):** +```xml + + + + + + + + + + +``` + +## Document Library (Python) + +Use the Document class from `scripts/document.py` for all tracked changes and comments. It automatically handles infrastructure setup (people.xml, RSIDs, settings.xml, comment files, relationships, content types). Only use direct XML manipulation for complex scenarios not supported by the library. + +**Working with Unicode and Entities:** +- **Searching**: Both entity notation and Unicode characters work - `contains="“Company"` and `contains="\u201cCompany"` find the same text +- **Replacing**: Use either entities (`“`) or Unicode (`\u201c`) - both work and will be converted appropriately based on the file's encoding (ascii → entities, utf-8 → Unicode) + +### Initialization + +**Find the docx skill root** (directory containing `scripts/` and `ooxml/`): +```bash +# Search for document.py to locate the skill root +# Note: /mnt/skills is used here as an example; check your context for the actual location +find /mnt/skills -name "document.py" -path "*/docx/scripts/*" 2>/dev/null | head -1 +# Example output: /mnt/skills/docx/scripts/document.py +# Skill root is: /mnt/skills/docx +``` + +**Run your script with PYTHONPATH** set to the docx skill root: +```bash +PYTHONPATH=/mnt/skills/docx python your_script.py +``` + +**In your script**, import from the skill root: +```python +from scripts.document import Document, DocxXMLEditor + +# Basic initialization (automatically creates temp copy and sets up infrastructure) +doc = Document('unpacked') + +# Customize author and initials +doc = Document('unpacked', author="John Doe", initials="JD") + +# Enable track revisions mode +doc = Document('unpacked', track_revisions=True) + +# Specify custom RSID (auto-generated if not provided) +doc = Document('unpacked', rsid="07DC5ECB") +``` + +### Creating Tracked Changes + +**CRITICAL**: Only mark text that actually changes. Keep ALL unchanged text outside ``/`` tags. Marking unchanged text makes edits unprofessional and harder to review. + +**Attribute Handling**: The Document class auto-injects attributes (w:id, w:date, w:rsidR, w:rsidDel, w16du:dateUtc, xml:space) into new elements. When preserving unchanged text from the original document, copy the original `` element with its existing attributes to maintain document integrity. + +**Method Selection Guide**: +- **Adding your own changes to regular text**: Use `replace_node()` with ``/`` tags, or `suggest_deletion()` for removing entire `` or `` elements +- **Partially modifying another author's tracked change**: Use `replace_node()` to nest your changes inside their ``/`` +- **Completely rejecting another author's insertion**: Use `revert_insertion()` on the `` element (NOT `suggest_deletion()`) +- **Completely rejecting another author's deletion**: Use `revert_deletion()` on the `` element to restore deleted content using tracked changes + +```python +# Minimal edit - change one word: "The report is monthly" → "The report is quarterly" +# Original: The report is monthly +node = doc["word/document.xml"].get_node(tag="w:r", contains="The report is monthly") +rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else "" +replacement = f'{rpr}The report is {rpr}monthly{rpr}quarterly' +doc["word/document.xml"].replace_node(node, replacement) + +# Minimal edit - change number: "within 30 days" → "within 45 days" +# Original: within 30 days +node = doc["word/document.xml"].get_node(tag="w:r", contains="within 30 days") +rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else "" +replacement = f'{rpr}within {rpr}30{rpr}45{rpr} days' +doc["word/document.xml"].replace_node(node, replacement) + +# Complete replacement - preserve formatting even when replacing all text +node = doc["word/document.xml"].get_node(tag="w:r", contains="apple") +rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else "" +replacement = f'{rpr}apple{rpr}banana orange' +doc["word/document.xml"].replace_node(node, replacement) + +# Insert new content (no attributes needed - auto-injected) +node = doc["word/document.xml"].get_node(tag="w:r", contains="existing text") +doc["word/document.xml"].insert_after(node, 'new text') + +# Partially delete another author's insertion +# Original: quarterly financial report +# Goal: Delete only "financial" to make it "quarterly report" +node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "5"}) +# IMPORTANT: Preserve w:author="Jane Smith" on the outer to maintain authorship +replacement = ''' + quarterly + financial + report +''' +doc["word/document.xml"].replace_node(node, replacement) + +# Change part of another author's insertion +# Original: in silence, safe and sound +# Goal: Change "safe and sound" to "soft and unbound" +node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "8"}) +replacement = f''' + in silence, + + + soft and unbound + + + safe and sound +''' +doc["word/document.xml"].replace_node(node, replacement) + +# Delete entire run (use only when deleting all content; use replace_node for partial deletions) +node = doc["word/document.xml"].get_node(tag="w:r", contains="text to delete") +doc["word/document.xml"].suggest_deletion(node) + +# Delete entire paragraph (in-place, handles both regular and numbered list paragraphs) +para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph to delete") +doc["word/document.xml"].suggest_deletion(para) + +# Add new numbered list item +target_para = doc["word/document.xml"].get_node(tag="w:p", contains="existing list item") +pPr = tags[0].toxml() if (tags := target_para.getElementsByTagName("w:pPr")) else "" +new_item = f'{pPr}New item' +tracked_para = DocxXMLEditor.suggest_paragraph(new_item) +doc["word/document.xml"].insert_after(target_para, tracked_para) +# Optional: add spacing paragraph before content for better visual separation +# spacing = DocxXMLEditor.suggest_paragraph('') +# doc["word/document.xml"].insert_after(target_para, spacing + tracked_para) +``` + +### Adding Comments + +```python +# Add comment spanning two existing tracked changes +# Note: w:id is auto-generated. Only search by w:id if you know it from XML inspection +start_node = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "1"}) +end_node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "2"}) +doc.add_comment(start=start_node, end=end_node, text="Explanation of this change") + +# Add comment on a paragraph +para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text") +doc.add_comment(start=para, end=para, text="Comment on this paragraph") + +# Add comment on newly created tracked change +# First create the tracked change +node = doc["word/document.xml"].get_node(tag="w:r", contains="old") +new_nodes = doc["word/document.xml"].replace_node( + node, + 'oldnew' +) +# Then add comment on the newly created elements +# new_nodes[0] is the , new_nodes[1] is the +doc.add_comment(start=new_nodes[0], end=new_nodes[1], text="Changed old to new per requirements") + +# Reply to existing comment +doc.reply_to_comment(parent_comment_id=0, text="I agree with this change") +``` + +### Rejecting Tracked Changes + +**IMPORTANT**: Use `revert_insertion()` to reject insertions and `revert_deletion()` to restore deletions using tracked changes. Use `suggest_deletion()` only for regular unmarked content. + +```python +# Reject insertion (wraps it in deletion) +# Use this when another author inserted text that you want to delete +ins = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "5"}) +nodes = doc["word/document.xml"].revert_insertion(ins) # Returns [ins] + +# Reject deletion (creates insertion to restore deleted content) +# Use this when another author deleted text that you want to restore +del_elem = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "3"}) +nodes = doc["word/document.xml"].revert_deletion(del_elem) # Returns [del_elem, new_ins] + +# Reject all insertions in a paragraph +para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text") +nodes = doc["word/document.xml"].revert_insertion(para) # Returns [para] + +# Reject all deletions in a paragraph +para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text") +nodes = doc["word/document.xml"].revert_deletion(para) # Returns [para] +``` + +### Inserting Images + +**CRITICAL**: The Document class works with a temporary copy at `doc.unpacked_path`. Always copy images to this temp directory, not the original unpacked folder. + +```python +from PIL import Image +import shutil, os + +# Initialize document first +doc = Document('unpacked') + +# Copy image and calculate full-width dimensions with aspect ratio +media_dir = os.path.join(doc.unpacked_path, 'word/media') +os.makedirs(media_dir, exist_ok=True) +shutil.copy('image.png', os.path.join(media_dir, 'image1.png')) +img = Image.open(os.path.join(media_dir, 'image1.png')) +width_emus = int(6.5 * 914400) # 6.5" usable width, 914400 EMUs/inch +height_emus = int(width_emus * img.size[1] / img.size[0]) + +# Add relationship and content type +rels_editor = doc['word/_rels/document.xml.rels'] +next_rid = rels_editor.get_next_rid() +rels_editor.append_to(rels_editor.dom.documentElement, + f'') +doc['[Content_Types].xml'].append_to(doc['[Content_Types].xml'].dom.documentElement, + '') + +# Insert image +node = doc["word/document.xml"].get_node(tag="w:p", line_number=100) +doc["word/document.xml"].insert_after(node, f''' + + + + + + + + + + + + + + + + + +''') +``` + +### Getting Nodes + +```python +# By text content +node = doc["word/document.xml"].get_node(tag="w:p", contains="specific text") + +# By line range +para = doc["word/document.xml"].get_node(tag="w:p", line_number=range(100, 150)) + +# By attributes +node = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "1"}) + +# By exact line number (must be line number where tag opens) +para = doc["word/document.xml"].get_node(tag="w:p", line_number=42) + +# Combine filters +node = doc["word/document.xml"].get_node(tag="w:r", line_number=range(40, 60), contains="text") + +# Disambiguate when text appears multiple times - add line_number range +node = doc["word/document.xml"].get_node(tag="w:r", contains="Section", line_number=range(2400, 2500)) +``` + +### Saving + +```python +# Save with automatic validation (copies back to original directory) +doc.save() # Validates by default, raises error if validation fails + +# Save to different location +doc.save('modified-unpacked') + +# Skip validation (debugging only - needing this in production indicates XML issues) +doc.save(validate=False) +``` + +### Direct DOM Manipulation + +For complex scenarios not covered by the library: + +```python +# Access any XML file +editor = doc["word/document.xml"] +editor = doc["word/comments.xml"] + +# Direct DOM access (defusedxml.minidom.Document) +node = doc["word/document.xml"].get_node(tag="w:p", line_number=5) +parent = node.parentNode +parent.removeChild(node) +parent.appendChild(node) # Move to end + +# General document manipulation (without tracked changes) +old_node = doc["word/document.xml"].get_node(tag="w:p", contains="original text") +doc["word/document.xml"].replace_node(old_node, "replacement text") + +# Multiple insertions - use return value to maintain order +node = doc["word/document.xml"].get_node(tag="w:r", line_number=100) +nodes = doc["word/document.xml"].insert_after(node, "A") +nodes = doc["word/document.xml"].insert_after(nodes[-1], "B") +nodes = doc["word/document.xml"].insert_after(nodes[-1], "C") +# Results in: original_node, A, B, C +``` + +## Tracked Changes (Redlining) + +**Use the Document class above for all tracked changes.** The patterns below are for reference when constructing replacement XML strings. + +### Validation Rules +The validator checks that the document text matches the original after reverting Claude's changes. This means: +- **NEVER modify text inside another author's `` or `` tags** +- **ALWAYS use nested deletions** to remove another author's insertions +- **Every edit must be properly tracked** with `` or `` tags + +### Tracked Change Patterns + +**CRITICAL RULES**: +1. Never modify the content inside another author's tracked changes. Always use nested deletions. +2. **XML Structure**: Always place `` and `` at paragraph level containing complete `` elements. Never nest inside `` elements - this creates invalid XML that breaks document processing. + +**Text Insertion:** +```xml + + + inserted text + + +``` + +**Text Deletion:** +```xml + + + deleted text + + +``` + +**Deleting Another Author's Insertion (MUST use nested structure):** +```xml + + + + monthly + + + + weekly + +``` + +**Restoring Another Author's Deletion:** +```xml + + + within 30 days + + + within 30 days + +``` \ No newline at end of file diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6454ef9a94d52512e4905df61280a3fd1dafc0a4 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd @@ -0,0 +1,1499 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..afa4f463e3140a3f626c73f7966d689270d89b2c --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd new file mode 100644 index 0000000000000000000000000000000000000000..64e66b8abd496d2e450c08110e40607e90b3fe13 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd @@ -0,0 +1,1085 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd new file mode 100644 index 0000000000000000000000000000000000000000..687eea8297caa298581aa06c40ca40ef7b8a58bc --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd @@ -0,0 +1,11 @@ + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6ac81b06b7a3ef916d677953084ae225504a77bc --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd @@ -0,0 +1,3081 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd new file mode 100644 index 0000000000000000000000000000000000000000..1dbf05140d07fa014f18a5630acc0613d8058e52 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f1af17db4e83b1c40152577eca4a575ab63a1ca7 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..0a185ab6ed0c2dd9b383a403c56c909bbab8b9a1 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..14ef488865f3ae6150776220ebbcf4788f89066a --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd @@ -0,0 +1,1676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd new file mode 100644 index 0000000000000000000000000000000000000000..c20f3bf14720d34247e8e1302dc3c40f7b832099 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ac60252262534e7b87523fd04d252f085b38bec0 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd new file mode 100644 index 0000000000000000000000000000000000000000..424b8ba8d1f9ebb22ba72cdd174c8a2ade0ecf89 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2bddce29214882eb9fdf2be8a50ed5bdd0c69dfc --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd new file mode 100644 index 0000000000000000000000000000000000000000..8a8c18ba2d5caa7e60a365e77ae0d1b872141bb0 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd new file mode 100644 index 0000000000000000000000000000000000000000..5c42706a0d53c5e8b96818d9c29a292d1504f720 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd new file mode 100644 index 0000000000000000000000000000000000000000..853c341c87feb51e10d757aea28b4185ae4c2731 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd new file mode 100644 index 0000000000000000000000000000000000000000..da835ee82d5cc31be9fa9792512930f7ae3fb5ce --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd new file mode 100644 index 0000000000000000000000000000000000000000..87ad2658fa51cb4c172f82d0fdf33a492a750eca --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd @@ -0,0 +1,582 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd new file mode 100644 index 0000000000000000000000000000000000000000..9e86f1b2be0d431cd3c250ddeaf4efabdbcb088b --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..d0be42e757f3cce533c1c80770239f0097d3de6b --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd @@ -0,0 +1,4439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd new file mode 100644 index 0000000000000000000000000000000000000000..8821dd183caf9c8c3b809e0e2d9e0df56c4e9ed9 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd @@ -0,0 +1,570 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ca2575c753be78cdaa96de56b3ff8770173c324d --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd @@ -0,0 +1,509 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..dd079e603f5770176212776a1d565b108875cf64 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..3dd6cf625a740398c2a4dc8abbe18e25e0ac5e9f --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f1041e34ef365926f5670b896f5bb23e553ac36d --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..9c5b7a633411c2313ce839f748e4623bb0a70efe --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd @@ -0,0 +1,3646 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..0f13678d80a762375223f060e23b56c7b2eac89e --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd @@ -0,0 +1,116 @@ + + + + + + See http://www.w3.org/XML/1998/namespace.html and + http://www.w3.org/TR/REC-xml for information about this namespace. + + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. + + Note that local names in this namespace are intended to be defined + only by the World Wide Web Consortium or its subgroups. The + following names are currently defined in this namespace and should + not be used with conflicting semantics by any Working Group, + specification, or document instance: + + base (as an attribute name): denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification. + + lang (as an attribute name): denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification. + + space (as an attribute name): denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification. + + Father (in any context at all): denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: + + In appreciation for his vision, leadership and dedication + the W3C XML Plenary on this 10th day of February, 2000 + reserves for Jon Bosak in perpetuity the XML name + xml:Father + + + + + This schema defines attributes and an attribute group + suitable for use by + schemas wishing to allow xml:base, xml:lang or xml:space attributes + on elements they define. + + To enable this, such a schema must import this schema + for the XML namespace, e.g. as follows: + <schema . . .> + . . . + <import namespace="http://www.w3.org/XML/1998/namespace" + schemaLocation="http://www.w3.org/2001/03/xml.xsd"/> + + Subsequently, qualified reference to any of the attributes + or the group defined below will have the desired effect, e.g. + + <type . . .> + . . . + <attributeGroup ref="xml:specialAttrs"/> + + will define a type which will schema-validate an instance + element with any of those attributes + + + + In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + http://www.w3.org/2001/03/xml.xsd. + At the date of issue it can also be found at + http://www.w3.org/2001/xml.xsd. + The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML Schema + itself. In other words, if the XML Schema namespace changes, the version + of this document at + http://www.w3.org/2001/xml.xsd will change + accordingly; the version at + http://www.w3.org/2001/03/xml.xsd will not change. + + + + + + In due course, we should install the relevant ISO 2- and 3-letter + codes as the enumerated possible values . . . + + + + + + + + + + + + + + + See http://www.w3.org/TR/xmlbase/ for + information about this attribute. + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd b/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd new file mode 100644 index 0000000000000000000000000000000000000000..a6de9d2733d3f0eea12ae87b24122495df1f0928 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd b/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd new file mode 100644 index 0000000000000000000000000000000000000000..10e978b661fc291cbeea9ab0c248601cf216f96d --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd b/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd new file mode 100644 index 0000000000000000000000000000000000000000..4248bf7a39c793c9f319bae7d09f92983efe5368 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd b/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd new file mode 100644 index 0000000000000000000000000000000000000000..56497467120b52b742756379959d4b2536be6df7 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/mce/mc.xsd b/src/flow/skills/docx/ooxml/schemas/mce/mc.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ef725457cf39116672e285d021be69b7ad022574 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/mce/mc.xsd @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f65f777730d82162f4248f03c703b1b85fda5fe2 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6b00755a9a8733f0fb171a47b25d0d7b4c70dee8 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f321d333a5e6ef98cbdfeeeaa334923e4cfc8a6c --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd new file mode 100644 index 0000000000000000000000000000000000000000..364c6a9b8df6e252bb5ae39e2ca031fcbfc4e8a5 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd new file mode 100644 index 0000000000000000000000000000000000000000..fed9d15b7f504e14ac82793d79db2d7ad83d56ed --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd new file mode 100644 index 0000000000000000000000000000000000000000..680cf15400cd57ce2bf17a58ecd29caa0d8a8422 --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd @@ -0,0 +1,4 @@ + + + + diff --git a/src/flow/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd new file mode 100644 index 0000000000000000000000000000000000000000..89ada90837b2db3f3d453212ae426520acedbbea --- /dev/null +++ b/src/flow/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/flow/skills/docx/ooxml/scripts/pack.py b/src/flow/skills/docx/ooxml/scripts/pack.py new file mode 100755 index 0000000000000000000000000000000000000000..68bc0886f6ef74e6e1d3fe6da0d6a296858a42d8 --- /dev/null +++ b/src/flow/skills/docx/ooxml/scripts/pack.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +""" +Tool to pack a directory into a .docx, .pptx, or .xlsx file with XML formatting undone. + +Example usage: + python pack.py [--force] +""" + +import argparse +import shutil +import subprocess +import sys +import tempfile +import defusedxml.minidom +import zipfile +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser(description="Pack a directory into an Office file") + parser.add_argument("input_directory", help="Unpacked Office document directory") + parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)") + parser.add_argument("--force", action="store_true", help="Skip validation") + args = parser.parse_args() + + try: + success = pack_document( + args.input_directory, args.output_file, validate=not args.force + ) + + # Show warning if validation was skipped + if args.force: + print("Warning: Skipped validation, file may be corrupt", file=sys.stderr) + # Exit with error if validation failed + elif not success: + print("Contents would produce a corrupt file.", file=sys.stderr) + print("Please validate XML before repacking.", file=sys.stderr) + print("Use --force to skip validation and pack anyway.", file=sys.stderr) + sys.exit(1) + + except ValueError as e: + sys.exit(f"Error: {e}") + + +def pack_document(input_dir, output_file, validate=False): + """Pack a directory into an Office file (.docx/.pptx/.xlsx). + + Args: + input_dir: Path to unpacked Office document directory + output_file: Path to output Office file + validate: If True, validates with soffice (default: False) + + Returns: + bool: True if successful, False if validation failed + """ + input_dir = Path(input_dir) + output_file = Path(output_file) + + if not input_dir.is_dir(): + raise ValueError(f"{input_dir} is not a directory") + if output_file.suffix.lower() not in {".docx", ".pptx", ".xlsx"}: + raise ValueError(f"{output_file} must be a .docx, .pptx, or .xlsx file") + + # Work in temporary directory to avoid modifying original + with tempfile.TemporaryDirectory() as temp_dir: + temp_content_dir = Path(temp_dir) / "content" + shutil.copytree(input_dir, temp_content_dir) + + # Process XML files to remove pretty-printing whitespace + for pattern in ["*.xml", "*.rels"]: + for xml_file in temp_content_dir.rglob(pattern): + condense_xml(xml_file) + + # Create final Office file as zip archive + output_file.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as zf: + for f in temp_content_dir.rglob("*"): + if f.is_file(): + zf.write(f, f.relative_to(temp_content_dir)) + + # Validate if requested + if validate: + if not validate_document(output_file): + output_file.unlink() # Delete the corrupt file + return False + + return True + + +def validate_document(doc_path): + """Validate document by converting to HTML with soffice.""" + # Determine the correct filter based on file extension + match doc_path.suffix.lower(): + case ".docx": + filter_name = "html:HTML" + case ".pptx": + filter_name = "html:impress_html_Export" + case ".xlsx": + filter_name = "html:HTML (StarCalc)" + + with tempfile.TemporaryDirectory() as temp_dir: + try: + result = subprocess.run( + [ + "soffice", + "--headless", + "--convert-to", + filter_name, + "--outdir", + temp_dir, + str(doc_path), + ], + capture_output=True, + timeout=10, + text=True, + ) + if not (Path(temp_dir) / f"{doc_path.stem}.html").exists(): + error_msg = result.stderr.strip() or "Document validation failed" + print(f"Validation error: {error_msg}", file=sys.stderr) + return False + return True + except FileNotFoundError: + print("Warning: soffice not found. Skipping validation.", file=sys.stderr) + return True + except subprocess.TimeoutExpired: + print("Validation error: Timeout during conversion", file=sys.stderr) + return False + except Exception as e: + print(f"Validation error: {e}", file=sys.stderr) + return False + + +def condense_xml(xml_file): + """Strip unnecessary whitespace and remove comments.""" + with open(xml_file, "r", encoding="utf-8") as f: + dom = defusedxml.minidom.parse(f) + + # Process each element to remove whitespace and comments + for element in dom.getElementsByTagName("*"): + # Skip w:t elements and their processing + if element.tagName.endswith(":t"): + continue + + # Remove whitespace-only text nodes and comment nodes + for child in list(element.childNodes): + if ( + child.nodeType == child.TEXT_NODE + and child.nodeValue + and child.nodeValue.strip() == "" + ) or child.nodeType == child.COMMENT_NODE: + element.removeChild(child) + + # Write back the condensed XML + with open(xml_file, "wb") as f: + f.write(dom.toxml(encoding="UTF-8")) + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/docx/ooxml/scripts/unpack.py b/src/flow/skills/docx/ooxml/scripts/unpack.py new file mode 100755 index 0000000000000000000000000000000000000000..4938798813e52daf03ab859f3c2faeece7f3aeea --- /dev/null +++ b/src/flow/skills/docx/ooxml/scripts/unpack.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Unpack and format XML contents of Office files (.docx, .pptx, .xlsx)""" + +import random +import sys +import defusedxml.minidom +import zipfile +from pathlib import Path + +# Get command line arguments +assert len(sys.argv) == 3, "Usage: python unpack.py " +input_file, output_dir = sys.argv[1], sys.argv[2] + +# Extract and format +output_path = Path(output_dir) +output_path.mkdir(parents=True, exist_ok=True) +zipfile.ZipFile(input_file).extractall(output_path) + +# Pretty print all XML files +xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels")) +for xml_file in xml_files: + content = xml_file.read_text(encoding="utf-8") + dom = defusedxml.minidom.parseString(content) + xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="ascii")) + +# For .docx files, suggest an RSID for tracked changes +if input_file.endswith(".docx"): + suggested_rsid = "".join(random.choices("0123456789ABCDEF", k=8)) + print(f"Suggested RSID for edit session: {suggested_rsid}") diff --git a/src/flow/skills/docx/ooxml/scripts/validate.py b/src/flow/skills/docx/ooxml/scripts/validate.py new file mode 100755 index 0000000000000000000000000000000000000000..508c5891faba622051a8d5566c118de7892d95bf --- /dev/null +++ b/src/flow/skills/docx/ooxml/scripts/validate.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +Command line tool to validate Office document XML files against XSD schemas and tracked changes. + +Usage: + python validate.py --original +""" + +import argparse +import sys +from pathlib import Path + +from validation import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator + + +def main(): + parser = argparse.ArgumentParser(description="Validate Office document XML files") + parser.add_argument( + "unpacked_dir", + help="Path to unpacked Office document directory", + ) + parser.add_argument( + "--original", + required=True, + help="Path to original file (.docx/.pptx/.xlsx)", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Enable verbose output", + ) + args = parser.parse_args() + + # Validate paths + unpacked_dir = Path(args.unpacked_dir) + original_file = Path(args.original) + file_extension = original_file.suffix.lower() + assert unpacked_dir.is_dir(), f"Error: {unpacked_dir} is not a directory" + assert original_file.is_file(), f"Error: {original_file} is not a file" + assert file_extension in [".docx", ".pptx", ".xlsx"], ( + f"Error: {original_file} must be a .docx, .pptx, or .xlsx file" + ) + + # Run validations + match file_extension: + case ".docx": + validators = [DOCXSchemaValidator, RedliningValidator] + case ".pptx": + validators = [PPTXSchemaValidator] + case _: + print(f"Error: Validation not supported for file type {file_extension}") + sys.exit(1) + + # Run validators + success = True + for V in validators: + validator = V(unpacked_dir, original_file, verbose=args.verbose) + if not validator.validate(): + success = False + + if success: + print("All validations PASSED!") + + sys.exit(0 if success else 1) + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/docx/ooxml/scripts/validation/__init__.py b/src/flow/skills/docx/ooxml/scripts/validation/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..db092ece7e21de98f87bd81471578259a1bc642f --- /dev/null +++ b/src/flow/skills/docx/ooxml/scripts/validation/__init__.py @@ -0,0 +1,15 @@ +""" +Validation modules for Word document processing. +""" + +from .base import BaseSchemaValidator +from .docx import DOCXSchemaValidator +from .pptx import PPTXSchemaValidator +from .redlining import RedliningValidator + +__all__ = [ + "BaseSchemaValidator", + "DOCXSchemaValidator", + "PPTXSchemaValidator", + "RedliningValidator", +] diff --git a/src/flow/skills/docx/ooxml/scripts/validation/base.py b/src/flow/skills/docx/ooxml/scripts/validation/base.py new file mode 100644 index 0000000000000000000000000000000000000000..0681b199c2f0539763eb115596acd82ca9c53bf3 --- /dev/null +++ b/src/flow/skills/docx/ooxml/scripts/validation/base.py @@ -0,0 +1,951 @@ +""" +Base validator with common validation logic for document files. +""" + +import re +from pathlib import Path + +import lxml.etree + + +class BaseSchemaValidator: + """Base validator with common validation logic for document files.""" + + # Elements whose 'id' attributes must be unique within their file + # Format: element_name -> (attribute_name, scope) + # scope can be 'file' (unique within file) or 'global' (unique across all files) + UNIQUE_ID_REQUIREMENTS = { + # Word elements + "comment": ("id", "file"), # Comment IDs in comments.xml + "commentrangestart": ("id", "file"), # Must match comment IDs + "commentrangeend": ("id", "file"), # Must match comment IDs + "bookmarkstart": ("id", "file"), # Bookmark start IDs + "bookmarkend": ("id", "file"), # Bookmark end IDs + # Note: ins and del (track changes) can share IDs when part of same revision + # PowerPoint elements + "sldid": ("id", "file"), # Slide IDs in presentation.xml + "sldmasterid": ("id", "global"), # Slide master IDs must be globally unique + "sldlayoutid": ("id", "global"), # Slide layout IDs must be globally unique + "cm": ("authorid", "file"), # Comment author IDs + # Excel elements + "sheet": ("sheetid", "file"), # Sheet IDs in workbook.xml + "definedname": ("id", "file"), # Named range IDs + # Drawing/Shape elements (all formats) + "cxnsp": ("id", "file"), # Connection shape IDs + "sp": ("id", "file"), # Shape IDs + "pic": ("id", "file"), # Picture IDs + "grpsp": ("id", "file"), # Group shape IDs + } + + # Mapping of element names to expected relationship types + # Subclasses should override this with format-specific mappings + ELEMENT_RELATIONSHIP_TYPES = {} + + # Unified schema mappings for all Office document types + SCHEMA_MAPPINGS = { + # Document type specific schemas + "word": "ISO-IEC29500-4_2016/wml.xsd", # Word documents + "ppt": "ISO-IEC29500-4_2016/pml.xsd", # PowerPoint presentations + "xl": "ISO-IEC29500-4_2016/sml.xsd", # Excel spreadsheets + # Common file types + "[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd", + "app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd", + "core.xml": "ecma/fouth-edition/opc-coreProperties.xsd", + "custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd", + ".rels": "ecma/fouth-edition/opc-relationships.xsd", + # Word-specific files + "people.xml": "microsoft/wml-2012.xsd", + "commentsIds.xml": "microsoft/wml-cid-2016.xsd", + "commentsExtensible.xml": "microsoft/wml-cex-2018.xsd", + "commentsExtended.xml": "microsoft/wml-2012.xsd", + # Chart files (common across document types) + "chart": "ISO-IEC29500-4_2016/dml-chart.xsd", + # Theme files (common across document types) + "theme": "ISO-IEC29500-4_2016/dml-main.xsd", + # Drawing and media files + "drawing": "ISO-IEC29500-4_2016/dml-main.xsd", + } + + # Unified namespace constants + MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006" + XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" + + # Common OOXML namespaces used across validators + PACKAGE_RELATIONSHIPS_NAMESPACE = ( + "http://schemas.openxmlformats.org/package/2006/relationships" + ) + OFFICE_RELATIONSHIPS_NAMESPACE = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships" + ) + CONTENT_TYPES_NAMESPACE = ( + "http://schemas.openxmlformats.org/package/2006/content-types" + ) + + # Folders where we should clean ignorable namespaces + MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"} + + # All allowed OOXML namespaces (superset of all document types) + OOXML_NAMESPACES = { + "http://schemas.openxmlformats.org/officeDocument/2006/math", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "http://schemas.openxmlformats.org/schemaLibrary/2006/main", + "http://schemas.openxmlformats.org/drawingml/2006/main", + "http://schemas.openxmlformats.org/drawingml/2006/chart", + "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing", + "http://schemas.openxmlformats.org/drawingml/2006/diagram", + "http://schemas.openxmlformats.org/drawingml/2006/picture", + "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "http://schemas.openxmlformats.org/presentationml/2006/main", + "http://schemas.openxmlformats.org/spreadsheetml/2006/main", + "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes", + "http://www.w3.org/XML/1998/namespace", + } + + def __init__(self, unpacked_dir, original_file, verbose=False): + self.unpacked_dir = Path(unpacked_dir).resolve() + self.original_file = Path(original_file) + self.verbose = verbose + + # Set schemas directory + self.schemas_dir = Path(__file__).parent.parent.parent / "schemas" + + # Get all XML and .rels files + patterns = ["*.xml", "*.rels"] + self.xml_files = [ + f for pattern in patterns for f in self.unpacked_dir.rglob(pattern) + ] + + if not self.xml_files: + print(f"Warning: No XML files found in {self.unpacked_dir}") + + def validate(self): + """Run all validation checks and return True if all pass.""" + raise NotImplementedError("Subclasses must implement the validate method") + + def validate_xml(self): + """Validate that all XML files are well-formed.""" + errors = [] + + for xml_file in self.xml_files: + try: + # Try to parse the XML file + lxml.etree.parse(str(xml_file)) + except lxml.etree.XMLSyntaxError as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {e.lineno}: {e.msg}" + ) + except Exception as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Unexpected error: {str(e)}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} XML violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All XML files are well-formed") + return True + + def validate_namespaces(self): + """Validate that namespace prefixes in Ignorable attributes are declared.""" + errors = [] + + for xml_file in self.xml_files: + try: + root = lxml.etree.parse(str(xml_file)).getroot() + declared = set(root.nsmap.keys()) - {None} # Exclude default namespace + + for attr_val in [ + v for k, v in root.attrib.items() if k.endswith("Ignorable") + ]: + undeclared = set(attr_val.split()) - declared + errors.extend( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Namespace '{ns}' in Ignorable but not declared" + for ns in undeclared + ) + except lxml.etree.XMLSyntaxError: + continue + + if errors: + print(f"FAILED - {len(errors)} namespace issues:") + for error in errors: + print(error) + return False + if self.verbose: + print("PASSED - All namespace prefixes properly declared") + return True + + def validate_unique_ids(self): + """Validate that specific IDs are unique according to OOXML requirements.""" + errors = [] + global_ids = {} # Track globally unique IDs across all files + + for xml_file in self.xml_files: + try: + root = lxml.etree.parse(str(xml_file)).getroot() + file_ids = {} # Track IDs that must be unique within this file + + # Remove all mc:AlternateContent elements from the tree + mc_elements = root.xpath( + ".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE} + ) + for elem in mc_elements: + elem.getparent().remove(elem) + + # Now check IDs in the cleaned tree + for elem in root.iter(): + # Get the element name without namespace + tag = ( + elem.tag.split("}")[-1].lower() + if "}" in elem.tag + else elem.tag.lower() + ) + + # Check if this element type has ID uniqueness requirements + if tag in self.UNIQUE_ID_REQUIREMENTS: + attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag] + + # Look for the specified attribute + id_value = None + for attr, value in elem.attrib.items(): + attr_local = ( + attr.split("}")[-1].lower() + if "}" in attr + else attr.lower() + ) + if attr_local == attr_name: + id_value = value + break + + if id_value is not None: + if scope == "global": + # Check global uniqueness + if id_value in global_ids: + prev_file, prev_line, prev_tag = global_ids[ + id_value + ] + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> " + f"already used in {prev_file} at line {prev_line} in <{prev_tag}>" + ) + else: + global_ids[id_value] = ( + xml_file.relative_to(self.unpacked_dir), + elem.sourceline, + tag, + ) + elif scope == "file": + # Check file-level uniqueness + key = (tag, attr_name) + if key not in file_ids: + file_ids[key] = {} + + if id_value in file_ids[key]: + prev_line = file_ids[key][id_value] + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> " + f"(first occurrence at line {prev_line})" + ) + else: + file_ids[key][id_value] = elem.sourceline + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} ID uniqueness violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All required IDs are unique") + return True + + def validate_file_references(self): + """ + Validate that all .rels files properly reference files and that all files are referenced. + """ + errors = [] + + # Find all .rels files + rels_files = list(self.unpacked_dir.rglob("*.rels")) + + if not rels_files: + if self.verbose: + print("PASSED - No .rels files found") + return True + + # Get all files in the unpacked directory (excluding reference files) + all_files = [] + for file_path in self.unpacked_dir.rglob("*"): + if ( + file_path.is_file() + and file_path.name != "[Content_Types].xml" + and not file_path.name.endswith(".rels") + ): # This file is not referenced by .rels + all_files.append(file_path.resolve()) + + # Track all files that are referenced by any .rels file + all_referenced_files = set() + + if self.verbose: + print( + f"Found {len(rels_files)} .rels files and {len(all_files)} target files" + ) + + # Check each .rels file + for rels_file in rels_files: + try: + # Parse relationships file + rels_root = lxml.etree.parse(str(rels_file)).getroot() + + # Get the directory where this .rels file is located + rels_dir = rels_file.parent + + # Find all relationships and their targets + referenced_files = set() + broken_refs = [] + + for rel in rels_root.findall( + ".//ns:Relationship", + namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE}, + ): + target = rel.get("Target") + if target and not target.startswith( + ("http", "mailto:") + ): # Skip external URLs + # Resolve the target path relative to the .rels file location + if rels_file.name == ".rels": + # Root .rels file - targets are relative to unpacked_dir + target_path = self.unpacked_dir / target + else: + # Other .rels files - targets are relative to their parent's parent + # e.g., word/_rels/document.xml.rels -> targets relative to word/ + base_dir = rels_dir.parent + target_path = base_dir / target + + # Normalize the path and check if it exists + try: + target_path = target_path.resolve() + if target_path.exists() and target_path.is_file(): + referenced_files.add(target_path) + all_referenced_files.add(target_path) + else: + broken_refs.append((target, rel.sourceline)) + except (OSError, ValueError): + broken_refs.append((target, rel.sourceline)) + + # Report broken references + if broken_refs: + rel_path = rels_file.relative_to(self.unpacked_dir) + for broken_ref, line_num in broken_refs: + errors.append( + f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}" + ) + + except Exception as e: + rel_path = rels_file.relative_to(self.unpacked_dir) + errors.append(f" Error parsing {rel_path}: {e}") + + # Check for unreferenced files (files that exist but are not referenced anywhere) + unreferenced_files = set(all_files) - all_referenced_files + + if unreferenced_files: + for unref_file in sorted(unreferenced_files): + unref_rel_path = unref_file.relative_to(self.unpacked_dir) + errors.append(f" Unreferenced file: {unref_rel_path}") + + if errors: + print(f"FAILED - Found {len(errors)} relationship validation errors:") + for error in errors: + print(error) + print( + "CRITICAL: These errors will cause the document to appear corrupt. " + + "Broken references MUST be fixed, " + + "and unreferenced files MUST be referenced or removed." + ) + return False + else: + if self.verbose: + print( + "PASSED - All references are valid and all files are properly referenced" + ) + return True + + def validate_all_relationship_ids(self): + """ + Validate that all r:id attributes in XML files reference existing IDs + in their corresponding .rels files, and optionally validate relationship types. + """ + import lxml.etree + + errors = [] + + # Process each XML file that might contain r:id references + for xml_file in self.xml_files: + # Skip .rels files themselves + if xml_file.suffix == ".rels": + continue + + # Determine the corresponding .rels file + # For dir/file.xml, it's dir/_rels/file.xml.rels + rels_dir = xml_file.parent / "_rels" + rels_file = rels_dir / f"{xml_file.name}.rels" + + # Skip if there's no corresponding .rels file (that's okay) + if not rels_file.exists(): + continue + + try: + # Parse the .rels file to get valid relationship IDs and their types + rels_root = lxml.etree.parse(str(rels_file)).getroot() + rid_to_type = {} + + for rel in rels_root.findall( + f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" + ): + rid = rel.get("Id") + rel_type = rel.get("Type", "") + if rid: + # Check for duplicate rIds + if rid in rid_to_type: + rels_rel_path = rels_file.relative_to(self.unpacked_dir) + errors.append( + f" {rels_rel_path}: Line {rel.sourceline}: " + f"Duplicate relationship ID '{rid}' (IDs must be unique)" + ) + # Extract just the type name from the full URL + type_name = ( + rel_type.split("/")[-1] if "/" in rel_type else rel_type + ) + rid_to_type[rid] = type_name + + # Parse the XML file to find all r:id references + xml_root = lxml.etree.parse(str(xml_file)).getroot() + + # Find all elements with r:id attributes + for elem in xml_root.iter(): + # Check for r:id attribute (relationship ID) + rid_attr = elem.get(f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id") + if rid_attr: + xml_rel_path = xml_file.relative_to(self.unpacked_dir) + elem_name = ( + elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag + ) + + # Check if the ID exists + if rid_attr not in rid_to_type: + errors.append( + f" {xml_rel_path}: Line {elem.sourceline}: " + f"<{elem_name}> references non-existent relationship '{rid_attr}' " + f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})" + ) + # Check if we have type expectations for this element + elif self.ELEMENT_RELATIONSHIP_TYPES: + expected_type = self._get_expected_relationship_type( + elem_name + ) + if expected_type: + actual_type = rid_to_type[rid_attr] + # Check if the actual type matches or contains the expected type + if expected_type not in actual_type.lower(): + errors.append( + f" {xml_rel_path}: Line {elem.sourceline}: " + f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' " + f"but should point to a '{expected_type}' relationship" + ) + + except Exception as e: + xml_rel_path = xml_file.relative_to(self.unpacked_dir) + errors.append(f" Error processing {xml_rel_path}: {e}") + + if errors: + print(f"FAILED - Found {len(errors)} relationship ID reference errors:") + for error in errors: + print(error) + print("\nThese ID mismatches will cause the document to appear corrupt!") + return False + else: + if self.verbose: + print("PASSED - All relationship ID references are valid") + return True + + def _get_expected_relationship_type(self, element_name): + """ + Get the expected relationship type for an element. + First checks the explicit mapping, then tries pattern detection. + """ + # Normalize element name to lowercase + elem_lower = element_name.lower() + + # Check explicit mapping first + if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES: + return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower] + + # Try pattern detection for common patterns + # Pattern 1: Elements ending in "Id" often expect a relationship of the prefix type + if elem_lower.endswith("id") and len(elem_lower) > 2: + # e.g., "sldId" -> "sld", "sldMasterId" -> "sldMaster" + prefix = elem_lower[:-2] # Remove "id" + # Check if this might be a compound like "sldMasterId" + if prefix.endswith("master"): + return prefix.lower() + elif prefix.endswith("layout"): + return prefix.lower() + else: + # Simple case like "sldId" -> "slide" + # Common transformations + if prefix == "sld": + return "slide" + return prefix.lower() + + # Pattern 2: Elements ending in "Reference" expect a relationship of the prefix type + if elem_lower.endswith("reference") and len(elem_lower) > 9: + prefix = elem_lower[:-9] # Remove "reference" + return prefix.lower() + + return None + + def validate_content_types(self): + """Validate that all content files are properly declared in [Content_Types].xml.""" + errors = [] + + # Find [Content_Types].xml file + content_types_file = self.unpacked_dir / "[Content_Types].xml" + if not content_types_file.exists(): + print("FAILED - [Content_Types].xml file not found") + return False + + try: + # Parse and get all declared parts and extensions + root = lxml.etree.parse(str(content_types_file)).getroot() + declared_parts = set() + declared_extensions = set() + + # Get Override declarations (specific files) + for override in root.findall( + f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override" + ): + part_name = override.get("PartName") + if part_name is not None: + declared_parts.add(part_name.lstrip("/")) + + # Get Default declarations (by extension) + for default in root.findall( + f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default" + ): + extension = default.get("Extension") + if extension is not None: + declared_extensions.add(extension.lower()) + + # Root elements that require content type declaration + declarable_roots = { + "sld", + "sldLayout", + "sldMaster", + "presentation", # PowerPoint + "document", # Word + "workbook", + "worksheet", # Excel + "theme", # Common + } + + # Common media file extensions that should be declared + media_extensions = { + "png": "image/png", + "jpg": "image/jpeg", + "jpeg": "image/jpeg", + "gif": "image/gif", + "bmp": "image/bmp", + "tiff": "image/tiff", + "wmf": "image/x-wmf", + "emf": "image/x-emf", + } + + # Get all files in the unpacked directory + all_files = list(self.unpacked_dir.rglob("*")) + all_files = [f for f in all_files if f.is_file()] + + # Check all XML files for Override declarations + for xml_file in self.xml_files: + path_str = str(xml_file.relative_to(self.unpacked_dir)).replace( + "\\", "/" + ) + + # Skip non-content files + if any( + skip in path_str + for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"] + ): + continue + + try: + root_tag = lxml.etree.parse(str(xml_file)).getroot().tag + root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag + + if root_name in declarable_roots and path_str not in declared_parts: + errors.append( + f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml" + ) + + except Exception: + continue # Skip unparseable files + + # Check all non-XML files for Default extension declarations + for file_path in all_files: + # Skip XML files and metadata files (already checked above) + if file_path.suffix.lower() in {".xml", ".rels"}: + continue + if file_path.name == "[Content_Types].xml": + continue + if "_rels" in file_path.parts or "docProps" in file_path.parts: + continue + + extension = file_path.suffix.lstrip(".").lower() + if extension and extension not in declared_extensions: + # Check if it's a known media extension that should be declared + if extension in media_extensions: + relative_path = file_path.relative_to(self.unpacked_dir) + errors.append( + f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: ' + ) + + except Exception as e: + errors.append(f" Error parsing [Content_Types].xml: {e}") + + if errors: + print(f"FAILED - Found {len(errors)} content type declaration errors:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print( + "PASSED - All content files are properly declared in [Content_Types].xml" + ) + return True + + def validate_file_against_xsd(self, xml_file, verbose=False): + """Validate a single XML file against XSD schema, comparing with original. + + Args: + xml_file: Path to XML file to validate + verbose: Enable verbose output + + Returns: + tuple: (is_valid, new_errors_set) where is_valid is True/False/None (skipped) + """ + # Resolve both paths to handle symlinks + xml_file = Path(xml_file).resolve() + unpacked_dir = self.unpacked_dir.resolve() + + # Validate current file + is_valid, current_errors = self._validate_single_file_xsd( + xml_file, unpacked_dir + ) + + if is_valid is None: + return None, set() # Skipped + elif is_valid: + return True, set() # Valid, no errors + + # Get errors from original file for this specific file + original_errors = self._get_original_file_errors(xml_file) + + # Compare with original (both are guaranteed to be sets here) + assert current_errors is not None + new_errors = current_errors - original_errors + + if new_errors: + if verbose: + relative_path = xml_file.relative_to(unpacked_dir) + print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)") + for error in list(new_errors)[:3]: + truncated = error[:250] + "..." if len(error) > 250 else error + print(f" - {truncated}") + return False, new_errors + else: + # All errors existed in original + if verbose: + print( + f"PASSED - No new errors (original had {len(current_errors)} errors)" + ) + return True, set() + + def validate_against_xsd(self): + """Validate XML files against XSD schemas, showing only new errors compared to original.""" + new_errors = [] + original_error_count = 0 + valid_count = 0 + skipped_count = 0 + + for xml_file in self.xml_files: + relative_path = str(xml_file.relative_to(self.unpacked_dir)) + is_valid, new_file_errors = self.validate_file_against_xsd( + xml_file, verbose=False + ) + + if is_valid is None: + skipped_count += 1 + continue + elif is_valid and not new_file_errors: + valid_count += 1 + continue + elif is_valid: + # Had errors but all existed in original + original_error_count += 1 + valid_count += 1 + continue + + # Has new errors + new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)") + for error in list(new_file_errors)[:3]: # Show first 3 errors + new_errors.append( + f" - {error[:250]}..." if len(error) > 250 else f" - {error}" + ) + + # Print summary + if self.verbose: + print(f"Validated {len(self.xml_files)} files:") + print(f" - Valid: {valid_count}") + print(f" - Skipped (no schema): {skipped_count}") + if original_error_count: + print(f" - With original errors (ignored): {original_error_count}") + print( + f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}" + ) + + if new_errors: + print("\nFAILED - Found NEW validation errors:") + for error in new_errors: + print(error) + return False + else: + if self.verbose: + print("\nPASSED - No new XSD validation errors introduced") + return True + + def _get_schema_path(self, xml_file): + """Determine the appropriate schema path for an XML file.""" + # Check exact filename match + if xml_file.name in self.SCHEMA_MAPPINGS: + return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name] + + # Check .rels files + if xml_file.suffix == ".rels": + return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"] + + # Check chart files + if "charts/" in str(xml_file) and xml_file.name.startswith("chart"): + return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"] + + # Check theme files + if "theme/" in str(xml_file) and xml_file.name.startswith("theme"): + return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"] + + # Check if file is in a main content folder and use appropriate schema + if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS: + return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name] + + return None + + def _clean_ignorable_namespaces(self, xml_doc): + """Remove attributes and elements not in allowed namespaces.""" + # Create a clean copy + xml_string = lxml.etree.tostring(xml_doc, encoding="unicode") + xml_copy = lxml.etree.fromstring(xml_string) + + # Remove attributes not in allowed namespaces + for elem in xml_copy.iter(): + attrs_to_remove = [] + + for attr in elem.attrib: + # Check if attribute is from a namespace other than allowed ones + if "{" in attr: + ns = attr.split("}")[0][1:] + if ns not in self.OOXML_NAMESPACES: + attrs_to_remove.append(attr) + + # Remove collected attributes + for attr in attrs_to_remove: + del elem.attrib[attr] + + # Remove elements not in allowed namespaces + self._remove_ignorable_elements(xml_copy) + + return lxml.etree.ElementTree(xml_copy) + + def _remove_ignorable_elements(self, root): + """Recursively remove all elements not in allowed namespaces.""" + elements_to_remove = [] + + # Find elements to remove + for elem in list(root): + # Skip non-element nodes (comments, processing instructions, etc.) + if not hasattr(elem, "tag") or callable(elem.tag): + continue + + tag_str = str(elem.tag) + if tag_str.startswith("{"): + ns = tag_str.split("}")[0][1:] + if ns not in self.OOXML_NAMESPACES: + elements_to_remove.append(elem) + continue + + # Recursively clean child elements + self._remove_ignorable_elements(elem) + + # Remove collected elements + for elem in elements_to_remove: + root.remove(elem) + + def _preprocess_for_mc_ignorable(self, xml_doc): + """Preprocess XML to handle mc:Ignorable attribute properly.""" + # Remove mc:Ignorable attributes before validation + root = xml_doc.getroot() + + # Remove mc:Ignorable attribute from root + if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib: + del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"] + + return xml_doc + + def _validate_single_file_xsd(self, xml_file, base_path): + """Validate a single XML file against XSD schema. Returns (is_valid, errors_set).""" + schema_path = self._get_schema_path(xml_file) + if not schema_path: + return None, None # Skip file + + try: + # Load schema + with open(schema_path, "rb") as xsd_file: + parser = lxml.etree.XMLParser() + xsd_doc = lxml.etree.parse( + xsd_file, parser=parser, base_url=str(schema_path) + ) + schema = lxml.etree.XMLSchema(xsd_doc) + + # Load and preprocess XML + with open(xml_file, "r") as f: + xml_doc = lxml.etree.parse(f) + + xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc) + xml_doc = self._preprocess_for_mc_ignorable(xml_doc) + + # Clean ignorable namespaces if needed + relative_path = xml_file.relative_to(base_path) + if ( + relative_path.parts + and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS + ): + xml_doc = self._clean_ignorable_namespaces(xml_doc) + + # Validate + if schema.validate(xml_doc): + return True, set() + else: + errors = set() + for error in schema.error_log: + # Store normalized error message (without line numbers for comparison) + errors.add(error.message) + return False, errors + + except Exception as e: + return False, {str(e)} + + def _get_original_file_errors(self, xml_file): + """Get XSD validation errors from a single file in the original document. + + Args: + xml_file: Path to the XML file in unpacked_dir to check + + Returns: + set: Set of error messages from the original file + """ + import tempfile + import zipfile + + # Resolve both paths to handle symlinks (e.g., /var vs /private/var on macOS) + xml_file = Path(xml_file).resolve() + unpacked_dir = self.unpacked_dir.resolve() + relative_path = xml_file.relative_to(unpacked_dir) + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Extract original file + with zipfile.ZipFile(self.original_file, "r") as zip_ref: + zip_ref.extractall(temp_path) + + # Find corresponding file in original + original_xml_file = temp_path / relative_path + + if not original_xml_file.exists(): + # File didn't exist in original, so no original errors + return set() + + # Validate the specific file in original + is_valid, errors = self._validate_single_file_xsd( + original_xml_file, temp_path + ) + return errors if errors else set() + + def _remove_template_tags_from_text_nodes(self, xml_doc): + """Remove template tags from XML text nodes and collect warnings. + + Template tags follow the pattern {{ ... }} and are used as placeholders + for content replacement. They should be removed from text content before + XSD validation while preserving XML structure. + + Returns: + tuple: (cleaned_xml_doc, warnings_list) + """ + warnings = [] + template_pattern = re.compile(r"\{\{[^}]*\}\}") + + # Create a copy of the document to avoid modifying the original + xml_string = lxml.etree.tostring(xml_doc, encoding="unicode") + xml_copy = lxml.etree.fromstring(xml_string) + + def process_text_content(text, content_type): + if not text: + return text + matches = list(template_pattern.finditer(text)) + if matches: + for match in matches: + warnings.append( + f"Found template tag in {content_type}: {match.group()}" + ) + return template_pattern.sub("", text) + return text + + # Process all text nodes in the document + for elem in xml_copy.iter(): + # Skip processing if this is a w:t element + if not hasattr(elem, "tag") or callable(elem.tag): + continue + tag_str = str(elem.tag) + if tag_str.endswith("}t") or tag_str == "t": + continue + + elem.text = process_text_content(elem.text, "text content") + elem.tail = process_text_content(elem.tail, "tail content") + + return lxml.etree.ElementTree(xml_copy), warnings + + +if __name__ == "__main__": + raise RuntimeError("This module should not be run directly.") diff --git a/src/flow/skills/docx/ooxml/scripts/validation/docx.py b/src/flow/skills/docx/ooxml/scripts/validation/docx.py new file mode 100644 index 0000000000000000000000000000000000000000..602c47087ada8d5a10e90abc864f1225abcb2345 --- /dev/null +++ b/src/flow/skills/docx/ooxml/scripts/validation/docx.py @@ -0,0 +1,274 @@ +""" +Validator for Word document XML files against XSD schemas. +""" + +import re +import tempfile +import zipfile + +import lxml.etree + +from .base import BaseSchemaValidator + + +class DOCXSchemaValidator(BaseSchemaValidator): + """Validator for Word document XML files against XSD schemas.""" + + # Word-specific namespace + WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" + + # Word-specific element to relationship type mappings + # Start with empty mapping - add specific cases as we discover them + ELEMENT_RELATIONSHIP_TYPES = {} + + def validate(self): + """Run all validation checks and return True if all pass.""" + # Test 0: XML well-formedness + if not self.validate_xml(): + return False + + # Test 1: Namespace declarations + all_valid = True + if not self.validate_namespaces(): + all_valid = False + + # Test 2: Unique IDs + if not self.validate_unique_ids(): + all_valid = False + + # Test 3: Relationship and file reference validation + if not self.validate_file_references(): + all_valid = False + + # Test 4: Content type declarations + if not self.validate_content_types(): + all_valid = False + + # Test 5: XSD schema validation + if not self.validate_against_xsd(): + all_valid = False + + # Test 6: Whitespace preservation + if not self.validate_whitespace_preservation(): + all_valid = False + + # Test 7: Deletion validation + if not self.validate_deletions(): + all_valid = False + + # Test 8: Insertion validation + if not self.validate_insertions(): + all_valid = False + + # Test 9: Relationship ID reference validation + if not self.validate_all_relationship_ids(): + all_valid = False + + # Count and compare paragraphs + self.compare_paragraph_counts() + + return all_valid + + def validate_whitespace_preservation(self): + """ + Validate that w:t elements with whitespace have xml:space='preserve'. + """ + errors = [] + + for xml_file in self.xml_files: + # Only check document.xml files + if xml_file.name != "document.xml": + continue + + try: + root = lxml.etree.parse(str(xml_file)).getroot() + + # Find all w:t elements + for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"): + if elem.text: + text = elem.text + # Check if text starts or ends with whitespace + if re.match(r"^\s.*", text) or re.match(r".*\s$", text): + # Check if xml:space="preserve" attribute exists + xml_space_attr = f"{{{self.XML_NAMESPACE}}}space" + if ( + xml_space_attr not in elem.attrib + or elem.attrib[xml_space_attr] != "preserve" + ): + # Show a preview of the text + text_preview = ( + repr(text)[:50] + "..." + if len(repr(text)) > 50 + else repr(text) + ) + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} whitespace preservation violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All whitespace is properly preserved") + return True + + def validate_deletions(self): + """ + Validate that w:t elements are not within w:del elements. + For some reason, XSD validation does not catch this, so we do it manually. + """ + errors = [] + + for xml_file in self.xml_files: + # Only check document.xml files + if xml_file.name != "document.xml": + continue + + try: + root = lxml.etree.parse(str(xml_file)).getroot() + + # Find all w:t elements that are descendants of w:del elements + namespaces = {"w": self.WORD_2006_NAMESPACE} + xpath_expression = ".//w:del//w:t" + problematic_t_elements = root.xpath( + xpath_expression, namespaces=namespaces + ) + for t_elem in problematic_t_elements: + if t_elem.text: + # Show a preview of the text + text_preview = ( + repr(t_elem.text)[:50] + "..." + if len(repr(t_elem.text)) > 50 + else repr(t_elem.text) + ) + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {t_elem.sourceline}: found within : {text_preview}" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} deletion validation violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - No w:t elements found within w:del elements") + return True + + def count_paragraphs_in_unpacked(self): + """Count the number of paragraphs in the unpacked document.""" + count = 0 + + for xml_file in self.xml_files: + # Only check document.xml files + if xml_file.name != "document.xml": + continue + + try: + root = lxml.etree.parse(str(xml_file)).getroot() + # Count all w:p elements + paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p") + count = len(paragraphs) + except Exception as e: + print(f"Error counting paragraphs in unpacked document: {e}") + + return count + + def count_paragraphs_in_original(self): + """Count the number of paragraphs in the original docx file.""" + count = 0 + + try: + # Create temporary directory to unpack original + with tempfile.TemporaryDirectory() as temp_dir: + # Unpack original docx + with zipfile.ZipFile(self.original_file, "r") as zip_ref: + zip_ref.extractall(temp_dir) + + # Parse document.xml + doc_xml_path = temp_dir + "/word/document.xml" + root = lxml.etree.parse(doc_xml_path).getroot() + + # Count all w:p elements + paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p") + count = len(paragraphs) + + except Exception as e: + print(f"Error counting paragraphs in original document: {e}") + + return count + + def validate_insertions(self): + """ + Validate that w:delText elements are not within w:ins elements. + w:delText is only allowed in w:ins if nested within a w:del. + """ + errors = [] + + for xml_file in self.xml_files: + if xml_file.name != "document.xml": + continue + + try: + root = lxml.etree.parse(str(xml_file)).getroot() + namespaces = {"w": self.WORD_2006_NAMESPACE} + + # Find w:delText in w:ins that are NOT within w:del + invalid_elements = root.xpath( + ".//w:ins//w:delText[not(ancestor::w:del)]", + namespaces=namespaces + ) + + for elem in invalid_elements: + text_preview = ( + repr(elem.text or "")[:50] + "..." + if len(repr(elem.text or "")) > 50 + else repr(elem.text or "") + ) + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: within : {text_preview}" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} insertion validation violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - No w:delText elements within w:ins elements") + return True + + def compare_paragraph_counts(self): + """Compare paragraph counts between original and new document.""" + original_count = self.count_paragraphs_in_original() + new_count = self.count_paragraphs_in_unpacked() + + diff = new_count - original_count + diff_str = f"+{diff}" if diff > 0 else str(diff) + print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})") + + +if __name__ == "__main__": + raise RuntimeError("This module should not be run directly.") diff --git a/src/flow/skills/docx/ooxml/scripts/validation/pptx.py b/src/flow/skills/docx/ooxml/scripts/validation/pptx.py new file mode 100644 index 0000000000000000000000000000000000000000..66d5b1e2dba6d1b3dbcb290fa6ef1699980435e6 --- /dev/null +++ b/src/flow/skills/docx/ooxml/scripts/validation/pptx.py @@ -0,0 +1,315 @@ +""" +Validator for PowerPoint presentation XML files against XSD schemas. +""" + +import re + +from .base import BaseSchemaValidator + + +class PPTXSchemaValidator(BaseSchemaValidator): + """Validator for PowerPoint presentation XML files against XSD schemas.""" + + # PowerPoint presentation namespace + PRESENTATIONML_NAMESPACE = ( + "http://schemas.openxmlformats.org/presentationml/2006/main" + ) + + # PowerPoint-specific element to relationship type mappings + ELEMENT_RELATIONSHIP_TYPES = { + "sldid": "slide", + "sldmasterid": "slidemaster", + "notesmasterid": "notesmaster", + "sldlayoutid": "slidelayout", + "themeid": "theme", + "tablestyleid": "tablestyles", + } + + def validate(self): + """Run all validation checks and return True if all pass.""" + # Test 0: XML well-formedness + if not self.validate_xml(): + return False + + # Test 1: Namespace declarations + all_valid = True + if not self.validate_namespaces(): + all_valid = False + + # Test 2: Unique IDs + if not self.validate_unique_ids(): + all_valid = False + + # Test 3: UUID ID validation + if not self.validate_uuid_ids(): + all_valid = False + + # Test 4: Relationship and file reference validation + if not self.validate_file_references(): + all_valid = False + + # Test 5: Slide layout ID validation + if not self.validate_slide_layout_ids(): + all_valid = False + + # Test 6: Content type declarations + if not self.validate_content_types(): + all_valid = False + + # Test 7: XSD schema validation + if not self.validate_against_xsd(): + all_valid = False + + # Test 8: Notes slide reference validation + if not self.validate_notes_slide_references(): + all_valid = False + + # Test 9: Relationship ID reference validation + if not self.validate_all_relationship_ids(): + all_valid = False + + # Test 10: Duplicate slide layout references validation + if not self.validate_no_duplicate_slide_layouts(): + all_valid = False + + return all_valid + + def validate_uuid_ids(self): + """Validate that ID attributes that look like UUIDs contain only hex values.""" + import lxml.etree + + errors = [] + # UUID pattern: 8-4-4-4-12 hex digits with optional braces/hyphens + uuid_pattern = re.compile( + r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$" + ) + + for xml_file in self.xml_files: + try: + root = lxml.etree.parse(str(xml_file)).getroot() + + # Check all elements for ID attributes + for elem in root.iter(): + for attr, value in elem.attrib.items(): + # Check if this is an ID attribute + attr_name = attr.split("}")[-1].lower() + if attr_name == "id" or attr_name.endswith("id"): + # Check if value looks like a UUID (has the right length and pattern structure) + if self._looks_like_uuid(value): + # Validate that it contains only hex characters in the right positions + if not uuid_pattern.match(value): + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} UUID ID validation errors:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All UUID-like IDs contain valid hex values") + return True + + def _looks_like_uuid(self, value): + """Check if a value has the general structure of a UUID.""" + # Remove common UUID delimiters + clean_value = value.strip("{}()").replace("-", "") + # Check if it's 32 hex-like characters (could include invalid hex chars) + return len(clean_value) == 32 and all(c.isalnum() for c in clean_value) + + def validate_slide_layout_ids(self): + """Validate that sldLayoutId elements in slide masters reference valid slide layouts.""" + import lxml.etree + + errors = [] + + # Find all slide master files + slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml")) + + if not slide_masters: + if self.verbose: + print("PASSED - No slide masters found") + return True + + for slide_master in slide_masters: + try: + # Parse the slide master file + root = lxml.etree.parse(str(slide_master)).getroot() + + # Find the corresponding _rels file for this slide master + rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels" + + if not rels_file.exists(): + errors.append( + f" {slide_master.relative_to(self.unpacked_dir)}: " + f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}" + ) + continue + + # Parse the relationships file + rels_root = lxml.etree.parse(str(rels_file)).getroot() + + # Build a set of valid relationship IDs that point to slide layouts + valid_layout_rids = set() + for rel in rels_root.findall( + f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" + ): + rel_type = rel.get("Type", "") + if "slideLayout" in rel_type: + valid_layout_rids.add(rel.get("Id")) + + # Find all sldLayoutId elements in the slide master + for sld_layout_id in root.findall( + f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId" + ): + r_id = sld_layout_id.get( + f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id" + ) + layout_id = sld_layout_id.get("id") + + if r_id and r_id not in valid_layout_rids: + errors.append( + f" {slide_master.relative_to(self.unpacked_dir)}: " + f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' " + f"references r:id='{r_id}' which is not found in slide layout relationships" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} slide layout ID validation errors:") + for error in errors: + print(error) + print( + "Remove invalid references or add missing slide layouts to the relationships file." + ) + return False + else: + if self.verbose: + print("PASSED - All slide layout IDs reference valid slide layouts") + return True + + def validate_no_duplicate_slide_layouts(self): + """Validate that each slide has exactly one slideLayout reference.""" + import lxml.etree + + errors = [] + slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels")) + + for rels_file in slide_rels_files: + try: + root = lxml.etree.parse(str(rels_file)).getroot() + + # Find all slideLayout relationships + layout_rels = [ + rel + for rel in root.findall( + f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" + ) + if "slideLayout" in rel.get("Type", "") + ] + + if len(layout_rels) > 1: + errors.append( + f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references" + ) + + except Exception as e: + errors.append( + f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print("FAILED - Found slides with duplicate slideLayout references:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All slides have exactly one slideLayout reference") + return True + + def validate_notes_slide_references(self): + """Validate that each notesSlide file is referenced by only one slide.""" + import lxml.etree + + errors = [] + notes_slide_references = {} # Track which slides reference each notesSlide + + # Find all slide relationship files + slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels")) + + if not slide_rels_files: + if self.verbose: + print("PASSED - No slide relationship files found") + return True + + for rels_file in slide_rels_files: + try: + # Parse the relationships file + root = lxml.etree.parse(str(rels_file)).getroot() + + # Find all notesSlide relationships + for rel in root.findall( + f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" + ): + rel_type = rel.get("Type", "") + if "notesSlide" in rel_type: + target = rel.get("Target", "") + if target: + # Normalize the target path to handle relative paths + normalized_target = target.replace("../", "") + + # Track which slide references this notesSlide + slide_name = rels_file.stem.replace( + ".xml", "" + ) # e.g., "slide1" + + if normalized_target not in notes_slide_references: + notes_slide_references[normalized_target] = [] + notes_slide_references[normalized_target].append( + (slide_name, rels_file) + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + # Check for duplicate references + for target, references in notes_slide_references.items(): + if len(references) > 1: + slide_names = [ref[0] for ref in references] + errors.append( + f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}" + ) + for slide_name, rels_file in references: + errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}") + + if errors: + print( + f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:" + ) + for error in errors: + print(error) + print("Each slide may optionally have its own slide file.") + return False + else: + if self.verbose: + print("PASSED - All notes slide references are unique") + return True + + +if __name__ == "__main__": + raise RuntimeError("This module should not be run directly.") diff --git a/src/flow/skills/docx/ooxml/scripts/validation/redlining.py b/src/flow/skills/docx/ooxml/scripts/validation/redlining.py new file mode 100644 index 0000000000000000000000000000000000000000..7ed425edf5336306b9eec87164e034d87decebb8 --- /dev/null +++ b/src/flow/skills/docx/ooxml/scripts/validation/redlining.py @@ -0,0 +1,279 @@ +""" +Validator for tracked changes in Word documents. +""" + +import subprocess +import tempfile +import zipfile +from pathlib import Path + + +class RedliningValidator: + """Validator for tracked changes in Word documents.""" + + def __init__(self, unpacked_dir, original_docx, verbose=False): + self.unpacked_dir = Path(unpacked_dir) + self.original_docx = Path(original_docx) + self.verbose = verbose + self.namespaces = { + "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main" + } + + def validate(self): + """Main validation method that returns True if valid, False otherwise.""" + # Verify unpacked directory exists and has correct structure + modified_file = self.unpacked_dir / "word" / "document.xml" + if not modified_file.exists(): + print(f"FAILED - Modified document.xml not found at {modified_file}") + return False + + # First, check if there are any tracked changes by Claude to validate + try: + import xml.etree.ElementTree as ET + + tree = ET.parse(modified_file) + root = tree.getroot() + + # Check for w:del or w:ins tags authored by Claude + del_elements = root.findall(".//w:del", self.namespaces) + ins_elements = root.findall(".//w:ins", self.namespaces) + + # Filter to only include changes by Claude + claude_del_elements = [ + elem + for elem in del_elements + if elem.get(f"{{{self.namespaces['w']}}}author") == "Claude" + ] + claude_ins_elements = [ + elem + for elem in ins_elements + if elem.get(f"{{{self.namespaces['w']}}}author") == "Claude" + ] + + # Redlining validation is only needed if tracked changes by Claude have been used. + if not claude_del_elements and not claude_ins_elements: + if self.verbose: + print("PASSED - No tracked changes by Claude found.") + return True + + except Exception: + # If we can't parse the XML, continue with full validation + pass + + # Create temporary directory for unpacking original docx + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Unpack original docx + try: + with zipfile.ZipFile(self.original_docx, "r") as zip_ref: + zip_ref.extractall(temp_path) + except Exception as e: + print(f"FAILED - Error unpacking original docx: {e}") + return False + + original_file = temp_path / "word" / "document.xml" + if not original_file.exists(): + print( + f"FAILED - Original document.xml not found in {self.original_docx}" + ) + return False + + # Parse both XML files using xml.etree.ElementTree for redlining validation + try: + import xml.etree.ElementTree as ET + + modified_tree = ET.parse(modified_file) + modified_root = modified_tree.getroot() + original_tree = ET.parse(original_file) + original_root = original_tree.getroot() + except ET.ParseError as e: + print(f"FAILED - Error parsing XML files: {e}") + return False + + # Remove Claude's tracked changes from both documents + self._remove_claude_tracked_changes(original_root) + self._remove_claude_tracked_changes(modified_root) + + # Extract and compare text content + modified_text = self._extract_text_content(modified_root) + original_text = self._extract_text_content(original_root) + + if modified_text != original_text: + # Show detailed character-level differences for each paragraph + error_message = self._generate_detailed_diff( + original_text, modified_text + ) + print(error_message) + return False + + if self.verbose: + print("PASSED - All changes by Claude are properly tracked") + return True + + def _generate_detailed_diff(self, original_text, modified_text): + """Generate detailed word-level differences using git word diff.""" + error_parts = [ + "FAILED - Document text doesn't match after removing Claude's tracked changes", + "", + "Likely causes:", + " 1. Modified text inside another author's or tags", + " 2. Made edits without proper tracked changes", + " 3. Didn't nest inside when deleting another's insertion", + "", + "For pre-redlined documents, use correct patterns:", + " - To reject another's INSERTION: Nest inside their ", + " - To restore another's DELETION: Add new AFTER their ", + "", + ] + + # Show git word diff + git_diff = self._get_git_word_diff(original_text, modified_text) + if git_diff: + error_parts.extend(["Differences:", "============", git_diff]) + else: + error_parts.append("Unable to generate word diff (git not available)") + + return "\n".join(error_parts) + + def _get_git_word_diff(self, original_text, modified_text): + """Generate word diff using git with character-level precision.""" + try: + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create two files + original_file = temp_path / "original.txt" + modified_file = temp_path / "modified.txt" + + original_file.write_text(original_text, encoding="utf-8") + modified_file.write_text(modified_text, encoding="utf-8") + + # Try character-level diff first for precise differences + result = subprocess.run( + [ + "git", + "diff", + "--word-diff=plain", + "--word-diff-regex=.", # Character-by-character diff + "-U0", # Zero lines of context - show only changed lines + "--no-index", + str(original_file), + str(modified_file), + ], + capture_output=True, + text=True, + ) + + if result.stdout.strip(): + # Clean up the output - remove git diff header lines + lines = result.stdout.split("\n") + # Skip the header lines (diff --git, index, +++, ---, @@) + content_lines = [] + in_content = False + for line in lines: + if line.startswith("@@"): + in_content = True + continue + if in_content and line.strip(): + content_lines.append(line) + + if content_lines: + return "\n".join(content_lines) + + # Fallback to word-level diff if character-level is too verbose + result = subprocess.run( + [ + "git", + "diff", + "--word-diff=plain", + "-U0", # Zero lines of context + "--no-index", + str(original_file), + str(modified_file), + ], + capture_output=True, + text=True, + ) + + if result.stdout.strip(): + lines = result.stdout.split("\n") + content_lines = [] + in_content = False + for line in lines: + if line.startswith("@@"): + in_content = True + continue + if in_content and line.strip(): + content_lines.append(line) + return "\n".join(content_lines) + + except (subprocess.CalledProcessError, FileNotFoundError, Exception): + # Git not available or other error, return None to use fallback + pass + + return None + + def _remove_claude_tracked_changes(self, root): + """Remove tracked changes authored by Claude from the XML root.""" + ins_tag = f"{{{self.namespaces['w']}}}ins" + del_tag = f"{{{self.namespaces['w']}}}del" + author_attr = f"{{{self.namespaces['w']}}}author" + + # Remove w:ins elements + for parent in root.iter(): + to_remove = [] + for child in parent: + if child.tag == ins_tag and child.get(author_attr) == "Claude": + to_remove.append(child) + for elem in to_remove: + parent.remove(elem) + + # Unwrap content in w:del elements where author is "Claude" + deltext_tag = f"{{{self.namespaces['w']}}}delText" + t_tag = f"{{{self.namespaces['w']}}}t" + + for parent in root.iter(): + to_process = [] + for child in parent: + if child.tag == del_tag and child.get(author_attr) == "Claude": + to_process.append((child, list(parent).index(child))) + + # Process in reverse order to maintain indices + for del_elem, del_index in reversed(to_process): + # Convert w:delText to w:t before moving + for elem in del_elem.iter(): + if elem.tag == deltext_tag: + elem.tag = t_tag + + # Move all children of w:del to its parent before removing w:del + for child in reversed(list(del_elem)): + parent.insert(del_index, child) + parent.remove(del_elem) + + def _extract_text_content(self, root): + """Extract text content from Word XML, preserving paragraph structure. + + Empty paragraphs are skipped to avoid false positives when tracked + insertions add only structural elements without text content. + """ + p_tag = f"{{{self.namespaces['w']}}}p" + t_tag = f"{{{self.namespaces['w']}}}t" + + paragraphs = [] + for p_elem in root.findall(f".//{p_tag}"): + # Get all text elements within this paragraph + text_parts = [] + for t_elem in p_elem.findall(f".//{t_tag}"): + if t_elem.text: + text_parts.append(t_elem.text) + paragraph_text = "".join(text_parts) + # Skip empty paragraphs - they don't affect content validation + if paragraph_text: + paragraphs.append(paragraph_text) + + return "\n".join(paragraphs) + + +if __name__ == "__main__": + raise RuntimeError("This module should not be run directly.") diff --git a/src/flow/skills/docx/scripts/__init__.py b/src/flow/skills/docx/scripts/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..bf9c56272f9c7a372e6581127d292728ccea046d --- /dev/null +++ b/src/flow/skills/docx/scripts/__init__.py @@ -0,0 +1 @@ +# Make scripts directory a package for relative imports in tests diff --git a/src/flow/skills/docx/scripts/document.py b/src/flow/skills/docx/scripts/document.py new file mode 100755 index 0000000000000000000000000000000000000000..ae9328ddf3a15802bd6b669b3b28e5cfbc1186d3 --- /dev/null +++ b/src/flow/skills/docx/scripts/document.py @@ -0,0 +1,1276 @@ +#!/usr/bin/env python3 +""" +Library for working with Word documents: comments, tracked changes, and editing. + +Usage: + from skills.docx.scripts.document import Document + + # Initialize + doc = Document('workspace/unpacked') + doc = Document('workspace/unpacked', author="John Doe", initials="JD") + + # Find nodes + node = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "1"}) + node = doc["word/document.xml"].get_node(tag="w:p", line_number=10) + + # Add comments + doc.add_comment(start=node, end=node, text="Comment text") + doc.reply_to_comment(parent_comment_id=0, text="Reply text") + + # Suggest tracked changes + doc["word/document.xml"].suggest_deletion(node) # Delete content + doc["word/document.xml"].revert_insertion(ins_node) # Reject insertion + doc["word/document.xml"].revert_deletion(del_node) # Reject deletion + + # Save + doc.save() +""" + +import html +import random +import shutil +import tempfile +from datetime import datetime, timezone +from pathlib import Path + +from defusedxml import minidom +from ooxml.scripts.pack import pack_document +from ooxml.scripts.validation.docx import DOCXSchemaValidator +from ooxml.scripts.validation.redlining import RedliningValidator + +from .utilities import XMLEditor + +# Path to template files +TEMPLATE_DIR = Path(__file__).parent / "templates" + + +class DocxXMLEditor(XMLEditor): + """XMLEditor that automatically applies RSID, author, and date to new elements. + + Automatically adds attributes to elements that support them when inserting new content: + - w:rsidR, w:rsidRDefault, w:rsidP (for w:p and w:r elements) + - w:author and w:date (for w:ins, w:del, w:comment elements) + - w:id (for w:ins and w:del elements) + + Attributes: + dom (defusedxml.minidom.Document): The DOM document for direct manipulation + """ + + def __init__( + self, xml_path, rsid: str, author: str = "Claude", initials: str = "C" + ): + """Initialize with required RSID and optional author. + + Args: + xml_path: Path to XML file to edit + rsid: RSID to automatically apply to new elements + author: Author name for tracked changes and comments (default: "Claude") + initials: Author initials (default: "C") + """ + super().__init__(xml_path) + self.rsid = rsid + self.author = author + self.initials = initials + + def _get_next_change_id(self): + """Get the next available change ID by checking all tracked change elements.""" + max_id = -1 + for tag in ("w:ins", "w:del"): + elements = self.dom.getElementsByTagName(tag) + for elem in elements: + change_id = elem.getAttribute("w:id") + if change_id: + try: + max_id = max(max_id, int(change_id)) + except ValueError: + pass + return max_id + 1 + + def _ensure_w16du_namespace(self): + """Ensure w16du namespace is declared on the root element.""" + root = self.dom.documentElement + if not root.hasAttribute("xmlns:w16du"): # type: ignore + root.setAttribute( # type: ignore + "xmlns:w16du", + "http://schemas.microsoft.com/office/word/2023/wordml/word16du", + ) + + def _ensure_w16cex_namespace(self): + """Ensure w16cex namespace is declared on the root element.""" + root = self.dom.documentElement + if not root.hasAttribute("xmlns:w16cex"): # type: ignore + root.setAttribute( # type: ignore + "xmlns:w16cex", + "http://schemas.microsoft.com/office/word/2018/wordml/cex", + ) + + def _ensure_w14_namespace(self): + """Ensure w14 namespace is declared on the root element.""" + root = self.dom.documentElement + if not root.hasAttribute("xmlns:w14"): # type: ignore + root.setAttribute( # type: ignore + "xmlns:w14", + "http://schemas.microsoft.com/office/word/2010/wordml", + ) + + def _inject_attributes_to_nodes(self, nodes): + """Inject RSID, author, and date attributes into DOM nodes where applicable. + + Adds attributes to elements that support them: + - w:r: gets w:rsidR (or w:rsidDel if inside w:del) + - w:p: gets w:rsidR, w:rsidRDefault, w:rsidP, w14:paraId, w14:textId + - w:t: gets xml:space="preserve" if text has leading/trailing whitespace + - w:ins, w:del: get w:id, w:author, w:date, w16du:dateUtc + - w:comment: gets w:author, w:date, w:initials + - w16cex:commentExtensible: gets w16cex:dateUtc + + Args: + nodes: List of DOM nodes to process + """ + from datetime import datetime, timezone + + timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + def is_inside_deletion(elem): + """Check if element is inside a w:del element.""" + parent = elem.parentNode + while parent: + if parent.nodeType == parent.ELEMENT_NODE and parent.tagName == "w:del": + return True + parent = parent.parentNode + return False + + def add_rsid_to_p(elem): + if not elem.hasAttribute("w:rsidR"): + elem.setAttribute("w:rsidR", self.rsid) + if not elem.hasAttribute("w:rsidRDefault"): + elem.setAttribute("w:rsidRDefault", self.rsid) + if not elem.hasAttribute("w:rsidP"): + elem.setAttribute("w:rsidP", self.rsid) + # Add w14:paraId and w14:textId if not present + if not elem.hasAttribute("w14:paraId"): + self._ensure_w14_namespace() + elem.setAttribute("w14:paraId", _generate_hex_id()) + if not elem.hasAttribute("w14:textId"): + self._ensure_w14_namespace() + elem.setAttribute("w14:textId", _generate_hex_id()) + + def add_rsid_to_r(elem): + # Use w:rsidDel for inside , otherwise w:rsidR + if is_inside_deletion(elem): + if not elem.hasAttribute("w:rsidDel"): + elem.setAttribute("w:rsidDel", self.rsid) + else: + if not elem.hasAttribute("w:rsidR"): + elem.setAttribute("w:rsidR", self.rsid) + + def add_tracked_change_attrs(elem): + # Auto-assign w:id if not present + if not elem.hasAttribute("w:id"): + elem.setAttribute("w:id", str(self._get_next_change_id())) + if not elem.hasAttribute("w:author"): + elem.setAttribute("w:author", self.author) + if not elem.hasAttribute("w:date"): + elem.setAttribute("w:date", timestamp) + # Add w16du:dateUtc for tracked changes (same as w:date since we generate UTC timestamps) + if elem.tagName in ("w:ins", "w:del") and not elem.hasAttribute( + "w16du:dateUtc" + ): + self._ensure_w16du_namespace() + elem.setAttribute("w16du:dateUtc", timestamp) + + def add_comment_attrs(elem): + if not elem.hasAttribute("w:author"): + elem.setAttribute("w:author", self.author) + if not elem.hasAttribute("w:date"): + elem.setAttribute("w:date", timestamp) + if not elem.hasAttribute("w:initials"): + elem.setAttribute("w:initials", self.initials) + + def add_comment_extensible_date(elem): + # Add w16cex:dateUtc for comment extensible elements + if not elem.hasAttribute("w16cex:dateUtc"): + self._ensure_w16cex_namespace() + elem.setAttribute("w16cex:dateUtc", timestamp) + + def add_xml_space_to_t(elem): + # Add xml:space="preserve" to w:t if text has leading/trailing whitespace + if ( + elem.firstChild + and elem.firstChild.nodeType == elem.firstChild.TEXT_NODE + ): + text = elem.firstChild.data + if text and (text[0].isspace() or text[-1].isspace()): + if not elem.hasAttribute("xml:space"): + elem.setAttribute("xml:space", "preserve") + + for node in nodes: + if node.nodeType != node.ELEMENT_NODE: + continue + + # Handle the node itself + if node.tagName == "w:p": + add_rsid_to_p(node) + elif node.tagName == "w:r": + add_rsid_to_r(node) + elif node.tagName == "w:t": + add_xml_space_to_t(node) + elif node.tagName in ("w:ins", "w:del"): + add_tracked_change_attrs(node) + elif node.tagName == "w:comment": + add_comment_attrs(node) + elif node.tagName == "w16cex:commentExtensible": + add_comment_extensible_date(node) + + # Process descendants (getElementsByTagName doesn't return the element itself) + for elem in node.getElementsByTagName("w:p"): + add_rsid_to_p(elem) + for elem in node.getElementsByTagName("w:r"): + add_rsid_to_r(elem) + for elem in node.getElementsByTagName("w:t"): + add_xml_space_to_t(elem) + for tag in ("w:ins", "w:del"): + for elem in node.getElementsByTagName(tag): + add_tracked_change_attrs(elem) + for elem in node.getElementsByTagName("w:comment"): + add_comment_attrs(elem) + for elem in node.getElementsByTagName("w16cex:commentExtensible"): + add_comment_extensible_date(elem) + + def replace_node(self, elem, new_content): + """Replace node with automatic attribute injection.""" + nodes = super().replace_node(elem, new_content) + self._inject_attributes_to_nodes(nodes) + return nodes + + def insert_after(self, elem, xml_content): + """Insert after with automatic attribute injection.""" + nodes = super().insert_after(elem, xml_content) + self._inject_attributes_to_nodes(nodes) + return nodes + + def insert_before(self, elem, xml_content): + """Insert before with automatic attribute injection.""" + nodes = super().insert_before(elem, xml_content) + self._inject_attributes_to_nodes(nodes) + return nodes + + def append_to(self, elem, xml_content): + """Append to with automatic attribute injection.""" + nodes = super().append_to(elem, xml_content) + self._inject_attributes_to_nodes(nodes) + return nodes + + def revert_insertion(self, elem): + """Reject an insertion by wrapping its content in a deletion. + + Wraps all runs inside w:ins in w:del, converting w:t to w:delText. + Can process a single w:ins element or a container element with multiple w:ins. + + Args: + elem: Element to process (w:ins, w:p, w:body, etc.) + + Returns: + list: List containing the processed element(s) + + Raises: + ValueError: If the element contains no w:ins elements + + Example: + # Reject a single insertion + ins = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "5"}) + doc["word/document.xml"].revert_insertion(ins) + + # Reject all insertions in a paragraph + para = doc["word/document.xml"].get_node(tag="w:p", line_number=42) + doc["word/document.xml"].revert_insertion(para) + """ + # Collect insertions + ins_elements = [] + if elem.tagName == "w:ins": + ins_elements.append(elem) + else: + ins_elements.extend(elem.getElementsByTagName("w:ins")) + + # Validate that there are insertions to reject + if not ins_elements: + raise ValueError( + f"revert_insertion requires w:ins elements. " + f"The provided element <{elem.tagName}> contains no insertions. " + ) + + # Process all insertions - wrap all children in w:del + for ins_elem in ins_elements: + runs = list(ins_elem.getElementsByTagName("w:r")) + if not runs: + continue + + # Create deletion wrapper + del_wrapper = self.dom.createElement("w:del") + + # Process each run + for run in runs: + # Convert w:t → w:delText and w:rsidR → w:rsidDel + if run.hasAttribute("w:rsidR"): + run.setAttribute("w:rsidDel", run.getAttribute("w:rsidR")) + run.removeAttribute("w:rsidR") + elif not run.hasAttribute("w:rsidDel"): + run.setAttribute("w:rsidDel", self.rsid) + + for t_elem in list(run.getElementsByTagName("w:t")): + del_text = self.dom.createElement("w:delText") + # Copy ALL child nodes (not just firstChild) to handle entities + while t_elem.firstChild: + del_text.appendChild(t_elem.firstChild) + for i in range(t_elem.attributes.length): + attr = t_elem.attributes.item(i) + del_text.setAttribute(attr.name, attr.value) + t_elem.parentNode.replaceChild(del_text, t_elem) + + # Move all children from ins to del wrapper + while ins_elem.firstChild: + del_wrapper.appendChild(ins_elem.firstChild) + + # Add del wrapper back to ins + ins_elem.appendChild(del_wrapper) + + # Inject attributes to the deletion wrapper + self._inject_attributes_to_nodes([del_wrapper]) + + return [elem] + + def revert_deletion(self, elem): + """Reject a deletion by re-inserting the deleted content. + + Creates w:ins elements after each w:del, copying deleted content and + converting w:delText back to w:t. + Can process a single w:del element or a container element with multiple w:del. + + Args: + elem: Element to process (w:del, w:p, w:body, etc.) + + Returns: + list: If elem is w:del, returns [elem, new_ins]. Otherwise returns [elem]. + + Raises: + ValueError: If the element contains no w:del elements + + Example: + # Reject a single deletion - returns [w:del, w:ins] + del_elem = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "3"}) + nodes = doc["word/document.xml"].revert_deletion(del_elem) + + # Reject all deletions in a paragraph - returns [para] + para = doc["word/document.xml"].get_node(tag="w:p", line_number=42) + nodes = doc["word/document.xml"].revert_deletion(para) + """ + # Collect deletions FIRST - before we modify the DOM + del_elements = [] + is_single_del = elem.tagName == "w:del" + + if is_single_del: + del_elements.append(elem) + else: + del_elements.extend(elem.getElementsByTagName("w:del")) + + # Validate that there are deletions to reject + if not del_elements: + raise ValueError( + f"revert_deletion requires w:del elements. " + f"The provided element <{elem.tagName}> contains no deletions. " + ) + + # Track created insertion (only relevant if elem is a single w:del) + created_insertion = None + + # Process all deletions - create insertions that copy the deleted content + for del_elem in del_elements: + # Clone the deleted runs and convert them to insertions + runs = list(del_elem.getElementsByTagName("w:r")) + if not runs: + continue + + # Create insertion wrapper + ins_elem = self.dom.createElement("w:ins") + + for run in runs: + # Clone the run + new_run = run.cloneNode(True) + + # Convert w:delText → w:t + for del_text in list(new_run.getElementsByTagName("w:delText")): + t_elem = self.dom.createElement("w:t") + # Copy ALL child nodes (not just firstChild) to handle entities + while del_text.firstChild: + t_elem.appendChild(del_text.firstChild) + for i in range(del_text.attributes.length): + attr = del_text.attributes.item(i) + t_elem.setAttribute(attr.name, attr.value) + del_text.parentNode.replaceChild(t_elem, del_text) + + # Update run attributes: w:rsidDel → w:rsidR + if new_run.hasAttribute("w:rsidDel"): + new_run.setAttribute("w:rsidR", new_run.getAttribute("w:rsidDel")) + new_run.removeAttribute("w:rsidDel") + elif not new_run.hasAttribute("w:rsidR"): + new_run.setAttribute("w:rsidR", self.rsid) + + ins_elem.appendChild(new_run) + + # Insert the new insertion after the deletion + nodes = self.insert_after(del_elem, ins_elem.toxml()) + + # If processing a single w:del, track the created insertion + if is_single_del and nodes: + created_insertion = nodes[0] + + # Return based on input type + if is_single_del and created_insertion: + return [elem, created_insertion] + else: + return [elem] + + @staticmethod + def suggest_paragraph(xml_content: str) -> str: + """Transform paragraph XML to add tracked change wrapping for insertion. + + Wraps runs in and adds to w:rPr in w:pPr for numbered lists. + + Args: + xml_content: XML string containing a element + + Returns: + str: Transformed XML with tracked change wrapping + """ + wrapper = f'{xml_content}' + doc = minidom.parseString(wrapper) + para = doc.getElementsByTagName("w:p")[0] + + # Ensure w:pPr exists + pPr_list = para.getElementsByTagName("w:pPr") + if not pPr_list: + pPr = doc.createElement("w:pPr") + para.insertBefore( + pPr, para.firstChild + ) if para.firstChild else para.appendChild(pPr) + else: + pPr = pPr_list[0] + + # Ensure w:rPr exists in w:pPr + rPr_list = pPr.getElementsByTagName("w:rPr") + if not rPr_list: + rPr = doc.createElement("w:rPr") + pPr.appendChild(rPr) + else: + rPr = rPr_list[0] + + # Add to w:rPr + ins_marker = doc.createElement("w:ins") + rPr.insertBefore( + ins_marker, rPr.firstChild + ) if rPr.firstChild else rPr.appendChild(ins_marker) + + # Wrap all non-pPr children in + ins_wrapper = doc.createElement("w:ins") + for child in [c for c in para.childNodes if c.nodeName != "w:pPr"]: + para.removeChild(child) + ins_wrapper.appendChild(child) + para.appendChild(ins_wrapper) + + return para.toxml() + + def suggest_deletion(self, elem): + """Mark a w:r or w:p element as deleted with tracked changes (in-place DOM manipulation). + + For w:r: wraps in , converts to , preserves w:rPr + For w:p (regular): wraps content in , converts to + For w:p (numbered list): adds to w:rPr in w:pPr, wraps content in + + Args: + elem: A w:r or w:p DOM element without existing tracked changes + + Returns: + Element: The modified element + + Raises: + ValueError: If element has existing tracked changes or invalid structure + """ + if elem.nodeName == "w:r": + # Check for existing w:delText + if elem.getElementsByTagName("w:delText"): + raise ValueError("w:r element already contains w:delText") + + # Convert w:t → w:delText + for t_elem in list(elem.getElementsByTagName("w:t")): + del_text = self.dom.createElement("w:delText") + # Copy ALL child nodes (not just firstChild) to handle entities + while t_elem.firstChild: + del_text.appendChild(t_elem.firstChild) + # Preserve attributes like xml:space + for i in range(t_elem.attributes.length): + attr = t_elem.attributes.item(i) + del_text.setAttribute(attr.name, attr.value) + t_elem.parentNode.replaceChild(del_text, t_elem) + + # Update run attributes: w:rsidR → w:rsidDel + if elem.hasAttribute("w:rsidR"): + elem.setAttribute("w:rsidDel", elem.getAttribute("w:rsidR")) + elem.removeAttribute("w:rsidR") + elif not elem.hasAttribute("w:rsidDel"): + elem.setAttribute("w:rsidDel", self.rsid) + + # Wrap in w:del + del_wrapper = self.dom.createElement("w:del") + parent = elem.parentNode + parent.insertBefore(del_wrapper, elem) + parent.removeChild(elem) + del_wrapper.appendChild(elem) + + # Inject attributes to the deletion wrapper + self._inject_attributes_to_nodes([del_wrapper]) + + return del_wrapper + + elif elem.nodeName == "w:p": + # Check for existing tracked changes + if elem.getElementsByTagName("w:ins") or elem.getElementsByTagName("w:del"): + raise ValueError("w:p element already contains tracked changes") + + # Check if it's a numbered list item + pPr_list = elem.getElementsByTagName("w:pPr") + is_numbered = pPr_list and pPr_list[0].getElementsByTagName("w:numPr") + + if is_numbered: + # Add to w:rPr in w:pPr + pPr = pPr_list[0] + rPr_list = pPr.getElementsByTagName("w:rPr") + + if not rPr_list: + rPr = self.dom.createElement("w:rPr") + pPr.appendChild(rPr) + else: + rPr = rPr_list[0] + + # Add marker + del_marker = self.dom.createElement("w:del") + rPr.insertBefore( + del_marker, rPr.firstChild + ) if rPr.firstChild else rPr.appendChild(del_marker) + + # Convert w:t → w:delText in all runs + for t_elem in list(elem.getElementsByTagName("w:t")): + del_text = self.dom.createElement("w:delText") + # Copy ALL child nodes (not just firstChild) to handle entities + while t_elem.firstChild: + del_text.appendChild(t_elem.firstChild) + # Preserve attributes like xml:space + for i in range(t_elem.attributes.length): + attr = t_elem.attributes.item(i) + del_text.setAttribute(attr.name, attr.value) + t_elem.parentNode.replaceChild(del_text, t_elem) + + # Update run attributes: w:rsidR → w:rsidDel + for run in elem.getElementsByTagName("w:r"): + if run.hasAttribute("w:rsidR"): + run.setAttribute("w:rsidDel", run.getAttribute("w:rsidR")) + run.removeAttribute("w:rsidR") + elif not run.hasAttribute("w:rsidDel"): + run.setAttribute("w:rsidDel", self.rsid) + + # Wrap all non-pPr children in + del_wrapper = self.dom.createElement("w:del") + for child in [c for c in elem.childNodes if c.nodeName != "w:pPr"]: + elem.removeChild(child) + del_wrapper.appendChild(child) + elem.appendChild(del_wrapper) + + # Inject attributes to the deletion wrapper + self._inject_attributes_to_nodes([del_wrapper]) + + return elem + + else: + raise ValueError(f"Element must be w:r or w:p, got {elem.nodeName}") + + +def _generate_hex_id() -> str: + """Generate random 8-character hex ID for para/durable IDs. + + Values are constrained to be less than 0x7FFFFFFF per OOXML spec: + - paraId must be < 0x80000000 + - durableId must be < 0x7FFFFFFF + We use the stricter constraint (0x7FFFFFFF) for both. + """ + return f"{random.randint(1, 0x7FFFFFFE):08X}" + + +def _generate_rsid() -> str: + """Generate random 8-character hex RSID.""" + return "".join(random.choices("0123456789ABCDEF", k=8)) + + +class Document: + """Manages comments in unpacked Word documents.""" + + def __init__( + self, + unpacked_dir, + rsid=None, + track_revisions=False, + author="Claude", + initials="C", + ): + """ + Initialize with path to unpacked Word document directory. + Automatically sets up comment infrastructure (people.xml, RSIDs). + + Args: + unpacked_dir: Path to unpacked DOCX directory (must contain word/ subdirectory) + rsid: Optional RSID to use for all comment elements. If not provided, one will be generated. + track_revisions: If True, enables track revisions in settings.xml (default: False) + author: Default author name for comments (default: "Claude") + initials: Default author initials for comments (default: "C") + """ + self.original_path = Path(unpacked_dir) + + if not self.original_path.exists() or not self.original_path.is_dir(): + raise ValueError(f"Directory not found: {unpacked_dir}") + + # Create temporary directory with subdirectories for unpacked content and baseline + self.temp_dir = tempfile.mkdtemp(prefix="docx_") + self.unpacked_path = Path(self.temp_dir) / "unpacked" + shutil.copytree(self.original_path, self.unpacked_path) + + # Pack original directory into temporary .docx for validation baseline (outside unpacked dir) + self.original_docx = Path(self.temp_dir) / "original.docx" + pack_document(self.original_path, self.original_docx, validate=False) + + self.word_path = self.unpacked_path / "word" + + # Generate RSID if not provided + self.rsid = rsid if rsid else _generate_rsid() + print(f"Using RSID: {self.rsid}") + + # Set default author and initials + self.author = author + self.initials = initials + + # Cache for lazy-loaded editors + self._editors = {} + + # Comment file paths + self.comments_path = self.word_path / "comments.xml" + self.comments_extended_path = self.word_path / "commentsExtended.xml" + self.comments_ids_path = self.word_path / "commentsIds.xml" + self.comments_extensible_path = self.word_path / "commentsExtensible.xml" + + # Load existing comments and determine next ID (before setup modifies files) + self.existing_comments = self._load_existing_comments() + self.next_comment_id = self._get_next_comment_id() + + # Convenient access to document.xml editor (semi-private) + self._document = self["word/document.xml"] + + # Setup tracked changes infrastructure + self._setup_tracking(track_revisions=track_revisions) + + # Add author to people.xml + self._add_author_to_people(author) + + def __getitem__(self, xml_path: str) -> DocxXMLEditor: + """ + Get or create a DocxXMLEditor for the specified XML file. + + Enables lazy-loaded editors with bracket notation: + node = doc["word/document.xml"].get_node(tag="w:p", line_number=42) + + Args: + xml_path: Relative path to XML file (e.g., "word/document.xml", "word/comments.xml") + + Returns: + DocxXMLEditor instance for the specified file + + Raises: + ValueError: If the file does not exist + + Example: + # Get node from document.xml + node = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "1"}) + + # Get node from comments.xml + comment = doc["word/comments.xml"].get_node(tag="w:comment", attrs={"w:id": "0"}) + """ + if xml_path not in self._editors: + file_path = self.unpacked_path / xml_path + if not file_path.exists(): + raise ValueError(f"XML file not found: {xml_path}") + # Use DocxXMLEditor with RSID, author, and initials for all editors + self._editors[xml_path] = DocxXMLEditor( + file_path, rsid=self.rsid, author=self.author, initials=self.initials + ) + return self._editors[xml_path] + + def add_comment(self, start, end, text: str) -> int: + """ + Add a comment spanning from one element to another. + + Args: + start: DOM element for the starting point + end: DOM element for the ending point + text: Comment content + + Returns: + The comment ID that was created + + Example: + start_node = cm.get_document_node(tag="w:del", id="1") + end_node = cm.get_document_node(tag="w:ins", id="2") + cm.add_comment(start=start_node, end=end_node, text="Explanation") + """ + comment_id = self.next_comment_id + para_id = _generate_hex_id() + durable_id = _generate_hex_id() + timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + # Add comment ranges to document.xml immediately + self._document.insert_before(start, self._comment_range_start_xml(comment_id)) + + # If end node is a paragraph, append comment markup inside it + # Otherwise insert after it (for run-level anchors) + if end.tagName == "w:p": + self._document.append_to(end, self._comment_range_end_xml(comment_id)) + else: + self._document.insert_after(end, self._comment_range_end_xml(comment_id)) + + # Add to comments.xml immediately + self._add_to_comments_xml( + comment_id, para_id, text, self.author, self.initials, timestamp + ) + + # Add to commentsExtended.xml immediately + self._add_to_comments_extended_xml(para_id, parent_para_id=None) + + # Add to commentsIds.xml immediately + self._add_to_comments_ids_xml(para_id, durable_id) + + # Add to commentsExtensible.xml immediately + self._add_to_comments_extensible_xml(durable_id) + + # Update existing_comments so replies work + self.existing_comments[comment_id] = {"para_id": para_id} + + self.next_comment_id += 1 + return comment_id + + def reply_to_comment( + self, + parent_comment_id: int, + text: str, + ) -> int: + """ + Add a reply to an existing comment. + + Args: + parent_comment_id: The w:id of the parent comment to reply to + text: Reply text + + Returns: + The comment ID that was created for the reply + + Example: + cm.reply_to_comment(parent_comment_id=0, text="I agree with this change") + """ + if parent_comment_id not in self.existing_comments: + raise ValueError(f"Parent comment with id={parent_comment_id} not found") + + parent_info = self.existing_comments[parent_comment_id] + comment_id = self.next_comment_id + para_id = _generate_hex_id() + durable_id = _generate_hex_id() + timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + # Add comment ranges to document.xml immediately + parent_start_elem = self._document.get_node( + tag="w:commentRangeStart", attrs={"w:id": str(parent_comment_id)} + ) + parent_ref_elem = self._document.get_node( + tag="w:commentReference", attrs={"w:id": str(parent_comment_id)} + ) + + self._document.insert_after( + parent_start_elem, self._comment_range_start_xml(comment_id) + ) + parent_ref_run = parent_ref_elem.parentNode + self._document.insert_after( + parent_ref_run, f'' + ) + self._document.insert_after( + parent_ref_run, self._comment_ref_run_xml(comment_id) + ) + + # Add to comments.xml immediately + self._add_to_comments_xml( + comment_id, para_id, text, self.author, self.initials, timestamp + ) + + # Add to commentsExtended.xml immediately (with parent) + self._add_to_comments_extended_xml( + para_id, parent_para_id=parent_info["para_id"] + ) + + # Add to commentsIds.xml immediately + self._add_to_comments_ids_xml(para_id, durable_id) + + # Add to commentsExtensible.xml immediately + self._add_to_comments_extensible_xml(durable_id) + + # Update existing_comments so replies work + self.existing_comments[comment_id] = {"para_id": para_id} + + self.next_comment_id += 1 + return comment_id + + def __del__(self): + """Clean up temporary directory on deletion.""" + if hasattr(self, "temp_dir") and Path(self.temp_dir).exists(): + shutil.rmtree(self.temp_dir) + + def validate(self) -> None: + """ + Validate the document against XSD schema and redlining rules. + + Raises: + ValueError: If validation fails. + """ + # Create validators with current state + schema_validator = DOCXSchemaValidator( + self.unpacked_path, self.original_docx, verbose=False + ) + redlining_validator = RedliningValidator( + self.unpacked_path, self.original_docx, verbose=False + ) + + # Run validations + if not schema_validator.validate(): + raise ValueError("Schema validation failed") + if not redlining_validator.validate(): + raise ValueError("Redlining validation failed") + + def save(self, destination=None, validate=True) -> None: + """ + Save all modified XML files to disk and copy to destination directory. + + This persists all changes made via add_comment() and reply_to_comment(). + + Args: + destination: Optional path to save to. If None, saves back to original directory. + validate: If True, validates document before saving (default: True). + """ + # Only ensure comment relationships and content types if comment files exist + if self.comments_path.exists(): + self._ensure_comment_relationships() + self._ensure_comment_content_types() + + # Save all modified XML files in temp directory + for editor in self._editors.values(): + editor.save() + + # Validate by default + if validate: + self.validate() + + # Copy contents from temp directory to destination (or original directory) + target_path = Path(destination) if destination else self.original_path + shutil.copytree(self.unpacked_path, target_path, dirs_exist_ok=True) + + # ==================== Private: Initialization ==================== + + def _get_next_comment_id(self): + """Get the next available comment ID.""" + if not self.comments_path.exists(): + return 0 + + editor = self["word/comments.xml"] + max_id = -1 + for comment_elem in editor.dom.getElementsByTagName("w:comment"): + comment_id = comment_elem.getAttribute("w:id") + if comment_id: + try: + max_id = max(max_id, int(comment_id)) + except ValueError: + pass + return max_id + 1 + + def _load_existing_comments(self): + """Load existing comments from files to enable replies.""" + if not self.comments_path.exists(): + return {} + + editor = self["word/comments.xml"] + existing = {} + + for comment_elem in editor.dom.getElementsByTagName("w:comment"): + comment_id = comment_elem.getAttribute("w:id") + if not comment_id: + continue + + # Find para_id from the w:p element within the comment + para_id = None + for p_elem in comment_elem.getElementsByTagName("w:p"): + para_id = p_elem.getAttribute("w14:paraId") + if para_id: + break + + if not para_id: + continue + + existing[int(comment_id)] = {"para_id": para_id} + + return existing + + # ==================== Private: Setup Methods ==================== + + def _setup_tracking(self, track_revisions=False): + """Set up comment infrastructure in unpacked directory. + + Args: + track_revisions: If True, enables track revisions in settings.xml + """ + # Create or update word/people.xml + people_file = self.word_path / "people.xml" + self._update_people_xml(people_file) + + # Update XML files + self._add_content_type_for_people(self.unpacked_path / "[Content_Types].xml") + self._add_relationship_for_people( + self.word_path / "_rels" / "document.xml.rels" + ) + + # Always add RSID to settings.xml, optionally enable trackRevisions + self._update_settings( + self.word_path / "settings.xml", track_revisions=track_revisions + ) + + def _update_people_xml(self, path): + """Create people.xml if it doesn't exist.""" + if not path.exists(): + # Copy from template + shutil.copy(TEMPLATE_DIR / "people.xml", path) + + def _add_content_type_for_people(self, path): + """Add people.xml content type to [Content_Types].xml if not already present.""" + editor = self["[Content_Types].xml"] + + if self._has_override(editor, "/word/people.xml"): + return + + # Add Override element + root = editor.dom.documentElement + override_xml = '' + editor.append_to(root, override_xml) + + def _add_relationship_for_people(self, path): + """Add people.xml relationship to document.xml.rels if not already present.""" + editor = self["word/_rels/document.xml.rels"] + + if self._has_relationship(editor, "people.xml"): + return + + root = editor.dom.documentElement + root_tag = root.tagName # type: ignore + prefix = root_tag.split(":")[0] + ":" if ":" in root_tag else "" + next_rid = editor.get_next_rid() + + # Create the relationship entry + rel_xml = f'<{prefix}Relationship Id="{next_rid}" Type="http://schemas.microsoft.com/office/2011/relationships/people" Target="people.xml"/>' + editor.append_to(root, rel_xml) + + def _update_settings(self, path, track_revisions=False): + """Add RSID and optionally enable track revisions in settings.xml. + + Args: + path: Path to settings.xml + track_revisions: If True, adds trackRevisions element + + Places elements per OOXML schema order: + - trackRevisions: early (before defaultTabStop) + - rsids: late (after compat) + """ + editor = self["word/settings.xml"] + root = editor.get_node(tag="w:settings") + prefix = root.tagName.split(":")[0] if ":" in root.tagName else "w" + + # Conditionally add trackRevisions if requested + if track_revisions: + track_revisions_exists = any( + elem.tagName == f"{prefix}:trackRevisions" + for elem in editor.dom.getElementsByTagName(f"{prefix}:trackRevisions") + ) + + if not track_revisions_exists: + track_rev_xml = f"<{prefix}:trackRevisions/>" + # Try to insert before documentProtection, defaultTabStop, or at start + inserted = False + for tag in [f"{prefix}:documentProtection", f"{prefix}:defaultTabStop"]: + elements = editor.dom.getElementsByTagName(tag) + if elements: + editor.insert_before(elements[0], track_rev_xml) + inserted = True + break + if not inserted: + # Insert as first child of settings + if root.firstChild: + editor.insert_before(root.firstChild, track_rev_xml) + else: + editor.append_to(root, track_rev_xml) + + # Always check if rsids section exists + rsids_elements = editor.dom.getElementsByTagName(f"{prefix}:rsids") + + if not rsids_elements: + # Add new rsids section + rsids_xml = f'''<{prefix}:rsids> + <{prefix}:rsidRoot {prefix}:val="{self.rsid}"/> + <{prefix}:rsid {prefix}:val="{self.rsid}"/> +''' + + # Try to insert after compat, before clrSchemeMapping, or before closing tag + inserted = False + compat_elements = editor.dom.getElementsByTagName(f"{prefix}:compat") + if compat_elements: + editor.insert_after(compat_elements[0], rsids_xml) + inserted = True + + if not inserted: + clr_elements = editor.dom.getElementsByTagName( + f"{prefix}:clrSchemeMapping" + ) + if clr_elements: + editor.insert_before(clr_elements[0], rsids_xml) + inserted = True + + if not inserted: + editor.append_to(root, rsids_xml) + else: + # Check if this rsid already exists + rsids_elem = rsids_elements[0] + rsid_exists = any( + elem.getAttribute(f"{prefix}:val") == self.rsid + for elem in rsids_elem.getElementsByTagName(f"{prefix}:rsid") + ) + + if not rsid_exists: + rsid_xml = f'<{prefix}:rsid {prefix}:val="{self.rsid}"/>' + editor.append_to(rsids_elem, rsid_xml) + + # ==================== Private: XML File Creation ==================== + + def _add_to_comments_xml( + self, comment_id, para_id, text, author, initials, timestamp + ): + """Add a single comment to comments.xml.""" + if not self.comments_path.exists(): + shutil.copy(TEMPLATE_DIR / "comments.xml", self.comments_path) + + editor = self["word/comments.xml"] + root = editor.get_node(tag="w:comments") + + escaped_text = ( + text.replace("&", "&").replace("<", "<").replace(">", ">") + ) + # Note: w:rsidR, w:rsidRDefault, w:rsidP on w:p, w:rsidR on w:r, + # and w:author, w:date, w:initials on w:comment are automatically added by DocxXMLEditor + comment_xml = f''' + + + {escaped_text} + +''' + editor.append_to(root, comment_xml) + + def _add_to_comments_extended_xml(self, para_id, parent_para_id): + """Add a single comment to commentsExtended.xml.""" + if not self.comments_extended_path.exists(): + shutil.copy( + TEMPLATE_DIR / "commentsExtended.xml", self.comments_extended_path + ) + + editor = self["word/commentsExtended.xml"] + root = editor.get_node(tag="w15:commentsEx") + + if parent_para_id: + xml = f'' + else: + xml = f'' + editor.append_to(root, xml) + + def _add_to_comments_ids_xml(self, para_id, durable_id): + """Add a single comment to commentsIds.xml.""" + if not self.comments_ids_path.exists(): + shutil.copy(TEMPLATE_DIR / "commentsIds.xml", self.comments_ids_path) + + editor = self["word/commentsIds.xml"] + root = editor.get_node(tag="w16cid:commentsIds") + + xml = f'' + editor.append_to(root, xml) + + def _add_to_comments_extensible_xml(self, durable_id): + """Add a single comment to commentsExtensible.xml.""" + if not self.comments_extensible_path.exists(): + shutil.copy( + TEMPLATE_DIR / "commentsExtensible.xml", self.comments_extensible_path + ) + + editor = self["word/commentsExtensible.xml"] + root = editor.get_node(tag="w16cex:commentsExtensible") + + xml = f'' + editor.append_to(root, xml) + + # ==================== Private: XML Fragments ==================== + + def _comment_range_start_xml(self, comment_id): + """Generate XML for comment range start.""" + return f'' + + def _comment_range_end_xml(self, comment_id): + """Generate XML for comment range end with reference run. + + Note: w:rsidR is automatically added by DocxXMLEditor. + """ + return f''' + + + +''' + + def _comment_ref_run_xml(self, comment_id): + """Generate XML for comment reference run. + + Note: w:rsidR is automatically added by DocxXMLEditor. + """ + return f''' + + +''' + + # ==================== Private: Metadata Updates ==================== + + def _has_relationship(self, editor, target): + """Check if a relationship with given target exists.""" + for rel_elem in editor.dom.getElementsByTagName("Relationship"): + if rel_elem.getAttribute("Target") == target: + return True + return False + + def _has_override(self, editor, part_name): + """Check if an override with given part name exists.""" + for override_elem in editor.dom.getElementsByTagName("Override"): + if override_elem.getAttribute("PartName") == part_name: + return True + return False + + def _has_author(self, editor, author): + """Check if an author already exists in people.xml.""" + for person_elem in editor.dom.getElementsByTagName("w15:person"): + if person_elem.getAttribute("w15:author") == author: + return True + return False + + def _add_author_to_people(self, author): + """Add author to people.xml (called during initialization).""" + people_path = self.word_path / "people.xml" + + # people.xml should already exist from _setup_tracking + if not people_path.exists(): + raise ValueError("people.xml should exist after _setup_tracking") + + editor = self["word/people.xml"] + root = editor.get_node(tag="w15:people") + + # Check if author already exists + if self._has_author(editor, author): + return + + # Add author with proper XML escaping to prevent injection + escaped_author = html.escape(author, quote=True) + person_xml = f''' + +''' + editor.append_to(root, person_xml) + + def _ensure_comment_relationships(self): + """Ensure word/_rels/document.xml.rels has comment relationships.""" + editor = self["word/_rels/document.xml.rels"] + + if self._has_relationship(editor, "comments.xml"): + return + + root = editor.dom.documentElement + root_tag = root.tagName # type: ignore + prefix = root_tag.split(":")[0] + ":" if ":" in root_tag else "" + next_rid_num = int(editor.get_next_rid()[3:]) + + # Add relationship elements + rels = [ + ( + next_rid_num, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments", + "comments.xml", + ), + ( + next_rid_num + 1, + "http://schemas.microsoft.com/office/2011/relationships/commentsExtended", + "commentsExtended.xml", + ), + ( + next_rid_num + 2, + "http://schemas.microsoft.com/office/2016/09/relationships/commentsIds", + "commentsIds.xml", + ), + ( + next_rid_num + 3, + "http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible", + "commentsExtensible.xml", + ), + ] + + for rel_id, rel_type, target in rels: + rel_xml = f'<{prefix}Relationship Id="rId{rel_id}" Type="{rel_type}" Target="{target}"/>' + editor.append_to(root, rel_xml) + + def _ensure_comment_content_types(self): + """Ensure [Content_Types].xml has comment content types.""" + editor = self["[Content_Types].xml"] + + if self._has_override(editor, "/word/comments.xml"): + return + + root = editor.dom.documentElement + + # Add Override elements + overrides = [ + ( + "/word/comments.xml", + "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml", + ), + ( + "/word/commentsExtended.xml", + "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml", + ), + ( + "/word/commentsIds.xml", + "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml", + ), + ( + "/word/commentsExtensible.xml", + "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml", + ), + ] + + for part_name, content_type in overrides: + override_xml = ( + f'' + ) + editor.append_to(root, override_xml) diff --git a/src/flow/skills/docx/scripts/templates/comments.xml b/src/flow/skills/docx/scripts/templates/comments.xml new file mode 100644 index 0000000000000000000000000000000000000000..b5dace0ef98998ba6ba55b781ef9c549c11b5f97 --- /dev/null +++ b/src/flow/skills/docx/scripts/templates/comments.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/flow/skills/docx/scripts/templates/commentsExtended.xml b/src/flow/skills/docx/scripts/templates/commentsExtended.xml new file mode 100644 index 0000000000000000000000000000000000000000..b4cf23e356918d30f4bbd3ed3ee7a47c1ab5c13d --- /dev/null +++ b/src/flow/skills/docx/scripts/templates/commentsExtended.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/flow/skills/docx/scripts/templates/commentsExtensible.xml b/src/flow/skills/docx/scripts/templates/commentsExtensible.xml new file mode 100644 index 0000000000000000000000000000000000000000..e32a05e0c3ce9419b48c0ee121daa195070797b4 --- /dev/null +++ b/src/flow/skills/docx/scripts/templates/commentsExtensible.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/flow/skills/docx/scripts/templates/commentsIds.xml b/src/flow/skills/docx/scripts/templates/commentsIds.xml new file mode 100644 index 0000000000000000000000000000000000000000..d04bc8e06dfd7370d39c29d10c7c968d17860d43 --- /dev/null +++ b/src/flow/skills/docx/scripts/templates/commentsIds.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/flow/skills/docx/scripts/templates/people.xml b/src/flow/skills/docx/scripts/templates/people.xml new file mode 100644 index 0000000000000000000000000000000000000000..a839cafeb3e5e7af2d6ee5c4f0ca71112e004041 --- /dev/null +++ b/src/flow/skills/docx/scripts/templates/people.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/flow/skills/docx/scripts/utilities.py b/src/flow/skills/docx/scripts/utilities.py new file mode 100755 index 0000000000000000000000000000000000000000..d92dae611d442cdfd498b2271a8221f98837be0a --- /dev/null +++ b/src/flow/skills/docx/scripts/utilities.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +""" +Utilities for editing OOXML documents. + +This module provides XMLEditor, a tool for manipulating XML files with support for +line-number-based node finding and DOM manipulation. Each element is automatically +annotated with its original line and column position during parsing. + +Example usage: + editor = XMLEditor("document.xml") + + # Find node by line number or range + elem = editor.get_node(tag="w:r", line_number=519) + elem = editor.get_node(tag="w:p", line_number=range(100, 200)) + + # Find node by text content + elem = editor.get_node(tag="w:p", contains="specific text") + + # Find node by attributes + elem = editor.get_node(tag="w:r", attrs={"w:id": "target"}) + + # Combine filters + elem = editor.get_node(tag="w:p", line_number=range(1, 50), contains="text") + + # Replace, insert, or manipulate + new_elem = editor.replace_node(elem, "new text") + editor.insert_after(new_elem, "more") + + # Save changes + editor.save() +""" + +import html +from pathlib import Path +from typing import Optional, Union + +import defusedxml.minidom +import defusedxml.sax + + +class XMLEditor: + """ + Editor for manipulating OOXML XML files with line-number-based node finding. + + This class parses XML files and tracks the original line and column position + of each element. This enables finding nodes by their line number in the original + file, which is useful when working with Read tool output. + + Attributes: + xml_path: Path to the XML file being edited + encoding: Detected encoding of the XML file ('ascii' or 'utf-8') + dom: Parsed DOM tree with parse_position attributes on elements + """ + + def __init__(self, xml_path): + """ + Initialize with path to XML file and parse with line number tracking. + + Args: + xml_path: Path to XML file to edit (str or Path) + + Raises: + ValueError: If the XML file does not exist + """ + self.xml_path = Path(xml_path) + if not self.xml_path.exists(): + raise ValueError(f"XML file not found: {xml_path}") + + with open(self.xml_path, "rb") as f: + header = f.read(200).decode("utf-8", errors="ignore") + self.encoding = "ascii" if 'encoding="ascii"' in header else "utf-8" + + parser = _create_line_tracking_parser() + self.dom = defusedxml.minidom.parse(str(self.xml_path), parser) + + def get_node( + self, + tag: str, + attrs: Optional[dict[str, str]] = None, + line_number: Optional[Union[int, range]] = None, + contains: Optional[str] = None, + ): + """ + Get a DOM element by tag and identifier. + + Finds an element by either its line number in the original file or by + matching attribute values. Exactly one match must be found. + + Args: + tag: The XML tag name (e.g., "w:del", "w:ins", "w:r") + attrs: Dictionary of attribute name-value pairs to match (e.g., {"w:id": "1"}) + line_number: Line number (int) or line range (range) in original XML file (1-indexed) + contains: Text string that must appear in any text node within the element. + Supports both entity notation (“) and Unicode characters (\u201c). + + Returns: + defusedxml.minidom.Element: The matching DOM element + + Raises: + ValueError: If node not found or multiple matches found + + Example: + elem = editor.get_node(tag="w:r", line_number=519) + elem = editor.get_node(tag="w:r", line_number=range(100, 200)) + elem = editor.get_node(tag="w:del", attrs={"w:id": "1"}) + elem = editor.get_node(tag="w:p", attrs={"w14:paraId": "12345678"}) + elem = editor.get_node(tag="w:commentRangeStart", attrs={"w:id": "0"}) + elem = editor.get_node(tag="w:p", contains="specific text") + elem = editor.get_node(tag="w:t", contains="“Agreement") # Entity notation + elem = editor.get_node(tag="w:t", contains="\u201cAgreement") # Unicode character + """ + matches = [] + for elem in self.dom.getElementsByTagName(tag): + # Check line_number filter + if line_number is not None: + parse_pos = getattr(elem, "parse_position", (None,)) + elem_line = parse_pos[0] + + # Handle both single line number and range + if isinstance(line_number, range): + if elem_line not in line_number: + continue + else: + if elem_line != line_number: + continue + + # Check attrs filter + if attrs is not None: + if not all( + elem.getAttribute(attr_name) == attr_value + for attr_name, attr_value in attrs.items() + ): + continue + + # Check contains filter + if contains is not None: + elem_text = self._get_element_text(elem) + # Normalize the search string: convert HTML entities to Unicode characters + # This allows searching for both "“Rowan" and ""Rowan" + normalized_contains = html.unescape(contains) + if normalized_contains not in elem_text: + continue + + # If all applicable filters passed, this is a match + matches.append(elem) + + if not matches: + # Build descriptive error message + filters = [] + if line_number is not None: + line_str = ( + f"lines {line_number.start}-{line_number.stop - 1}" + if isinstance(line_number, range) + else f"line {line_number}" + ) + filters.append(f"at {line_str}") + if attrs is not None: + filters.append(f"with attributes {attrs}") + if contains is not None: + filters.append(f"containing '{contains}'") + + filter_desc = " ".join(filters) if filters else "" + base_msg = f"Node not found: <{tag}> {filter_desc}".strip() + + # Add helpful hint based on filters used + if contains: + hint = "Text may be split across elements or use different wording." + elif line_number: + hint = "Line numbers may have changed if document was modified." + elif attrs: + hint = "Verify attribute values are correct." + else: + hint = "Try adding filters (attrs, line_number, or contains)." + + raise ValueError(f"{base_msg}. {hint}") + if len(matches) > 1: + raise ValueError( + f"Multiple nodes found: <{tag}>. " + f"Add more filters (attrs, line_number, or contains) to narrow the search." + ) + return matches[0] + + def _get_element_text(self, elem): + """ + Recursively extract all text content from an element. + + Skips text nodes that contain only whitespace (spaces, tabs, newlines), + which typically represent XML formatting rather than document content. + + Args: + elem: defusedxml.minidom.Element to extract text from + + Returns: + str: Concatenated text from all non-whitespace text nodes within the element + """ + text_parts = [] + for node in elem.childNodes: + if node.nodeType == node.TEXT_NODE: + # Skip whitespace-only text nodes (XML formatting) + if node.data.strip(): + text_parts.append(node.data) + elif node.nodeType == node.ELEMENT_NODE: + text_parts.append(self._get_element_text(node)) + return "".join(text_parts) + + def replace_node(self, elem, new_content): + """ + Replace a DOM element with new XML content. + + Args: + elem: defusedxml.minidom.Element to replace + new_content: String containing XML to replace the node with + + Returns: + List[defusedxml.minidom.Node]: All inserted nodes + + Example: + new_nodes = editor.replace_node(old_elem, "text") + """ + parent = elem.parentNode + nodes = self._parse_fragment(new_content) + for node in nodes: + parent.insertBefore(node, elem) + parent.removeChild(elem) + return nodes + + def insert_after(self, elem, xml_content): + """ + Insert XML content after a DOM element. + + Args: + elem: defusedxml.minidom.Element to insert after + xml_content: String containing XML to insert + + Returns: + List[defusedxml.minidom.Node]: All inserted nodes + + Example: + new_nodes = editor.insert_after(elem, "text") + """ + parent = elem.parentNode + next_sibling = elem.nextSibling + nodes = self._parse_fragment(xml_content) + for node in nodes: + if next_sibling: + parent.insertBefore(node, next_sibling) + else: + parent.appendChild(node) + return nodes + + def insert_before(self, elem, xml_content): + """ + Insert XML content before a DOM element. + + Args: + elem: defusedxml.minidom.Element to insert before + xml_content: String containing XML to insert + + Returns: + List[defusedxml.minidom.Node]: All inserted nodes + + Example: + new_nodes = editor.insert_before(elem, "text") + """ + parent = elem.parentNode + nodes = self._parse_fragment(xml_content) + for node in nodes: + parent.insertBefore(node, elem) + return nodes + + def append_to(self, elem, xml_content): + """ + Append XML content as a child of a DOM element. + + Args: + elem: defusedxml.minidom.Element to append to + xml_content: String containing XML to append + + Returns: + List[defusedxml.minidom.Node]: All inserted nodes + + Example: + new_nodes = editor.append_to(elem, "text") + """ + nodes = self._parse_fragment(xml_content) + for node in nodes: + elem.appendChild(node) + return nodes + + def get_next_rid(self): + """Get the next available rId for relationships files.""" + max_id = 0 + for rel_elem in self.dom.getElementsByTagName("Relationship"): + rel_id = rel_elem.getAttribute("Id") + if rel_id.startswith("rId"): + try: + max_id = max(max_id, int(rel_id[3:])) + except ValueError: + pass + return f"rId{max_id + 1}" + + def save(self): + """ + Save the edited XML back to the file. + + Serializes the DOM tree and writes it back to the original file path, + preserving the original encoding (ascii or utf-8). + """ + content = self.dom.toxml(encoding=self.encoding) + self.xml_path.write_bytes(content) + + def _parse_fragment(self, xml_content): + """ + Parse XML fragment and return list of imported nodes. + + Args: + xml_content: String containing XML fragment + + Returns: + List of defusedxml.minidom.Node objects imported into this document + + Raises: + AssertionError: If fragment contains no element nodes + """ + # Extract namespace declarations from the root document element + root_elem = self.dom.documentElement + namespaces = [] + if root_elem and root_elem.attributes: + for i in range(root_elem.attributes.length): + attr = root_elem.attributes.item(i) + if attr.name.startswith("xmlns"): # type: ignore + namespaces.append(f'{attr.name}="{attr.value}"') # type: ignore + + ns_decl = " ".join(namespaces) + wrapper = f"{xml_content}" + fragment_doc = defusedxml.minidom.parseString(wrapper) + nodes = [ + self.dom.importNode(child, deep=True) + for child in fragment_doc.documentElement.childNodes # type: ignore + ] + elements = [n for n in nodes if n.nodeType == n.ELEMENT_NODE] + assert elements, "Fragment must contain at least one element" + return nodes + + +def _create_line_tracking_parser(): + """ + Create a SAX parser that tracks line and column numbers for each element. + + Monkey patches the SAX content handler to store the current line and column + position from the underlying expat parser onto each element as a parse_position + attribute (line, column) tuple. + + Returns: + defusedxml.sax.xmlreader.XMLReader: Configured SAX parser + """ + + def set_content_handler(dom_handler): + def startElementNS(name, tagName, attrs): + orig_start_cb(name, tagName, attrs) + cur_elem = dom_handler.elementStack[-1] + cur_elem.parse_position = ( + parser._parser.CurrentLineNumber, # type: ignore + parser._parser.CurrentColumnNumber, # type: ignore + ) + + orig_start_cb = dom_handler.startElementNS + dom_handler.startElementNS = startElementNS + orig_set_content_handler(dom_handler) + + parser = defusedxml.sax.make_parser() + orig_set_content_handler = parser.setContentHandler + parser.setContentHandler = set_content_handler # type: ignore + return parser diff --git a/src/flow/skills/frontend-design/LICENSE.txt b/src/flow/skills/frontend-design/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..f433b1a53f5b830a205fd2df78e2b34974656c7b --- /dev/null +++ b/src/flow/skills/frontend-design/LICENSE.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/src/flow/skills/frontend-design/SKILL.md b/src/flow/skills/frontend-design/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..5be498e2585843c7137bf9a74e262f57415de5ce --- /dev/null +++ b/src/flow/skills/frontend-design/SKILL.md @@ -0,0 +1,42 @@ +--- +name: frontend-design +description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics. +license: Complete terms in LICENSE.txt +--- + +This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. + +The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. + +## Design Thinking + +Before coding, understand the context and commit to a BOLD aesthetic direction: +- **Purpose**: What problem does this interface solve? Who uses it? +- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. +- **Constraints**: Technical requirements (framework, performance, accessibility). +- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? + +**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. + +Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: +- Production-grade and functional +- Visually striking and memorable +- Cohesive with a clear aesthetic point-of-view +- Meticulously refined in every detail + +## Frontend Aesthetics Guidelines + +Focus on: +- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. +- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. +- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. +- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. +- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. + +NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. + +Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. + +**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. + +Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. diff --git a/src/flow/skills/pdf/LICENSE.txt b/src/flow/skills/pdf/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..c55ab42224874608473643de0a85736b7fec0730 --- /dev/null +++ b/src/flow/skills/pdf/LICENSE.txt @@ -0,0 +1,30 @@ +© 2025 Anthropic, PBC. All rights reserved. + +LICENSE: Use of these materials (including all code, prompts, assets, files, +and other components of this Skill) is governed by your agreement with +Anthropic regarding use of Anthropic's services. If no separate agreement +exists, use is governed by Anthropic's Consumer Terms of Service or +Commercial Terms of Service, as applicable: +https://www.anthropic.com/legal/consumer-terms +https://www.anthropic.com/legal/commercial-terms +Your applicable agreement is referred to as the "Agreement." "Services" are +as defined in the Agreement. + +ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the +contrary, users may not: + +- Extract these materials from the Services or retain copies of these + materials outside the Services +- Reproduce or copy these materials, except for temporary copies created + automatically during authorized use of the Services +- Create derivative works based on these materials +- Distribute, sublicense, or transfer these materials to any third party +- Make, offer to sell, sell, or import any inventions embodied in these + materials +- Reverse engineer, decompile, or disassemble these materials + +The receipt, viewing, or possession of these materials does not convey or +imply any license or right beyond those expressly granted above. + +Anthropic retains all right, title, and interest in these materials, +including all copyrights, patents, and other intellectual property rights. diff --git a/src/flow/skills/pdf/SKILL.md b/src/flow/skills/pdf/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..f6a22ddf88fdc7e7b7603f4c9064cc51bd930ad9 --- /dev/null +++ b/src/flow/skills/pdf/SKILL.md @@ -0,0 +1,294 @@ +--- +name: pdf +description: Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale. +license: Proprietary. LICENSE.txt has complete terms +--- + +# PDF Processing Guide + +## Overview + +This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see reference.md. If you need to fill out a PDF form, read forms.md and follow its instructions. + +## Quick Start + +```python +from pypdf import PdfReader, PdfWriter + +# Read a PDF +reader = PdfReader("document.pdf") +print(f"Pages: {len(reader.pages)}") + +# Extract text +text = "" +for page in reader.pages: + text += page.extract_text() +``` + +## Python Libraries + +### pypdf - Basic Operations + +#### Merge PDFs +```python +from pypdf import PdfWriter, PdfReader + +writer = PdfWriter() +for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]: + reader = PdfReader(pdf_file) + for page in reader.pages: + writer.add_page(page) + +with open("merged.pdf", "wb") as output: + writer.write(output) +``` + +#### Split PDF +```python +reader = PdfReader("input.pdf") +for i, page in enumerate(reader.pages): + writer = PdfWriter() + writer.add_page(page) + with open(f"page_{i+1}.pdf", "wb") as output: + writer.write(output) +``` + +#### Extract Metadata +```python +reader = PdfReader("document.pdf") +meta = reader.metadata +print(f"Title: {meta.title}") +print(f"Author: {meta.author}") +print(f"Subject: {meta.subject}") +print(f"Creator: {meta.creator}") +``` + +#### Rotate Pages +```python +reader = PdfReader("input.pdf") +writer = PdfWriter() + +page = reader.pages[0] +page.rotate(90) # Rotate 90 degrees clockwise +writer.add_page(page) + +with open("rotated.pdf", "wb") as output: + writer.write(output) +``` + +### pdfplumber - Text and Table Extraction + +#### Extract Text with Layout +```python +import pdfplumber + +with pdfplumber.open("document.pdf") as pdf: + for page in pdf.pages: + text = page.extract_text() + print(text) +``` + +#### Extract Tables +```python +with pdfplumber.open("document.pdf") as pdf: + for i, page in enumerate(pdf.pages): + tables = page.extract_tables() + for j, table in enumerate(tables): + print(f"Table {j+1} on page {i+1}:") + for row in table: + print(row) +``` + +#### Advanced Table Extraction +```python +import pandas as pd + +with pdfplumber.open("document.pdf") as pdf: + all_tables = [] + for page in pdf.pages: + tables = page.extract_tables() + for table in tables: + if table: # Check if table is not empty + df = pd.DataFrame(table[1:], columns=table[0]) + all_tables.append(df) + +# Combine all tables +if all_tables: + combined_df = pd.concat(all_tables, ignore_index=True) + combined_df.to_excel("extracted_tables.xlsx", index=False) +``` + +### reportlab - Create PDFs + +#### Basic PDF Creation +```python +from reportlab.lib.pagesizes import letter +from reportlab.pdfgen import canvas + +c = canvas.Canvas("hello.pdf", pagesize=letter) +width, height = letter + +# Add text +c.drawString(100, height - 100, "Hello World!") +c.drawString(100, height - 120, "This is a PDF created with reportlab") + +# Add a line +c.line(100, height - 140, 400, height - 140) + +# Save +c.save() +``` + +#### Create PDF with Multiple Pages +```python +from reportlab.lib.pagesizes import letter +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak +from reportlab.lib.styles import getSampleStyleSheet + +doc = SimpleDocTemplate("report.pdf", pagesize=letter) +styles = getSampleStyleSheet() +story = [] + +# Add content +title = Paragraph("Report Title", styles['Title']) +story.append(title) +story.append(Spacer(1, 12)) + +body = Paragraph("This is the body of the report. " * 20, styles['Normal']) +story.append(body) +story.append(PageBreak()) + +# Page 2 +story.append(Paragraph("Page 2", styles['Heading1'])) +story.append(Paragraph("Content for page 2", styles['Normal'])) + +# Build PDF +doc.build(story) +``` + +## Command-Line Tools + +### pdftotext (poppler-utils) +```bash +# Extract text +pdftotext input.pdf output.txt + +# Extract text preserving layout +pdftotext -layout input.pdf output.txt + +# Extract specific pages +pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5 +``` + +### qpdf +```bash +# Merge PDFs +qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf + +# Split pages +qpdf input.pdf --pages . 1-5 -- pages1-5.pdf +qpdf input.pdf --pages . 6-10 -- pages6-10.pdf + +# Rotate pages +qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees + +# Remove password +qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf +``` + +### pdftk (if available) +```bash +# Merge +pdftk file1.pdf file2.pdf cat output merged.pdf + +# Split +pdftk input.pdf burst + +# Rotate +pdftk input.pdf rotate 1east output rotated.pdf +``` + +## Common Tasks + +### Extract Text from Scanned PDFs +```python +# Requires: pip install pytesseract pdf2image +import pytesseract +from pdf2image import convert_from_path + +# Convert PDF to images +images = convert_from_path('scanned.pdf') + +# OCR each page +text = "" +for i, image in enumerate(images): + text += f"Page {i+1}:\n" + text += pytesseract.image_to_string(image) + text += "\n\n" + +print(text) +``` + +### Add Watermark +```python +from pypdf import PdfReader, PdfWriter + +# Create watermark (or load existing) +watermark = PdfReader("watermark.pdf").pages[0] + +# Apply to all pages +reader = PdfReader("document.pdf") +writer = PdfWriter() + +for page in reader.pages: + page.merge_page(watermark) + writer.add_page(page) + +with open("watermarked.pdf", "wb") as output: + writer.write(output) +``` + +### Extract Images +```bash +# Using pdfimages (poppler-utils) +pdfimages -j input.pdf output_prefix + +# This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc. +``` + +### Password Protection +```python +from pypdf import PdfReader, PdfWriter + +reader = PdfReader("input.pdf") +writer = PdfWriter() + +for page in reader.pages: + writer.add_page(page) + +# Add password +writer.encrypt("userpassword", "ownerpassword") + +with open("encrypted.pdf", "wb") as output: + writer.write(output) +``` + +## Quick Reference + +| Task | Best Tool | Command/Code | +|------|-----------|--------------| +| Merge PDFs | pypdf | `writer.add_page(page)` | +| Split PDFs | pypdf | One page per file | +| Extract text | pdfplumber | `page.extract_text()` | +| Extract tables | pdfplumber | `page.extract_tables()` | +| Create PDFs | reportlab | Canvas or Platypus | +| Command line merge | qpdf | `qpdf --empty --pages ...` | +| OCR scanned PDFs | pytesseract | Convert to image first | +| Fill PDF forms | pdf-lib or pypdf (see forms.md) | See forms.md | + +## Next Steps + +- For advanced pypdfium2 usage, see reference.md +- For JavaScript libraries (pdf-lib), see reference.md +- If you need to fill out a PDF form, follow the instructions in forms.md +- For troubleshooting guides, see reference.md diff --git a/src/flow/skills/pdf/forms.md b/src/flow/skills/pdf/forms.md new file mode 100644 index 0000000000000000000000000000000000000000..4e234506dcb180158eeb7e057f7c576c263a2d43 --- /dev/null +++ b/src/flow/skills/pdf/forms.md @@ -0,0 +1,205 @@ +**CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.** + +If you need to fill out a PDF form, first check to see if the PDF has fillable form fields. Run this script from this file's directory: + `python scripts/check_fillable_fields `, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions. + +# Fillable fields +If the PDF has fillable form fields: +- Run this script from this file's directory: `python scripts/extract_form_field_info.py `. It will create a JSON file with a list of fields in this format: +``` +[ + { + "field_id": (unique ID for the field), + "page": (page number, 1-based), + "rect": ([left, bottom, right, top] bounding box in PDF coordinates, y=0 is the bottom of the page), + "type": ("text", "checkbox", "radio_group", or "choice"), + }, + // Checkboxes have "checked_value" and "unchecked_value" properties: + { + "field_id": (unique ID for the field), + "page": (page number, 1-based), + "type": "checkbox", + "checked_value": (Set the field to this value to check the checkbox), + "unchecked_value": (Set the field to this value to uncheck the checkbox), + }, + // Radio groups have a "radio_options" list with the possible choices. + { + "field_id": (unique ID for the field), + "page": (page number, 1-based), + "type": "radio_group", + "radio_options": [ + { + "value": (set the field to this value to select this radio option), + "rect": (bounding box for the radio button for this option) + }, + // Other radio options + ] + }, + // Multiple choice fields have a "choice_options" list with the possible choices: + { + "field_id": (unique ID for the field), + "page": (page number, 1-based), + "type": "choice", + "choice_options": [ + { + "value": (set the field to this value to select this option), + "text": (display text of the option) + }, + // Other choice options + ], + } +] +``` +- Convert the PDF to PNGs (one image for each page) with this script (run from this file's directory): +`python scripts/convert_pdf_to_images.py ` +Then analyze the images to determine the purpose of each form field (make sure to convert the bounding box PDF coordinates to image coordinates). +- Create a `field_values.json` file in this format with the values to be entered for each field: +``` +[ + { + "field_id": "last_name", // Must match the field_id from `extract_form_field_info.py` + "description": "The user's last name", + "page": 1, // Must match the "page" value in field_info.json + "value": "Simpson" + }, + { + "field_id": "Checkbox12", + "description": "Checkbox to be checked if the user is 18 or over", + "page": 1, + "value": "/On" // If this is a checkbox, use its "checked_value" value to check it. If it's a radio button group, use one of the "value" values in "radio_options". + }, + // more fields +] +``` +- Run the `fill_fillable_fields.py` script from this file's directory to create a filled-in PDF: +`python scripts/fill_fillable_fields.py ` +This script will verify that the field IDs and values you provide are valid; if it prints error messages, correct the appropriate fields and try again. + +# Non-fillable fields +If the PDF doesn't have fillable form fields, you'll need to visually determine where the data should be added and create text annotations. Follow the below steps *exactly*. You MUST perform all of these steps to ensure that the the form is accurately completed. Details for each step are below. +- Convert the PDF to PNG images and determine field bounding boxes. +- Create a JSON file with field information and validation images showing the bounding boxes. +- Validate the the bounding boxes. +- Use the bounding boxes to fill in the form. + +## Step 1: Visual Analysis (REQUIRED) +- Convert the PDF to PNG images. Run this script from this file's directory: +`python scripts/convert_pdf_to_images.py ` +The script will create a PNG image for each page in the PDF. +- Carefully examine each PNG image and identify all form fields and areas where the user should enter data. For each form field where the user should enter text, determine bounding boxes for both the form field label, and the area where the user should enter text. The label and entry bounding boxes MUST NOT INTERSECT; the text entry box should only include the area where data should be entered. Usually this area will be immediately to the side, above, or below its label. Entry bounding boxes must be tall and wide enough to contain their text. + +These are some examples of form structures that you might see: + +*Label inside box* +``` +┌────────────────────────┐ +│ Name: │ +└────────────────────────┘ +``` +The input area should be to the right of the "Name" label and extend to the edge of the box. + +*Label before line* +``` +Email: _______________________ +``` +The input area should be above the line and include its entire width. + +*Label under line* +``` +_________________________ +Name +``` +The input area should be above the line and include the entire width of the line. This is common for signature and date fields. + +*Label above line* +``` +Please enter any special requests: +________________________________________________ +``` +The input area should extend from the bottom of the label to the line, and should include the entire width of the line. + +*Checkboxes* +``` +Are you a US citizen? Yes □ No □ +``` +For checkboxes: +- Look for small square boxes (□) - these are the actual checkboxes to target. They may be to the left or right of their labels. +- Distinguish between label text ("Yes", "No") and the clickable checkbox squares. +- The entry bounding box should cover ONLY the small square, not the text label. + +### Step 2: Create fields.json and validation images (REQUIRED) +- Create a file named `fields.json` with information for the form fields and bounding boxes in this format: +``` +{ + "pages": [ + { + "page_number": 1, + "image_width": (first page image width in pixels), + "image_height": (first page image height in pixels), + }, + { + "page_number": 2, + "image_width": (second page image width in pixels), + "image_height": (second page image height in pixels), + } + // additional pages + ], + "form_fields": [ + // Example for a text field. + { + "page_number": 1, + "description": "The user's last name should be entered here", + // Bounding boxes are [left, top, right, bottom]. The bounding boxes for the label and text entry should not overlap. + "field_label": "Last name", + "label_bounding_box": [30, 125, 95, 142], + "entry_bounding_box": [100, 125, 280, 142], + "entry_text": { + "text": "Johnson", // This text will be added as an annotation at the entry_bounding_box location + "font_size": 14, // optional, defaults to 14 + "font_color": "000000", // optional, RRGGBB format, defaults to 000000 (black) + } + }, + // Example for a checkbox. TARGET THE SQUARE for the entry bounding box, NOT THE TEXT + { + "page_number": 2, + "description": "Checkbox that should be checked if the user is over 18", + "entry_bounding_box": [140, 525, 155, 540], // Small box over checkbox square + "field_label": "Yes", + "label_bounding_box": [100, 525, 132, 540], // Box containing "Yes" text + // Use "X" to check a checkbox. + "entry_text": { + "text": "X", + } + } + // additional form field entries + ] +} +``` + +Create validation images by running this script from this file's directory for each page: +`python scripts/create_validation_image.py + +The validation images will have red rectangles where text should be entered, and blue rectangles covering label text. + +### Step 3: Validate Bounding Boxes (REQUIRED) +#### Automated intersection check +- Verify that none of bounding boxes intersect and that the entry bounding boxes are tall enough by checking the fields.json file with the `check_bounding_boxes.py` script (run from this file's directory): +`python scripts/check_bounding_boxes.py ` + +If there are errors, reanalyze the relevant fields, adjust the bounding boxes, and iterate until there are no remaining errors. Remember: label (blue) bounding boxes should contain text labels, entry (red) boxes should not. + +#### Manual image inspection +**CRITICAL: Do not proceed without visually inspecting validation images** +- Red rectangles must ONLY cover input areas +- Red rectangles MUST NOT contain any text +- Blue rectangles should contain label text +- For checkboxes: + - Red rectangle MUST be centered on the checkbox square + - Blue rectangle should cover the text label for the checkbox + +- If any rectangles look wrong, fix fields.json, regenerate the validation images, and verify again. Repeat this process until the bounding boxes are fully accurate. + + +### Step 4: Add annotations to the PDF +Run this script from this file's directory to create a filled-out PDF using the information in fields.json: +`python scripts/fill_pdf_form_with_annotations.py diff --git a/src/flow/skills/pdf/reference.md b/src/flow/skills/pdf/reference.md new file mode 100644 index 0000000000000000000000000000000000000000..41400bf4fc67f15fb062d43695ec92f078226023 --- /dev/null +++ b/src/flow/skills/pdf/reference.md @@ -0,0 +1,612 @@ +# PDF Processing Advanced Reference + +This document contains advanced PDF processing features, detailed examples, and additional libraries not covered in the main skill instructions. + +## pypdfium2 Library (Apache/BSD License) + +### Overview +pypdfium2 is a Python binding for PDFium (Chromium's PDF library). It's excellent for fast PDF rendering, image generation, and serves as a PyMuPDF replacement. + +### Render PDF to Images +```python +import pypdfium2 as pdfium +from PIL import Image + +# Load PDF +pdf = pdfium.PdfDocument("document.pdf") + +# Render page to image +page = pdf[0] # First page +bitmap = page.render( + scale=2.0, # Higher resolution + rotation=0 # No rotation +) + +# Convert to PIL Image +img = bitmap.to_pil() +img.save("page_1.png", "PNG") + +# Process multiple pages +for i, page in enumerate(pdf): + bitmap = page.render(scale=1.5) + img = bitmap.to_pil() + img.save(f"page_{i+1}.jpg", "JPEG", quality=90) +``` + +### Extract Text with pypdfium2 +```python +import pypdfium2 as pdfium + +pdf = pdfium.PdfDocument("document.pdf") +for i, page in enumerate(pdf): + text = page.get_text() + print(f"Page {i+1} text length: {len(text)} chars") +``` + +## JavaScript Libraries + +### pdf-lib (MIT License) + +pdf-lib is a powerful JavaScript library for creating and modifying PDF documents in any JavaScript environment. + +#### Load and Manipulate Existing PDF +```javascript +import { PDFDocument } from 'pdf-lib'; +import fs from 'fs'; + +async function manipulatePDF() { + // Load existing PDF + const existingPdfBytes = fs.readFileSync('input.pdf'); + const pdfDoc = await PDFDocument.load(existingPdfBytes); + + // Get page count + const pageCount = pdfDoc.getPageCount(); + console.log(`Document has ${pageCount} pages`); + + // Add new page + const newPage = pdfDoc.addPage([600, 400]); + newPage.drawText('Added by pdf-lib', { + x: 100, + y: 300, + size: 16 + }); + + // Save modified PDF + const pdfBytes = await pdfDoc.save(); + fs.writeFileSync('modified.pdf', pdfBytes); +} +``` + +#### Create Complex PDFs from Scratch +```javascript +import { PDFDocument, rgb, StandardFonts } from 'pdf-lib'; +import fs from 'fs'; + +async function createPDF() { + const pdfDoc = await PDFDocument.create(); + + // Add fonts + const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica); + const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold); + + // Add page + const page = pdfDoc.addPage([595, 842]); // A4 size + const { width, height } = page.getSize(); + + // Add text with styling + page.drawText('Invoice #12345', { + x: 50, + y: height - 50, + size: 18, + font: helveticaBold, + color: rgb(0.2, 0.2, 0.8) + }); + + // Add rectangle (header background) + page.drawRectangle({ + x: 40, + y: height - 100, + width: width - 80, + height: 30, + color: rgb(0.9, 0.9, 0.9) + }); + + // Add table-like content + const items = [ + ['Item', 'Qty', 'Price', 'Total'], + ['Widget', '2', '$50', '$100'], + ['Gadget', '1', '$75', '$75'] + ]; + + let yPos = height - 150; + items.forEach(row => { + let xPos = 50; + row.forEach(cell => { + page.drawText(cell, { + x: xPos, + y: yPos, + size: 12, + font: helveticaFont + }); + xPos += 120; + }); + yPos -= 25; + }); + + const pdfBytes = await pdfDoc.save(); + fs.writeFileSync('created.pdf', pdfBytes); +} +``` + +#### Advanced Merge and Split Operations +```javascript +import { PDFDocument } from 'pdf-lib'; +import fs from 'fs'; + +async function mergePDFs() { + // Create new document + const mergedPdf = await PDFDocument.create(); + + // Load source PDFs + const pdf1Bytes = fs.readFileSync('doc1.pdf'); + const pdf2Bytes = fs.readFileSync('doc2.pdf'); + + const pdf1 = await PDFDocument.load(pdf1Bytes); + const pdf2 = await PDFDocument.load(pdf2Bytes); + + // Copy pages from first PDF + const pdf1Pages = await mergedPdf.copyPages(pdf1, pdf1.getPageIndices()); + pdf1Pages.forEach(page => mergedPdf.addPage(page)); + + // Copy specific pages from second PDF (pages 0, 2, 4) + const pdf2Pages = await mergedPdf.copyPages(pdf2, [0, 2, 4]); + pdf2Pages.forEach(page => mergedPdf.addPage(page)); + + const mergedPdfBytes = await mergedPdf.save(); + fs.writeFileSync('merged.pdf', mergedPdfBytes); +} +``` + +### pdfjs-dist (Apache License) + +PDF.js is Mozilla's JavaScript library for rendering PDFs in the browser. + +#### Basic PDF Loading and Rendering +```javascript +import * as pdfjsLib from 'pdfjs-dist'; + +// Configure worker (important for performance) +pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.js'; + +async function renderPDF() { + // Load PDF + const loadingTask = pdfjsLib.getDocument('document.pdf'); + const pdf = await loadingTask.promise; + + console.log(`Loaded PDF with ${pdf.numPages} pages`); + + // Get first page + const page = await pdf.getPage(1); + const viewport = page.getViewport({ scale: 1.5 }); + + // Render to canvas + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; + + const renderContext = { + canvasContext: context, + viewport: viewport + }; + + await page.render(renderContext).promise; + document.body.appendChild(canvas); +} +``` + +#### Extract Text with Coordinates +```javascript +import * as pdfjsLib from 'pdfjs-dist'; + +async function extractText() { + const loadingTask = pdfjsLib.getDocument('document.pdf'); + const pdf = await loadingTask.promise; + + let fullText = ''; + + // Extract text from all pages + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const textContent = await page.getTextContent(); + + const pageText = textContent.items + .map(item => item.str) + .join(' '); + + fullText += `\n--- Page ${i} ---\n${pageText}`; + + // Get text with coordinates for advanced processing + const textWithCoords = textContent.items.map(item => ({ + text: item.str, + x: item.transform[4], + y: item.transform[5], + width: item.width, + height: item.height + })); + } + + console.log(fullText); + return fullText; +} +``` + +#### Extract Annotations and Forms +```javascript +import * as pdfjsLib from 'pdfjs-dist'; + +async function extractAnnotations() { + const loadingTask = pdfjsLib.getDocument('annotated.pdf'); + const pdf = await loadingTask.promise; + + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const annotations = await page.getAnnotations(); + + annotations.forEach(annotation => { + console.log(`Annotation type: ${annotation.subtype}`); + console.log(`Content: ${annotation.contents}`); + console.log(`Coordinates: ${JSON.stringify(annotation.rect)}`); + }); + } +} +``` + +## Advanced Command-Line Operations + +### poppler-utils Advanced Features + +#### Extract Text with Bounding Box Coordinates +```bash +# Extract text with bounding box coordinates (essential for structured data) +pdftotext -bbox-layout document.pdf output.xml + +# The XML output contains precise coordinates for each text element +``` + +#### Advanced Image Conversion +```bash +# Convert to PNG images with specific resolution +pdftoppm -png -r 300 document.pdf output_prefix + +# Convert specific page range with high resolution +pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res_pages + +# Convert to JPEG with quality setting +pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output +``` + +#### Extract Embedded Images +```bash +# Extract all embedded images with metadata +pdfimages -j -p document.pdf page_images + +# List image info without extracting +pdfimages -list document.pdf + +# Extract images in their original format +pdfimages -all document.pdf images/img +``` + +### qpdf Advanced Features + +#### Complex Page Manipulation +```bash +# Split PDF into groups of pages +qpdf --split-pages=3 input.pdf output_group_%02d.pdf + +# Extract specific pages with complex ranges +qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf + +# Merge specific pages from multiple PDFs +qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf +``` + +#### PDF Optimization and Repair +```bash +# Optimize PDF for web (linearize for streaming) +qpdf --linearize input.pdf optimized.pdf + +# Remove unused objects and compress +qpdf --optimize-level=all input.pdf compressed.pdf + +# Attempt to repair corrupted PDF structure +qpdf --check input.pdf +qpdf --fix-qdf damaged.pdf repaired.pdf + +# Show detailed PDF structure for debugging +qpdf --show-all-pages input.pdf > structure.txt +``` + +#### Advanced Encryption +```bash +# Add password protection with specific permissions +qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf + +# Check encryption status +qpdf --show-encryption encrypted.pdf + +# Remove password protection (requires password) +qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf +``` + +## Advanced Python Techniques + +### pdfplumber Advanced Features + +#### Extract Text with Precise Coordinates +```python +import pdfplumber + +with pdfplumber.open("document.pdf") as pdf: + page = pdf.pages[0] + + # Extract all text with coordinates + chars = page.chars + for char in chars[:10]: # First 10 characters + print(f"Char: '{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}") + + # Extract text by bounding box (left, top, right, bottom) + bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text() +``` + +#### Advanced Table Extraction with Custom Settings +```python +import pdfplumber +import pandas as pd + +with pdfplumber.open("complex_table.pdf") as pdf: + page = pdf.pages[0] + + # Extract tables with custom settings for complex layouts + table_settings = { + "vertical_strategy": "lines", + "horizontal_strategy": "lines", + "snap_tolerance": 3, + "intersection_tolerance": 15 + } + tables = page.extract_tables(table_settings) + + # Visual debugging for table extraction + img = page.to_image(resolution=150) + img.save("debug_layout.png") +``` + +### reportlab Advanced Features + +#### Create Professional Reports with Tables +```python +from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.lib import colors + +# Sample data +data = [ + ['Product', 'Q1', 'Q2', 'Q3', 'Q4'], + ['Widgets', '120', '135', '142', '158'], + ['Gadgets', '85', '92', '98', '105'] +] + +# Create PDF with table +doc = SimpleDocTemplate("report.pdf") +elements = [] + +# Add title +styles = getSampleStyleSheet() +title = Paragraph("Quarterly Sales Report", styles['Title']) +elements.append(title) + +# Add table with advanced styling +table = Table(data) +table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (-1, 0), colors.grey), + ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 14), + ('BOTTOMPADDING', (0, 0), (-1, 0), 12), + ('BACKGROUND', (0, 1), (-1, -1), colors.beige), + ('GRID', (0, 0), (-1, -1), 1, colors.black) +])) +elements.append(table) + +doc.build(elements) +``` + +## Complex Workflows + +### Extract Figures/Images from PDF + +#### Method 1: Using pdfimages (fastest) +```bash +# Extract all images with original quality +pdfimages -all document.pdf images/img +``` + +#### Method 2: Using pypdfium2 + Image Processing +```python +import pypdfium2 as pdfium +from PIL import Image +import numpy as np + +def extract_figures(pdf_path, output_dir): + pdf = pdfium.PdfDocument(pdf_path) + + for page_num, page in enumerate(pdf): + # Render high-resolution page + bitmap = page.render(scale=3.0) + img = bitmap.to_pil() + + # Convert to numpy for processing + img_array = np.array(img) + + # Simple figure detection (non-white regions) + mask = np.any(img_array != [255, 255, 255], axis=2) + + # Find contours and extract bounding boxes + # (This is simplified - real implementation would need more sophisticated detection) + + # Save detected figures + # ... implementation depends on specific needs +``` + +### Batch PDF Processing with Error Handling +```python +import os +import glob +from pypdf import PdfReader, PdfWriter +import logging + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +def batch_process_pdfs(input_dir, operation='merge'): + pdf_files = glob.glob(os.path.join(input_dir, "*.pdf")) + + if operation == 'merge': + writer = PdfWriter() + for pdf_file in pdf_files: + try: + reader = PdfReader(pdf_file) + for page in reader.pages: + writer.add_page(page) + logger.info(f"Processed: {pdf_file}") + except Exception as e: + logger.error(f"Failed to process {pdf_file}: {e}") + continue + + with open("batch_merged.pdf", "wb") as output: + writer.write(output) + + elif operation == 'extract_text': + for pdf_file in pdf_files: + try: + reader = PdfReader(pdf_file) + text = "" + for page in reader.pages: + text += page.extract_text() + + output_file = pdf_file.replace('.pdf', '.txt') + with open(output_file, 'w', encoding='utf-8') as f: + f.write(text) + logger.info(f"Extracted text from: {pdf_file}") + + except Exception as e: + logger.error(f"Failed to extract text from {pdf_file}: {e}") + continue +``` + +### Advanced PDF Cropping +```python +from pypdf import PdfWriter, PdfReader + +reader = PdfReader("input.pdf") +writer = PdfWriter() + +# Crop page (left, bottom, right, top in points) +page = reader.pages[0] +page.mediabox.left = 50 +page.mediabox.bottom = 50 +page.mediabox.right = 550 +page.mediabox.top = 750 + +writer.add_page(page) +with open("cropped.pdf", "wb") as output: + writer.write(output) +``` + +## Performance Optimization Tips + +### 1. For Large PDFs +- Use streaming approaches instead of loading entire PDF in memory +- Use `qpdf --split-pages` for splitting large files +- Process pages individually with pypdfium2 + +### 2. For Text Extraction +- `pdftotext -bbox-layout` is fastest for plain text extraction +- Use pdfplumber for structured data and tables +- Avoid `pypdf.extract_text()` for very large documents + +### 3. For Image Extraction +- `pdfimages` is much faster than rendering pages +- Use low resolution for previews, high resolution for final output + +### 4. For Form Filling +- pdf-lib maintains form structure better than most alternatives +- Pre-validate form fields before processing + +### 5. Memory Management +```python +# Process PDFs in chunks +def process_large_pdf(pdf_path, chunk_size=10): + reader = PdfReader(pdf_path) + total_pages = len(reader.pages) + + for start_idx in range(0, total_pages, chunk_size): + end_idx = min(start_idx + chunk_size, total_pages) + writer = PdfWriter() + + for i in range(start_idx, end_idx): + writer.add_page(reader.pages[i]) + + # Process chunk + with open(f"chunk_{start_idx//chunk_size}.pdf", "wb") as output: + writer.write(output) +``` + +## Troubleshooting Common Issues + +### Encrypted PDFs +```python +# Handle password-protected PDFs +from pypdf import PdfReader + +try: + reader = PdfReader("encrypted.pdf") + if reader.is_encrypted: + reader.decrypt("password") +except Exception as e: + print(f"Failed to decrypt: {e}") +``` + +### Corrupted PDFs +```bash +# Use qpdf to repair +qpdf --check corrupted.pdf +qpdf --replace-input corrupted.pdf +``` + +### Text Extraction Issues +```python +# Fallback to OCR for scanned PDFs +import pytesseract +from pdf2image import convert_from_path + +def extract_text_with_ocr(pdf_path): + images = convert_from_path(pdf_path) + text = "" + for i, image in enumerate(images): + text += pytesseract.image_to_string(image) + return text +``` + +## License Information + +- **pypdf**: BSD License +- **pdfplumber**: MIT License +- **pypdfium2**: Apache/BSD License +- **reportlab**: BSD License +- **poppler-utils**: GPL-2 License +- **qpdf**: Apache License +- **pdf-lib**: MIT License +- **pdfjs-dist**: Apache License \ No newline at end of file diff --git a/src/flow/skills/pdf/scripts/check_bounding_boxes.py b/src/flow/skills/pdf/scripts/check_bounding_boxes.py new file mode 100644 index 0000000000000000000000000000000000000000..7443660c07171df1a2793d2303ebd1461772696a --- /dev/null +++ b/src/flow/skills/pdf/scripts/check_bounding_boxes.py @@ -0,0 +1,70 @@ +from dataclasses import dataclass +import json +import sys + + +# Script to check that the `fields.json` file that Claude creates when analyzing PDFs +# does not have overlapping bounding boxes. See forms.md. + + +@dataclass +class RectAndField: + rect: list[float] + rect_type: str + field: dict + + +# Returns a list of messages that are printed to stdout for Claude to read. +def get_bounding_box_messages(fields_json_stream) -> list[str]: + messages = [] + fields = json.load(fields_json_stream) + messages.append(f"Read {len(fields['form_fields'])} fields") + + def rects_intersect(r1, r2): + disjoint_horizontal = r1[0] >= r2[2] or r1[2] <= r2[0] + disjoint_vertical = r1[1] >= r2[3] or r1[3] <= r2[1] + return not (disjoint_horizontal or disjoint_vertical) + + rects_and_fields = [] + for f in fields["form_fields"]: + rects_and_fields.append(RectAndField(f["label_bounding_box"], "label", f)) + rects_and_fields.append(RectAndField(f["entry_bounding_box"], "entry", f)) + + has_error = False + for i, ri in enumerate(rects_and_fields): + # This is O(N^2); we can optimize if it becomes a problem. + for j in range(i + 1, len(rects_and_fields)): + rj = rects_and_fields[j] + if ri.field["page_number"] == rj.field["page_number"] and rects_intersect(ri.rect, rj.rect): + has_error = True + if ri.field is rj.field: + messages.append(f"FAILURE: intersection between label and entry bounding boxes for `{ri.field['description']}` ({ri.rect}, {rj.rect})") + else: + messages.append(f"FAILURE: intersection between {ri.rect_type} bounding box for `{ri.field['description']}` ({ri.rect}) and {rj.rect_type} bounding box for `{rj.field['description']}` ({rj.rect})") + if len(messages) >= 20: + messages.append("Aborting further checks; fix bounding boxes and try again") + return messages + if ri.rect_type == "entry": + if "entry_text" in ri.field: + font_size = ri.field["entry_text"].get("font_size", 14) + entry_height = ri.rect[3] - ri.rect[1] + if entry_height < font_size: + has_error = True + messages.append(f"FAILURE: entry bounding box height ({entry_height}) for `{ri.field['description']}` is too short for the text content (font size: {font_size}). Increase the box height or decrease the font size.") + if len(messages) >= 20: + messages.append("Aborting further checks; fix bounding boxes and try again") + return messages + + if not has_error: + messages.append("SUCCESS: All bounding boxes are valid") + return messages + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: check_bounding_boxes.py [fields.json]") + sys.exit(1) + # Input file should be in the `fields.json` format described in forms.md. + with open(sys.argv[1]) as f: + messages = get_bounding_box_messages(f) + for msg in messages: + print(msg) diff --git a/src/flow/skills/pdf/scripts/check_bounding_boxes_test.py b/src/flow/skills/pdf/scripts/check_bounding_boxes_test.py new file mode 100644 index 0000000000000000000000000000000000000000..1dbb463c878566001448eb1fcaa058980305f2f4 --- /dev/null +++ b/src/flow/skills/pdf/scripts/check_bounding_boxes_test.py @@ -0,0 +1,226 @@ +import unittest +import json +import io +from check_bounding_boxes import get_bounding_box_messages + + +# Currently this is not run automatically in CI; it's just for documentation and manual checking. +class TestGetBoundingBoxMessages(unittest.TestCase): + + def create_json_stream(self, data): + """Helper to create a JSON stream from data""" + return io.StringIO(json.dumps(data)) + + def test_no_intersections(self): + """Test case with no bounding box intersections""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], + "entry_bounding_box": [60, 10, 150, 30] + }, + { + "description": "Email", + "page_number": 1, + "label_bounding_box": [10, 40, 50, 60], + "entry_bounding_box": [60, 40, 150, 60] + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("SUCCESS" in msg for msg in messages)) + self.assertFalse(any("FAILURE" in msg for msg in messages)) + + def test_label_entry_intersection_same_field(self): + """Test intersection between label and entry of the same field""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 60, 30], + "entry_bounding_box": [50, 10, 150, 30] # Overlaps with label + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages)) + self.assertFalse(any("SUCCESS" in msg for msg in messages)) + + def test_intersection_between_different_fields(self): + """Test intersection between bounding boxes of different fields""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], + "entry_bounding_box": [60, 10, 150, 30] + }, + { + "description": "Email", + "page_number": 1, + "label_bounding_box": [40, 20, 80, 40], # Overlaps with Name's boxes + "entry_bounding_box": [160, 10, 250, 30] + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages)) + self.assertFalse(any("SUCCESS" in msg for msg in messages)) + + def test_different_pages_no_intersection(self): + """Test that boxes on different pages don't count as intersecting""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], + "entry_bounding_box": [60, 10, 150, 30] + }, + { + "description": "Email", + "page_number": 2, + "label_bounding_box": [10, 10, 50, 30], # Same coordinates but different page + "entry_bounding_box": [60, 10, 150, 30] + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("SUCCESS" in msg for msg in messages)) + self.assertFalse(any("FAILURE" in msg for msg in messages)) + + def test_entry_height_too_small(self): + """Test that entry box height is checked against font size""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], + "entry_bounding_box": [60, 10, 150, 20], # Height is 10 + "entry_text": { + "font_size": 14 # Font size larger than height + } + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages)) + self.assertFalse(any("SUCCESS" in msg for msg in messages)) + + def test_entry_height_adequate(self): + """Test that adequate entry box height passes""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], + "entry_bounding_box": [60, 10, 150, 30], # Height is 20 + "entry_text": { + "font_size": 14 # Font size smaller than height + } + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("SUCCESS" in msg for msg in messages)) + self.assertFalse(any("FAILURE" in msg for msg in messages)) + + def test_default_font_size(self): + """Test that default font size is used when not specified""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], + "entry_bounding_box": [60, 10, 150, 20], # Height is 10 + "entry_text": {} # No font_size specified, should use default 14 + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages)) + self.assertFalse(any("SUCCESS" in msg for msg in messages)) + + def test_no_entry_text(self): + """Test that missing entry_text doesn't cause height check""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], + "entry_bounding_box": [60, 10, 150, 20] # Small height but no entry_text + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("SUCCESS" in msg for msg in messages)) + self.assertFalse(any("FAILURE" in msg for msg in messages)) + + def test_multiple_errors_limit(self): + """Test that error messages are limited to prevent excessive output""" + fields = [] + # Create many overlapping fields + for i in range(25): + fields.append({ + "description": f"Field{i}", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], # All overlap + "entry_bounding_box": [20, 15, 60, 35] # All overlap + }) + + data = {"form_fields": fields} + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + # Should abort after ~20 messages + self.assertTrue(any("Aborting" in msg for msg in messages)) + # Should have some FAILURE messages but not hundreds + failure_count = sum(1 for msg in messages if "FAILURE" in msg) + self.assertGreater(failure_count, 0) + self.assertLess(len(messages), 30) # Should be limited + + def test_edge_touching_boxes(self): + """Test that boxes touching at edges don't count as intersecting""" + data = { + "form_fields": [ + { + "description": "Name", + "page_number": 1, + "label_bounding_box": [10, 10, 50, 30], + "entry_bounding_box": [50, 10, 150, 30] # Touches at x=50 + } + ] + } + + stream = self.create_json_stream(data) + messages = get_bounding_box_messages(stream) + self.assertTrue(any("SUCCESS" in msg for msg in messages)) + self.assertFalse(any("FAILURE" in msg for msg in messages)) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/flow/skills/pdf/scripts/check_fillable_fields.py b/src/flow/skills/pdf/scripts/check_fillable_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..dc43d18213e818844212c225eeecdd5d70e2578e --- /dev/null +++ b/src/flow/skills/pdf/scripts/check_fillable_fields.py @@ -0,0 +1,12 @@ +import sys +from pypdf import PdfReader + + +# Script for Claude to run to determine whether a PDF has fillable form fields. See forms.md. + + +reader = PdfReader(sys.argv[1]) +if (reader.get_fields()): + print("This PDF has fillable form fields") +else: + print("This PDF does not have fillable form fields; you will need to visually determine where to enter data") diff --git a/src/flow/skills/pdf/scripts/convert_pdf_to_images.py b/src/flow/skills/pdf/scripts/convert_pdf_to_images.py new file mode 100644 index 0000000000000000000000000000000000000000..f8a4ec524b28eb1b380a536c24ea589faa3b7a2e --- /dev/null +++ b/src/flow/skills/pdf/scripts/convert_pdf_to_images.py @@ -0,0 +1,35 @@ +import os +import sys + +from pdf2image import convert_from_path + + +# Converts each page of a PDF to a PNG image. + + +def convert(pdf_path, output_dir, max_dim=1000): + images = convert_from_path(pdf_path, dpi=200) + + for i, image in enumerate(images): + # Scale image if needed to keep width/height under `max_dim` + width, height = image.size + if width > max_dim or height > max_dim: + scale_factor = min(max_dim / width, max_dim / height) + new_width = int(width * scale_factor) + new_height = int(height * scale_factor) + image = image.resize((new_width, new_height)) + + image_path = os.path.join(output_dir, f"page_{i+1}.png") + image.save(image_path) + print(f"Saved page {i+1} as {image_path} (size: {image.size})") + + print(f"Converted {len(images)} pages to PNG images") + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: convert_pdf_to_images.py [input pdf] [output directory]") + sys.exit(1) + pdf_path = sys.argv[1] + output_directory = sys.argv[2] + convert(pdf_path, output_directory) diff --git a/src/flow/skills/pdf/scripts/create_validation_image.py b/src/flow/skills/pdf/scripts/create_validation_image.py new file mode 100644 index 0000000000000000000000000000000000000000..4913f8f8d60e3dcadac05e07eb36504a1afd6a0e --- /dev/null +++ b/src/flow/skills/pdf/scripts/create_validation_image.py @@ -0,0 +1,41 @@ +import json +import sys + +from PIL import Image, ImageDraw + + +# Creates "validation" images with rectangles for the bounding box information that +# Claude creates when determining where to add text annotations in PDFs. See forms.md. + + +def create_validation_image(page_number, fields_json_path, input_path, output_path): + # Input file should be in the `fields.json` format described in forms.md. + with open(fields_json_path, 'r') as f: + data = json.load(f) + + img = Image.open(input_path) + draw = ImageDraw.Draw(img) + num_boxes = 0 + + for field in data["form_fields"]: + if field["page_number"] == page_number: + entry_box = field['entry_bounding_box'] + label_box = field['label_bounding_box'] + # Draw red rectangle over entry bounding box and blue rectangle over the label. + draw.rectangle(entry_box, outline='red', width=2) + draw.rectangle(label_box, outline='blue', width=2) + num_boxes += 2 + + img.save(output_path) + print(f"Created validation image at {output_path} with {num_boxes} bounding boxes") + + +if __name__ == "__main__": + if len(sys.argv) != 5: + print("Usage: create_validation_image.py [page number] [fields.json file] [input image path] [output image path]") + sys.exit(1) + page_number = int(sys.argv[1]) + fields_json_path = sys.argv[2] + input_image_path = sys.argv[3] + output_image_path = sys.argv[4] + create_validation_image(page_number, fields_json_path, input_image_path, output_image_path) diff --git a/src/flow/skills/pdf/scripts/extract_form_field_info.py b/src/flow/skills/pdf/scripts/extract_form_field_info.py new file mode 100644 index 0000000000000000000000000000000000000000..f42a2df847f4af044f652f5dbd2c9200227ffde3 --- /dev/null +++ b/src/flow/skills/pdf/scripts/extract_form_field_info.py @@ -0,0 +1,152 @@ +import json +import sys + +from pypdf import PdfReader + + +# Extracts data for the fillable form fields in a PDF and outputs JSON that +# Claude uses to fill the fields. See forms.md. + + +# This matches the format used by PdfReader `get_fields` and `update_page_form_field_values` methods. +def get_full_annotation_field_id(annotation): + components = [] + while annotation: + field_name = annotation.get('/T') + if field_name: + components.append(field_name) + annotation = annotation.get('/Parent') + return ".".join(reversed(components)) if components else None + + +def make_field_dict(field, field_id): + field_dict = {"field_id": field_id} + ft = field.get('/FT') + if ft == "/Tx": + field_dict["type"] = "text" + elif ft == "/Btn": + field_dict["type"] = "checkbox" # radio groups handled separately + states = field.get("/_States_", []) + if len(states) == 2: + # "/Off" seems to always be the unchecked value, as suggested by + # https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=448 + # It can be either first or second in the "/_States_" list. + if "/Off" in states: + field_dict["checked_value"] = states[0] if states[0] != "/Off" else states[1] + field_dict["unchecked_value"] = "/Off" + else: + print(f"Unexpected state values for checkbox `${field_id}`. Its checked and unchecked values may not be correct; if you're trying to check it, visually verify the results.") + field_dict["checked_value"] = states[0] + field_dict["unchecked_value"] = states[1] + elif ft == "/Ch": + field_dict["type"] = "choice" + states = field.get("/_States_", []) + field_dict["choice_options"] = [{ + "value": state[0], + "text": state[1], + } for state in states] + else: + field_dict["type"] = f"unknown ({ft})" + return field_dict + + +# Returns a list of fillable PDF fields: +# [ +# { +# "field_id": "name", +# "page": 1, +# "type": ("text", "checkbox", "radio_group", or "choice") +# // Per-type additional fields described in forms.md +# }, +# ] +def get_field_info(reader: PdfReader): + fields = reader.get_fields() + + field_info_by_id = {} + possible_radio_names = set() + + for field_id, field in fields.items(): + # Skip if this is a container field with children, except that it might be + # a parent group for radio button options. + if field.get("/Kids"): + if field.get("/FT") == "/Btn": + possible_radio_names.add(field_id) + continue + field_info_by_id[field_id] = make_field_dict(field, field_id) + + # Bounding rects are stored in annotations in page objects. + + # Radio button options have a separate annotation for each choice; + # all choices have the same field name. + # See https://westhealth.github.io/exploring-fillable-forms-with-pdfrw.html + radio_fields_by_id = {} + + for page_index, page in enumerate(reader.pages): + annotations = page.get('/Annots', []) + for ann in annotations: + field_id = get_full_annotation_field_id(ann) + if field_id in field_info_by_id: + field_info_by_id[field_id]["page"] = page_index + 1 + field_info_by_id[field_id]["rect"] = ann.get('/Rect') + elif field_id in possible_radio_names: + try: + # ann['/AP']['/N'] should have two items. One of them is '/Off', + # the other is the active value. + on_values = [v for v in ann["/AP"]["/N"] if v != "/Off"] + except KeyError: + continue + if len(on_values) == 1: + rect = ann.get("/Rect") + if field_id not in radio_fields_by_id: + radio_fields_by_id[field_id] = { + "field_id": field_id, + "type": "radio_group", + "page": page_index + 1, + "radio_options": [], + } + # Note: at least on macOS 15.7, Preview.app doesn't show selected + # radio buttons correctly. (It does if you remove the leading slash + # from the value, but that causes them not to appear correctly in + # Chrome/Firefox/Acrobat/etc). + radio_fields_by_id[field_id]["radio_options"].append({ + "value": on_values[0], + "rect": rect, + }) + + # Some PDFs have form field definitions without corresponding annotations, + # so we can't tell where they are. Ignore these fields for now. + fields_with_location = [] + for field_info in field_info_by_id.values(): + if "page" in field_info: + fields_with_location.append(field_info) + else: + print(f"Unable to determine location for field id: {field_info.get('field_id')}, ignoring") + + # Sort by page number, then Y position (flipped in PDF coordinate system), then X. + def sort_key(f): + if "radio_options" in f: + rect = f["radio_options"][0]["rect"] or [0, 0, 0, 0] + else: + rect = f.get("rect") or [0, 0, 0, 0] + adjusted_position = [-rect[1], rect[0]] + return [f.get("page"), adjusted_position] + + sorted_fields = fields_with_location + list(radio_fields_by_id.values()) + sorted_fields.sort(key=sort_key) + + return sorted_fields + + +def write_field_info(pdf_path: str, json_output_path: str): + reader = PdfReader(pdf_path) + field_info = get_field_info(reader) + with open(json_output_path, "w") as f: + json.dump(field_info, f, indent=2) + print(f"Wrote {len(field_info)} fields to {json_output_path}") + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: extract_form_field_info.py [input pdf] [output json]") + sys.exit(1) + write_field_info(sys.argv[1], sys.argv[2]) diff --git a/src/flow/skills/pdf/scripts/fill_fillable_fields.py b/src/flow/skills/pdf/scripts/fill_fillable_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..ac35753c5c63f565ed7ec4b89d6c6fd792aa5a8c --- /dev/null +++ b/src/flow/skills/pdf/scripts/fill_fillable_fields.py @@ -0,0 +1,114 @@ +import json +import sys + +from pypdf import PdfReader, PdfWriter + +from extract_form_field_info import get_field_info + + +# Fills fillable form fields in a PDF. See forms.md. + + +def fill_pdf_fields(input_pdf_path: str, fields_json_path: str, output_pdf_path: str): + with open(fields_json_path) as f: + fields = json.load(f) + # Group by page number. + fields_by_page = {} + for field in fields: + if "value" in field: + field_id = field["field_id"] + page = field["page"] + if page not in fields_by_page: + fields_by_page[page] = {} + fields_by_page[page][field_id] = field["value"] + + reader = PdfReader(input_pdf_path) + + has_error = False + field_info = get_field_info(reader) + fields_by_ids = {f["field_id"]: f for f in field_info} + for field in fields: + existing_field = fields_by_ids.get(field["field_id"]) + if not existing_field: + has_error = True + print(f"ERROR: `{field['field_id']}` is not a valid field ID") + elif field["page"] != existing_field["page"]: + has_error = True + print(f"ERROR: Incorrect page number for `{field['field_id']}` (got {field['page']}, expected {existing_field['page']})") + else: + if "value" in field: + err = validation_error_for_field_value(existing_field, field["value"]) + if err: + print(err) + has_error = True + if has_error: + sys.exit(1) + + writer = PdfWriter(clone_from=reader) + for page, field_values in fields_by_page.items(): + writer.update_page_form_field_values(writer.pages[page - 1], field_values, auto_regenerate=False) + + # This seems to be necessary for many PDF viewers to format the form values correctly. + # It may cause the viewer to show a "save changes" dialog even if the user doesn't make any changes. + writer.set_need_appearances_writer(True) + + with open(output_pdf_path, "wb") as f: + writer.write(f) + + +def validation_error_for_field_value(field_info, field_value): + field_type = field_info["type"] + field_id = field_info["field_id"] + if field_type == "checkbox": + checked_val = field_info["checked_value"] + unchecked_val = field_info["unchecked_value"] + if field_value != checked_val and field_value != unchecked_val: + return f'ERROR: Invalid value "{field_value}" for checkbox field "{field_id}". The checked value is "{checked_val}" and the unchecked value is "{unchecked_val}"' + elif field_type == "radio_group": + option_values = [opt["value"] for opt in field_info["radio_options"]] + if field_value not in option_values: + return f'ERROR: Invalid value "{field_value}" for radio group field "{field_id}". Valid values are: {option_values}' + elif field_type == "choice": + choice_values = [opt["value"] for opt in field_info["choice_options"]] + if field_value not in choice_values: + return f'ERROR: Invalid value "{field_value}" for choice field "{field_id}". Valid values are: {choice_values}' + return None + + +# pypdf (at least version 5.7.0) has a bug when setting the value for a selection list field. +# In _writer.py around line 966: +# +# if field.get(FA.FT, "/Tx") == "/Ch" and field_flags & FA.FfBits.Combo == 0: +# txt = "\n".join(annotation.get_inherited(FA.Opt, [])) +# +# The problem is that for selection lists, `get_inherited` returns a list of two-element lists like +# [["value1", "Text 1"], ["value2", "Text 2"], ...] +# This causes `join` to throw a TypeError because it expects an iterable of strings. +# The horrible workaround is to patch `get_inherited` to return a list of the value strings. +# We call the original method and adjust the return value only if the argument to `get_inherited` +# is `FA.Opt` and if the return value is a list of two-element lists. +def monkeypatch_pydpf_method(): + from pypdf.generic import DictionaryObject + from pypdf.constants import FieldDictionaryAttributes + + original_get_inherited = DictionaryObject.get_inherited + + def patched_get_inherited(self, key: str, default = None): + result = original_get_inherited(self, key, default) + if key == FieldDictionaryAttributes.Opt: + if isinstance(result, list) and all(isinstance(v, list) and len(v) == 2 for v in result): + result = [r[0] for r in result] + return result + + DictionaryObject.get_inherited = patched_get_inherited + + +if __name__ == "__main__": + if len(sys.argv) != 4: + print("Usage: fill_fillable_fields.py [input pdf] [field_values.json] [output pdf]") + sys.exit(1) + monkeypatch_pydpf_method() + input_pdf = sys.argv[1] + fields_json = sys.argv[2] + output_pdf = sys.argv[3] + fill_pdf_fields(input_pdf, fields_json, output_pdf) diff --git a/src/flow/skills/pdf/scripts/fill_pdf_form_with_annotations.py b/src/flow/skills/pdf/scripts/fill_pdf_form_with_annotations.py new file mode 100644 index 0000000000000000000000000000000000000000..f98053135007739d8a017846a2fc3d299f947411 --- /dev/null +++ b/src/flow/skills/pdf/scripts/fill_pdf_form_with_annotations.py @@ -0,0 +1,108 @@ +import json +import sys + +from pypdf import PdfReader, PdfWriter +from pypdf.annotations import FreeText + + +# Fills a PDF by adding text annotations defined in `fields.json`. See forms.md. + + +def transform_coordinates(bbox, image_width, image_height, pdf_width, pdf_height): + """Transform bounding box from image coordinates to PDF coordinates""" + # Image coordinates: origin at top-left, y increases downward + # PDF coordinates: origin at bottom-left, y increases upward + x_scale = pdf_width / image_width + y_scale = pdf_height / image_height + + left = bbox[0] * x_scale + right = bbox[2] * x_scale + + # Flip Y coordinates for PDF + top = pdf_height - (bbox[1] * y_scale) + bottom = pdf_height - (bbox[3] * y_scale) + + return left, bottom, right, top + + +def fill_pdf_form(input_pdf_path, fields_json_path, output_pdf_path): + """Fill the PDF form with data from fields.json""" + + # `fields.json` format described in forms.md. + with open(fields_json_path, "r") as f: + fields_data = json.load(f) + + # Open the PDF + reader = PdfReader(input_pdf_path) + writer = PdfWriter() + + # Copy all pages to writer + writer.append(reader) + + # Get PDF dimensions for each page + pdf_dimensions = {} + for i, page in enumerate(reader.pages): + mediabox = page.mediabox + pdf_dimensions[i + 1] = [mediabox.width, mediabox.height] + + # Process each form field + annotations = [] + for field in fields_data["form_fields"]: + page_num = field["page_number"] + + # Get page dimensions and transform coordinates. + page_info = next(p for p in fields_data["pages"] if p["page_number"] == page_num) + image_width = page_info["image_width"] + image_height = page_info["image_height"] + pdf_width, pdf_height = pdf_dimensions[page_num] + + transformed_entry_box = transform_coordinates( + field["entry_bounding_box"], + image_width, image_height, + pdf_width, pdf_height + ) + + # Skip empty fields + if "entry_text" not in field or "text" not in field["entry_text"]: + continue + entry_text = field["entry_text"] + text = entry_text["text"] + if not text: + continue + + font_name = entry_text.get("font", "Arial") + font_size = str(entry_text.get("font_size", 14)) + "pt" + font_color = entry_text.get("font_color", "000000") + + # Font size/color seems to not work reliably across viewers: + # https://github.com/py-pdf/pypdf/issues/2084 + annotation = FreeText( + text=text, + rect=transformed_entry_box, + font=font_name, + font_size=font_size, + font_color=font_color, + border_color=None, + background_color=None, + ) + annotations.append(annotation) + # page_number is 0-based for pypdf + writer.add_annotation(page_number=page_num - 1, annotation=annotation) + + # Save the filled PDF + with open(output_pdf_path, "wb") as output: + writer.write(output) + + print(f"Successfully filled PDF form and saved to {output_pdf_path}") + print(f"Added {len(annotations)} text annotations") + + +if __name__ == "__main__": + if len(sys.argv) != 4: + print("Usage: fill_pdf_form_with_annotations.py [input pdf] [fields.json] [output pdf]") + sys.exit(1) + input_pdf = sys.argv[1] + fields_json = sys.argv[2] + output_pdf = sys.argv[3] + + fill_pdf_form(input_pdf, fields_json, output_pdf) \ No newline at end of file diff --git a/src/flow/skills/pptx/LICENSE.txt b/src/flow/skills/pptx/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..c55ab42224874608473643de0a85736b7fec0730 --- /dev/null +++ b/src/flow/skills/pptx/LICENSE.txt @@ -0,0 +1,30 @@ +© 2025 Anthropic, PBC. All rights reserved. + +LICENSE: Use of these materials (including all code, prompts, assets, files, +and other components of this Skill) is governed by your agreement with +Anthropic regarding use of Anthropic's services. If no separate agreement +exists, use is governed by Anthropic's Consumer Terms of Service or +Commercial Terms of Service, as applicable: +https://www.anthropic.com/legal/consumer-terms +https://www.anthropic.com/legal/commercial-terms +Your applicable agreement is referred to as the "Agreement." "Services" are +as defined in the Agreement. + +ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the +contrary, users may not: + +- Extract these materials from the Services or retain copies of these + materials outside the Services +- Reproduce or copy these materials, except for temporary copies created + automatically during authorized use of the Services +- Create derivative works based on these materials +- Distribute, sublicense, or transfer these materials to any third party +- Make, offer to sell, sell, or import any inventions embodied in these + materials +- Reverse engineer, decompile, or disassemble these materials + +The receipt, viewing, or possession of these materials does not convey or +imply any license or right beyond those expressly granted above. + +Anthropic retains all right, title, and interest in these materials, +including all copyrights, patents, and other intellectual property rights. diff --git a/src/flow/skills/pptx/SKILL.md b/src/flow/skills/pptx/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..b93b875fe11cf805bdfbbe5f0e7878a7562896ac --- /dev/null +++ b/src/flow/skills/pptx/SKILL.md @@ -0,0 +1,484 @@ +--- +name: pptx +description: "Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying or editing content, (3) Working with layouts, (4) Adding comments or speaker notes, or any other presentation tasks" +license: Proprietary. LICENSE.txt has complete terms +--- + +# PPTX creation, editing, and analysis + +## Overview + +A user may ask you to create, edit, or analyze the contents of a .pptx file. A .pptx file is essentially a ZIP archive containing XML files and other resources that you can read or edit. You have different tools and workflows available for different tasks. + +## Reading and analyzing content + +### Text extraction +If you just need to read the text contents of a presentation, you should convert the document to markdown: + +```bash +# Convert document to markdown +python -m markitdown path-to-file.pptx +``` + +### Raw XML access +You need raw XML access for: comments, speaker notes, slide layouts, animations, design elements, and complex formatting. For any of these features, you'll need to unpack a presentation and read its raw XML contents. + +#### Unpacking a file +`python ooxml/scripts/unpack.py ` + +**Note**: The unpack.py script is located at `skills/pptx/ooxml/scripts/unpack.py` relative to the project root. If the script doesn't exist at this path, use `find . -name "unpack.py"` to locate it. + +#### Key file structures +* `ppt/presentation.xml` - Main presentation metadata and slide references +* `ppt/slides/slide{N}.xml` - Individual slide contents (slide1.xml, slide2.xml, etc.) +* `ppt/notesSlides/notesSlide{N}.xml` - Speaker notes for each slide +* `ppt/comments/modernComment_*.xml` - Comments for specific slides +* `ppt/slideLayouts/` - Layout templates for slides +* `ppt/slideMasters/` - Master slide templates +* `ppt/theme/` - Theme and styling information +* `ppt/media/` - Images and other media files + +#### Typography and color extraction +**When given an example design to emulate**: Always analyze the presentation's typography and colors first using the methods below: +1. **Read theme file**: Check `ppt/theme/theme1.xml` for colors (``) and fonts (``) +2. **Sample slide content**: Examine `ppt/slides/slide1.xml` for actual font usage (``) and colors +3. **Search for patterns**: Use grep to find color (``, ``) and font references across all XML files + +## Creating a new PowerPoint presentation **without a template** + +When creating a new PowerPoint presentation from scratch, use the **html2pptx** workflow to convert HTML slides to PowerPoint with accurate positioning. + +### Design Principles + +**CRITICAL**: Before creating any presentation, analyze the content and choose appropriate design elements: +1. **Consider the subject matter**: What is this presentation about? What tone, industry, or mood does it suggest? +2. **Check for branding**: If the user mentions a company/organization, consider their brand colors and identity +3. **Match palette to content**: Select colors that reflect the subject +4. **State your approach**: Explain your design choices before writing code + +**Requirements**: +- ✅ State your content-informed design approach BEFORE writing code +- ✅ Use web-safe fonts only: Arial, Helvetica, Times New Roman, Georgia, Courier New, Verdana, Tahoma, Trebuchet MS, Impact +- ✅ Create clear visual hierarchy through size, weight, and color +- ✅ Ensure readability: strong contrast, appropriately sized text, clean alignment +- ✅ Be consistent: repeat patterns, spacing, and visual language across slides + +#### Color Palette Selection + +**Choosing colors creatively**: +- **Think beyond defaults**: What colors genuinely match this specific topic? Avoid autopilot choices. +- **Consider multiple angles**: Topic, industry, mood, energy level, target audience, brand identity (if mentioned) +- **Be adventurous**: Try unexpected combinations - a healthcare presentation doesn't have to be green, finance doesn't have to be navy +- **Build your palette**: Pick 3-5 colors that work together (dominant colors + supporting tones + accent) +- **Ensure contrast**: Text must be clearly readable on backgrounds + +**Example color palettes** (use these to spark creativity - choose one, adapt it, or create your own): + +1. **Classic Blue**: Deep navy (#1C2833), slate gray (#2E4053), silver (#AAB7B8), off-white (#F4F6F6) +2. **Teal & Coral**: Teal (#5EA8A7), deep teal (#277884), coral (#FE4447), white (#FFFFFF) +3. **Bold Red**: Red (#C0392B), bright red (#E74C3C), orange (#F39C12), yellow (#F1C40F), green (#2ECC71) +4. **Warm Blush**: Mauve (#A49393), blush (#EED6D3), rose (#E8B4B8), cream (#FAF7F2) +5. **Burgundy Luxury**: Burgundy (#5D1D2E), crimson (#951233), rust (#C15937), gold (#997929) +6. **Deep Purple & Emerald**: Purple (#B165FB), dark blue (#181B24), emerald (#40695B), white (#FFFFFF) +7. **Cream & Forest Green**: Cream (#FFE1C7), forest green (#40695B), white (#FCFCFC) +8. **Pink & Purple**: Pink (#F8275B), coral (#FF574A), rose (#FF737D), purple (#3D2F68) +9. **Lime & Plum**: Lime (#C5DE82), plum (#7C3A5F), coral (#FD8C6E), blue-gray (#98ACB5) +10. **Black & Gold**: Gold (#BF9A4A), black (#000000), cream (#F4F6F6) +11. **Sage & Terracotta**: Sage (#87A96B), terracotta (#E07A5F), cream (#F4F1DE), charcoal (#2C2C2C) +12. **Charcoal & Red**: Charcoal (#292929), red (#E33737), light gray (#CCCBCB) +13. **Vibrant Orange**: Orange (#F96D00), light gray (#F2F2F2), charcoal (#222831) +14. **Forest Green**: Black (#191A19), green (#4E9F3D), dark green (#1E5128), white (#FFFFFF) +15. **Retro Rainbow**: Purple (#722880), pink (#D72D51), orange (#EB5C18), amber (#F08800), gold (#DEB600) +16. **Vintage Earthy**: Mustard (#E3B448), sage (#CBD18F), forest green (#3A6B35), cream (#F4F1DE) +17. **Coastal Rose**: Old rose (#AD7670), beaver (#B49886), eggshell (#F3ECDC), ash gray (#BFD5BE) +18. **Orange & Turquoise**: Light orange (#FC993E), grayish turquoise (#667C6F), white (#FCFCFC) + +#### Visual Details Options + +**Geometric Patterns**: +- Diagonal section dividers instead of horizontal +- Asymmetric column widths (30/70, 40/60, 25/75) +- Rotated text headers at 90° or 270° +- Circular/hexagonal frames for images +- Triangular accent shapes in corners +- Overlapping shapes for depth + +**Border & Frame Treatments**: +- Thick single-color borders (10-20pt) on one side only +- Double-line borders with contrasting colors +- Corner brackets instead of full frames +- L-shaped borders (top+left or bottom+right) +- Underline accents beneath headers (3-5pt thick) + +**Typography Treatments**: +- Extreme size contrast (72pt headlines vs 11pt body) +- All-caps headers with wide letter spacing +- Numbered sections in oversized display type +- Monospace (Courier New) for data/stats/technical content +- Condensed fonts (Arial Narrow) for dense information +- Outlined text for emphasis + +**Chart & Data Styling**: +- Monochrome charts with single accent color for key data +- Horizontal bar charts instead of vertical +- Dot plots instead of bar charts +- Minimal gridlines or none at all +- Data labels directly on elements (no legends) +- Oversized numbers for key metrics + +**Layout Innovations**: +- Full-bleed images with text overlays +- Sidebar column (20-30% width) for navigation/context +- Modular grid systems (3×3, 4×4 blocks) +- Z-pattern or F-pattern content flow +- Floating text boxes over colored shapes +- Magazine-style multi-column layouts + +**Background Treatments**: +- Solid color blocks occupying 40-60% of slide +- Gradient fills (vertical or diagonal only) +- Split backgrounds (two colors, diagonal or vertical) +- Edge-to-edge color bands +- Negative space as a design element + +### Layout Tips +**When creating slides with charts or tables:** +- **Two-column layout (PREFERRED)**: Use a header spanning the full width, then two columns below - text/bullets in one column and the featured content in the other. This provides better balance and makes charts/tables more readable. Use flexbox with unequal column widths (e.g., 40%/60% split) to optimize space for each content type. +- **Full-slide layout**: Let the featured content (chart/table) take up the entire slide for maximum impact and readability +- **NEVER vertically stack**: Do not place charts/tables below text in a single column - this causes poor readability and layout issues + +### Workflow +1. **MANDATORY - READ ENTIRE FILE**: Read [`html2pptx.md`](html2pptx.md) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for detailed syntax, critical formatting rules, and best practices before proceeding with presentation creation. +2. Create an HTML file for each slide with proper dimensions (e.g., 720pt × 405pt for 16:9) + - Use `

`, `

`-`

`, `
    `, `
      ` for all text content + - Use `class="placeholder"` for areas where charts/tables will be added (render with gray background for visibility) + - **CRITICAL**: Rasterize gradients and icons as PNG images FIRST using Sharp, then reference in HTML + - **LAYOUT**: For slides with charts/tables/images, use either full-slide layout or two-column layout for better readability +3. Create and run a JavaScript file using the [`html2pptx.js`](scripts/html2pptx.js) library to convert HTML slides to PowerPoint and save the presentation + - Use the `html2pptx()` function to process each HTML file + - Add charts and tables to placeholder areas using PptxGenJS API + - Save the presentation using `pptx.writeFile()` +4. **Visual validation**: Generate thumbnails and inspect for layout issues + - Create thumbnail grid: `python scripts/thumbnail.py output.pptx workspace/thumbnails --cols 4` + - Read and carefully examine the thumbnail image for: + - **Text cutoff**: Text being cut off by header bars, shapes, or slide edges + - **Text overlap**: Text overlapping with other text or shapes + - **Positioning issues**: Content too close to slide boundaries or other elements + - **Contrast issues**: Insufficient contrast between text and backgrounds + - If issues found, adjust HTML margins/spacing/colors and regenerate the presentation + - Repeat until all slides are visually correct + +## Editing an existing PowerPoint presentation + +When edit slides in an existing PowerPoint presentation, you need to work with the raw Office Open XML (OOXML) format. This involves unpacking the .pptx file, editing the XML content, and repacking it. + +### Workflow +1. **MANDATORY - READ ENTIRE FILE**: Read [`ooxml.md`](ooxml.md) (~500 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for detailed guidance on OOXML structure and editing workflows before any presentation editing. +2. Unpack the presentation: `python ooxml/scripts/unpack.py ` +3. Edit the XML files (primarily `ppt/slides/slide{N}.xml` and related files) +4. **CRITICAL**: Validate immediately after each edit and fix any validation errors before proceeding: `python ooxml/scripts/validate.py --original ` +5. Pack the final presentation: `python ooxml/scripts/pack.py ` + +## Creating a new PowerPoint presentation **using a template** + +When you need to create a presentation that follows an existing template's design, you'll need to duplicate and re-arrange template slides before then replacing placeholder context. + +### Workflow +1. **Extract template text AND create visual thumbnail grid**: + * Extract text: `python -m markitdown template.pptx > template-content.md` + * Read `template-content.md`: Read the entire file to understand the contents of the template presentation. **NEVER set any range limits when reading this file.** + * Create thumbnail grids: `python scripts/thumbnail.py template.pptx` + * See [Creating Thumbnail Grids](#creating-thumbnail-grids) section for more details + +2. **Analyze template and save inventory to a file**: + * **Visual Analysis**: Review thumbnail grid(s) to understand slide layouts, design patterns, and visual structure + * Create and save a template inventory file at `template-inventory.md` containing: + ```markdown + # Template Inventory Analysis + **Total Slides: [count]** + **IMPORTANT: Slides are 0-indexed (first slide = 0, last slide = count-1)** + + ## [Category Name] + - Slide 0: [Layout code if available] - Description/purpose + - Slide 1: [Layout code] - Description/purpose + - Slide 2: [Layout code] - Description/purpose + [... EVERY slide must be listed individually with its index ...] + ``` + * **Using the thumbnail grid**: Reference the visual thumbnails to identify: + - Layout patterns (title slides, content layouts, section dividers) + - Image placeholder locations and counts + - Design consistency across slide groups + - Visual hierarchy and structure + * This inventory file is REQUIRED for selecting appropriate templates in the next step + +3. **Create presentation outline based on template inventory**: + * Review available templates from step 2. + * Choose an intro or title template for the first slide. This should be one of the first templates. + * Choose safe, text-based layouts for the other slides. + * **CRITICAL: Match layout structure to actual content**: + - Single-column layouts: Use for unified narrative or single topic + - Two-column layouts: Use ONLY when you have exactly 2 distinct items/concepts + - Three-column layouts: Use ONLY when you have exactly 3 distinct items/concepts + - Image + text layouts: Use ONLY when you have actual images to insert + - Quote layouts: Use ONLY for actual quotes from people (with attribution), never for emphasis + - Never use layouts with more placeholders than you have content + - If you have 2 items, don't force them into a 3-column layout + - If you have 4+ items, consider breaking into multiple slides or using a list format + * Count your actual content pieces BEFORE selecting the layout + * Verify each placeholder in the chosen layout will be filled with meaningful content + * Select one option representing the **best** layout for each content section. + * Save `outline.md` with content AND template mapping that leverages available designs + * Example template mapping: + ``` + # Template slides to use (0-based indexing) + # WARNING: Verify indices are within range! Template with 73 slides has indices 0-72 + # Mapping: slide numbers from outline -> template slide indices + template_mapping = [ + 0, # Use slide 0 (Title/Cover) + 34, # Use slide 34 (B1: Title and body) + 34, # Use slide 34 again (duplicate for second B1) + 50, # Use slide 50 (E1: Quote) + 54, # Use slide 54 (F2: Closing + Text) + ] + ``` + +4. **Duplicate, reorder, and delete slides using `rearrange.py`**: + * Use the `scripts/rearrange.py` script to create a new presentation with slides in the desired order: + ```bash + python scripts/rearrange.py template.pptx working.pptx 0,34,34,50,52 + ``` + * The script handles duplicating repeated slides, deleting unused slides, and reordering automatically + * Slide indices are 0-based (first slide is 0, second is 1, etc.) + * The same slide index can appear multiple times to duplicate that slide + +5. **Extract ALL text using the `inventory.py` script**: + * **Run inventory extraction**: + ```bash + python scripts/inventory.py working.pptx text-inventory.json + ``` + * **Read text-inventory.json**: Read the entire text-inventory.json file to understand all shapes and their properties. **NEVER set any range limits when reading this file.** + + * The inventory JSON structure: + ```json + { + "slide-0": { + "shape-0": { + "placeholder_type": "TITLE", // or null for non-placeholders + "left": 1.5, // position in inches + "top": 2.0, + "width": 7.5, + "height": 1.2, + "paragraphs": [ + { + "text": "Paragraph text", + // Optional properties (only included when non-default): + "bullet": true, // explicit bullet detected + "level": 0, // only included when bullet is true + "alignment": "CENTER", // CENTER, RIGHT (not LEFT) + "space_before": 10.0, // space before paragraph in points + "space_after": 6.0, // space after paragraph in points + "line_spacing": 22.4, // line spacing in points + "font_name": "Arial", // from first run + "font_size": 14.0, // in points + "bold": true, + "italic": false, + "underline": false, + "color": "FF0000" // RGB color + } + ] + } + } + } + ``` + + * Key features: + - **Slides**: Named as "slide-0", "slide-1", etc. + - **Shapes**: Ordered by visual position (top-to-bottom, left-to-right) as "shape-0", "shape-1", etc. + - **Placeholder types**: TITLE, CENTER_TITLE, SUBTITLE, BODY, OBJECT, or null + - **Default font size**: `default_font_size` in points extracted from layout placeholders (when available) + - **Slide numbers are filtered**: Shapes with SLIDE_NUMBER placeholder type are automatically excluded from inventory + - **Bullets**: When `bullet: true`, `level` is always included (even if 0) + - **Spacing**: `space_before`, `space_after`, and `line_spacing` in points (only included when set) + - **Colors**: `color` for RGB (e.g., "FF0000"), `theme_color` for theme colors (e.g., "DARK_1") + - **Properties**: Only non-default values are included in the output + +6. **Generate replacement text and save the data to a JSON file** + Based on the text inventory from the previous step: + - **CRITICAL**: First verify which shapes exist in the inventory - only reference shapes that are actually present + - **VALIDATION**: The replace.py script will validate that all shapes in your replacement JSON exist in the inventory + - If you reference a non-existent shape, you'll get an error showing available shapes + - If you reference a non-existent slide, you'll get an error indicating the slide doesn't exist + - All validation errors are shown at once before the script exits + - **IMPORTANT**: The replace.py script uses inventory.py internally to identify ALL text shapes + - **AUTOMATIC CLEARING**: ALL text shapes from the inventory will be cleared unless you provide "paragraphs" for them + - Add a "paragraphs" field to shapes that need content (not "replacement_paragraphs") + - Shapes without "paragraphs" in the replacement JSON will have their text cleared automatically + - Paragraphs with bullets will be automatically left aligned. Don't set the `alignment` property on when `"bullet": true` + - Generate appropriate replacement content for placeholder text + - Use shape size to determine appropriate content length + - **CRITICAL**: Include paragraph properties from the original inventory - don't just provide text + - **IMPORTANT**: When bullet: true, do NOT include bullet symbols (•, -, *) in text - they're added automatically + - **ESSENTIAL FORMATTING RULES**: + - Headers/titles should typically have `"bold": true` + - List items should have `"bullet": true, "level": 0` (level is required when bullet is true) + - Preserve any alignment properties (e.g., `"alignment": "CENTER"` for centered text) + - Include font properties when different from default (e.g., `"font_size": 14.0`, `"font_name": "Lora"`) + - Colors: Use `"color": "FF0000"` for RGB or `"theme_color": "DARK_1"` for theme colors + - The replacement script expects **properly formatted paragraphs**, not just text strings + - **Overlapping shapes**: Prefer shapes with larger default_font_size or more appropriate placeholder_type + - Save the updated inventory with replacements to `replacement-text.json` + - **WARNING**: Different template layouts have different shape counts - always check the actual inventory before creating replacements + + Example paragraphs field showing proper formatting: + ```json + "paragraphs": [ + { + "text": "New presentation title text", + "alignment": "CENTER", + "bold": true + }, + { + "text": "Section Header", + "bold": true + }, + { + "text": "First bullet point without bullet symbol", + "bullet": true, + "level": 0 + }, + { + "text": "Red colored text", + "color": "FF0000" + }, + { + "text": "Theme colored text", + "theme_color": "DARK_1" + }, + { + "text": "Regular paragraph text without special formatting" + } + ] + ``` + + **Shapes not listed in the replacement JSON are automatically cleared**: + ```json + { + "slide-0": { + "shape-0": { + "paragraphs": [...] // This shape gets new text + } + // shape-1 and shape-2 from inventory will be cleared automatically + } + } + ``` + + **Common formatting patterns for presentations**: + - Title slides: Bold text, sometimes centered + - Section headers within slides: Bold text + - Bullet lists: Each item needs `"bullet": true, "level": 0` + - Body text: Usually no special properties needed + - Quotes: May have special alignment or font properties + +7. **Apply replacements using the `replace.py` script** + ```bash + python scripts/replace.py working.pptx replacement-text.json output.pptx + ``` + + The script will: + - First extract the inventory of ALL text shapes using functions from inventory.py + - Validate that all shapes in the replacement JSON exist in the inventory + - Clear text from ALL shapes identified in the inventory + - Apply new text only to shapes with "paragraphs" defined in the replacement JSON + - Preserve formatting by applying paragraph properties from the JSON + - Handle bullets, alignment, font properties, and colors automatically + - Save the updated presentation + + Example validation errors: + ``` + ERROR: Invalid shapes in replacement JSON: + - Shape 'shape-99' not found on 'slide-0'. Available shapes: shape-0, shape-1, shape-4 + - Slide 'slide-999' not found in inventory + ``` + + ``` + ERROR: Replacement text made overflow worse in these shapes: + - slide-0/shape-2: overflow worsened by 1.25" (was 0.00", now 1.25") + ``` + +## Creating Thumbnail Grids + +To create visual thumbnail grids of PowerPoint slides for quick analysis and reference: + +```bash +python scripts/thumbnail.py template.pptx [output_prefix] +``` + +**Features**: +- Creates: `thumbnails.jpg` (or `thumbnails-1.jpg`, `thumbnails-2.jpg`, etc. for large decks) +- Default: 5 columns, max 30 slides per grid (5×6) +- Custom prefix: `python scripts/thumbnail.py template.pptx my-grid` + - Note: The output prefix should include the path if you want output in a specific directory (e.g., `workspace/my-grid`) +- Adjust columns: `--cols 4` (range: 3-6, affects slides per grid) +- Grid limits: 3 cols = 12 slides/grid, 4 cols = 20, 5 cols = 30, 6 cols = 42 +- Slides are zero-indexed (Slide 0, Slide 1, etc.) + +**Use cases**: +- Template analysis: Quickly understand slide layouts and design patterns +- Content review: Visual overview of entire presentation +- Navigation reference: Find specific slides by their visual appearance +- Quality check: Verify all slides are properly formatted + +**Examples**: +```bash +# Basic usage +python scripts/thumbnail.py presentation.pptx + +# Combine options: custom name, columns +python scripts/thumbnail.py template.pptx analysis --cols 4 +``` + +## Converting Slides to Images + +To visually analyze PowerPoint slides, convert them to images using a two-step process: + +1. **Convert PPTX to PDF**: + ```bash + soffice --headless --convert-to pdf template.pptx + ``` + +2. **Convert PDF pages to JPEG images**: + ```bash + pdftoppm -jpeg -r 150 template.pdf slide + ``` + This creates files like `slide-1.jpg`, `slide-2.jpg`, etc. + +Options: +- `-r 150`: Sets resolution to 150 DPI (adjust for quality/size balance) +- `-jpeg`: Output JPEG format (use `-png` for PNG if preferred) +- `-f N`: First page to convert (e.g., `-f 2` starts from page 2) +- `-l N`: Last page to convert (e.g., `-l 5` stops at page 5) +- `slide`: Prefix for output files + +Example for specific range: +```bash +pdftoppm -jpeg -r 150 -f 2 -l 5 template.pdf slide # Converts only pages 2-5 +``` + +## Code Style Guidelines +**IMPORTANT**: When generating code for PPTX operations: +- Write concise code +- Avoid verbose variable names and redundant operations +- Avoid unnecessary print statements + +## Dependencies + +Required dependencies (should already be installed): + +- **markitdown**: `pip install "markitdown[pptx]"` (for text extraction from presentations) +- **pptxgenjs**: `npm install -g pptxgenjs` (for creating presentations via html2pptx) +- **playwright**: `npm install -g playwright` (for HTML rendering in html2pptx) +- **react-icons**: `npm install -g react-icons react react-dom` (for icons) +- **sharp**: `npm install -g sharp` (for SVG rasterization and image processing) +- **LibreOffice**: `sudo apt-get install libreoffice` (for PDF conversion) +- **Poppler**: `sudo apt-get install poppler-utils` (for pdftoppm to convert PDF to images) +- **defusedxml**: `pip install defusedxml` (for secure XML parsing) \ No newline at end of file diff --git a/src/flow/skills/pptx/html2pptx.md b/src/flow/skills/pptx/html2pptx.md new file mode 100644 index 0000000000000000000000000000000000000000..106adf72d281cee3868ddbdcb20ce750b8eca9c0 --- /dev/null +++ b/src/flow/skills/pptx/html2pptx.md @@ -0,0 +1,625 @@ +# HTML to PowerPoint Guide + +Convert HTML slides to PowerPoint presentations with accurate positioning using the `html2pptx.js` library. + +## Table of Contents + +1. [Creating HTML Slides](#creating-html-slides) +2. [Using the html2pptx Library](#using-the-html2pptx-library) +3. [Using PptxGenJS](#using-pptxgenjs) + +--- + +## Creating HTML Slides + +Every HTML slide must include proper body dimensions: + +### Layout Dimensions + +- **16:9** (default): `width: 720pt; height: 405pt` +- **4:3**: `width: 720pt; height: 540pt` +- **16:10**: `width: 720pt; height: 450pt` + +### Supported Elements + +- `

      `, `

      `-`

      ` - Text with styling +- `
        `, `
          ` - Lists (never use manual bullets •, -, *) +- ``, `` - Bold text (inline formatting) +- ``, `` - Italic text (inline formatting) +- `` - Underlined text (inline formatting) +- `` - Inline formatting with CSS styles (bold, italic, underline, color) +- `
          ` - Line breaks +- `
          ` with bg/border - Becomes shape +- `` - Images +- `class="placeholder"` - Reserved space for charts (returns `{ id, x, y, w, h }`) + +### Critical Text Rules + +**ALL text MUST be inside `

          `, `

          `-`

          `, `
            `, or `
              ` tags:** +- ✅ Correct: `

              Text here

              ` +- ❌ Wrong: `
              Text here
              ` - **Text will NOT appear in PowerPoint** +- ❌ Wrong: `Text` - **Text will NOT appear in PowerPoint** +- Text in `
              ` or `` without a text tag will be silently ignored + +**NEVER use manual bullet symbols (•, -, *, etc.)** - Use `
                ` or `
                  ` lists instead + +**ONLY use web-safe fonts that are universally available:** +- ✅ Web-safe fonts: `Arial`, `Helvetica`, `Times New Roman`, `Georgia`, `Courier New`, `Verdana`, `Tahoma`, `Trebuchet MS`, `Impact`, `Comic Sans MS` +- ❌ Wrong: `'Segoe UI'`, `'SF Pro'`, `'Roboto'`, custom fonts - **Might cause rendering issues** + +### Styling + +- Use `display: flex` on body to prevent margin collapse from breaking overflow validation +- Use `margin` for spacing (padding included in size) +- Inline formatting: Use ``, ``, `` tags OR `` with CSS styles + - `` supports: `font-weight: bold`, `font-style: italic`, `text-decoration: underline`, `color: #rrggbb` + - `` does NOT support: `margin`, `padding` (not supported in PowerPoint text runs) + - Example: `Bold blue text` +- Flexbox works - positions calculated from rendered layout +- Use hex colors with `#` prefix in CSS +- **Text alignment**: Use CSS `text-align` (`center`, `right`, etc.) when needed as a hint to PptxGenJS for text formatting if text lengths are slightly off + +### Shape Styling (DIV elements only) + +**IMPORTANT: Backgrounds, borders, and shadows only work on `
                  ` elements, NOT on text elements (`

                  `, `

                  `-`

                  `, `
                    `, `
                      `)** + +- **Backgrounds**: CSS `background` or `background-color` on `
                      ` elements only + - Example: `
                      ` - Creates a shape with background +- **Borders**: CSS `border` on `
                      ` elements converts to PowerPoint shape borders + - Supports uniform borders: `border: 2px solid #333333` + - Supports partial borders: `border-left`, `border-right`, `border-top`, `border-bottom` (rendered as line shapes) + - Example: `
                      ` +- **Border radius**: CSS `border-radius` on `
                      ` elements for rounded corners + - `border-radius: 50%` or higher creates circular shape + - Percentages <50% calculated relative to shape's smaller dimension + - Supports px and pt units (e.g., `border-radius: 8pt;`, `border-radius: 12px;`) + - Example: `
                      ` on 100x200px box = 25% of 100px = 25px radius +- **Box shadows**: CSS `box-shadow` on `
                      ` elements converts to PowerPoint shadows + - Supports outer shadows only (inset shadows are ignored to prevent corruption) + - Example: `
                      ` + - Note: Inset/inner shadows are not supported by PowerPoint and will be skipped + +### Icons & Gradients + +- **CRITICAL: Never use CSS gradients (`linear-gradient`, `radial-gradient`)** - They don't convert to PowerPoint +- **ALWAYS create gradient/icon PNGs FIRST using Sharp, then reference in HTML** +- For gradients: Rasterize SVG to PNG background images +- For icons: Rasterize react-icons SVG to PNG images +- All visual effects must be pre-rendered as raster images before HTML rendering + +**Rasterizing Icons with Sharp:** + +```javascript +const React = require('react'); +const ReactDOMServer = require('react-dom/server'); +const sharp = require('sharp'); +const { FaHome } = require('react-icons/fa'); + +async function rasterizeIconPng(IconComponent, color, size = "256", filename) { + const svgString = ReactDOMServer.renderToStaticMarkup( + React.createElement(IconComponent, { color: `#${color}`, size: size }) + ); + + // Convert SVG to PNG using Sharp + await sharp(Buffer.from(svgString)) + .png() + .toFile(filename); + + return filename; +} + +// Usage: Rasterize icon before using in HTML +const iconPath = await rasterizeIconPng(FaHome, "4472c4", "256", "home-icon.png"); +// Then reference in HTML: +``` + +**Rasterizing Gradients with Sharp:** + +```javascript +const sharp = require('sharp'); + +async function createGradientBackground(filename) { + const svg = ` + + + + + + + + `; + + await sharp(Buffer.from(svg)) + .png() + .toFile(filename); + + return filename; +} + +// Usage: Create gradient background before HTML +const bgPath = await createGradientBackground("gradient-bg.png"); +// Then in HTML: +``` + +### Example + +```html + + + + + + +
                      +

                      Recipe Title

                      +
                        +
                      • Item: Description
                      • +
                      +

                      Text with bold, italic, underline.

                      +
                      + + +
                      +

                      5

                      +
                      +
                      + + +``` + +## Using the html2pptx Library + +### Dependencies + +These libraries have been globally installed and are available to use: +- `pptxgenjs` +- `playwright` +- `sharp` + +### Basic Usage + +```javascript +const pptxgen = require('pptxgenjs'); +const html2pptx = require('./html2pptx'); + +const pptx = new pptxgen(); +pptx.layout = 'LAYOUT_16x9'; // Must match HTML body dimensions + +const { slide, placeholders } = await html2pptx('slide1.html', pptx); + +// Add chart to placeholder area +if (placeholders.length > 0) { + slide.addChart(pptx.charts.LINE, chartData, placeholders[0]); +} + +await pptx.writeFile('output.pptx'); +``` + +### API Reference + +#### Function Signature +```javascript +await html2pptx(htmlFile, pres, options) +``` + +#### Parameters +- `htmlFile` (string): Path to HTML file (absolute or relative) +- `pres` (pptxgen): PptxGenJS presentation instance with layout already set +- `options` (object, optional): + - `tmpDir` (string): Temporary directory for generated files (default: `process.env.TMPDIR || '/tmp'`) + - `slide` (object): Existing slide to reuse (default: creates new slide) + +#### Returns +```javascript +{ + slide: pptxgenSlide, // The created/updated slide + placeholders: [ // Array of placeholder positions + { id: string, x: number, y: number, w: number, h: number }, + ... + ] +} +``` + +### Validation + +The library automatically validates and collects all errors before throwing: + +1. **HTML dimensions must match presentation layout** - Reports dimension mismatches +2. **Content must not overflow body** - Reports overflow with exact measurements +3. **CSS gradients** - Reports unsupported gradient usage +4. **Text element styling** - Reports backgrounds/borders/shadows on text elements (only allowed on divs) + +**All validation errors are collected and reported together** in a single error message, allowing you to fix all issues at once instead of one at a time. + +### Working with Placeholders + +```javascript +const { slide, placeholders } = await html2pptx('slide.html', pptx); + +// Use first placeholder +slide.addChart(pptx.charts.BAR, data, placeholders[0]); + +// Find by ID +const chartArea = placeholders.find(p => p.id === 'chart-area'); +slide.addChart(pptx.charts.LINE, data, chartArea); +``` + +### Complete Example + +```javascript +const pptxgen = require('pptxgenjs'); +const html2pptx = require('./html2pptx'); + +async function createPresentation() { + const pptx = new pptxgen(); + pptx.layout = 'LAYOUT_16x9'; + pptx.author = 'Your Name'; + pptx.title = 'My Presentation'; + + // Slide 1: Title + const { slide: slide1 } = await html2pptx('slides/title.html', pptx); + + // Slide 2: Content with chart + const { slide: slide2, placeholders } = await html2pptx('slides/data.html', pptx); + + const chartData = [{ + name: 'Sales', + labels: ['Q1', 'Q2', 'Q3', 'Q4'], + values: [4500, 5500, 6200, 7100] + }]; + + slide2.addChart(pptx.charts.BAR, chartData, { + ...placeholders[0], + showTitle: true, + title: 'Quarterly Sales', + showCatAxisTitle: true, + catAxisTitle: 'Quarter', + showValAxisTitle: true, + valAxisTitle: 'Sales ($000s)' + }); + + // Save + await pptx.writeFile({ fileName: 'presentation.pptx' }); + console.log('Presentation created successfully!'); +} + +createPresentation().catch(console.error); +``` + +## Using PptxGenJS + +After converting HTML to slides with `html2pptx`, you'll use PptxGenJS to add dynamic content like charts, images, and additional elements. + +### ⚠️ Critical Rules + +#### Colors +- **NEVER use `#` prefix** with hex colors in PptxGenJS - causes file corruption +- ✅ Correct: `color: "FF0000"`, `fill: { color: "0066CC" }` +- ❌ Wrong: `color: "#FF0000"` (breaks document) + +### Adding Images + +Always calculate aspect ratios from actual image dimensions: + +```javascript +// Get image dimensions: identify image.png | grep -o '[0-9]* x [0-9]*' +const imgWidth = 1860, imgHeight = 1519; // From actual file +const aspectRatio = imgWidth / imgHeight; + +const h = 3; // Max height +const w = h * aspectRatio; +const x = (10 - w) / 2; // Center on 16:9 slide + +slide.addImage({ path: "chart.png", x, y: 1.5, w, h }); +``` + +### Adding Text + +```javascript +// Rich text with formatting +slide.addText([ + { text: "Bold ", options: { bold: true } }, + { text: "Italic ", options: { italic: true } }, + { text: "Normal" } +], { + x: 1, y: 2, w: 8, h: 1 +}); +``` + +### Adding Shapes + +```javascript +// Rectangle +slide.addShape(pptx.shapes.RECTANGLE, { + x: 1, y: 1, w: 3, h: 2, + fill: { color: "4472C4" }, + line: { color: "000000", width: 2 } +}); + +// Circle +slide.addShape(pptx.shapes.OVAL, { + x: 5, y: 1, w: 2, h: 2, + fill: { color: "ED7D31" } +}); + +// Rounded rectangle +slide.addShape(pptx.shapes.ROUNDED_RECTANGLE, { + x: 1, y: 4, w: 3, h: 1.5, + fill: { color: "70AD47" }, + rectRadius: 0.2 +}); +``` + +### Adding Charts + +**Required for most charts:** Axis labels using `catAxisTitle` (category) and `valAxisTitle` (value). + +**Chart Data Format:** +- Use **single series with all labels** for simple bar/line charts +- Each series creates a separate legend entry +- Labels array defines X-axis values + +**Time Series Data - Choose Correct Granularity:** +- **< 30 days**: Use daily grouping (e.g., "10-01", "10-02") - avoid monthly aggregation that creates single-point charts +- **30-365 days**: Use monthly grouping (e.g., "2024-01", "2024-02") +- **> 365 days**: Use yearly grouping (e.g., "2023", "2024") +- **Validate**: Charts with only 1 data point likely indicate incorrect aggregation for the time period + +```javascript +const { slide, placeholders } = await html2pptx('slide.html', pptx); + +// CORRECT: Single series with all labels +slide.addChart(pptx.charts.BAR, [{ + name: "Sales 2024", + labels: ["Q1", "Q2", "Q3", "Q4"], + values: [4500, 5500, 6200, 7100] +}], { + ...placeholders[0], // Use placeholder position + barDir: 'col', // 'col' = vertical bars, 'bar' = horizontal + showTitle: true, + title: 'Quarterly Sales', + showLegend: false, // No legend needed for single series + // Required axis labels + showCatAxisTitle: true, + catAxisTitle: 'Quarter', + showValAxisTitle: true, + valAxisTitle: 'Sales ($000s)', + // Optional: Control scaling (adjust min based on data range for better visualization) + valAxisMaxVal: 8000, + valAxisMinVal: 0, // Use 0 for counts/amounts; for clustered data (e.g., 4500-7100), consider starting closer to min value + valAxisMajorUnit: 2000, // Control y-axis label spacing to prevent crowding + catAxisLabelRotate: 45, // Rotate labels if crowded + dataLabelPosition: 'outEnd', + dataLabelColor: '000000', + // Use single color for single-series charts + chartColors: ["4472C4"] // All bars same color +}); +``` + +#### Scatter Chart + +**IMPORTANT**: Scatter chart data format is unusual - first series contains X-axis values, subsequent series contain Y-values: + +```javascript +// Prepare data +const data1 = [{ x: 10, y: 20 }, { x: 15, y: 25 }, { x: 20, y: 30 }]; +const data2 = [{ x: 12, y: 18 }, { x: 18, y: 22 }]; + +const allXValues = [...data1.map(d => d.x), ...data2.map(d => d.x)]; + +slide.addChart(pptx.charts.SCATTER, [ + { name: 'X-Axis', values: allXValues }, // First series = X values + { name: 'Series 1', values: data1.map(d => d.y) }, // Y values only + { name: 'Series 2', values: data2.map(d => d.y) } // Y values only +], { + x: 1, y: 1, w: 8, h: 4, + lineSize: 0, // 0 = no connecting lines + lineDataSymbol: 'circle', + lineDataSymbolSize: 6, + showCatAxisTitle: true, + catAxisTitle: 'X Axis', + showValAxisTitle: true, + valAxisTitle: 'Y Axis', + chartColors: ["4472C4", "ED7D31"] +}); +``` + +#### Line Chart + +```javascript +slide.addChart(pptx.charts.LINE, [{ + name: "Temperature", + labels: ["Jan", "Feb", "Mar", "Apr"], + values: [32, 35, 42, 55] +}], { + x: 1, y: 1, w: 8, h: 4, + lineSize: 4, + lineSmooth: true, + // Required axis labels + showCatAxisTitle: true, + catAxisTitle: 'Month', + showValAxisTitle: true, + valAxisTitle: 'Temperature (°F)', + // Optional: Y-axis range (set min based on data range for better visualization) + valAxisMinVal: 0, // For ranges starting at 0 (counts, percentages, etc.) + valAxisMaxVal: 60, + valAxisMajorUnit: 20, // Control y-axis label spacing to prevent crowding (e.g., 10, 20, 25) + // valAxisMinVal: 30, // PREFERRED: For data clustered in a range (e.g., 32-55 or ratings 3-5), start axis closer to min value to show variation + // Optional: Chart colors + chartColors: ["4472C4", "ED7D31", "A5A5A5"] +}); +``` + +#### Pie Chart (No Axis Labels Required) + +**CRITICAL**: Pie charts require a **single data series** with all categories in the `labels` array and corresponding values in the `values` array. + +```javascript +slide.addChart(pptx.charts.PIE, [{ + name: "Market Share", + labels: ["Product A", "Product B", "Other"], // All categories in one array + values: [35, 45, 20] // All values in one array +}], { + x: 2, y: 1, w: 6, h: 4, + showPercent: true, + showLegend: true, + legendPos: 'r', // right + chartColors: ["4472C4", "ED7D31", "A5A5A5"] +}); +``` + +#### Multiple Data Series + +```javascript +slide.addChart(pptx.charts.LINE, [ + { + name: "Product A", + labels: ["Q1", "Q2", "Q3", "Q4"], + values: [10, 20, 30, 40] + }, + { + name: "Product B", + labels: ["Q1", "Q2", "Q3", "Q4"], + values: [15, 25, 20, 35] + } +], { + x: 1, y: 1, w: 8, h: 4, + showCatAxisTitle: true, + catAxisTitle: 'Quarter', + showValAxisTitle: true, + valAxisTitle: 'Revenue ($M)' +}); +``` + +### Chart Colors + +**CRITICAL**: Use hex colors **without** the `#` prefix - including `#` causes file corruption. + +**Align chart colors with your chosen design palette**, ensuring sufficient contrast and distinctiveness for data visualization. Adjust colors for: +- Strong contrast between adjacent series +- Readability against slide backgrounds +- Accessibility (avoid red-green only combinations) + +```javascript +// Example: Ocean palette-inspired chart colors (adjusted for contrast) +const chartColors = ["16A085", "FF6B9D", "2C3E50", "F39C12", "9B59B6"]; + +// Single-series chart: Use one color for all bars/points +slide.addChart(pptx.charts.BAR, [{ + name: "Sales", + labels: ["Q1", "Q2", "Q3", "Q4"], + values: [4500, 5500, 6200, 7100] +}], { + ...placeholders[0], + chartColors: ["16A085"], // All bars same color + showLegend: false +}); + +// Multi-series chart: Each series gets a different color +slide.addChart(pptx.charts.LINE, [ + { name: "Product A", labels: ["Q1", "Q2", "Q3"], values: [10, 20, 30] }, + { name: "Product B", labels: ["Q1", "Q2", "Q3"], values: [15, 25, 20] } +], { + ...placeholders[0], + chartColors: ["16A085", "FF6B9D"] // One color per series +}); +``` + +### Adding Tables + +Tables can be added with basic or advanced formatting: + +#### Basic Table + +```javascript +slide.addTable([ + ["Header 1", "Header 2", "Header 3"], + ["Row 1, Col 1", "Row 1, Col 2", "Row 1, Col 3"], + ["Row 2, Col 1", "Row 2, Col 2", "Row 2, Col 3"] +], { + x: 0.5, + y: 1, + w: 9, + h: 3, + border: { pt: 1, color: "999999" }, + fill: { color: "F1F1F1" } +}); +``` + +#### Table with Custom Formatting + +```javascript +const tableData = [ + // Header row with custom styling + [ + { text: "Product", options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } }, + { text: "Revenue", options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } }, + { text: "Growth", options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } } + ], + // Data rows + ["Product A", "$50M", "+15%"], + ["Product B", "$35M", "+22%"], + ["Product C", "$28M", "+8%"] +]; + +slide.addTable(tableData, { + x: 1, + y: 1.5, + w: 8, + h: 3, + colW: [3, 2.5, 2.5], // Column widths + rowH: [0.5, 0.6, 0.6, 0.6], // Row heights + border: { pt: 1, color: "CCCCCC" }, + align: "center", + valign: "middle", + fontSize: 14 +}); +``` + +#### Table with Merged Cells + +```javascript +const mergedTableData = [ + [ + { text: "Q1 Results", options: { colspan: 3, fill: { color: "4472C4" }, color: "FFFFFF", bold: true } } + ], + ["Product", "Sales", "Market Share"], + ["Product A", "$25M", "35%"], + ["Product B", "$18M", "25%"] +]; + +slide.addTable(mergedTableData, { + x: 1, + y: 1, + w: 8, + h: 2.5, + colW: [3, 2.5, 2.5], + border: { pt: 1, color: "DDDDDD" } +}); +``` + +### Table Options + +Common table options: +- `x, y, w, h` - Position and size +- `colW` - Array of column widths (in inches) +- `rowH` - Array of row heights (in inches) +- `border` - Border style: `{ pt: 1, color: "999999" }` +- `fill` - Background color (no # prefix) +- `align` - Text alignment: "left", "center", "right" +- `valign` - Vertical alignment: "top", "middle", "bottom" +- `fontSize` - Text size +- `autoPage` - Auto-create new slides if content overflows \ No newline at end of file diff --git a/src/flow/skills/pptx/ooxml.md b/src/flow/skills/pptx/ooxml.md new file mode 100644 index 0000000000000000000000000000000000000000..951b3cf65aeae566dc8fe927ee7a69f5cc4b9580 --- /dev/null +++ b/src/flow/skills/pptx/ooxml.md @@ -0,0 +1,427 @@ +# Office Open XML Technical Reference for PowerPoint + +**Important: Read this entire document before starting.** Critical XML schema rules and formatting requirements are covered throughout. Incorrect implementation can create invalid PPTX files that PowerPoint cannot open. + +## Technical Guidelines + +### Schema Compliance +- **Element ordering in ``**: ``, ``, `` +- **Whitespace**: Add `xml:space='preserve'` to `` elements with leading/trailing spaces +- **Unicode**: Escape characters in ASCII content: `"` becomes `“` +- **Images**: Add to `ppt/media/`, reference in slide XML, set dimensions to fit slide bounds +- **Relationships**: Update `ppt/slides/_rels/slideN.xml.rels` for each slide's resources +- **Dirty attribute**: Add `dirty="0"` to `` and `` elements to indicate clean state + +## Presentation Structure + +### Basic Slide Structure +```xml + + + + + ... + ... + + + + +``` + +### Text Box / Shape with Text +```xml + + + + + + + + + + + + + + + + + + + + + + Slide Title + + + + +``` + +### Text Formatting +```xml + + + + Bold Text + + + + + + Italic Text + + + + + + Underlined + + + + + + + + + + Highlighted Text + + + + + + + + + + Colored Arial 24pt + + + + + + + + + + Formatted text + +``` + +### Lists +```xml + + + + + + + First bullet point + + + + + + + + + + First numbered item + + + + + + + + + + Indented bullet + + +``` + +### Shapes +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### Images +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### Tables +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + Cell 1 + + + + + + + + + + + Cell 2 + + + + + + + + + +``` + +### Slide Layouts + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## File Updates + +When adding content, update these files: + +**`ppt/_rels/presentation.xml.rels`:** +```xml + + +``` + +**`ppt/slides/_rels/slide1.xml.rels`:** +```xml + + +``` + +**`[Content_Types].xml`:** +```xml + + + +``` + +**`ppt/presentation.xml`:** +```xml + + + + +``` + +**`docProps/app.xml`:** Update slide count and statistics +```xml +2 +10 +50 +``` + +## Slide Operations + +### Adding a New Slide +When adding a slide to the end of the presentation: + +1. **Create the slide file** (`ppt/slides/slideN.xml`) +2. **Update `[Content_Types].xml`**: Add Override for the new slide +3. **Update `ppt/_rels/presentation.xml.rels`**: Add relationship for the new slide +4. **Update `ppt/presentation.xml`**: Add slide ID to `` +5. **Create slide relationships** (`ppt/slides/_rels/slideN.xml.rels`) if needed +6. **Update `docProps/app.xml`**: Increment slide count and update statistics (if present) + +### Duplicating a Slide +1. Copy the source slide XML file with a new name +2. Update all IDs in the new slide to be unique +3. Follow the "Adding a New Slide" steps above +4. **CRITICAL**: Remove or update any notes slide references in `_rels` files +5. Remove references to unused media files + +### Reordering Slides +1. **Update `ppt/presentation.xml`**: Reorder `` elements in `` +2. The order of `` elements determines slide order +3. Keep slide IDs and relationship IDs unchanged + +Example: +```xml + + + + + + + + + + + + + +``` + +### Deleting a Slide +1. **Remove from `ppt/presentation.xml`**: Delete the `` entry +2. **Remove from `ppt/_rels/presentation.xml.rels`**: Delete the relationship +3. **Remove from `[Content_Types].xml`**: Delete the Override entry +4. **Delete files**: Remove `ppt/slides/slideN.xml` and `ppt/slides/_rels/slideN.xml.rels` +5. **Update `docProps/app.xml`**: Decrement slide count and update statistics +6. **Clean up unused media**: Remove orphaned images from `ppt/media/` + +Note: Don't renumber remaining slides - keep their original IDs and filenames. + + +## Common Errors to Avoid + +- **Encodings**: Escape unicode characters in ASCII content: `"` becomes `“` +- **Images**: Add to `ppt/media/` and update relationship files +- **Lists**: Omit bullets from list headers +- **IDs**: Use valid hexadecimal values for UUIDs +- **Themes**: Check all themes in `theme` directory for colors + +## Validation Checklist for Template-Based Presentations + +### Before Packing, Always: +- **Clean unused resources**: Remove unreferenced media, fonts, and notes directories +- **Fix Content_Types.xml**: Declare ALL slides, layouts, and themes present in the package +- **Fix relationship IDs**: + - Remove font embed references if not using embedded fonts +- **Remove broken references**: Check all `_rels` files for references to deleted resources + +### Common Template Duplication Pitfalls: +- Multiple slides referencing the same notes slide after duplication +- Image/media references from template slides that no longer exist +- Font embedding references when fonts aren't included +- Missing slideLayout declarations for layouts 12-25 +- docProps directory may not unpack - this is optional \ No newline at end of file diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6454ef9a94d52512e4905df61280a3fd1dafc0a4 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd @@ -0,0 +1,1499 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..afa4f463e3140a3f626c73f7966d689270d89b2c --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd new file mode 100644 index 0000000000000000000000000000000000000000..64e66b8abd496d2e450c08110e40607e90b3fe13 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd @@ -0,0 +1,1085 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd new file mode 100644 index 0000000000000000000000000000000000000000..687eea8297caa298581aa06c40ca40ef7b8a58bc --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd @@ -0,0 +1,11 @@ + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6ac81b06b7a3ef916d677953084ae225504a77bc --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd @@ -0,0 +1,3081 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd new file mode 100644 index 0000000000000000000000000000000000000000..1dbf05140d07fa014f18a5630acc0613d8058e52 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f1af17db4e83b1c40152577eca4a575ab63a1ca7 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..0a185ab6ed0c2dd9b383a403c56c909bbab8b9a1 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..14ef488865f3ae6150776220ebbcf4788f89066a --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd @@ -0,0 +1,1676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd new file mode 100644 index 0000000000000000000000000000000000000000..c20f3bf14720d34247e8e1302dc3c40f7b832099 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ac60252262534e7b87523fd04d252f085b38bec0 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd new file mode 100644 index 0000000000000000000000000000000000000000..424b8ba8d1f9ebb22ba72cdd174c8a2ade0ecf89 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2bddce29214882eb9fdf2be8a50ed5bdd0c69dfc --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd new file mode 100644 index 0000000000000000000000000000000000000000..8a8c18ba2d5caa7e60a365e77ae0d1b872141bb0 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd new file mode 100644 index 0000000000000000000000000000000000000000..5c42706a0d53c5e8b96818d9c29a292d1504f720 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd new file mode 100644 index 0000000000000000000000000000000000000000..853c341c87feb51e10d757aea28b4185ae4c2731 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd new file mode 100644 index 0000000000000000000000000000000000000000..da835ee82d5cc31be9fa9792512930f7ae3fb5ce --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd new file mode 100644 index 0000000000000000000000000000000000000000..87ad2658fa51cb4c172f82d0fdf33a492a750eca --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd @@ -0,0 +1,582 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd new file mode 100644 index 0000000000000000000000000000000000000000..9e86f1b2be0d431cd3c250ddeaf4efabdbcb088b --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..d0be42e757f3cce533c1c80770239f0097d3de6b --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd @@ -0,0 +1,4439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd new file mode 100644 index 0000000000000000000000000000000000000000..8821dd183caf9c8c3b809e0e2d9e0df56c4e9ed9 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd @@ -0,0 +1,570 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ca2575c753be78cdaa96de56b3ff8770173c324d --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd @@ -0,0 +1,509 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..dd079e603f5770176212776a1d565b108875cf64 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..3dd6cf625a740398c2a4dc8abbe18e25e0ac5e9f --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f1041e34ef365926f5670b896f5bb23e553ac36d --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..9c5b7a633411c2313ce839f748e4623bb0a70efe --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd @@ -0,0 +1,3646 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd new file mode 100644 index 0000000000000000000000000000000000000000..0f13678d80a762375223f060e23b56c7b2eac89e --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd @@ -0,0 +1,116 @@ + + + + + + See http://www.w3.org/XML/1998/namespace.html and + http://www.w3.org/TR/REC-xml for information about this namespace. + + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. + + Note that local names in this namespace are intended to be defined + only by the World Wide Web Consortium or its subgroups. The + following names are currently defined in this namespace and should + not be used with conflicting semantics by any Working Group, + specification, or document instance: + + base (as an attribute name): denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification. + + lang (as an attribute name): denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification. + + space (as an attribute name): denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification. + + Father (in any context at all): denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: + + In appreciation for his vision, leadership and dedication + the W3C XML Plenary on this 10th day of February, 2000 + reserves for Jon Bosak in perpetuity the XML name + xml:Father + + + + + This schema defines attributes and an attribute group + suitable for use by + schemas wishing to allow xml:base, xml:lang or xml:space attributes + on elements they define. + + To enable this, such a schema must import this schema + for the XML namespace, e.g. as follows: + <schema . . .> + . . . + <import namespace="http://www.w3.org/XML/1998/namespace" + schemaLocation="http://www.w3.org/2001/03/xml.xsd"/> + + Subsequently, qualified reference to any of the attributes + or the group defined below will have the desired effect, e.g. + + <type . . .> + . . . + <attributeGroup ref="xml:specialAttrs"/> + + will define a type which will schema-validate an instance + element with any of those attributes + + + + In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + http://www.w3.org/2001/03/xml.xsd. + At the date of issue it can also be found at + http://www.w3.org/2001/xml.xsd. + The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML Schema + itself. In other words, if the XML Schema namespace changes, the version + of this document at + http://www.w3.org/2001/xml.xsd will change + accordingly; the version at + http://www.w3.org/2001/03/xml.xsd will not change. + + + + + + In due course, we should install the relevant ISO 2- and 3-letter + codes as the enumerated possible values . . . + + + + + + + + + + + + + + + See http://www.w3.org/TR/xmlbase/ for + information about this attribute. + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd b/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd new file mode 100644 index 0000000000000000000000000000000000000000..a6de9d2733d3f0eea12ae87b24122495df1f0928 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd b/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd new file mode 100644 index 0000000000000000000000000000000000000000..10e978b661fc291cbeea9ab0c248601cf216f96d --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd b/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd new file mode 100644 index 0000000000000000000000000000000000000000..4248bf7a39c793c9f319bae7d09f92983efe5368 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd b/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd new file mode 100644 index 0000000000000000000000000000000000000000..56497467120b52b742756379959d4b2536be6df7 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/mce/mc.xsd b/src/flow/skills/pptx/ooxml/schemas/mce/mc.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ef725457cf39116672e285d021be69b7ad022574 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/mce/mc.xsd @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f65f777730d82162f4248f03c703b1b85fda5fe2 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6b00755a9a8733f0fb171a47b25d0d7b4c70dee8 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f321d333a5e6ef98cbdfeeeaa334923e4cfc8a6c --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd new file mode 100644 index 0000000000000000000000000000000000000000..364c6a9b8df6e252bb5ae39e2ca031fcbfc4e8a5 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd new file mode 100644 index 0000000000000000000000000000000000000000..fed9d15b7f504e14ac82793d79db2d7ad83d56ed --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd new file mode 100644 index 0000000000000000000000000000000000000000..680cf15400cd57ce2bf17a58ecd29caa0d8a8422 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd @@ -0,0 +1,4 @@ + + + + diff --git a/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd new file mode 100644 index 0000000000000000000000000000000000000000..89ada90837b2db3f3d453212ae426520acedbbea --- /dev/null +++ b/src/flow/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/flow/skills/pptx/ooxml/scripts/pack.py b/src/flow/skills/pptx/ooxml/scripts/pack.py new file mode 100755 index 0000000000000000000000000000000000000000..68bc0886f6ef74e6e1d3fe6da0d6a296858a42d8 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/scripts/pack.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +""" +Tool to pack a directory into a .docx, .pptx, or .xlsx file with XML formatting undone. + +Example usage: + python pack.py [--force] +""" + +import argparse +import shutil +import subprocess +import sys +import tempfile +import defusedxml.minidom +import zipfile +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser(description="Pack a directory into an Office file") + parser.add_argument("input_directory", help="Unpacked Office document directory") + parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)") + parser.add_argument("--force", action="store_true", help="Skip validation") + args = parser.parse_args() + + try: + success = pack_document( + args.input_directory, args.output_file, validate=not args.force + ) + + # Show warning if validation was skipped + if args.force: + print("Warning: Skipped validation, file may be corrupt", file=sys.stderr) + # Exit with error if validation failed + elif not success: + print("Contents would produce a corrupt file.", file=sys.stderr) + print("Please validate XML before repacking.", file=sys.stderr) + print("Use --force to skip validation and pack anyway.", file=sys.stderr) + sys.exit(1) + + except ValueError as e: + sys.exit(f"Error: {e}") + + +def pack_document(input_dir, output_file, validate=False): + """Pack a directory into an Office file (.docx/.pptx/.xlsx). + + Args: + input_dir: Path to unpacked Office document directory + output_file: Path to output Office file + validate: If True, validates with soffice (default: False) + + Returns: + bool: True if successful, False if validation failed + """ + input_dir = Path(input_dir) + output_file = Path(output_file) + + if not input_dir.is_dir(): + raise ValueError(f"{input_dir} is not a directory") + if output_file.suffix.lower() not in {".docx", ".pptx", ".xlsx"}: + raise ValueError(f"{output_file} must be a .docx, .pptx, or .xlsx file") + + # Work in temporary directory to avoid modifying original + with tempfile.TemporaryDirectory() as temp_dir: + temp_content_dir = Path(temp_dir) / "content" + shutil.copytree(input_dir, temp_content_dir) + + # Process XML files to remove pretty-printing whitespace + for pattern in ["*.xml", "*.rels"]: + for xml_file in temp_content_dir.rglob(pattern): + condense_xml(xml_file) + + # Create final Office file as zip archive + output_file.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as zf: + for f in temp_content_dir.rglob("*"): + if f.is_file(): + zf.write(f, f.relative_to(temp_content_dir)) + + # Validate if requested + if validate: + if not validate_document(output_file): + output_file.unlink() # Delete the corrupt file + return False + + return True + + +def validate_document(doc_path): + """Validate document by converting to HTML with soffice.""" + # Determine the correct filter based on file extension + match doc_path.suffix.lower(): + case ".docx": + filter_name = "html:HTML" + case ".pptx": + filter_name = "html:impress_html_Export" + case ".xlsx": + filter_name = "html:HTML (StarCalc)" + + with tempfile.TemporaryDirectory() as temp_dir: + try: + result = subprocess.run( + [ + "soffice", + "--headless", + "--convert-to", + filter_name, + "--outdir", + temp_dir, + str(doc_path), + ], + capture_output=True, + timeout=10, + text=True, + ) + if not (Path(temp_dir) / f"{doc_path.stem}.html").exists(): + error_msg = result.stderr.strip() or "Document validation failed" + print(f"Validation error: {error_msg}", file=sys.stderr) + return False + return True + except FileNotFoundError: + print("Warning: soffice not found. Skipping validation.", file=sys.stderr) + return True + except subprocess.TimeoutExpired: + print("Validation error: Timeout during conversion", file=sys.stderr) + return False + except Exception as e: + print(f"Validation error: {e}", file=sys.stderr) + return False + + +def condense_xml(xml_file): + """Strip unnecessary whitespace and remove comments.""" + with open(xml_file, "r", encoding="utf-8") as f: + dom = defusedxml.minidom.parse(f) + + # Process each element to remove whitespace and comments + for element in dom.getElementsByTagName("*"): + # Skip w:t elements and their processing + if element.tagName.endswith(":t"): + continue + + # Remove whitespace-only text nodes and comment nodes + for child in list(element.childNodes): + if ( + child.nodeType == child.TEXT_NODE + and child.nodeValue + and child.nodeValue.strip() == "" + ) or child.nodeType == child.COMMENT_NODE: + element.removeChild(child) + + # Write back the condensed XML + with open(xml_file, "wb") as f: + f.write(dom.toxml(encoding="UTF-8")) + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/pptx/ooxml/scripts/unpack.py b/src/flow/skills/pptx/ooxml/scripts/unpack.py new file mode 100755 index 0000000000000000000000000000000000000000..4938798813e52daf03ab859f3c2faeece7f3aeea --- /dev/null +++ b/src/flow/skills/pptx/ooxml/scripts/unpack.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Unpack and format XML contents of Office files (.docx, .pptx, .xlsx)""" + +import random +import sys +import defusedxml.minidom +import zipfile +from pathlib import Path + +# Get command line arguments +assert len(sys.argv) == 3, "Usage: python unpack.py " +input_file, output_dir = sys.argv[1], sys.argv[2] + +# Extract and format +output_path = Path(output_dir) +output_path.mkdir(parents=True, exist_ok=True) +zipfile.ZipFile(input_file).extractall(output_path) + +# Pretty print all XML files +xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels")) +for xml_file in xml_files: + content = xml_file.read_text(encoding="utf-8") + dom = defusedxml.minidom.parseString(content) + xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="ascii")) + +# For .docx files, suggest an RSID for tracked changes +if input_file.endswith(".docx"): + suggested_rsid = "".join(random.choices("0123456789ABCDEF", k=8)) + print(f"Suggested RSID for edit session: {suggested_rsid}") diff --git a/src/flow/skills/pptx/ooxml/scripts/validate.py b/src/flow/skills/pptx/ooxml/scripts/validate.py new file mode 100755 index 0000000000000000000000000000000000000000..508c5891faba622051a8d5566c118de7892d95bf --- /dev/null +++ b/src/flow/skills/pptx/ooxml/scripts/validate.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +Command line tool to validate Office document XML files against XSD schemas and tracked changes. + +Usage: + python validate.py --original +""" + +import argparse +import sys +from pathlib import Path + +from validation import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator + + +def main(): + parser = argparse.ArgumentParser(description="Validate Office document XML files") + parser.add_argument( + "unpacked_dir", + help="Path to unpacked Office document directory", + ) + parser.add_argument( + "--original", + required=True, + help="Path to original file (.docx/.pptx/.xlsx)", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Enable verbose output", + ) + args = parser.parse_args() + + # Validate paths + unpacked_dir = Path(args.unpacked_dir) + original_file = Path(args.original) + file_extension = original_file.suffix.lower() + assert unpacked_dir.is_dir(), f"Error: {unpacked_dir} is not a directory" + assert original_file.is_file(), f"Error: {original_file} is not a file" + assert file_extension in [".docx", ".pptx", ".xlsx"], ( + f"Error: {original_file} must be a .docx, .pptx, or .xlsx file" + ) + + # Run validations + match file_extension: + case ".docx": + validators = [DOCXSchemaValidator, RedliningValidator] + case ".pptx": + validators = [PPTXSchemaValidator] + case _: + print(f"Error: Validation not supported for file type {file_extension}") + sys.exit(1) + + # Run validators + success = True + for V in validators: + validator = V(unpacked_dir, original_file, verbose=args.verbose) + if not validator.validate(): + success = False + + if success: + print("All validations PASSED!") + + sys.exit(0 if success else 1) + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/pptx/ooxml/scripts/validation/__init__.py b/src/flow/skills/pptx/ooxml/scripts/validation/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..db092ece7e21de98f87bd81471578259a1bc642f --- /dev/null +++ b/src/flow/skills/pptx/ooxml/scripts/validation/__init__.py @@ -0,0 +1,15 @@ +""" +Validation modules for Word document processing. +""" + +from .base import BaseSchemaValidator +from .docx import DOCXSchemaValidator +from .pptx import PPTXSchemaValidator +from .redlining import RedliningValidator + +__all__ = [ + "BaseSchemaValidator", + "DOCXSchemaValidator", + "PPTXSchemaValidator", + "RedliningValidator", +] diff --git a/src/flow/skills/pptx/ooxml/scripts/validation/base.py b/src/flow/skills/pptx/ooxml/scripts/validation/base.py new file mode 100644 index 0000000000000000000000000000000000000000..0681b199c2f0539763eb115596acd82ca9c53bf3 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/scripts/validation/base.py @@ -0,0 +1,951 @@ +""" +Base validator with common validation logic for document files. +""" + +import re +from pathlib import Path + +import lxml.etree + + +class BaseSchemaValidator: + """Base validator with common validation logic for document files.""" + + # Elements whose 'id' attributes must be unique within their file + # Format: element_name -> (attribute_name, scope) + # scope can be 'file' (unique within file) or 'global' (unique across all files) + UNIQUE_ID_REQUIREMENTS = { + # Word elements + "comment": ("id", "file"), # Comment IDs in comments.xml + "commentrangestart": ("id", "file"), # Must match comment IDs + "commentrangeend": ("id", "file"), # Must match comment IDs + "bookmarkstart": ("id", "file"), # Bookmark start IDs + "bookmarkend": ("id", "file"), # Bookmark end IDs + # Note: ins and del (track changes) can share IDs when part of same revision + # PowerPoint elements + "sldid": ("id", "file"), # Slide IDs in presentation.xml + "sldmasterid": ("id", "global"), # Slide master IDs must be globally unique + "sldlayoutid": ("id", "global"), # Slide layout IDs must be globally unique + "cm": ("authorid", "file"), # Comment author IDs + # Excel elements + "sheet": ("sheetid", "file"), # Sheet IDs in workbook.xml + "definedname": ("id", "file"), # Named range IDs + # Drawing/Shape elements (all formats) + "cxnsp": ("id", "file"), # Connection shape IDs + "sp": ("id", "file"), # Shape IDs + "pic": ("id", "file"), # Picture IDs + "grpsp": ("id", "file"), # Group shape IDs + } + + # Mapping of element names to expected relationship types + # Subclasses should override this with format-specific mappings + ELEMENT_RELATIONSHIP_TYPES = {} + + # Unified schema mappings for all Office document types + SCHEMA_MAPPINGS = { + # Document type specific schemas + "word": "ISO-IEC29500-4_2016/wml.xsd", # Word documents + "ppt": "ISO-IEC29500-4_2016/pml.xsd", # PowerPoint presentations + "xl": "ISO-IEC29500-4_2016/sml.xsd", # Excel spreadsheets + # Common file types + "[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd", + "app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd", + "core.xml": "ecma/fouth-edition/opc-coreProperties.xsd", + "custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd", + ".rels": "ecma/fouth-edition/opc-relationships.xsd", + # Word-specific files + "people.xml": "microsoft/wml-2012.xsd", + "commentsIds.xml": "microsoft/wml-cid-2016.xsd", + "commentsExtensible.xml": "microsoft/wml-cex-2018.xsd", + "commentsExtended.xml": "microsoft/wml-2012.xsd", + # Chart files (common across document types) + "chart": "ISO-IEC29500-4_2016/dml-chart.xsd", + # Theme files (common across document types) + "theme": "ISO-IEC29500-4_2016/dml-main.xsd", + # Drawing and media files + "drawing": "ISO-IEC29500-4_2016/dml-main.xsd", + } + + # Unified namespace constants + MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006" + XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" + + # Common OOXML namespaces used across validators + PACKAGE_RELATIONSHIPS_NAMESPACE = ( + "http://schemas.openxmlformats.org/package/2006/relationships" + ) + OFFICE_RELATIONSHIPS_NAMESPACE = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships" + ) + CONTENT_TYPES_NAMESPACE = ( + "http://schemas.openxmlformats.org/package/2006/content-types" + ) + + # Folders where we should clean ignorable namespaces + MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"} + + # All allowed OOXML namespaces (superset of all document types) + OOXML_NAMESPACES = { + "http://schemas.openxmlformats.org/officeDocument/2006/math", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "http://schemas.openxmlformats.org/schemaLibrary/2006/main", + "http://schemas.openxmlformats.org/drawingml/2006/main", + "http://schemas.openxmlformats.org/drawingml/2006/chart", + "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing", + "http://schemas.openxmlformats.org/drawingml/2006/diagram", + "http://schemas.openxmlformats.org/drawingml/2006/picture", + "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "http://schemas.openxmlformats.org/presentationml/2006/main", + "http://schemas.openxmlformats.org/spreadsheetml/2006/main", + "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes", + "http://www.w3.org/XML/1998/namespace", + } + + def __init__(self, unpacked_dir, original_file, verbose=False): + self.unpacked_dir = Path(unpacked_dir).resolve() + self.original_file = Path(original_file) + self.verbose = verbose + + # Set schemas directory + self.schemas_dir = Path(__file__).parent.parent.parent / "schemas" + + # Get all XML and .rels files + patterns = ["*.xml", "*.rels"] + self.xml_files = [ + f for pattern in patterns for f in self.unpacked_dir.rglob(pattern) + ] + + if not self.xml_files: + print(f"Warning: No XML files found in {self.unpacked_dir}") + + def validate(self): + """Run all validation checks and return True if all pass.""" + raise NotImplementedError("Subclasses must implement the validate method") + + def validate_xml(self): + """Validate that all XML files are well-formed.""" + errors = [] + + for xml_file in self.xml_files: + try: + # Try to parse the XML file + lxml.etree.parse(str(xml_file)) + except lxml.etree.XMLSyntaxError as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {e.lineno}: {e.msg}" + ) + except Exception as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Unexpected error: {str(e)}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} XML violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All XML files are well-formed") + return True + + def validate_namespaces(self): + """Validate that namespace prefixes in Ignorable attributes are declared.""" + errors = [] + + for xml_file in self.xml_files: + try: + root = lxml.etree.parse(str(xml_file)).getroot() + declared = set(root.nsmap.keys()) - {None} # Exclude default namespace + + for attr_val in [ + v for k, v in root.attrib.items() if k.endswith("Ignorable") + ]: + undeclared = set(attr_val.split()) - declared + errors.extend( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Namespace '{ns}' in Ignorable but not declared" + for ns in undeclared + ) + except lxml.etree.XMLSyntaxError: + continue + + if errors: + print(f"FAILED - {len(errors)} namespace issues:") + for error in errors: + print(error) + return False + if self.verbose: + print("PASSED - All namespace prefixes properly declared") + return True + + def validate_unique_ids(self): + """Validate that specific IDs are unique according to OOXML requirements.""" + errors = [] + global_ids = {} # Track globally unique IDs across all files + + for xml_file in self.xml_files: + try: + root = lxml.etree.parse(str(xml_file)).getroot() + file_ids = {} # Track IDs that must be unique within this file + + # Remove all mc:AlternateContent elements from the tree + mc_elements = root.xpath( + ".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE} + ) + for elem in mc_elements: + elem.getparent().remove(elem) + + # Now check IDs in the cleaned tree + for elem in root.iter(): + # Get the element name without namespace + tag = ( + elem.tag.split("}")[-1].lower() + if "}" in elem.tag + else elem.tag.lower() + ) + + # Check if this element type has ID uniqueness requirements + if tag in self.UNIQUE_ID_REQUIREMENTS: + attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag] + + # Look for the specified attribute + id_value = None + for attr, value in elem.attrib.items(): + attr_local = ( + attr.split("}")[-1].lower() + if "}" in attr + else attr.lower() + ) + if attr_local == attr_name: + id_value = value + break + + if id_value is not None: + if scope == "global": + # Check global uniqueness + if id_value in global_ids: + prev_file, prev_line, prev_tag = global_ids[ + id_value + ] + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> " + f"already used in {prev_file} at line {prev_line} in <{prev_tag}>" + ) + else: + global_ids[id_value] = ( + xml_file.relative_to(self.unpacked_dir), + elem.sourceline, + tag, + ) + elif scope == "file": + # Check file-level uniqueness + key = (tag, attr_name) + if key not in file_ids: + file_ids[key] = {} + + if id_value in file_ids[key]: + prev_line = file_ids[key][id_value] + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> " + f"(first occurrence at line {prev_line})" + ) + else: + file_ids[key][id_value] = elem.sourceline + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} ID uniqueness violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All required IDs are unique") + return True + + def validate_file_references(self): + """ + Validate that all .rels files properly reference files and that all files are referenced. + """ + errors = [] + + # Find all .rels files + rels_files = list(self.unpacked_dir.rglob("*.rels")) + + if not rels_files: + if self.verbose: + print("PASSED - No .rels files found") + return True + + # Get all files in the unpacked directory (excluding reference files) + all_files = [] + for file_path in self.unpacked_dir.rglob("*"): + if ( + file_path.is_file() + and file_path.name != "[Content_Types].xml" + and not file_path.name.endswith(".rels") + ): # This file is not referenced by .rels + all_files.append(file_path.resolve()) + + # Track all files that are referenced by any .rels file + all_referenced_files = set() + + if self.verbose: + print( + f"Found {len(rels_files)} .rels files and {len(all_files)} target files" + ) + + # Check each .rels file + for rels_file in rels_files: + try: + # Parse relationships file + rels_root = lxml.etree.parse(str(rels_file)).getroot() + + # Get the directory where this .rels file is located + rels_dir = rels_file.parent + + # Find all relationships and their targets + referenced_files = set() + broken_refs = [] + + for rel in rels_root.findall( + ".//ns:Relationship", + namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE}, + ): + target = rel.get("Target") + if target and not target.startswith( + ("http", "mailto:") + ): # Skip external URLs + # Resolve the target path relative to the .rels file location + if rels_file.name == ".rels": + # Root .rels file - targets are relative to unpacked_dir + target_path = self.unpacked_dir / target + else: + # Other .rels files - targets are relative to their parent's parent + # e.g., word/_rels/document.xml.rels -> targets relative to word/ + base_dir = rels_dir.parent + target_path = base_dir / target + + # Normalize the path and check if it exists + try: + target_path = target_path.resolve() + if target_path.exists() and target_path.is_file(): + referenced_files.add(target_path) + all_referenced_files.add(target_path) + else: + broken_refs.append((target, rel.sourceline)) + except (OSError, ValueError): + broken_refs.append((target, rel.sourceline)) + + # Report broken references + if broken_refs: + rel_path = rels_file.relative_to(self.unpacked_dir) + for broken_ref, line_num in broken_refs: + errors.append( + f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}" + ) + + except Exception as e: + rel_path = rels_file.relative_to(self.unpacked_dir) + errors.append(f" Error parsing {rel_path}: {e}") + + # Check for unreferenced files (files that exist but are not referenced anywhere) + unreferenced_files = set(all_files) - all_referenced_files + + if unreferenced_files: + for unref_file in sorted(unreferenced_files): + unref_rel_path = unref_file.relative_to(self.unpacked_dir) + errors.append(f" Unreferenced file: {unref_rel_path}") + + if errors: + print(f"FAILED - Found {len(errors)} relationship validation errors:") + for error in errors: + print(error) + print( + "CRITICAL: These errors will cause the document to appear corrupt. " + + "Broken references MUST be fixed, " + + "and unreferenced files MUST be referenced or removed." + ) + return False + else: + if self.verbose: + print( + "PASSED - All references are valid and all files are properly referenced" + ) + return True + + def validate_all_relationship_ids(self): + """ + Validate that all r:id attributes in XML files reference existing IDs + in their corresponding .rels files, and optionally validate relationship types. + """ + import lxml.etree + + errors = [] + + # Process each XML file that might contain r:id references + for xml_file in self.xml_files: + # Skip .rels files themselves + if xml_file.suffix == ".rels": + continue + + # Determine the corresponding .rels file + # For dir/file.xml, it's dir/_rels/file.xml.rels + rels_dir = xml_file.parent / "_rels" + rels_file = rels_dir / f"{xml_file.name}.rels" + + # Skip if there's no corresponding .rels file (that's okay) + if not rels_file.exists(): + continue + + try: + # Parse the .rels file to get valid relationship IDs and their types + rels_root = lxml.etree.parse(str(rels_file)).getroot() + rid_to_type = {} + + for rel in rels_root.findall( + f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" + ): + rid = rel.get("Id") + rel_type = rel.get("Type", "") + if rid: + # Check for duplicate rIds + if rid in rid_to_type: + rels_rel_path = rels_file.relative_to(self.unpacked_dir) + errors.append( + f" {rels_rel_path}: Line {rel.sourceline}: " + f"Duplicate relationship ID '{rid}' (IDs must be unique)" + ) + # Extract just the type name from the full URL + type_name = ( + rel_type.split("/")[-1] if "/" in rel_type else rel_type + ) + rid_to_type[rid] = type_name + + # Parse the XML file to find all r:id references + xml_root = lxml.etree.parse(str(xml_file)).getroot() + + # Find all elements with r:id attributes + for elem in xml_root.iter(): + # Check for r:id attribute (relationship ID) + rid_attr = elem.get(f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id") + if rid_attr: + xml_rel_path = xml_file.relative_to(self.unpacked_dir) + elem_name = ( + elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag + ) + + # Check if the ID exists + if rid_attr not in rid_to_type: + errors.append( + f" {xml_rel_path}: Line {elem.sourceline}: " + f"<{elem_name}> references non-existent relationship '{rid_attr}' " + f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})" + ) + # Check if we have type expectations for this element + elif self.ELEMENT_RELATIONSHIP_TYPES: + expected_type = self._get_expected_relationship_type( + elem_name + ) + if expected_type: + actual_type = rid_to_type[rid_attr] + # Check if the actual type matches or contains the expected type + if expected_type not in actual_type.lower(): + errors.append( + f" {xml_rel_path}: Line {elem.sourceline}: " + f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' " + f"but should point to a '{expected_type}' relationship" + ) + + except Exception as e: + xml_rel_path = xml_file.relative_to(self.unpacked_dir) + errors.append(f" Error processing {xml_rel_path}: {e}") + + if errors: + print(f"FAILED - Found {len(errors)} relationship ID reference errors:") + for error in errors: + print(error) + print("\nThese ID mismatches will cause the document to appear corrupt!") + return False + else: + if self.verbose: + print("PASSED - All relationship ID references are valid") + return True + + def _get_expected_relationship_type(self, element_name): + """ + Get the expected relationship type for an element. + First checks the explicit mapping, then tries pattern detection. + """ + # Normalize element name to lowercase + elem_lower = element_name.lower() + + # Check explicit mapping first + if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES: + return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower] + + # Try pattern detection for common patterns + # Pattern 1: Elements ending in "Id" often expect a relationship of the prefix type + if elem_lower.endswith("id") and len(elem_lower) > 2: + # e.g., "sldId" -> "sld", "sldMasterId" -> "sldMaster" + prefix = elem_lower[:-2] # Remove "id" + # Check if this might be a compound like "sldMasterId" + if prefix.endswith("master"): + return prefix.lower() + elif prefix.endswith("layout"): + return prefix.lower() + else: + # Simple case like "sldId" -> "slide" + # Common transformations + if prefix == "sld": + return "slide" + return prefix.lower() + + # Pattern 2: Elements ending in "Reference" expect a relationship of the prefix type + if elem_lower.endswith("reference") and len(elem_lower) > 9: + prefix = elem_lower[:-9] # Remove "reference" + return prefix.lower() + + return None + + def validate_content_types(self): + """Validate that all content files are properly declared in [Content_Types].xml.""" + errors = [] + + # Find [Content_Types].xml file + content_types_file = self.unpacked_dir / "[Content_Types].xml" + if not content_types_file.exists(): + print("FAILED - [Content_Types].xml file not found") + return False + + try: + # Parse and get all declared parts and extensions + root = lxml.etree.parse(str(content_types_file)).getroot() + declared_parts = set() + declared_extensions = set() + + # Get Override declarations (specific files) + for override in root.findall( + f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override" + ): + part_name = override.get("PartName") + if part_name is not None: + declared_parts.add(part_name.lstrip("/")) + + # Get Default declarations (by extension) + for default in root.findall( + f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default" + ): + extension = default.get("Extension") + if extension is not None: + declared_extensions.add(extension.lower()) + + # Root elements that require content type declaration + declarable_roots = { + "sld", + "sldLayout", + "sldMaster", + "presentation", # PowerPoint + "document", # Word + "workbook", + "worksheet", # Excel + "theme", # Common + } + + # Common media file extensions that should be declared + media_extensions = { + "png": "image/png", + "jpg": "image/jpeg", + "jpeg": "image/jpeg", + "gif": "image/gif", + "bmp": "image/bmp", + "tiff": "image/tiff", + "wmf": "image/x-wmf", + "emf": "image/x-emf", + } + + # Get all files in the unpacked directory + all_files = list(self.unpacked_dir.rglob("*")) + all_files = [f for f in all_files if f.is_file()] + + # Check all XML files for Override declarations + for xml_file in self.xml_files: + path_str = str(xml_file.relative_to(self.unpacked_dir)).replace( + "\\", "/" + ) + + # Skip non-content files + if any( + skip in path_str + for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"] + ): + continue + + try: + root_tag = lxml.etree.parse(str(xml_file)).getroot().tag + root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag + + if root_name in declarable_roots and path_str not in declared_parts: + errors.append( + f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml" + ) + + except Exception: + continue # Skip unparseable files + + # Check all non-XML files for Default extension declarations + for file_path in all_files: + # Skip XML files and metadata files (already checked above) + if file_path.suffix.lower() in {".xml", ".rels"}: + continue + if file_path.name == "[Content_Types].xml": + continue + if "_rels" in file_path.parts or "docProps" in file_path.parts: + continue + + extension = file_path.suffix.lstrip(".").lower() + if extension and extension not in declared_extensions: + # Check if it's a known media extension that should be declared + if extension in media_extensions: + relative_path = file_path.relative_to(self.unpacked_dir) + errors.append( + f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: ' + ) + + except Exception as e: + errors.append(f" Error parsing [Content_Types].xml: {e}") + + if errors: + print(f"FAILED - Found {len(errors)} content type declaration errors:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print( + "PASSED - All content files are properly declared in [Content_Types].xml" + ) + return True + + def validate_file_against_xsd(self, xml_file, verbose=False): + """Validate a single XML file against XSD schema, comparing with original. + + Args: + xml_file: Path to XML file to validate + verbose: Enable verbose output + + Returns: + tuple: (is_valid, new_errors_set) where is_valid is True/False/None (skipped) + """ + # Resolve both paths to handle symlinks + xml_file = Path(xml_file).resolve() + unpacked_dir = self.unpacked_dir.resolve() + + # Validate current file + is_valid, current_errors = self._validate_single_file_xsd( + xml_file, unpacked_dir + ) + + if is_valid is None: + return None, set() # Skipped + elif is_valid: + return True, set() # Valid, no errors + + # Get errors from original file for this specific file + original_errors = self._get_original_file_errors(xml_file) + + # Compare with original (both are guaranteed to be sets here) + assert current_errors is not None + new_errors = current_errors - original_errors + + if new_errors: + if verbose: + relative_path = xml_file.relative_to(unpacked_dir) + print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)") + for error in list(new_errors)[:3]: + truncated = error[:250] + "..." if len(error) > 250 else error + print(f" - {truncated}") + return False, new_errors + else: + # All errors existed in original + if verbose: + print( + f"PASSED - No new errors (original had {len(current_errors)} errors)" + ) + return True, set() + + def validate_against_xsd(self): + """Validate XML files against XSD schemas, showing only new errors compared to original.""" + new_errors = [] + original_error_count = 0 + valid_count = 0 + skipped_count = 0 + + for xml_file in self.xml_files: + relative_path = str(xml_file.relative_to(self.unpacked_dir)) + is_valid, new_file_errors = self.validate_file_against_xsd( + xml_file, verbose=False + ) + + if is_valid is None: + skipped_count += 1 + continue + elif is_valid and not new_file_errors: + valid_count += 1 + continue + elif is_valid: + # Had errors but all existed in original + original_error_count += 1 + valid_count += 1 + continue + + # Has new errors + new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)") + for error in list(new_file_errors)[:3]: # Show first 3 errors + new_errors.append( + f" - {error[:250]}..." if len(error) > 250 else f" - {error}" + ) + + # Print summary + if self.verbose: + print(f"Validated {len(self.xml_files)} files:") + print(f" - Valid: {valid_count}") + print(f" - Skipped (no schema): {skipped_count}") + if original_error_count: + print(f" - With original errors (ignored): {original_error_count}") + print( + f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}" + ) + + if new_errors: + print("\nFAILED - Found NEW validation errors:") + for error in new_errors: + print(error) + return False + else: + if self.verbose: + print("\nPASSED - No new XSD validation errors introduced") + return True + + def _get_schema_path(self, xml_file): + """Determine the appropriate schema path for an XML file.""" + # Check exact filename match + if xml_file.name in self.SCHEMA_MAPPINGS: + return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name] + + # Check .rels files + if xml_file.suffix == ".rels": + return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"] + + # Check chart files + if "charts/" in str(xml_file) and xml_file.name.startswith("chart"): + return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"] + + # Check theme files + if "theme/" in str(xml_file) and xml_file.name.startswith("theme"): + return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"] + + # Check if file is in a main content folder and use appropriate schema + if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS: + return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name] + + return None + + def _clean_ignorable_namespaces(self, xml_doc): + """Remove attributes and elements not in allowed namespaces.""" + # Create a clean copy + xml_string = lxml.etree.tostring(xml_doc, encoding="unicode") + xml_copy = lxml.etree.fromstring(xml_string) + + # Remove attributes not in allowed namespaces + for elem in xml_copy.iter(): + attrs_to_remove = [] + + for attr in elem.attrib: + # Check if attribute is from a namespace other than allowed ones + if "{" in attr: + ns = attr.split("}")[0][1:] + if ns not in self.OOXML_NAMESPACES: + attrs_to_remove.append(attr) + + # Remove collected attributes + for attr in attrs_to_remove: + del elem.attrib[attr] + + # Remove elements not in allowed namespaces + self._remove_ignorable_elements(xml_copy) + + return lxml.etree.ElementTree(xml_copy) + + def _remove_ignorable_elements(self, root): + """Recursively remove all elements not in allowed namespaces.""" + elements_to_remove = [] + + # Find elements to remove + for elem in list(root): + # Skip non-element nodes (comments, processing instructions, etc.) + if not hasattr(elem, "tag") or callable(elem.tag): + continue + + tag_str = str(elem.tag) + if tag_str.startswith("{"): + ns = tag_str.split("}")[0][1:] + if ns not in self.OOXML_NAMESPACES: + elements_to_remove.append(elem) + continue + + # Recursively clean child elements + self._remove_ignorable_elements(elem) + + # Remove collected elements + for elem in elements_to_remove: + root.remove(elem) + + def _preprocess_for_mc_ignorable(self, xml_doc): + """Preprocess XML to handle mc:Ignorable attribute properly.""" + # Remove mc:Ignorable attributes before validation + root = xml_doc.getroot() + + # Remove mc:Ignorable attribute from root + if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib: + del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"] + + return xml_doc + + def _validate_single_file_xsd(self, xml_file, base_path): + """Validate a single XML file against XSD schema. Returns (is_valid, errors_set).""" + schema_path = self._get_schema_path(xml_file) + if not schema_path: + return None, None # Skip file + + try: + # Load schema + with open(schema_path, "rb") as xsd_file: + parser = lxml.etree.XMLParser() + xsd_doc = lxml.etree.parse( + xsd_file, parser=parser, base_url=str(schema_path) + ) + schema = lxml.etree.XMLSchema(xsd_doc) + + # Load and preprocess XML + with open(xml_file, "r") as f: + xml_doc = lxml.etree.parse(f) + + xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc) + xml_doc = self._preprocess_for_mc_ignorable(xml_doc) + + # Clean ignorable namespaces if needed + relative_path = xml_file.relative_to(base_path) + if ( + relative_path.parts + and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS + ): + xml_doc = self._clean_ignorable_namespaces(xml_doc) + + # Validate + if schema.validate(xml_doc): + return True, set() + else: + errors = set() + for error in schema.error_log: + # Store normalized error message (without line numbers for comparison) + errors.add(error.message) + return False, errors + + except Exception as e: + return False, {str(e)} + + def _get_original_file_errors(self, xml_file): + """Get XSD validation errors from a single file in the original document. + + Args: + xml_file: Path to the XML file in unpacked_dir to check + + Returns: + set: Set of error messages from the original file + """ + import tempfile + import zipfile + + # Resolve both paths to handle symlinks (e.g., /var vs /private/var on macOS) + xml_file = Path(xml_file).resolve() + unpacked_dir = self.unpacked_dir.resolve() + relative_path = xml_file.relative_to(unpacked_dir) + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Extract original file + with zipfile.ZipFile(self.original_file, "r") as zip_ref: + zip_ref.extractall(temp_path) + + # Find corresponding file in original + original_xml_file = temp_path / relative_path + + if not original_xml_file.exists(): + # File didn't exist in original, so no original errors + return set() + + # Validate the specific file in original + is_valid, errors = self._validate_single_file_xsd( + original_xml_file, temp_path + ) + return errors if errors else set() + + def _remove_template_tags_from_text_nodes(self, xml_doc): + """Remove template tags from XML text nodes and collect warnings. + + Template tags follow the pattern {{ ... }} and are used as placeholders + for content replacement. They should be removed from text content before + XSD validation while preserving XML structure. + + Returns: + tuple: (cleaned_xml_doc, warnings_list) + """ + warnings = [] + template_pattern = re.compile(r"\{\{[^}]*\}\}") + + # Create a copy of the document to avoid modifying the original + xml_string = lxml.etree.tostring(xml_doc, encoding="unicode") + xml_copy = lxml.etree.fromstring(xml_string) + + def process_text_content(text, content_type): + if not text: + return text + matches = list(template_pattern.finditer(text)) + if matches: + for match in matches: + warnings.append( + f"Found template tag in {content_type}: {match.group()}" + ) + return template_pattern.sub("", text) + return text + + # Process all text nodes in the document + for elem in xml_copy.iter(): + # Skip processing if this is a w:t element + if not hasattr(elem, "tag") or callable(elem.tag): + continue + tag_str = str(elem.tag) + if tag_str.endswith("}t") or tag_str == "t": + continue + + elem.text = process_text_content(elem.text, "text content") + elem.tail = process_text_content(elem.tail, "tail content") + + return lxml.etree.ElementTree(xml_copy), warnings + + +if __name__ == "__main__": + raise RuntimeError("This module should not be run directly.") diff --git a/src/flow/skills/pptx/ooxml/scripts/validation/docx.py b/src/flow/skills/pptx/ooxml/scripts/validation/docx.py new file mode 100644 index 0000000000000000000000000000000000000000..602c47087ada8d5a10e90abc864f1225abcb2345 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/scripts/validation/docx.py @@ -0,0 +1,274 @@ +""" +Validator for Word document XML files against XSD schemas. +""" + +import re +import tempfile +import zipfile + +import lxml.etree + +from .base import BaseSchemaValidator + + +class DOCXSchemaValidator(BaseSchemaValidator): + """Validator for Word document XML files against XSD schemas.""" + + # Word-specific namespace + WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" + + # Word-specific element to relationship type mappings + # Start with empty mapping - add specific cases as we discover them + ELEMENT_RELATIONSHIP_TYPES = {} + + def validate(self): + """Run all validation checks and return True if all pass.""" + # Test 0: XML well-formedness + if not self.validate_xml(): + return False + + # Test 1: Namespace declarations + all_valid = True + if not self.validate_namespaces(): + all_valid = False + + # Test 2: Unique IDs + if not self.validate_unique_ids(): + all_valid = False + + # Test 3: Relationship and file reference validation + if not self.validate_file_references(): + all_valid = False + + # Test 4: Content type declarations + if not self.validate_content_types(): + all_valid = False + + # Test 5: XSD schema validation + if not self.validate_against_xsd(): + all_valid = False + + # Test 6: Whitespace preservation + if not self.validate_whitespace_preservation(): + all_valid = False + + # Test 7: Deletion validation + if not self.validate_deletions(): + all_valid = False + + # Test 8: Insertion validation + if not self.validate_insertions(): + all_valid = False + + # Test 9: Relationship ID reference validation + if not self.validate_all_relationship_ids(): + all_valid = False + + # Count and compare paragraphs + self.compare_paragraph_counts() + + return all_valid + + def validate_whitespace_preservation(self): + """ + Validate that w:t elements with whitespace have xml:space='preserve'. + """ + errors = [] + + for xml_file in self.xml_files: + # Only check document.xml files + if xml_file.name != "document.xml": + continue + + try: + root = lxml.etree.parse(str(xml_file)).getroot() + + # Find all w:t elements + for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"): + if elem.text: + text = elem.text + # Check if text starts or ends with whitespace + if re.match(r"^\s.*", text) or re.match(r".*\s$", text): + # Check if xml:space="preserve" attribute exists + xml_space_attr = f"{{{self.XML_NAMESPACE}}}space" + if ( + xml_space_attr not in elem.attrib + or elem.attrib[xml_space_attr] != "preserve" + ): + # Show a preview of the text + text_preview = ( + repr(text)[:50] + "..." + if len(repr(text)) > 50 + else repr(text) + ) + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} whitespace preservation violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All whitespace is properly preserved") + return True + + def validate_deletions(self): + """ + Validate that w:t elements are not within w:del elements. + For some reason, XSD validation does not catch this, so we do it manually. + """ + errors = [] + + for xml_file in self.xml_files: + # Only check document.xml files + if xml_file.name != "document.xml": + continue + + try: + root = lxml.etree.parse(str(xml_file)).getroot() + + # Find all w:t elements that are descendants of w:del elements + namespaces = {"w": self.WORD_2006_NAMESPACE} + xpath_expression = ".//w:del//w:t" + problematic_t_elements = root.xpath( + xpath_expression, namespaces=namespaces + ) + for t_elem in problematic_t_elements: + if t_elem.text: + # Show a preview of the text + text_preview = ( + repr(t_elem.text)[:50] + "..." + if len(repr(t_elem.text)) > 50 + else repr(t_elem.text) + ) + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {t_elem.sourceline}: found within : {text_preview}" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} deletion validation violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - No w:t elements found within w:del elements") + return True + + def count_paragraphs_in_unpacked(self): + """Count the number of paragraphs in the unpacked document.""" + count = 0 + + for xml_file in self.xml_files: + # Only check document.xml files + if xml_file.name != "document.xml": + continue + + try: + root = lxml.etree.parse(str(xml_file)).getroot() + # Count all w:p elements + paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p") + count = len(paragraphs) + except Exception as e: + print(f"Error counting paragraphs in unpacked document: {e}") + + return count + + def count_paragraphs_in_original(self): + """Count the number of paragraphs in the original docx file.""" + count = 0 + + try: + # Create temporary directory to unpack original + with tempfile.TemporaryDirectory() as temp_dir: + # Unpack original docx + with zipfile.ZipFile(self.original_file, "r") as zip_ref: + zip_ref.extractall(temp_dir) + + # Parse document.xml + doc_xml_path = temp_dir + "/word/document.xml" + root = lxml.etree.parse(doc_xml_path).getroot() + + # Count all w:p elements + paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p") + count = len(paragraphs) + + except Exception as e: + print(f"Error counting paragraphs in original document: {e}") + + return count + + def validate_insertions(self): + """ + Validate that w:delText elements are not within w:ins elements. + w:delText is only allowed in w:ins if nested within a w:del. + """ + errors = [] + + for xml_file in self.xml_files: + if xml_file.name != "document.xml": + continue + + try: + root = lxml.etree.parse(str(xml_file)).getroot() + namespaces = {"w": self.WORD_2006_NAMESPACE} + + # Find w:delText in w:ins that are NOT within w:del + invalid_elements = root.xpath( + ".//w:ins//w:delText[not(ancestor::w:del)]", + namespaces=namespaces + ) + + for elem in invalid_elements: + text_preview = ( + repr(elem.text or "")[:50] + "..." + if len(repr(elem.text or "")) > 50 + else repr(elem.text or "") + ) + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: within : {text_preview}" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} insertion validation violations:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - No w:delText elements within w:ins elements") + return True + + def compare_paragraph_counts(self): + """Compare paragraph counts between original and new document.""" + original_count = self.count_paragraphs_in_original() + new_count = self.count_paragraphs_in_unpacked() + + diff = new_count - original_count + diff_str = f"+{diff}" if diff > 0 else str(diff) + print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})") + + +if __name__ == "__main__": + raise RuntimeError("This module should not be run directly.") diff --git a/src/flow/skills/pptx/ooxml/scripts/validation/pptx.py b/src/flow/skills/pptx/ooxml/scripts/validation/pptx.py new file mode 100644 index 0000000000000000000000000000000000000000..66d5b1e2dba6d1b3dbcb290fa6ef1699980435e6 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/scripts/validation/pptx.py @@ -0,0 +1,315 @@ +""" +Validator for PowerPoint presentation XML files against XSD schemas. +""" + +import re + +from .base import BaseSchemaValidator + + +class PPTXSchemaValidator(BaseSchemaValidator): + """Validator for PowerPoint presentation XML files against XSD schemas.""" + + # PowerPoint presentation namespace + PRESENTATIONML_NAMESPACE = ( + "http://schemas.openxmlformats.org/presentationml/2006/main" + ) + + # PowerPoint-specific element to relationship type mappings + ELEMENT_RELATIONSHIP_TYPES = { + "sldid": "slide", + "sldmasterid": "slidemaster", + "notesmasterid": "notesmaster", + "sldlayoutid": "slidelayout", + "themeid": "theme", + "tablestyleid": "tablestyles", + } + + def validate(self): + """Run all validation checks and return True if all pass.""" + # Test 0: XML well-formedness + if not self.validate_xml(): + return False + + # Test 1: Namespace declarations + all_valid = True + if not self.validate_namespaces(): + all_valid = False + + # Test 2: Unique IDs + if not self.validate_unique_ids(): + all_valid = False + + # Test 3: UUID ID validation + if not self.validate_uuid_ids(): + all_valid = False + + # Test 4: Relationship and file reference validation + if not self.validate_file_references(): + all_valid = False + + # Test 5: Slide layout ID validation + if not self.validate_slide_layout_ids(): + all_valid = False + + # Test 6: Content type declarations + if not self.validate_content_types(): + all_valid = False + + # Test 7: XSD schema validation + if not self.validate_against_xsd(): + all_valid = False + + # Test 8: Notes slide reference validation + if not self.validate_notes_slide_references(): + all_valid = False + + # Test 9: Relationship ID reference validation + if not self.validate_all_relationship_ids(): + all_valid = False + + # Test 10: Duplicate slide layout references validation + if not self.validate_no_duplicate_slide_layouts(): + all_valid = False + + return all_valid + + def validate_uuid_ids(self): + """Validate that ID attributes that look like UUIDs contain only hex values.""" + import lxml.etree + + errors = [] + # UUID pattern: 8-4-4-4-12 hex digits with optional braces/hyphens + uuid_pattern = re.compile( + r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$" + ) + + for xml_file in self.xml_files: + try: + root = lxml.etree.parse(str(xml_file)).getroot() + + # Check all elements for ID attributes + for elem in root.iter(): + for attr, value in elem.attrib.items(): + # Check if this is an ID attribute + attr_name = attr.split("}")[-1].lower() + if attr_name == "id" or attr_name.endswith("id"): + # Check if value looks like a UUID (has the right length and pattern structure) + if self._looks_like_uuid(value): + # Validate that it contains only hex characters in the right positions + if not uuid_pattern.match(value): + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: " + f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} UUID ID validation errors:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All UUID-like IDs contain valid hex values") + return True + + def _looks_like_uuid(self, value): + """Check if a value has the general structure of a UUID.""" + # Remove common UUID delimiters + clean_value = value.strip("{}()").replace("-", "") + # Check if it's 32 hex-like characters (could include invalid hex chars) + return len(clean_value) == 32 and all(c.isalnum() for c in clean_value) + + def validate_slide_layout_ids(self): + """Validate that sldLayoutId elements in slide masters reference valid slide layouts.""" + import lxml.etree + + errors = [] + + # Find all slide master files + slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml")) + + if not slide_masters: + if self.verbose: + print("PASSED - No slide masters found") + return True + + for slide_master in slide_masters: + try: + # Parse the slide master file + root = lxml.etree.parse(str(slide_master)).getroot() + + # Find the corresponding _rels file for this slide master + rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels" + + if not rels_file.exists(): + errors.append( + f" {slide_master.relative_to(self.unpacked_dir)}: " + f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}" + ) + continue + + # Parse the relationships file + rels_root = lxml.etree.parse(str(rels_file)).getroot() + + # Build a set of valid relationship IDs that point to slide layouts + valid_layout_rids = set() + for rel in rels_root.findall( + f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" + ): + rel_type = rel.get("Type", "") + if "slideLayout" in rel_type: + valid_layout_rids.add(rel.get("Id")) + + # Find all sldLayoutId elements in the slide master + for sld_layout_id in root.findall( + f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId" + ): + r_id = sld_layout_id.get( + f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id" + ) + layout_id = sld_layout_id.get("id") + + if r_id and r_id not in valid_layout_rids: + errors.append( + f" {slide_master.relative_to(self.unpacked_dir)}: " + f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' " + f"references r:id='{r_id}' which is not found in slide layout relationships" + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print(f"FAILED - Found {len(errors)} slide layout ID validation errors:") + for error in errors: + print(error) + print( + "Remove invalid references or add missing slide layouts to the relationships file." + ) + return False + else: + if self.verbose: + print("PASSED - All slide layout IDs reference valid slide layouts") + return True + + def validate_no_duplicate_slide_layouts(self): + """Validate that each slide has exactly one slideLayout reference.""" + import lxml.etree + + errors = [] + slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels")) + + for rels_file in slide_rels_files: + try: + root = lxml.etree.parse(str(rels_file)).getroot() + + # Find all slideLayout relationships + layout_rels = [ + rel + for rel in root.findall( + f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" + ) + if "slideLayout" in rel.get("Type", "") + ] + + if len(layout_rels) > 1: + errors.append( + f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references" + ) + + except Exception as e: + errors.append( + f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + if errors: + print("FAILED - Found slides with duplicate slideLayout references:") + for error in errors: + print(error) + return False + else: + if self.verbose: + print("PASSED - All slides have exactly one slideLayout reference") + return True + + def validate_notes_slide_references(self): + """Validate that each notesSlide file is referenced by only one slide.""" + import lxml.etree + + errors = [] + notes_slide_references = {} # Track which slides reference each notesSlide + + # Find all slide relationship files + slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels")) + + if not slide_rels_files: + if self.verbose: + print("PASSED - No slide relationship files found") + return True + + for rels_file in slide_rels_files: + try: + # Parse the relationships file + root = lxml.etree.parse(str(rels_file)).getroot() + + # Find all notesSlide relationships + for rel in root.findall( + f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" + ): + rel_type = rel.get("Type", "") + if "notesSlide" in rel_type: + target = rel.get("Target", "") + if target: + # Normalize the target path to handle relative paths + normalized_target = target.replace("../", "") + + # Track which slide references this notesSlide + slide_name = rels_file.stem.replace( + ".xml", "" + ) # e.g., "slide1" + + if normalized_target not in notes_slide_references: + notes_slide_references[normalized_target] = [] + notes_slide_references[normalized_target].append( + (slide_name, rels_file) + ) + + except (lxml.etree.XMLSyntaxError, Exception) as e: + errors.append( + f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}" + ) + + # Check for duplicate references + for target, references in notes_slide_references.items(): + if len(references) > 1: + slide_names = [ref[0] for ref in references] + errors.append( + f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}" + ) + for slide_name, rels_file in references: + errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}") + + if errors: + print( + f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:" + ) + for error in errors: + print(error) + print("Each slide may optionally have its own slide file.") + return False + else: + if self.verbose: + print("PASSED - All notes slide references are unique") + return True + + +if __name__ == "__main__": + raise RuntimeError("This module should not be run directly.") diff --git a/src/flow/skills/pptx/ooxml/scripts/validation/redlining.py b/src/flow/skills/pptx/ooxml/scripts/validation/redlining.py new file mode 100644 index 0000000000000000000000000000000000000000..7ed425edf5336306b9eec87164e034d87decebb8 --- /dev/null +++ b/src/flow/skills/pptx/ooxml/scripts/validation/redlining.py @@ -0,0 +1,279 @@ +""" +Validator for tracked changes in Word documents. +""" + +import subprocess +import tempfile +import zipfile +from pathlib import Path + + +class RedliningValidator: + """Validator for tracked changes in Word documents.""" + + def __init__(self, unpacked_dir, original_docx, verbose=False): + self.unpacked_dir = Path(unpacked_dir) + self.original_docx = Path(original_docx) + self.verbose = verbose + self.namespaces = { + "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main" + } + + def validate(self): + """Main validation method that returns True if valid, False otherwise.""" + # Verify unpacked directory exists and has correct structure + modified_file = self.unpacked_dir / "word" / "document.xml" + if not modified_file.exists(): + print(f"FAILED - Modified document.xml not found at {modified_file}") + return False + + # First, check if there are any tracked changes by Claude to validate + try: + import xml.etree.ElementTree as ET + + tree = ET.parse(modified_file) + root = tree.getroot() + + # Check for w:del or w:ins tags authored by Claude + del_elements = root.findall(".//w:del", self.namespaces) + ins_elements = root.findall(".//w:ins", self.namespaces) + + # Filter to only include changes by Claude + claude_del_elements = [ + elem + for elem in del_elements + if elem.get(f"{{{self.namespaces['w']}}}author") == "Claude" + ] + claude_ins_elements = [ + elem + for elem in ins_elements + if elem.get(f"{{{self.namespaces['w']}}}author") == "Claude" + ] + + # Redlining validation is only needed if tracked changes by Claude have been used. + if not claude_del_elements and not claude_ins_elements: + if self.verbose: + print("PASSED - No tracked changes by Claude found.") + return True + + except Exception: + # If we can't parse the XML, continue with full validation + pass + + # Create temporary directory for unpacking original docx + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Unpack original docx + try: + with zipfile.ZipFile(self.original_docx, "r") as zip_ref: + zip_ref.extractall(temp_path) + except Exception as e: + print(f"FAILED - Error unpacking original docx: {e}") + return False + + original_file = temp_path / "word" / "document.xml" + if not original_file.exists(): + print( + f"FAILED - Original document.xml not found in {self.original_docx}" + ) + return False + + # Parse both XML files using xml.etree.ElementTree for redlining validation + try: + import xml.etree.ElementTree as ET + + modified_tree = ET.parse(modified_file) + modified_root = modified_tree.getroot() + original_tree = ET.parse(original_file) + original_root = original_tree.getroot() + except ET.ParseError as e: + print(f"FAILED - Error parsing XML files: {e}") + return False + + # Remove Claude's tracked changes from both documents + self._remove_claude_tracked_changes(original_root) + self._remove_claude_tracked_changes(modified_root) + + # Extract and compare text content + modified_text = self._extract_text_content(modified_root) + original_text = self._extract_text_content(original_root) + + if modified_text != original_text: + # Show detailed character-level differences for each paragraph + error_message = self._generate_detailed_diff( + original_text, modified_text + ) + print(error_message) + return False + + if self.verbose: + print("PASSED - All changes by Claude are properly tracked") + return True + + def _generate_detailed_diff(self, original_text, modified_text): + """Generate detailed word-level differences using git word diff.""" + error_parts = [ + "FAILED - Document text doesn't match after removing Claude's tracked changes", + "", + "Likely causes:", + " 1. Modified text inside another author's or tags", + " 2. Made edits without proper tracked changes", + " 3. Didn't nest inside when deleting another's insertion", + "", + "For pre-redlined documents, use correct patterns:", + " - To reject another's INSERTION: Nest inside their ", + " - To restore another's DELETION: Add new AFTER their ", + "", + ] + + # Show git word diff + git_diff = self._get_git_word_diff(original_text, modified_text) + if git_diff: + error_parts.extend(["Differences:", "============", git_diff]) + else: + error_parts.append("Unable to generate word diff (git not available)") + + return "\n".join(error_parts) + + def _get_git_word_diff(self, original_text, modified_text): + """Generate word diff using git with character-level precision.""" + try: + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create two files + original_file = temp_path / "original.txt" + modified_file = temp_path / "modified.txt" + + original_file.write_text(original_text, encoding="utf-8") + modified_file.write_text(modified_text, encoding="utf-8") + + # Try character-level diff first for precise differences + result = subprocess.run( + [ + "git", + "diff", + "--word-diff=plain", + "--word-diff-regex=.", # Character-by-character diff + "-U0", # Zero lines of context - show only changed lines + "--no-index", + str(original_file), + str(modified_file), + ], + capture_output=True, + text=True, + ) + + if result.stdout.strip(): + # Clean up the output - remove git diff header lines + lines = result.stdout.split("\n") + # Skip the header lines (diff --git, index, +++, ---, @@) + content_lines = [] + in_content = False + for line in lines: + if line.startswith("@@"): + in_content = True + continue + if in_content and line.strip(): + content_lines.append(line) + + if content_lines: + return "\n".join(content_lines) + + # Fallback to word-level diff if character-level is too verbose + result = subprocess.run( + [ + "git", + "diff", + "--word-diff=plain", + "-U0", # Zero lines of context + "--no-index", + str(original_file), + str(modified_file), + ], + capture_output=True, + text=True, + ) + + if result.stdout.strip(): + lines = result.stdout.split("\n") + content_lines = [] + in_content = False + for line in lines: + if line.startswith("@@"): + in_content = True + continue + if in_content and line.strip(): + content_lines.append(line) + return "\n".join(content_lines) + + except (subprocess.CalledProcessError, FileNotFoundError, Exception): + # Git not available or other error, return None to use fallback + pass + + return None + + def _remove_claude_tracked_changes(self, root): + """Remove tracked changes authored by Claude from the XML root.""" + ins_tag = f"{{{self.namespaces['w']}}}ins" + del_tag = f"{{{self.namespaces['w']}}}del" + author_attr = f"{{{self.namespaces['w']}}}author" + + # Remove w:ins elements + for parent in root.iter(): + to_remove = [] + for child in parent: + if child.tag == ins_tag and child.get(author_attr) == "Claude": + to_remove.append(child) + for elem in to_remove: + parent.remove(elem) + + # Unwrap content in w:del elements where author is "Claude" + deltext_tag = f"{{{self.namespaces['w']}}}delText" + t_tag = f"{{{self.namespaces['w']}}}t" + + for parent in root.iter(): + to_process = [] + for child in parent: + if child.tag == del_tag and child.get(author_attr) == "Claude": + to_process.append((child, list(parent).index(child))) + + # Process in reverse order to maintain indices + for del_elem, del_index in reversed(to_process): + # Convert w:delText to w:t before moving + for elem in del_elem.iter(): + if elem.tag == deltext_tag: + elem.tag = t_tag + + # Move all children of w:del to its parent before removing w:del + for child in reversed(list(del_elem)): + parent.insert(del_index, child) + parent.remove(del_elem) + + def _extract_text_content(self, root): + """Extract text content from Word XML, preserving paragraph structure. + + Empty paragraphs are skipped to avoid false positives when tracked + insertions add only structural elements without text content. + """ + p_tag = f"{{{self.namespaces['w']}}}p" + t_tag = f"{{{self.namespaces['w']}}}t" + + paragraphs = [] + for p_elem in root.findall(f".//{p_tag}"): + # Get all text elements within this paragraph + text_parts = [] + for t_elem in p_elem.findall(f".//{t_tag}"): + if t_elem.text: + text_parts.append(t_elem.text) + paragraph_text = "".join(text_parts) + # Skip empty paragraphs - they don't affect content validation + if paragraph_text: + paragraphs.append(paragraph_text) + + return "\n".join(paragraphs) + + +if __name__ == "__main__": + raise RuntimeError("This module should not be run directly.") diff --git a/src/flow/skills/pptx/scripts/html2pptx.js b/src/flow/skills/pptx/scripts/html2pptx.js new file mode 100755 index 0000000000000000000000000000000000000000..437bf7c533ac155cba23c2ad8f60ebf2bdd3d76c --- /dev/null +++ b/src/flow/skills/pptx/scripts/html2pptx.js @@ -0,0 +1,979 @@ +/** + * html2pptx - Convert HTML slide to pptxgenjs slide with positioned elements + * + * USAGE: + * const pptx = new pptxgen(); + * pptx.layout = 'LAYOUT_16x9'; // Must match HTML body dimensions + * + * const { slide, placeholders } = await html2pptx('slide.html', pptx); + * slide.addChart(pptx.charts.LINE, data, placeholders[0]); + * + * await pptx.writeFile('output.pptx'); + * + * FEATURES: + * - Converts HTML to PowerPoint with accurate positioning + * - Supports text, images, shapes, and bullet lists + * - Extracts placeholder elements (class="placeholder") with positions + * - Handles CSS gradients, borders, and margins + * + * VALIDATION: + * - Uses body width/height from HTML for viewport sizing + * - Throws error if HTML dimensions don't match presentation layout + * - Throws error if content overflows body (with overflow details) + * + * RETURNS: + * { slide, placeholders } where placeholders is an array of { id, x, y, w, h } + */ + +const { chromium } = require('playwright'); +const path = require('path'); +const sharp = require('sharp'); + +const PT_PER_PX = 0.75; +const PX_PER_IN = 96; +const EMU_PER_IN = 914400; + +// Helper: Get body dimensions and check for overflow +async function getBodyDimensions(page) { + const bodyDimensions = await page.evaluate(() => { + const body = document.body; + const style = window.getComputedStyle(body); + + return { + width: parseFloat(style.width), + height: parseFloat(style.height), + scrollWidth: body.scrollWidth, + scrollHeight: body.scrollHeight + }; + }); + + const errors = []; + const widthOverflowPx = Math.max(0, bodyDimensions.scrollWidth - bodyDimensions.width - 1); + const heightOverflowPx = Math.max(0, bodyDimensions.scrollHeight - bodyDimensions.height - 1); + + const widthOverflowPt = widthOverflowPx * PT_PER_PX; + const heightOverflowPt = heightOverflowPx * PT_PER_PX; + + if (widthOverflowPt > 0 || heightOverflowPt > 0) { + const directions = []; + if (widthOverflowPt > 0) directions.push(`${widthOverflowPt.toFixed(1)}pt horizontally`); + if (heightOverflowPt > 0) directions.push(`${heightOverflowPt.toFixed(1)}pt vertically`); + const reminder = heightOverflowPt > 0 ? ' (Remember: leave 0.5" margin at bottom of slide)' : ''; + errors.push(`HTML content overflows body by ${directions.join(' and ')}${reminder}`); + } + + return { ...bodyDimensions, errors }; +} + +// Helper: Validate dimensions match presentation layout +function validateDimensions(bodyDimensions, pres) { + const errors = []; + const widthInches = bodyDimensions.width / PX_PER_IN; + const heightInches = bodyDimensions.height / PX_PER_IN; + + if (pres.presLayout) { + const layoutWidth = pres.presLayout.width / EMU_PER_IN; + const layoutHeight = pres.presLayout.height / EMU_PER_IN; + + if (Math.abs(layoutWidth - widthInches) > 0.1 || Math.abs(layoutHeight - heightInches) > 0.1) { + errors.push( + `HTML dimensions (${widthInches.toFixed(1)}" × ${heightInches.toFixed(1)}") ` + + `don't match presentation layout (${layoutWidth.toFixed(1)}" × ${layoutHeight.toFixed(1)}")` + ); + } + } + return errors; +} + +function validateTextBoxPosition(slideData, bodyDimensions) { + const errors = []; + const slideHeightInches = bodyDimensions.height / PX_PER_IN; + const minBottomMargin = 0.5; // 0.5 inches from bottom + + for (const el of slideData.elements) { + // Check text elements (p, h1-h6, list) + if (['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'list'].includes(el.type)) { + const fontSize = el.style?.fontSize || 0; + const bottomEdge = el.position.y + el.position.h; + const distanceFromBottom = slideHeightInches - bottomEdge; + + if (fontSize > 12 && distanceFromBottom < minBottomMargin) { + const getText = () => { + if (typeof el.text === 'string') return el.text; + if (Array.isArray(el.text)) return el.text.find(t => t.text)?.text || ''; + if (Array.isArray(el.items)) return el.items.find(item => item.text)?.text || ''; + return ''; + }; + const textPrefix = getText().substring(0, 50) + (getText().length > 50 ? '...' : ''); + + errors.push( + `Text box "${textPrefix}" ends too close to bottom edge ` + + `(${distanceFromBottom.toFixed(2)}" from bottom, minimum ${minBottomMargin}" required)` + ); + } + } + } + + return errors; +} + +// Helper: Add background to slide +async function addBackground(slideData, targetSlide, tmpDir) { + if (slideData.background.type === 'image' && slideData.background.path) { + let imagePath = slideData.background.path.startsWith('file://') + ? slideData.background.path.replace('file://', '') + : slideData.background.path; + targetSlide.background = { path: imagePath }; + } else if (slideData.background.type === 'color' && slideData.background.value) { + targetSlide.background = { color: slideData.background.value }; + } +} + +// Helper: Add elements to slide +function addElements(slideData, targetSlide, pres) { + for (const el of slideData.elements) { + if (el.type === 'image') { + let imagePath = el.src.startsWith('file://') ? el.src.replace('file://', '') : el.src; + targetSlide.addImage({ + path: imagePath, + x: el.position.x, + y: el.position.y, + w: el.position.w, + h: el.position.h + }); + } else if (el.type === 'line') { + targetSlide.addShape(pres.ShapeType.line, { + x: el.x1, + y: el.y1, + w: el.x2 - el.x1, + h: el.y2 - el.y1, + line: { color: el.color, width: el.width } + }); + } else if (el.type === 'shape') { + const shapeOptions = { + x: el.position.x, + y: el.position.y, + w: el.position.w, + h: el.position.h, + shape: el.shape.rectRadius > 0 ? pres.ShapeType.roundRect : pres.ShapeType.rect + }; + + if (el.shape.fill) { + shapeOptions.fill = { color: el.shape.fill }; + if (el.shape.transparency != null) shapeOptions.fill.transparency = el.shape.transparency; + } + if (el.shape.line) shapeOptions.line = el.shape.line; + if (el.shape.rectRadius > 0) shapeOptions.rectRadius = el.shape.rectRadius; + if (el.shape.shadow) shapeOptions.shadow = el.shape.shadow; + + targetSlide.addText(el.text || '', shapeOptions); + } else if (el.type === 'list') { + const listOptions = { + x: el.position.x, + y: el.position.y, + w: el.position.w, + h: el.position.h, + fontSize: el.style.fontSize, + fontFace: el.style.fontFace, + color: el.style.color, + align: el.style.align, + valign: 'top', + lineSpacing: el.style.lineSpacing, + paraSpaceBefore: el.style.paraSpaceBefore, + paraSpaceAfter: el.style.paraSpaceAfter, + margin: el.style.margin + }; + if (el.style.margin) listOptions.margin = el.style.margin; + targetSlide.addText(el.items, listOptions); + } else { + // Check if text is single-line (height suggests one line) + const lineHeight = el.style.lineSpacing || el.style.fontSize * 1.2; + const isSingleLine = el.position.h <= lineHeight * 1.5; + + let adjustedX = el.position.x; + let adjustedW = el.position.w; + + // Make single-line text 2% wider to account for underestimate + if (isSingleLine) { + const widthIncrease = el.position.w * 0.02; + const align = el.style.align; + + if (align === 'center') { + // Center: expand both sides + adjustedX = el.position.x - (widthIncrease / 2); + adjustedW = el.position.w + widthIncrease; + } else if (align === 'right') { + // Right: expand to the left + adjustedX = el.position.x - widthIncrease; + adjustedW = el.position.w + widthIncrease; + } else { + // Left (default): expand to the right + adjustedW = el.position.w + widthIncrease; + } + } + + const textOptions = { + x: adjustedX, + y: el.position.y, + w: adjustedW, + h: el.position.h, + fontSize: el.style.fontSize, + fontFace: el.style.fontFace, + color: el.style.color, + bold: el.style.bold, + italic: el.style.italic, + underline: el.style.underline, + valign: 'top', + lineSpacing: el.style.lineSpacing, + paraSpaceBefore: el.style.paraSpaceBefore, + paraSpaceAfter: el.style.paraSpaceAfter, + inset: 0 // Remove default PowerPoint internal padding + }; + + if (el.style.align) textOptions.align = el.style.align; + if (el.style.margin) textOptions.margin = el.style.margin; + if (el.style.rotate !== undefined) textOptions.rotate = el.style.rotate; + if (el.style.transparency !== null && el.style.transparency !== undefined) textOptions.transparency = el.style.transparency; + + targetSlide.addText(el.text, textOptions); + } + } +} + +// Helper: Extract slide data from HTML page +async function extractSlideData(page) { + return await page.evaluate(() => { + const PT_PER_PX = 0.75; + const PX_PER_IN = 96; + + // Fonts that are single-weight and should not have bold applied + // (applying bold causes PowerPoint to use faux bold which makes text wider) + const SINGLE_WEIGHT_FONTS = ['impact']; + + // Helper: Check if a font should skip bold formatting + const shouldSkipBold = (fontFamily) => { + if (!fontFamily) return false; + const normalizedFont = fontFamily.toLowerCase().replace(/['"]/g, '').split(',')[0].trim(); + return SINGLE_WEIGHT_FONTS.includes(normalizedFont); + }; + + // Unit conversion helpers + const pxToInch = (px) => px / PX_PER_IN; + const pxToPoints = (pxStr) => parseFloat(pxStr) * PT_PER_PX; + const rgbToHex = (rgbStr) => { + // Handle transparent backgrounds by defaulting to white + if (rgbStr === 'rgba(0, 0, 0, 0)' || rgbStr === 'transparent') return 'FFFFFF'; + + const match = rgbStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + if (!match) return 'FFFFFF'; + return match.slice(1).map(n => parseInt(n).toString(16).padStart(2, '0')).join(''); + }; + + const extractAlpha = (rgbStr) => { + const match = rgbStr.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/); + if (!match || !match[4]) return null; + const alpha = parseFloat(match[4]); + return Math.round((1 - alpha) * 100); + }; + + const applyTextTransform = (text, textTransform) => { + if (textTransform === 'uppercase') return text.toUpperCase(); + if (textTransform === 'lowercase') return text.toLowerCase(); + if (textTransform === 'capitalize') { + return text.replace(/\b\w/g, c => c.toUpperCase()); + } + return text; + }; + + // Extract rotation angle from CSS transform and writing-mode + const getRotation = (transform, writingMode) => { + let angle = 0; + + // Handle writing-mode first + // PowerPoint: 90° = text rotated 90° clockwise (reads top to bottom, letters upright) + // PowerPoint: 270° = text rotated 270° clockwise (reads bottom to top, letters upright) + if (writingMode === 'vertical-rl') { + // vertical-rl alone = text reads top to bottom = 90° in PowerPoint + angle = 90; + } else if (writingMode === 'vertical-lr') { + // vertical-lr alone = text reads bottom to top = 270° in PowerPoint + angle = 270; + } + + // Then add any transform rotation + if (transform && transform !== 'none') { + // Try to match rotate() function + const rotateMatch = transform.match(/rotate\((-?\d+(?:\.\d+)?)deg\)/); + if (rotateMatch) { + angle += parseFloat(rotateMatch[1]); + } else { + // Browser may compute as matrix - extract rotation from matrix + const matrixMatch = transform.match(/matrix\(([^)]+)\)/); + if (matrixMatch) { + const values = matrixMatch[1].split(',').map(parseFloat); + // matrix(a, b, c, d, e, f) where rotation = atan2(b, a) + const matrixAngle = Math.atan2(values[1], values[0]) * (180 / Math.PI); + angle += Math.round(matrixAngle); + } + } + } + + // Normalize to 0-359 range + angle = angle % 360; + if (angle < 0) angle += 360; + + return angle === 0 ? null : angle; + }; + + // Get position/dimensions accounting for rotation + const getPositionAndSize = (el, rect, rotation) => { + if (rotation === null) { + return { x: rect.left, y: rect.top, w: rect.width, h: rect.height }; + } + + // For 90° or 270° rotations, swap width and height + // because PowerPoint applies rotation to the original (unrotated) box + const isVertical = rotation === 90 || rotation === 270; + + if (isVertical) { + // The browser shows us the rotated dimensions (tall box for vertical text) + // But PowerPoint needs the pre-rotation dimensions (wide box that will be rotated) + // So we swap: browser's height becomes PPT's width, browser's width becomes PPT's height + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + + return { + x: centerX - rect.height / 2, + y: centerY - rect.width / 2, + w: rect.height, + h: rect.width + }; + } + + // For other rotations, use element's offset dimensions + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + return { + x: centerX - el.offsetWidth / 2, + y: centerY - el.offsetHeight / 2, + w: el.offsetWidth, + h: el.offsetHeight + }; + }; + + // Parse CSS box-shadow into PptxGenJS shadow properties + const parseBoxShadow = (boxShadow) => { + if (!boxShadow || boxShadow === 'none') return null; + + // Browser computed style format: "rgba(0, 0, 0, 0.3) 2px 2px 8px 0px [inset]" + // CSS format: "[inset] 2px 2px 8px 0px rgba(0, 0, 0, 0.3)" + + const insetMatch = boxShadow.match(/inset/); + + // IMPORTANT: PptxGenJS/PowerPoint doesn't properly support inset shadows + // Only process outer shadows to avoid file corruption + if (insetMatch) return null; + + // Extract color first (rgba or rgb at start) + const colorMatch = boxShadow.match(/rgba?\([^)]+\)/); + + // Extract numeric values (handles both px and pt units) + const parts = boxShadow.match(/([-\d.]+)(px|pt)/g); + + if (!parts || parts.length < 2) return null; + + const offsetX = parseFloat(parts[0]); + const offsetY = parseFloat(parts[1]); + const blur = parts.length > 2 ? parseFloat(parts[2]) : 0; + + // Calculate angle from offsets (in degrees, 0 = right, 90 = down) + let angle = 0; + if (offsetX !== 0 || offsetY !== 0) { + angle = Math.atan2(offsetY, offsetX) * (180 / Math.PI); + if (angle < 0) angle += 360; + } + + // Calculate offset distance (hypotenuse) + const offset = Math.sqrt(offsetX * offsetX + offsetY * offsetY) * PT_PER_PX; + + // Extract opacity from rgba + let opacity = 0.5; + if (colorMatch) { + const opacityMatch = colorMatch[0].match(/[\d.]+\)$/); + if (opacityMatch) { + opacity = parseFloat(opacityMatch[0].replace(')', '')); + } + } + + return { + type: 'outer', + angle: Math.round(angle), + blur: blur * 0.75, // Convert to points + color: colorMatch ? rgbToHex(colorMatch[0]) : '000000', + offset: offset, + opacity + }; + }; + + // Parse inline formatting tags (, , , , , ) into text runs + const parseInlineFormatting = (element, baseOptions = {}, runs = [], baseTextTransform = (x) => x) => { + let prevNodeIsText = false; + + element.childNodes.forEach((node) => { + let textTransform = baseTextTransform; + + const isText = node.nodeType === Node.TEXT_NODE || node.tagName === 'BR'; + if (isText) { + const text = node.tagName === 'BR' ? '\n' : textTransform(node.textContent.replace(/\s+/g, ' ')); + const prevRun = runs[runs.length - 1]; + if (prevNodeIsText && prevRun) { + prevRun.text += text; + } else { + runs.push({ text, options: { ...baseOptions } }); + } + + } else if (node.nodeType === Node.ELEMENT_NODE && node.textContent.trim()) { + const options = { ...baseOptions }; + const computed = window.getComputedStyle(node); + + // Handle inline elements with computed styles + if (node.tagName === 'SPAN' || node.tagName === 'B' || node.tagName === 'STRONG' || node.tagName === 'I' || node.tagName === 'EM' || node.tagName === 'U') { + const isBold = computed.fontWeight === 'bold' || parseInt(computed.fontWeight) >= 600; + if (isBold && !shouldSkipBold(computed.fontFamily)) options.bold = true; + if (computed.fontStyle === 'italic') options.italic = true; + if (computed.textDecoration && computed.textDecoration.includes('underline')) options.underline = true; + if (computed.color && computed.color !== 'rgb(0, 0, 0)') { + options.color = rgbToHex(computed.color); + const transparency = extractAlpha(computed.color); + if (transparency !== null) options.transparency = transparency; + } + if (computed.fontSize) options.fontSize = pxToPoints(computed.fontSize); + + // Apply text-transform on the span element itself + if (computed.textTransform && computed.textTransform !== 'none') { + const transformStr = computed.textTransform; + textTransform = (text) => applyTextTransform(text, transformStr); + } + + // Validate: Check for margins on inline elements + if (computed.marginLeft && parseFloat(computed.marginLeft) > 0) { + errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-left which is not supported in PowerPoint. Remove margin from inline elements.`); + } + if (computed.marginRight && parseFloat(computed.marginRight) > 0) { + errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-right which is not supported in PowerPoint. Remove margin from inline elements.`); + } + if (computed.marginTop && parseFloat(computed.marginTop) > 0) { + errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-top which is not supported in PowerPoint. Remove margin from inline elements.`); + } + if (computed.marginBottom && parseFloat(computed.marginBottom) > 0) { + errors.push(`Inline element <${node.tagName.toLowerCase()}> has margin-bottom which is not supported in PowerPoint. Remove margin from inline elements.`); + } + + // Recursively process the child node. This will flatten nested spans into multiple runs. + parseInlineFormatting(node, options, runs, textTransform); + } + } + + prevNodeIsText = isText; + }); + + // Trim leading space from first run and trailing space from last run + if (runs.length > 0) { + runs[0].text = runs[0].text.replace(/^\s+/, ''); + runs[runs.length - 1].text = runs[runs.length - 1].text.replace(/\s+$/, ''); + } + + return runs.filter(r => r.text.length > 0); + }; + + // Extract background from body (image or color) + const body = document.body; + const bodyStyle = window.getComputedStyle(body); + const bgImage = bodyStyle.backgroundImage; + const bgColor = bodyStyle.backgroundColor; + + // Collect validation errors + const errors = []; + + // Validate: Check for CSS gradients + if (bgImage && (bgImage.includes('linear-gradient') || bgImage.includes('radial-gradient'))) { + errors.push( + 'CSS gradients are not supported. Use Sharp to rasterize gradients as PNG images first, ' + + 'then reference with background-image: url(\'gradient.png\')' + ); + } + + let background; + if (bgImage && bgImage !== 'none') { + // Extract URL from url("...") or url(...) + const urlMatch = bgImage.match(/url\(["']?([^"')]+)["']?\)/); + if (urlMatch) { + background = { + type: 'image', + path: urlMatch[1] + }; + } else { + background = { + type: 'color', + value: rgbToHex(bgColor) + }; + } + } else { + background = { + type: 'color', + value: rgbToHex(bgColor) + }; + } + + // Process all elements + const elements = []; + const placeholders = []; + const textTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'UL', 'OL', 'LI']; + const processed = new Set(); + + document.querySelectorAll('*').forEach((el) => { + if (processed.has(el)) return; + + // Validate text elements don't have backgrounds, borders, or shadows + if (textTags.includes(el.tagName)) { + const computed = window.getComputedStyle(el); + const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)'; + const hasBorder = (computed.borderWidth && parseFloat(computed.borderWidth) > 0) || + (computed.borderTopWidth && parseFloat(computed.borderTopWidth) > 0) || + (computed.borderRightWidth && parseFloat(computed.borderRightWidth) > 0) || + (computed.borderBottomWidth && parseFloat(computed.borderBottomWidth) > 0) || + (computed.borderLeftWidth && parseFloat(computed.borderLeftWidth) > 0); + const hasShadow = computed.boxShadow && computed.boxShadow !== 'none'; + + if (hasBg || hasBorder || hasShadow) { + errors.push( + `Text element <${el.tagName.toLowerCase()}> has ${hasBg ? 'background' : hasBorder ? 'border' : 'shadow'}. ` + + 'Backgrounds, borders, and shadows are only supported on
                      elements, not text elements.' + ); + return; + } + } + + // Extract placeholder elements (for charts, etc.) + if (el.className && el.className.includes('placeholder')) { + const rect = el.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) { + errors.push( + `Placeholder "${el.id || 'unnamed'}" has ${rect.width === 0 ? 'width: 0' : 'height: 0'}. Check the layout CSS.` + ); + } else { + placeholders.push({ + id: el.id || `placeholder-${placeholders.length}`, + x: pxToInch(rect.left), + y: pxToInch(rect.top), + w: pxToInch(rect.width), + h: pxToInch(rect.height) + }); + } + processed.add(el); + return; + } + + // Extract images + if (el.tagName === 'IMG') { + const rect = el.getBoundingClientRect(); + if (rect.width > 0 && rect.height > 0) { + elements.push({ + type: 'image', + src: el.src, + position: { + x: pxToInch(rect.left), + y: pxToInch(rect.top), + w: pxToInch(rect.width), + h: pxToInch(rect.height) + } + }); + processed.add(el); + return; + } + } + + // Extract DIVs with backgrounds/borders as shapes + const isContainer = el.tagName === 'DIV' && !textTags.includes(el.tagName); + if (isContainer) { + const computed = window.getComputedStyle(el); + const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)'; + + // Validate: Check for unwrapped text content in DIV + for (const node of el.childNodes) { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent.trim(); + if (text) { + errors.push( + `DIV element contains unwrapped text "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}". ` + + 'All text must be wrapped in

                      ,

                      -

                      ,
                        , or
                          tags to appear in PowerPoint.' + ); + } + } + } + + // Check for background images on shapes + const bgImage = computed.backgroundImage; + if (bgImage && bgImage !== 'none') { + errors.push( + 'Background images on DIV elements are not supported. ' + + 'Use solid colors or borders for shapes, or use slide.addImage() in PptxGenJS to layer images.' + ); + return; + } + + // Check for borders - both uniform and partial + const borderTop = computed.borderTopWidth; + const borderRight = computed.borderRightWidth; + const borderBottom = computed.borderBottomWidth; + const borderLeft = computed.borderLeftWidth; + const borders = [borderTop, borderRight, borderBottom, borderLeft].map(b => parseFloat(b) || 0); + const hasBorder = borders.some(b => b > 0); + const hasUniformBorder = hasBorder && borders.every(b => b === borders[0]); + const borderLines = []; + + if (hasBorder && !hasUniformBorder) { + const rect = el.getBoundingClientRect(); + const x = pxToInch(rect.left); + const y = pxToInch(rect.top); + const w = pxToInch(rect.width); + const h = pxToInch(rect.height); + + // Collect lines to add after shape (inset by half the line width to center on edge) + if (parseFloat(borderTop) > 0) { + const widthPt = pxToPoints(borderTop); + const inset = (widthPt / 72) / 2; // Convert points to inches, then half + borderLines.push({ + type: 'line', + x1: x, y1: y + inset, x2: x + w, y2: y + inset, + width: widthPt, + color: rgbToHex(computed.borderTopColor) + }); + } + if (parseFloat(borderRight) > 0) { + const widthPt = pxToPoints(borderRight); + const inset = (widthPt / 72) / 2; + borderLines.push({ + type: 'line', + x1: x + w - inset, y1: y, x2: x + w - inset, y2: y + h, + width: widthPt, + color: rgbToHex(computed.borderRightColor) + }); + } + if (parseFloat(borderBottom) > 0) { + const widthPt = pxToPoints(borderBottom); + const inset = (widthPt / 72) / 2; + borderLines.push({ + type: 'line', + x1: x, y1: y + h - inset, x2: x + w, y2: y + h - inset, + width: widthPt, + color: rgbToHex(computed.borderBottomColor) + }); + } + if (parseFloat(borderLeft) > 0) { + const widthPt = pxToPoints(borderLeft); + const inset = (widthPt / 72) / 2; + borderLines.push({ + type: 'line', + x1: x + inset, y1: y, x2: x + inset, y2: y + h, + width: widthPt, + color: rgbToHex(computed.borderLeftColor) + }); + } + } + + if (hasBg || hasBorder) { + const rect = el.getBoundingClientRect(); + if (rect.width > 0 && rect.height > 0) { + const shadow = parseBoxShadow(computed.boxShadow); + + // Only add shape if there's background or uniform border + if (hasBg || hasUniformBorder) { + elements.push({ + type: 'shape', + text: '', // Shape only - child text elements render on top + position: { + x: pxToInch(rect.left), + y: pxToInch(rect.top), + w: pxToInch(rect.width), + h: pxToInch(rect.height) + }, + shape: { + fill: hasBg ? rgbToHex(computed.backgroundColor) : null, + transparency: hasBg ? extractAlpha(computed.backgroundColor) : null, + line: hasUniformBorder ? { + color: rgbToHex(computed.borderColor), + width: pxToPoints(computed.borderWidth) + } : null, + // Convert border-radius to rectRadius (in inches) + // % values: 50%+ = circle (1), <50% = percentage of min dimension + // pt values: divide by 72 (72pt = 1 inch) + // px values: divide by 96 (96px = 1 inch) + rectRadius: (() => { + const radius = computed.borderRadius; + const radiusValue = parseFloat(radius); + if (radiusValue === 0) return 0; + + if (radius.includes('%')) { + if (radiusValue >= 50) return 1; + // Calculate percentage of smaller dimension + const minDim = Math.min(rect.width, rect.height); + return (radiusValue / 100) * pxToInch(minDim); + } + + if (radius.includes('pt')) return radiusValue / 72; + return radiusValue / PX_PER_IN; + })(), + shadow: shadow + } + }); + } + + // Add partial border lines + elements.push(...borderLines); + + processed.add(el); + return; + } + } + } + + // Extract bullet lists as single text block + if (el.tagName === 'UL' || el.tagName === 'OL') { + const rect = el.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; + + const liElements = Array.from(el.querySelectorAll('li')); + const items = []; + const ulComputed = window.getComputedStyle(el); + const ulPaddingLeftPt = pxToPoints(ulComputed.paddingLeft); + + // Split: margin-left for bullet position, indent for text position + // margin-left + indent = ul padding-left + const marginLeft = ulPaddingLeftPt * 0.5; + const textIndent = ulPaddingLeftPt * 0.5; + + liElements.forEach((li, idx) => { + const isLast = idx === liElements.length - 1; + const runs = parseInlineFormatting(li, { breakLine: false }); + // Clean manual bullets from first run + if (runs.length > 0) { + runs[0].text = runs[0].text.replace(/^[•\-\*▪▸]\s*/, ''); + runs[0].options.bullet = { indent: textIndent }; + } + // Set breakLine on last run + if (runs.length > 0 && !isLast) { + runs[runs.length - 1].options.breakLine = true; + } + items.push(...runs); + }); + + const computed = window.getComputedStyle(liElements[0] || el); + + elements.push({ + type: 'list', + items: items, + position: { + x: pxToInch(rect.left), + y: pxToInch(rect.top), + w: pxToInch(rect.width), + h: pxToInch(rect.height) + }, + style: { + fontSize: pxToPoints(computed.fontSize), + fontFace: computed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(), + color: rgbToHex(computed.color), + transparency: extractAlpha(computed.color), + align: computed.textAlign === 'start' ? 'left' : computed.textAlign, + lineSpacing: computed.lineHeight && computed.lineHeight !== 'normal' ? pxToPoints(computed.lineHeight) : null, + paraSpaceBefore: 0, + paraSpaceAfter: pxToPoints(computed.marginBottom), + // PptxGenJS margin array is [left, right, bottom, top] + margin: [marginLeft, 0, 0, 0] + } + }); + + liElements.forEach(li => processed.add(li)); + processed.add(el); + return; + } + + // Extract text elements (P, H1, H2, etc.) + if (!textTags.includes(el.tagName)) return; + + const rect = el.getBoundingClientRect(); + const text = el.textContent.trim(); + if (rect.width === 0 || rect.height === 0 || !text) return; + + // Validate: Check for manual bullet symbols in text elements (not in lists) + if (el.tagName !== 'LI' && /^[•\-\*▪▸○●◆◇■□]\s/.test(text.trimStart())) { + errors.push( + `Text element <${el.tagName.toLowerCase()}> starts with bullet symbol "${text.substring(0, 20)}...". ` + + 'Use
                            or
                              lists instead of manual bullet symbols.' + ); + return; + } + + const computed = window.getComputedStyle(el); + const rotation = getRotation(computed.transform, computed.writingMode); + const { x, y, w, h } = getPositionAndSize(el, rect, rotation); + + const baseStyle = { + fontSize: pxToPoints(computed.fontSize), + fontFace: computed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(), + color: rgbToHex(computed.color), + align: computed.textAlign === 'start' ? 'left' : computed.textAlign, + lineSpacing: pxToPoints(computed.lineHeight), + paraSpaceBefore: pxToPoints(computed.marginTop), + paraSpaceAfter: pxToPoints(computed.marginBottom), + // PptxGenJS margin array is [left, right, bottom, top] (not [top, right, bottom, left] as documented) + margin: [ + pxToPoints(computed.paddingLeft), + pxToPoints(computed.paddingRight), + pxToPoints(computed.paddingBottom), + pxToPoints(computed.paddingTop) + ] + }; + + const transparency = extractAlpha(computed.color); + if (transparency !== null) baseStyle.transparency = transparency; + + if (rotation !== null) baseStyle.rotate = rotation; + + const hasFormatting = el.querySelector('b, i, u, strong, em, span, br'); + + if (hasFormatting) { + // Text with inline formatting + const transformStr = computed.textTransform; + const runs = parseInlineFormatting(el, {}, [], (str) => applyTextTransform(str, transformStr)); + + // Adjust lineSpacing based on largest fontSize in runs + const adjustedStyle = { ...baseStyle }; + if (adjustedStyle.lineSpacing) { + const maxFontSize = Math.max( + adjustedStyle.fontSize, + ...runs.map(r => r.options?.fontSize || 0) + ); + if (maxFontSize > adjustedStyle.fontSize) { + const lineHeightMultiplier = adjustedStyle.lineSpacing / adjustedStyle.fontSize; + adjustedStyle.lineSpacing = maxFontSize * lineHeightMultiplier; + } + } + + elements.push({ + type: el.tagName.toLowerCase(), + text: runs, + position: { x: pxToInch(x), y: pxToInch(y), w: pxToInch(w), h: pxToInch(h) }, + style: adjustedStyle + }); + } else { + // Plain text - inherit CSS formatting + const textTransform = computed.textTransform; + const transformedText = applyTextTransform(text, textTransform); + + const isBold = computed.fontWeight === 'bold' || parseInt(computed.fontWeight) >= 600; + + elements.push({ + type: el.tagName.toLowerCase(), + text: transformedText, + position: { x: pxToInch(x), y: pxToInch(y), w: pxToInch(w), h: pxToInch(h) }, + style: { + ...baseStyle, + bold: isBold && !shouldSkipBold(computed.fontFamily), + italic: computed.fontStyle === 'italic', + underline: computed.textDecoration.includes('underline') + } + }); + } + + processed.add(el); + }); + + return { background, elements, placeholders, errors }; + }); +} + +async function html2pptx(htmlFile, pres, options = {}) { + const { + tmpDir = process.env.TMPDIR || '/tmp', + slide = null + } = options; + + try { + // Use Chrome on macOS, default Chromium on Unix + const launchOptions = { env: { TMPDIR: tmpDir } }; + if (process.platform === 'darwin') { + launchOptions.channel = 'chrome'; + } + + const browser = await chromium.launch(launchOptions); + + let bodyDimensions; + let slideData; + + const filePath = path.isAbsolute(htmlFile) ? htmlFile : path.join(process.cwd(), htmlFile); + const validationErrors = []; + + try { + const page = await browser.newPage(); + page.on('console', (msg) => { + // Log the message text to your test runner's console + console.log(`Browser console: ${msg.text()}`); + }); + + await page.goto(`file://${filePath}`); + + bodyDimensions = await getBodyDimensions(page); + + await page.setViewportSize({ + width: Math.round(bodyDimensions.width), + height: Math.round(bodyDimensions.height) + }); + + slideData = await extractSlideData(page); + } finally { + await browser.close(); + } + + // Collect all validation errors + if (bodyDimensions.errors && bodyDimensions.errors.length > 0) { + validationErrors.push(...bodyDimensions.errors); + } + + const dimensionErrors = validateDimensions(bodyDimensions, pres); + if (dimensionErrors.length > 0) { + validationErrors.push(...dimensionErrors); + } + + const textBoxPositionErrors = validateTextBoxPosition(slideData, bodyDimensions); + if (textBoxPositionErrors.length > 0) { + validationErrors.push(...textBoxPositionErrors); + } + + if (slideData.errors && slideData.errors.length > 0) { + validationErrors.push(...slideData.errors); + } + + // Throw all errors at once if any exist + if (validationErrors.length > 0) { + const errorMessage = validationErrors.length === 1 + ? validationErrors[0] + : `Multiple validation errors found:\n${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}`; + throw new Error(errorMessage); + } + + const targetSlide = slide || pres.addSlide(); + + await addBackground(slideData, targetSlide, tmpDir); + addElements(slideData, targetSlide, pres); + + return { slide: targetSlide, placeholders: slideData.placeholders }; + } catch (error) { + if (!error.message.startsWith(htmlFile)) { + throw new Error(`${htmlFile}: ${error.message}`); + } + throw error; + } +} + +module.exports = html2pptx; \ No newline at end of file diff --git a/src/flow/skills/pptx/scripts/inventory.py b/src/flow/skills/pptx/scripts/inventory.py new file mode 100755 index 0000000000000000000000000000000000000000..edda390e721f3e621002eb1986f50f1046d9a6ba --- /dev/null +++ b/src/flow/skills/pptx/scripts/inventory.py @@ -0,0 +1,1020 @@ +#!/usr/bin/env python3 +""" +Extract structured text content from PowerPoint presentations. + +This module provides functionality to: +- Extract all text content from PowerPoint shapes +- Preserve paragraph formatting (alignment, bullets, fonts, spacing) +- Handle nested GroupShapes recursively with correct absolute positions +- Sort shapes by visual position on slides +- Filter out slide numbers and non-content placeholders +- Export to JSON with clean, structured data + +Classes: + ParagraphData: Represents a text paragraph with formatting + ShapeData: Represents a shape with position and text content + +Main Functions: + extract_text_inventory: Extract all text from a presentation + save_inventory: Save extracted data to JSON + +Usage: + python inventory.py input.pptx output.json +""" + +import argparse +import json +import platform +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple, Union + +from PIL import Image, ImageDraw, ImageFont +from pptx import Presentation +from pptx.enum.text import PP_ALIGN +from pptx.shapes.base import BaseShape + +# Type aliases for cleaner signatures +JsonValue = Union[str, int, float, bool, None] +ParagraphDict = Dict[str, JsonValue] +ShapeDict = Dict[ + str, Union[str, float, bool, List[ParagraphDict], List[str], Dict[str, Any], None] +] +InventoryData = Dict[ + str, Dict[str, "ShapeData"] +] # Dict of slide_id -> {shape_id -> ShapeData} +InventoryDict = Dict[str, Dict[str, ShapeDict]] # JSON-serializable inventory + + +def main(): + """Main entry point for command-line usage.""" + parser = argparse.ArgumentParser( + description="Extract text inventory from PowerPoint with proper GroupShape support.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python inventory.py presentation.pptx inventory.json + Extracts text inventory with correct absolute positions for grouped shapes + + python inventory.py presentation.pptx inventory.json --issues-only + Extracts only text shapes that have overflow or overlap issues + +The output JSON includes: + - All text content organized by slide and shape + - Correct absolute positions for shapes in groups + - Visual position and size in inches + - Paragraph properties and formatting + - Issue detection: text overflow and shape overlaps + """, + ) + + parser.add_argument("input", help="Input PowerPoint file (.pptx)") + parser.add_argument("output", help="Output JSON file for inventory") + parser.add_argument( + "--issues-only", + action="store_true", + help="Include only text shapes that have overflow or overlap issues", + ) + + args = parser.parse_args() + + input_path = Path(args.input) + if not input_path.exists(): + print(f"Error: Input file not found: {args.input}") + sys.exit(1) + + if not input_path.suffix.lower() == ".pptx": + print("Error: Input must be a PowerPoint file (.pptx)") + sys.exit(1) + + try: + print(f"Extracting text inventory from: {args.input}") + if args.issues_only: + print( + "Filtering to include only text shapes with issues (overflow/overlap)" + ) + inventory = extract_text_inventory(input_path, issues_only=args.issues_only) + + output_path = Path(args.output) + output_path.parent.mkdir(parents=True, exist_ok=True) + save_inventory(inventory, output_path) + + print(f"Output saved to: {args.output}") + + # Report statistics + total_slides = len(inventory) + total_shapes = sum(len(shapes) for shapes in inventory.values()) + if args.issues_only: + if total_shapes > 0: + print( + f"Found {total_shapes} text elements with issues in {total_slides} slides" + ) + else: + print("No issues discovered") + else: + print( + f"Found text in {total_slides} slides with {total_shapes} text elements" + ) + + except Exception as e: + print(f"Error processing presentation: {e}") + import traceback + + traceback.print_exc() + sys.exit(1) + + +@dataclass +class ShapeWithPosition: + """A shape with its absolute position on the slide.""" + + shape: BaseShape + absolute_left: int # in EMUs + absolute_top: int # in EMUs + + +class ParagraphData: + """Data structure for paragraph properties extracted from a PowerPoint paragraph.""" + + def __init__(self, paragraph: Any): + """Initialize from a PowerPoint paragraph object. + + Args: + paragraph: The PowerPoint paragraph object + """ + self.text: str = paragraph.text.strip() + self.bullet: bool = False + self.level: Optional[int] = None + self.alignment: Optional[str] = None + self.space_before: Optional[float] = None + self.space_after: Optional[float] = None + self.font_name: Optional[str] = None + self.font_size: Optional[float] = None + self.bold: Optional[bool] = None + self.italic: Optional[bool] = None + self.underline: Optional[bool] = None + self.color: Optional[str] = None + self.theme_color: Optional[str] = None + self.line_spacing: Optional[float] = None + + # Check for bullet formatting + if ( + hasattr(paragraph, "_p") + and paragraph._p is not None + and paragraph._p.pPr is not None + ): + pPr = paragraph._p.pPr + ns = "{http://schemas.openxmlformats.org/drawingml/2006/main}" + if ( + pPr.find(f"{ns}buChar") is not None + or pPr.find(f"{ns}buAutoNum") is not None + ): + self.bullet = True + if hasattr(paragraph, "level"): + self.level = paragraph.level + + # Add alignment if not LEFT (default) + if hasattr(paragraph, "alignment") and paragraph.alignment is not None: + alignment_map = { + PP_ALIGN.CENTER: "CENTER", + PP_ALIGN.RIGHT: "RIGHT", + PP_ALIGN.JUSTIFY: "JUSTIFY", + } + if paragraph.alignment in alignment_map: + self.alignment = alignment_map[paragraph.alignment] + + # Add spacing properties if set + if hasattr(paragraph, "space_before") and paragraph.space_before: + self.space_before = paragraph.space_before.pt + if hasattr(paragraph, "space_after") and paragraph.space_after: + self.space_after = paragraph.space_after.pt + + # Extract font properties from first run + if paragraph.runs: + first_run = paragraph.runs[0] + if hasattr(first_run, "font"): + font = first_run.font + if font.name: + self.font_name = font.name + if font.size: + self.font_size = font.size.pt + if font.bold is not None: + self.bold = font.bold + if font.italic is not None: + self.italic = font.italic + if font.underline is not None: + self.underline = font.underline + + # Handle color - both RGB and theme colors + try: + # Try RGB color first + if font.color.rgb: + self.color = str(font.color.rgb) + except (AttributeError, TypeError): + # Fall back to theme color + try: + if font.color.theme_color: + self.theme_color = font.color.theme_color.name + except (AttributeError, TypeError): + pass + + # Add line spacing if set + if hasattr(paragraph, "line_spacing") and paragraph.line_spacing is not None: + if hasattr(paragraph.line_spacing, "pt"): + self.line_spacing = round(paragraph.line_spacing.pt, 2) + else: + # Multiplier - convert to points + font_size = self.font_size if self.font_size else 12.0 + self.line_spacing = round(paragraph.line_spacing * font_size, 2) + + def to_dict(self) -> ParagraphDict: + """Convert to dictionary for JSON serialization, excluding None values.""" + result: ParagraphDict = {"text": self.text} + + # Add optional fields only if they have values + if self.bullet: + result["bullet"] = self.bullet + if self.level is not None: + result["level"] = self.level + if self.alignment: + result["alignment"] = self.alignment + if self.space_before is not None: + result["space_before"] = self.space_before + if self.space_after is not None: + result["space_after"] = self.space_after + if self.font_name: + result["font_name"] = self.font_name + if self.font_size is not None: + result["font_size"] = self.font_size + if self.bold is not None: + result["bold"] = self.bold + if self.italic is not None: + result["italic"] = self.italic + if self.underline is not None: + result["underline"] = self.underline + if self.color: + result["color"] = self.color + if self.theme_color: + result["theme_color"] = self.theme_color + if self.line_spacing is not None: + result["line_spacing"] = self.line_spacing + + return result + + +class ShapeData: + """Data structure for shape properties extracted from a PowerPoint shape.""" + + @staticmethod + def emu_to_inches(emu: int) -> float: + """Convert EMUs (English Metric Units) to inches.""" + return emu / 914400.0 + + @staticmethod + def inches_to_pixels(inches: float, dpi: int = 96) -> int: + """Convert inches to pixels at given DPI.""" + return int(inches * dpi) + + @staticmethod + def get_font_path(font_name: str) -> Optional[str]: + """Get the font file path for a given font name. + + Args: + font_name: Name of the font (e.g., 'Arial', 'Calibri') + + Returns: + Path to the font file, or None if not found + """ + system = platform.system() + + # Common font file variations to try + font_variations = [ + font_name, + font_name.lower(), + font_name.replace(" ", ""), + font_name.replace(" ", "-"), + ] + + # Define font directories and extensions by platform + if system == "Darwin": # macOS + font_dirs = [ + "/System/Library/Fonts/", + "/Library/Fonts/", + "~/Library/Fonts/", + ] + extensions = [".ttf", ".otf", ".ttc", ".dfont"] + else: # Linux + font_dirs = [ + "/usr/share/fonts/truetype/", + "/usr/local/share/fonts/", + "~/.fonts/", + ] + extensions = [".ttf", ".otf"] + + # Try to find the font file + from pathlib import Path + + for font_dir in font_dirs: + font_dir_path = Path(font_dir).expanduser() + if not font_dir_path.exists(): + continue + + # First try exact matches + for variant in font_variations: + for ext in extensions: + font_path = font_dir_path / f"{variant}{ext}" + if font_path.exists(): + return str(font_path) + + # Then try fuzzy matching - find files containing the font name + try: + for file_path in font_dir_path.iterdir(): + if file_path.is_file(): + file_name_lower = file_path.name.lower() + font_name_lower = font_name.lower().replace(" ", "") + if font_name_lower in file_name_lower and any( + file_name_lower.endswith(ext) for ext in extensions + ): + return str(file_path) + except (OSError, PermissionError): + continue + + return None + + @staticmethod + def get_slide_dimensions(slide: Any) -> tuple[Optional[int], Optional[int]]: + """Get slide dimensions from slide object. + + Args: + slide: Slide object + + Returns: + Tuple of (width_emu, height_emu) or (None, None) if not found + """ + try: + prs = slide.part.package.presentation_part.presentation + return prs.slide_width, prs.slide_height + except (AttributeError, TypeError): + return None, None + + @staticmethod + def get_default_font_size(shape: BaseShape, slide_layout: Any) -> Optional[float]: + """Extract default font size from slide layout for a placeholder shape. + + Args: + shape: Placeholder shape + slide_layout: Slide layout containing the placeholder definition + + Returns: + Default font size in points, or None if not found + """ + try: + if not hasattr(shape, "placeholder_format"): + return None + + shape_type = shape.placeholder_format.type # type: ignore + for layout_placeholder in slide_layout.placeholders: + if layout_placeholder.placeholder_format.type == shape_type: + # Find first defRPr element with sz (size) attribute + for elem in layout_placeholder.element.iter(): + if "defRPr" in elem.tag and (sz := elem.get("sz")): + return float(sz) / 100.0 # Convert EMUs to points + break + except Exception: + pass + return None + + def __init__( + self, + shape: BaseShape, + absolute_left: Optional[int] = None, + absolute_top: Optional[int] = None, + slide: Optional[Any] = None, + ): + """Initialize from a PowerPoint shape object. + + Args: + shape: The PowerPoint shape object (should be pre-validated) + absolute_left: Absolute left position in EMUs (for shapes in groups) + absolute_top: Absolute top position in EMUs (for shapes in groups) + slide: Optional slide object to get dimensions and layout information + """ + self.shape = shape # Store reference to original shape + self.shape_id: str = "" # Will be set after sorting + + # Get slide dimensions from slide object + self.slide_width_emu, self.slide_height_emu = ( + self.get_slide_dimensions(slide) if slide else (None, None) + ) + + # Get placeholder type if applicable + self.placeholder_type: Optional[str] = None + self.default_font_size: Optional[float] = None + if hasattr(shape, "is_placeholder") and shape.is_placeholder: # type: ignore + if shape.placeholder_format and shape.placeholder_format.type: # type: ignore + self.placeholder_type = ( + str(shape.placeholder_format.type).split(".")[-1].split(" ")[0] # type: ignore + ) + + # Get default font size from layout + if slide and hasattr(slide, "slide_layout"): + self.default_font_size = self.get_default_font_size( + shape, slide.slide_layout + ) + + # Get position information + # Use absolute positions if provided (for shapes in groups), otherwise use shape's position + left_emu = ( + absolute_left + if absolute_left is not None + else (shape.left if hasattr(shape, "left") else 0) + ) + top_emu = ( + absolute_top + if absolute_top is not None + else (shape.top if hasattr(shape, "top") else 0) + ) + + self.left: float = round(self.emu_to_inches(left_emu), 2) # type: ignore + self.top: float = round(self.emu_to_inches(top_emu), 2) # type: ignore + self.width: float = round( + self.emu_to_inches(shape.width if hasattr(shape, "width") else 0), + 2, # type: ignore + ) + self.height: float = round( + self.emu_to_inches(shape.height if hasattr(shape, "height") else 0), + 2, # type: ignore + ) + + # Store EMU positions for overflow calculations + self.left_emu = left_emu + self.top_emu = top_emu + self.width_emu = shape.width if hasattr(shape, "width") else 0 + self.height_emu = shape.height if hasattr(shape, "height") else 0 + + # Calculate overflow status + self.frame_overflow_bottom: Optional[float] = None + self.slide_overflow_right: Optional[float] = None + self.slide_overflow_bottom: Optional[float] = None + self.overlapping_shapes: Dict[ + str, float + ] = {} # Dict of shape_id -> overlap area in sq inches + self.warnings: List[str] = [] + self._estimate_frame_overflow() + self._calculate_slide_overflow() + self._detect_bullet_issues() + + @property + def paragraphs(self) -> List[ParagraphData]: + """Calculate paragraphs from the shape's text frame.""" + if not self.shape or not hasattr(self.shape, "text_frame"): + return [] + + paragraphs = [] + for paragraph in self.shape.text_frame.paragraphs: # type: ignore + if paragraph.text.strip(): + paragraphs.append(ParagraphData(paragraph)) + return paragraphs + + def _get_default_font_size(self) -> int: + """Get default font size from theme text styles or use conservative default.""" + try: + if not ( + hasattr(self.shape, "part") and hasattr(self.shape.part, "slide_layout") + ): + return 14 + + slide_master = self.shape.part.slide_layout.slide_master # type: ignore + if not hasattr(slide_master, "element"): + return 14 + + # Determine theme style based on placeholder type + style_name = "bodyStyle" # Default + if self.placeholder_type and "TITLE" in self.placeholder_type: + style_name = "titleStyle" + + # Find font size in theme styles + for child in slide_master.element.iter(): + tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag + if tag == style_name: + for elem in child.iter(): + if "sz" in elem.attrib: + return int(elem.attrib["sz"]) // 100 + except Exception: + pass + + return 14 # Conservative default for body text + + def _get_usable_dimensions(self, text_frame) -> Tuple[int, int]: + """Get usable width and height in pixels after accounting for margins.""" + # Default PowerPoint margins in inches + margins = {"top": 0.05, "bottom": 0.05, "left": 0.1, "right": 0.1} + + # Override with actual margins if set + if hasattr(text_frame, "margin_top") and text_frame.margin_top: + margins["top"] = self.emu_to_inches(text_frame.margin_top) + if hasattr(text_frame, "margin_bottom") and text_frame.margin_bottom: + margins["bottom"] = self.emu_to_inches(text_frame.margin_bottom) + if hasattr(text_frame, "margin_left") and text_frame.margin_left: + margins["left"] = self.emu_to_inches(text_frame.margin_left) + if hasattr(text_frame, "margin_right") and text_frame.margin_right: + margins["right"] = self.emu_to_inches(text_frame.margin_right) + + # Calculate usable area + usable_width = self.width - margins["left"] - margins["right"] + usable_height = self.height - margins["top"] - margins["bottom"] + + # Convert to pixels + return ( + self.inches_to_pixels(usable_width), + self.inches_to_pixels(usable_height), + ) + + def _wrap_text_line(self, line: str, max_width_px: int, draw, font) -> List[str]: + """Wrap a single line of text to fit within max_width_px.""" + if not line: + return [""] + + # Use textlength for efficient width calculation + if draw.textlength(line, font=font) <= max_width_px: + return [line] + + # Need to wrap - split into words + wrapped = [] + words = line.split(" ") + current_line = "" + + for word in words: + test_line = current_line + (" " if current_line else "") + word + if draw.textlength(test_line, font=font) <= max_width_px: + current_line = test_line + else: + if current_line: + wrapped.append(current_line) + current_line = word + + if current_line: + wrapped.append(current_line) + + return wrapped + + def _estimate_frame_overflow(self) -> None: + """Estimate if text overflows the shape bounds using PIL text measurement.""" + if not self.shape or not hasattr(self.shape, "text_frame"): + return + + text_frame = self.shape.text_frame # type: ignore + if not text_frame or not text_frame.paragraphs: + return + + # Get usable dimensions after accounting for margins + usable_width_px, usable_height_px = self._get_usable_dimensions(text_frame) + if usable_width_px <= 0 or usable_height_px <= 0: + return + + # Set up PIL for text measurement + dummy_img = Image.new("RGB", (1, 1)) + draw = ImageDraw.Draw(dummy_img) + + # Get default font size from placeholder or use conservative estimate + default_font_size = self._get_default_font_size() + + # Calculate total height of all paragraphs + total_height_px = 0 + + for para_idx, paragraph in enumerate(text_frame.paragraphs): + if not paragraph.text.strip(): + continue + + para_data = ParagraphData(paragraph) + + # Load font for this paragraph + font_name = para_data.font_name or "Arial" + font_size = int(para_data.font_size or default_font_size) + + font = None + font_path = self.get_font_path(font_name) + if font_path: + try: + font = ImageFont.truetype(font_path, size=font_size) + except Exception: + font = ImageFont.load_default() + else: + font = ImageFont.load_default() + + # Wrap all lines in this paragraph + all_wrapped_lines = [] + for line in paragraph.text.split("\n"): + wrapped = self._wrap_text_line(line, usable_width_px, draw, font) + all_wrapped_lines.extend(wrapped) + + if all_wrapped_lines: + # Calculate line height + if para_data.line_spacing: + # Custom line spacing explicitly set + line_height_px = para_data.line_spacing * 96 / 72 + else: + # PowerPoint default single spacing (1.0x font size) + line_height_px = font_size * 96 / 72 + + # Add space_before (except first paragraph) + if para_idx > 0 and para_data.space_before: + total_height_px += para_data.space_before * 96 / 72 + + # Add paragraph text height + total_height_px += len(all_wrapped_lines) * line_height_px + + # Add space_after + if para_data.space_after: + total_height_px += para_data.space_after * 96 / 72 + + # Check for overflow (ignore negligible overflows <= 0.05") + if total_height_px > usable_height_px: + overflow_px = total_height_px - usable_height_px + overflow_inches = round(overflow_px / 96.0, 2) + if overflow_inches > 0.05: # Only report significant overflows + self.frame_overflow_bottom = overflow_inches + + def _calculate_slide_overflow(self) -> None: + """Calculate if shape overflows the slide boundaries.""" + if self.slide_width_emu is None or self.slide_height_emu is None: + return + + # Check right overflow (ignore negligible overflows <= 0.01") + right_edge_emu = self.left_emu + self.width_emu + if right_edge_emu > self.slide_width_emu: + overflow_emu = right_edge_emu - self.slide_width_emu + overflow_inches = round(self.emu_to_inches(overflow_emu), 2) + if overflow_inches > 0.01: # Only report significant overflows + self.slide_overflow_right = overflow_inches + + # Check bottom overflow (ignore negligible overflows <= 0.01") + bottom_edge_emu = self.top_emu + self.height_emu + if bottom_edge_emu > self.slide_height_emu: + overflow_emu = bottom_edge_emu - self.slide_height_emu + overflow_inches = round(self.emu_to_inches(overflow_emu), 2) + if overflow_inches > 0.01: # Only report significant overflows + self.slide_overflow_bottom = overflow_inches + + def _detect_bullet_issues(self) -> None: + """Detect bullet point formatting issues in paragraphs.""" + if not self.shape or not hasattr(self.shape, "text_frame"): + return + + text_frame = self.shape.text_frame # type: ignore + if not text_frame or not text_frame.paragraphs: + return + + # Common bullet symbols that indicate manual bullets + bullet_symbols = ["•", "●", "○"] + + for paragraph in text_frame.paragraphs: + text = paragraph.text.strip() + # Check for manual bullet symbols + if text and any(text.startswith(symbol + " ") for symbol in bullet_symbols): + self.warnings.append( + "manual_bullet_symbol: use proper bullet formatting" + ) + break + + @property + def has_any_issues(self) -> bool: + """Check if shape has any issues (overflow, overlap, or warnings).""" + return ( + self.frame_overflow_bottom is not None + or self.slide_overflow_right is not None + or self.slide_overflow_bottom is not None + or len(self.overlapping_shapes) > 0 + or len(self.warnings) > 0 + ) + + def to_dict(self) -> ShapeDict: + """Convert to dictionary for JSON serialization.""" + result: ShapeDict = { + "left": self.left, + "top": self.top, + "width": self.width, + "height": self.height, + } + + # Add optional fields if present + if self.placeholder_type: + result["placeholder_type"] = self.placeholder_type + + if self.default_font_size: + result["default_font_size"] = self.default_font_size + + # Add overflow information only if there is overflow + overflow_data = {} + + # Add frame overflow if present + if self.frame_overflow_bottom is not None: + overflow_data["frame"] = {"overflow_bottom": self.frame_overflow_bottom} + + # Add slide overflow if present + slide_overflow = {} + if self.slide_overflow_right is not None: + slide_overflow["overflow_right"] = self.slide_overflow_right + if self.slide_overflow_bottom is not None: + slide_overflow["overflow_bottom"] = self.slide_overflow_bottom + if slide_overflow: + overflow_data["slide"] = slide_overflow + + # Only add overflow field if there is overflow + if overflow_data: + result["overflow"] = overflow_data + + # Add overlap field if there are overlapping shapes + if self.overlapping_shapes: + result["overlap"] = {"overlapping_shapes": self.overlapping_shapes} + + # Add warnings field if there are warnings + if self.warnings: + result["warnings"] = self.warnings + + # Add paragraphs after placeholder_type + result["paragraphs"] = [para.to_dict() for para in self.paragraphs] + + return result + + +def is_valid_shape(shape: BaseShape) -> bool: + """Check if a shape contains meaningful text content.""" + # Must have a text frame with content + if not hasattr(shape, "text_frame") or not shape.text_frame: # type: ignore + return False + + text = shape.text_frame.text.strip() # type: ignore + if not text: + return False + + # Skip slide numbers and numeric footers + if hasattr(shape, "is_placeholder") and shape.is_placeholder: # type: ignore + if shape.placeholder_format and shape.placeholder_format.type: # type: ignore + placeholder_type = ( + str(shape.placeholder_format.type).split(".")[-1].split(" ")[0] # type: ignore + ) + if placeholder_type == "SLIDE_NUMBER": + return False + if placeholder_type == "FOOTER" and text.isdigit(): + return False + + return True + + +def collect_shapes_with_absolute_positions( + shape: BaseShape, parent_left: int = 0, parent_top: int = 0 +) -> List[ShapeWithPosition]: + """Recursively collect all shapes with valid text, calculating absolute positions. + + For shapes within groups, their positions are relative to the group. + This function calculates the absolute position on the slide by accumulating + parent group offsets. + + Args: + shape: The shape to process + parent_left: Accumulated left offset from parent groups (in EMUs) + parent_top: Accumulated top offset from parent groups (in EMUs) + + Returns: + List of ShapeWithPosition objects with absolute positions + """ + if hasattr(shape, "shapes"): # GroupShape + result = [] + # Get this group's position + group_left = shape.left if hasattr(shape, "left") else 0 + group_top = shape.top if hasattr(shape, "top") else 0 + + # Calculate absolute position for this group + abs_group_left = parent_left + group_left + abs_group_top = parent_top + group_top + + # Process children with accumulated offsets + for child in shape.shapes: # type: ignore + result.extend( + collect_shapes_with_absolute_positions( + child, abs_group_left, abs_group_top + ) + ) + return result + + # Regular shape - check if it has valid text + if is_valid_shape(shape): + # Calculate absolute position + shape_left = shape.left if hasattr(shape, "left") else 0 + shape_top = shape.top if hasattr(shape, "top") else 0 + + return [ + ShapeWithPosition( + shape=shape, + absolute_left=parent_left + shape_left, + absolute_top=parent_top + shape_top, + ) + ] + + return [] + + +def sort_shapes_by_position(shapes: List[ShapeData]) -> List[ShapeData]: + """Sort shapes by visual position (top-to-bottom, left-to-right). + + Shapes within 0.5 inches vertically are considered on the same row. + """ + if not shapes: + return shapes + + # Sort by top position first + shapes = sorted(shapes, key=lambda s: (s.top, s.left)) + + # Group shapes by row (within 0.5 inches vertically) + result = [] + row = [shapes[0]] + row_top = shapes[0].top + + for shape in shapes[1:]: + if abs(shape.top - row_top) <= 0.5: + row.append(shape) + else: + # Sort current row by left position and add to result + result.extend(sorted(row, key=lambda s: s.left)) + row = [shape] + row_top = shape.top + + # Don't forget the last row + result.extend(sorted(row, key=lambda s: s.left)) + return result + + +def calculate_overlap( + rect1: Tuple[float, float, float, float], + rect2: Tuple[float, float, float, float], + tolerance: float = 0.05, +) -> Tuple[bool, float]: + """Calculate if and how much two rectangles overlap. + + Args: + rect1: (left, top, width, height) of first rectangle in inches + rect2: (left, top, width, height) of second rectangle in inches + tolerance: Minimum overlap in inches to consider as overlapping (default: 0.05") + + Returns: + Tuple of (overlaps, overlap_area) where: + - overlaps: True if rectangles overlap by more than tolerance + - overlap_area: Area of overlap in square inches + """ + left1, top1, w1, h1 = rect1 + left2, top2, w2, h2 = rect2 + + # Calculate overlap dimensions + overlap_width = min(left1 + w1, left2 + w2) - max(left1, left2) + overlap_height = min(top1 + h1, top2 + h2) - max(top1, top2) + + # Check if there's meaningful overlap (more than tolerance) + if overlap_width > tolerance and overlap_height > tolerance: + # Calculate overlap area in square inches + overlap_area = overlap_width * overlap_height + return True, round(overlap_area, 2) + + return False, 0 + + +def detect_overlaps(shapes: List[ShapeData]) -> None: + """Detect overlapping shapes and update their overlapping_shapes dictionaries. + + This function requires each ShapeData to have its shape_id already set. + It modifies the shapes in-place, adding shape IDs with overlap areas in square inches. + + Args: + shapes: List of ShapeData objects with shape_id attributes set + """ + n = len(shapes) + + # Compare each pair of shapes + for i in range(n): + for j in range(i + 1, n): + shape1 = shapes[i] + shape2 = shapes[j] + + # Ensure shape IDs are set + assert shape1.shape_id, f"Shape at index {i} has no shape_id" + assert shape2.shape_id, f"Shape at index {j} has no shape_id" + + rect1 = (shape1.left, shape1.top, shape1.width, shape1.height) + rect2 = (shape2.left, shape2.top, shape2.width, shape2.height) + + overlaps, overlap_area = calculate_overlap(rect1, rect2) + + if overlaps: + # Add shape IDs with overlap area in square inches + shape1.overlapping_shapes[shape2.shape_id] = overlap_area + shape2.overlapping_shapes[shape1.shape_id] = overlap_area + + +def extract_text_inventory( + pptx_path: Path, prs: Optional[Any] = None, issues_only: bool = False +) -> InventoryData: + """Extract text content from all slides in a PowerPoint presentation. + + Args: + pptx_path: Path to the PowerPoint file + prs: Optional Presentation object to use. If not provided, will load from pptx_path. + issues_only: If True, only include shapes that have overflow or overlap issues + + Returns a nested dictionary: {slide-N: {shape-N: ShapeData}} + Shapes are sorted by visual position (top-to-bottom, left-to-right). + The ShapeData objects contain the full shape information and can be + converted to dictionaries for JSON serialization using to_dict(). + """ + if prs is None: + prs = Presentation(str(pptx_path)) + inventory: InventoryData = {} + + for slide_idx, slide in enumerate(prs.slides): + # Collect all valid shapes from this slide with absolute positions + shapes_with_positions = [] + for shape in slide.shapes: # type: ignore + shapes_with_positions.extend(collect_shapes_with_absolute_positions(shape)) + + if not shapes_with_positions: + continue + + # Convert to ShapeData with absolute positions and slide reference + shape_data_list = [ + ShapeData( + swp.shape, + swp.absolute_left, + swp.absolute_top, + slide, + ) + for swp in shapes_with_positions + ] + + # Sort by visual position and assign stable IDs in one step + sorted_shapes = sort_shapes_by_position(shape_data_list) + for idx, shape_data in enumerate(sorted_shapes): + shape_data.shape_id = f"shape-{idx}" + + # Detect overlaps using the stable shape IDs + if len(sorted_shapes) > 1: + detect_overlaps(sorted_shapes) + + # Filter for issues only if requested (after overlap detection) + if issues_only: + sorted_shapes = [sd for sd in sorted_shapes if sd.has_any_issues] + + if not sorted_shapes: + continue + + # Create slide inventory using the stable shape IDs + inventory[f"slide-{slide_idx}"] = { + shape_data.shape_id: shape_data for shape_data in sorted_shapes + } + + return inventory + + +def get_inventory_as_dict(pptx_path: Path, issues_only: bool = False) -> InventoryDict: + """Extract text inventory and return as JSON-serializable dictionaries. + + This is a convenience wrapper around extract_text_inventory that returns + dictionaries instead of ShapeData objects, useful for testing and direct + JSON serialization. + + Args: + pptx_path: Path to the PowerPoint file + issues_only: If True, only include shapes that have overflow or overlap issues + + Returns: + Nested dictionary with all data serialized for JSON + """ + inventory = extract_text_inventory(pptx_path, issues_only=issues_only) + + # Convert ShapeData objects to dictionaries + dict_inventory: InventoryDict = {} + for slide_key, shapes in inventory.items(): + dict_inventory[slide_key] = { + shape_key: shape_data.to_dict() for shape_key, shape_data in shapes.items() + } + + return dict_inventory + + +def save_inventory(inventory: InventoryData, output_path: Path) -> None: + """Save inventory to JSON file with proper formatting. + + Converts ShapeData objects to dictionaries for JSON serialization. + """ + # Convert ShapeData objects to dictionaries + json_inventory: InventoryDict = {} + for slide_key, shapes in inventory.items(): + json_inventory[slide_key] = { + shape_key: shape_data.to_dict() for shape_key, shape_data in shapes.items() + } + + with open(output_path, "w", encoding="utf-8") as f: + json.dump(json_inventory, f, indent=2, ensure_ascii=False) + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/pptx/scripts/rearrange.py b/src/flow/skills/pptx/scripts/rearrange.py new file mode 100755 index 0000000000000000000000000000000000000000..2519911f152beb7c2cf300354b5c4a88a1ea3c10 --- /dev/null +++ b/src/flow/skills/pptx/scripts/rearrange.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +""" +Rearrange PowerPoint slides based on a sequence of indices. + +Usage: + python rearrange.py template.pptx output.pptx 0,34,34,50,52 + +This will create output.pptx using slides from template.pptx in the specified order. +Slides can be repeated (e.g., 34 appears twice). +""" + +import argparse +import shutil +import sys +from copy import deepcopy +from pathlib import Path + +import six +from pptx import Presentation + + +def main(): + parser = argparse.ArgumentParser( + description="Rearrange PowerPoint slides based on a sequence of indices.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python rearrange.py template.pptx output.pptx 0,34,34,50,52 + Creates output.pptx using slides 0, 34 (twice), 50, and 52 from template.pptx + + python rearrange.py template.pptx output.pptx 5,3,1,2,4 + Creates output.pptx with slides reordered as specified + +Note: Slide indices are 0-based (first slide is 0, second is 1, etc.) + """, + ) + + parser.add_argument("template", help="Path to template PPTX file") + parser.add_argument("output", help="Path for output PPTX file") + parser.add_argument( + "sequence", help="Comma-separated sequence of slide indices (0-based)" + ) + + args = parser.parse_args() + + # Parse the slide sequence + try: + slide_sequence = [int(x.strip()) for x in args.sequence.split(",")] + except ValueError: + print( + "Error: Invalid sequence format. Use comma-separated integers (e.g., 0,34,34,50,52)" + ) + sys.exit(1) + + # Check template exists + template_path = Path(args.template) + if not template_path.exists(): + print(f"Error: Template file not found: {args.template}") + sys.exit(1) + + # Create output directory if needed + output_path = Path(args.output) + output_path.parent.mkdir(parents=True, exist_ok=True) + + try: + rearrange_presentation(template_path, output_path, slide_sequence) + except ValueError as e: + print(f"Error: {e}") + sys.exit(1) + except Exception as e: + print(f"Error processing presentation: {e}") + sys.exit(1) + + +def duplicate_slide(pres, index): + """Duplicate a slide in the presentation.""" + source = pres.slides[index] + + # Use source's layout to preserve formatting + new_slide = pres.slides.add_slide(source.slide_layout) + + # Collect all image and media relationships from the source slide + image_rels = {} + for rel_id, rel in six.iteritems(source.part.rels): + if "image" in rel.reltype or "media" in rel.reltype: + image_rels[rel_id] = rel + + # CRITICAL: Clear placeholder shapes to avoid duplicates + for shape in new_slide.shapes: + sp = shape.element + sp.getparent().remove(sp) + + # Copy all shapes from source + for shape in source.shapes: + el = shape.element + new_el = deepcopy(el) + new_slide.shapes._spTree.insert_element_before(new_el, "p:extLst") + + # Handle picture shapes - need to update the blip reference + # Look for all blip elements (they can be in pic or other contexts) + # Using the element's own xpath method without namespaces argument + blips = new_el.xpath(".//a:blip[@r:embed]") + for blip in blips: + old_rId = blip.get( + "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed" + ) + if old_rId in image_rels: + # Create a new relationship in the destination slide for this image + old_rel = image_rels[old_rId] + # get_or_add returns the rId directly, or adds and returns new rId + new_rId = new_slide.part.rels.get_or_add( + old_rel.reltype, old_rel._target + ) + # Update the blip's embed reference to use the new relationship ID + blip.set( + "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed", + new_rId, + ) + + # Copy any additional image/media relationships that might be referenced elsewhere + for rel_id, rel in image_rels.items(): + try: + new_slide.part.rels.get_or_add(rel.reltype, rel._target) + except Exception: + pass # Relationship might already exist + + return new_slide + + +def delete_slide(pres, index): + """Delete a slide from the presentation.""" + rId = pres.slides._sldIdLst[index].rId + pres.part.drop_rel(rId) + del pres.slides._sldIdLst[index] + + +def reorder_slides(pres, slide_index, target_index): + """Move a slide from one position to another.""" + slides = pres.slides._sldIdLst + + # Remove slide element from current position + slide_element = slides[slide_index] + slides.remove(slide_element) + + # Insert at target position + slides.insert(target_index, slide_element) + + +def rearrange_presentation(template_path, output_path, slide_sequence): + """ + Create a new presentation with slides from template in specified order. + + Args: + template_path: Path to template PPTX file + output_path: Path for output PPTX file + slide_sequence: List of slide indices (0-based) to include + """ + # Copy template to preserve dimensions and theme + if template_path != output_path: + shutil.copy2(template_path, output_path) + prs = Presentation(output_path) + else: + prs = Presentation(template_path) + + total_slides = len(prs.slides) + + # Validate indices + for idx in slide_sequence: + if idx < 0 or idx >= total_slides: + raise ValueError(f"Slide index {idx} out of range (0-{total_slides - 1})") + + # Track original slides and their duplicates + slide_map = [] # List of actual slide indices for final presentation + duplicated = {} # Track duplicates: original_idx -> [duplicate_indices] + + # Step 1: DUPLICATE repeated slides + print(f"Processing {len(slide_sequence)} slides from template...") + for i, template_idx in enumerate(slide_sequence): + if template_idx in duplicated and duplicated[template_idx]: + # Already duplicated this slide, use the duplicate + slide_map.append(duplicated[template_idx].pop(0)) + print(f" [{i}] Using duplicate of slide {template_idx}") + elif slide_sequence.count(template_idx) > 1 and template_idx not in duplicated: + # First occurrence of a repeated slide - create duplicates + slide_map.append(template_idx) + duplicates = [] + count = slide_sequence.count(template_idx) - 1 + print( + f" [{i}] Using original slide {template_idx}, creating {count} duplicate(s)" + ) + for _ in range(count): + duplicate_slide(prs, template_idx) + duplicates.append(len(prs.slides) - 1) + duplicated[template_idx] = duplicates + else: + # Unique slide or first occurrence already handled, use original + slide_map.append(template_idx) + print(f" [{i}] Using original slide {template_idx}") + + # Step 2: DELETE unwanted slides (work backwards) + slides_to_keep = set(slide_map) + print(f"\nDeleting {len(prs.slides) - len(slides_to_keep)} unused slides...") + for i in range(len(prs.slides) - 1, -1, -1): + if i not in slides_to_keep: + delete_slide(prs, i) + # Update slide_map indices after deletion + slide_map = [idx - 1 if idx > i else idx for idx in slide_map] + + # Step 3: REORDER to final sequence + print(f"Reordering {len(slide_map)} slides to final sequence...") + for target_pos in range(len(slide_map)): + # Find which slide should be at target_pos + current_pos = slide_map[target_pos] + if current_pos != target_pos: + reorder_slides(prs, current_pos, target_pos) + # Update slide_map: the move shifts other slides + for i in range(len(slide_map)): + if slide_map[i] > current_pos and slide_map[i] <= target_pos: + slide_map[i] -= 1 + elif slide_map[i] < current_pos and slide_map[i] >= target_pos: + slide_map[i] += 1 + slide_map[target_pos] = target_pos + + # Save the presentation + prs.save(output_path) + print(f"\nSaved rearranged presentation to: {output_path}") + print(f"Final presentation has {len(prs.slides)} slides") + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/pptx/scripts/replace.py b/src/flow/skills/pptx/scripts/replace.py new file mode 100755 index 0000000000000000000000000000000000000000..8f7a8b1ba38c62fa190c158f96182ed795b9ddc1 --- /dev/null +++ b/src/flow/skills/pptx/scripts/replace.py @@ -0,0 +1,385 @@ +#!/usr/bin/env python3 +"""Apply text replacements to PowerPoint presentation. + +Usage: + python replace.py + +The replacements JSON should have the structure output by inventory.py. +ALL text shapes identified by inventory.py will have their text cleared +unless "paragraphs" is specified in the replacements for that shape. +""" + +import json +import sys +from pathlib import Path +from typing import Any, Dict, List + +from inventory import InventoryData, extract_text_inventory +from pptx import Presentation +from pptx.dml.color import RGBColor +from pptx.enum.dml import MSO_THEME_COLOR +from pptx.enum.text import PP_ALIGN +from pptx.oxml.xmlchemy import OxmlElement +from pptx.util import Pt + + +def clear_paragraph_bullets(paragraph): + """Clear bullet formatting from a paragraph.""" + pPr = paragraph._element.get_or_add_pPr() + + # Remove existing bullet elements + for child in list(pPr): + if ( + child.tag.endswith("buChar") + or child.tag.endswith("buNone") + or child.tag.endswith("buAutoNum") + or child.tag.endswith("buFont") + ): + pPr.remove(child) + + return pPr + + +def apply_paragraph_properties(paragraph, para_data: Dict[str, Any]): + """Apply formatting properties to a paragraph.""" + # Get the text but don't set it on paragraph directly yet + text = para_data.get("text", "") + + # Get or create paragraph properties + pPr = clear_paragraph_bullets(paragraph) + + # Handle bullet formatting + if para_data.get("bullet", False): + level = para_data.get("level", 0) + paragraph.level = level + + # Calculate font-proportional indentation + font_size = para_data.get("font_size", 18.0) + level_indent_emu = int((font_size * (1.6 + level * 1.6)) * 12700) + hanging_indent_emu = int(-font_size * 0.8 * 12700) + + # Set indentation + pPr.attrib["marL"] = str(level_indent_emu) + pPr.attrib["indent"] = str(hanging_indent_emu) + + # Add bullet character + buChar = OxmlElement("a:buChar") + buChar.set("char", "•") + pPr.append(buChar) + + # Default to left alignment for bullets if not specified + if "alignment" not in para_data: + paragraph.alignment = PP_ALIGN.LEFT + else: + # Remove indentation for non-bullet text + pPr.attrib["marL"] = "0" + pPr.attrib["indent"] = "0" + + # Add buNone element + buNone = OxmlElement("a:buNone") + pPr.insert(0, buNone) + + # Apply alignment + if "alignment" in para_data: + alignment_map = { + "LEFT": PP_ALIGN.LEFT, + "CENTER": PP_ALIGN.CENTER, + "RIGHT": PP_ALIGN.RIGHT, + "JUSTIFY": PP_ALIGN.JUSTIFY, + } + if para_data["alignment"] in alignment_map: + paragraph.alignment = alignment_map[para_data["alignment"]] + + # Apply spacing + if "space_before" in para_data: + paragraph.space_before = Pt(para_data["space_before"]) + if "space_after" in para_data: + paragraph.space_after = Pt(para_data["space_after"]) + if "line_spacing" in para_data: + paragraph.line_spacing = Pt(para_data["line_spacing"]) + + # Apply run-level formatting + if not paragraph.runs: + run = paragraph.add_run() + run.text = text + else: + run = paragraph.runs[0] + run.text = text + + # Apply font properties + apply_font_properties(run, para_data) + + +def apply_font_properties(run, para_data: Dict[str, Any]): + """Apply font properties to a text run.""" + if "bold" in para_data: + run.font.bold = para_data["bold"] + if "italic" in para_data: + run.font.italic = para_data["italic"] + if "underline" in para_data: + run.font.underline = para_data["underline"] + if "font_size" in para_data: + run.font.size = Pt(para_data["font_size"]) + if "font_name" in para_data: + run.font.name = para_data["font_name"] + + # Apply color - prefer RGB, fall back to theme_color + if "color" in para_data: + color_hex = para_data["color"].lstrip("#") + if len(color_hex) == 6: + r = int(color_hex[0:2], 16) + g = int(color_hex[2:4], 16) + b = int(color_hex[4:6], 16) + run.font.color.rgb = RGBColor(r, g, b) + elif "theme_color" in para_data: + # Get theme color by name (e.g., "DARK_1", "ACCENT_1") + theme_name = para_data["theme_color"] + try: + run.font.color.theme_color = getattr(MSO_THEME_COLOR, theme_name) + except AttributeError: + print(f" WARNING: Unknown theme color name '{theme_name}'") + + +def detect_frame_overflow(inventory: InventoryData) -> Dict[str, Dict[str, float]]: + """Detect text overflow in shapes (text exceeding shape bounds). + + Returns dict of slide_key -> shape_key -> overflow_inches. + Only includes shapes that have text overflow. + """ + overflow_map = {} + + for slide_key, shapes_dict in inventory.items(): + for shape_key, shape_data in shapes_dict.items(): + # Check for frame overflow (text exceeding shape bounds) + if shape_data.frame_overflow_bottom is not None: + if slide_key not in overflow_map: + overflow_map[slide_key] = {} + overflow_map[slide_key][shape_key] = shape_data.frame_overflow_bottom + + return overflow_map + + +def validate_replacements(inventory: InventoryData, replacements: Dict) -> List[str]: + """Validate that all shapes in replacements exist in inventory. + + Returns list of error messages. + """ + errors = [] + + for slide_key, shapes_data in replacements.items(): + if not slide_key.startswith("slide-"): + continue + + # Check if slide exists + if slide_key not in inventory: + errors.append(f"Slide '{slide_key}' not found in inventory") + continue + + # Check each shape + for shape_key in shapes_data.keys(): + if shape_key not in inventory[slide_key]: + # Find shapes without replacements defined and show their content + unused_with_content = [] + for k in inventory[slide_key].keys(): + if k not in shapes_data: + shape_data = inventory[slide_key][k] + # Get text from paragraphs as preview + paragraphs = shape_data.paragraphs + if paragraphs and paragraphs[0].text: + first_text = paragraphs[0].text[:50] + if len(paragraphs[0].text) > 50: + first_text += "..." + unused_with_content.append(f"{k} ('{first_text}')") + else: + unused_with_content.append(k) + + errors.append( + f"Shape '{shape_key}' not found on '{slide_key}'. " + f"Shapes without replacements: {', '.join(sorted(unused_with_content)) if unused_with_content else 'none'}" + ) + + return errors + + +def check_duplicate_keys(pairs): + """Check for duplicate keys when loading JSON.""" + result = {} + for key, value in pairs: + if key in result: + raise ValueError(f"Duplicate key found in JSON: '{key}'") + result[key] = value + return result + + +def apply_replacements(pptx_file: str, json_file: str, output_file: str): + """Apply text replacements from JSON to PowerPoint presentation.""" + + # Load presentation + prs = Presentation(pptx_file) + + # Get inventory of all text shapes (returns ShapeData objects) + # Pass prs to use same Presentation instance + inventory = extract_text_inventory(Path(pptx_file), prs) + + # Detect text overflow in original presentation + original_overflow = detect_frame_overflow(inventory) + + # Load replacement data with duplicate key detection + with open(json_file, "r") as f: + replacements = json.load(f, object_pairs_hook=check_duplicate_keys) + + # Validate replacements + errors = validate_replacements(inventory, replacements) + if errors: + print("ERROR: Invalid shapes in replacement JSON:") + for error in errors: + print(f" - {error}") + print("\nPlease check the inventory and update your replacement JSON.") + print( + "You can regenerate the inventory with: python inventory.py " + ) + raise ValueError(f"Found {len(errors)} validation error(s)") + + # Track statistics + shapes_processed = 0 + shapes_cleared = 0 + shapes_replaced = 0 + + # Process each slide from inventory + for slide_key, shapes_dict in inventory.items(): + if not slide_key.startswith("slide-"): + continue + + slide_index = int(slide_key.split("-")[1]) + + if slide_index >= len(prs.slides): + print(f"Warning: Slide {slide_index} not found") + continue + + # Process each shape from inventory + for shape_key, shape_data in shapes_dict.items(): + shapes_processed += 1 + + # Get the shape directly from ShapeData + shape = shape_data.shape + if not shape: + print(f"Warning: {shape_key} has no shape reference") + continue + + # ShapeData already validates text_frame in __init__ + text_frame = shape.text_frame # type: ignore + + text_frame.clear() # type: ignore + shapes_cleared += 1 + + # Check for replacement paragraphs + replacement_shape_data = replacements.get(slide_key, {}).get(shape_key, {}) + if "paragraphs" not in replacement_shape_data: + continue + + shapes_replaced += 1 + + # Add replacement paragraphs + for i, para_data in enumerate(replacement_shape_data["paragraphs"]): + if i == 0: + p = text_frame.paragraphs[0] # type: ignore + else: + p = text_frame.add_paragraph() # type: ignore + + apply_paragraph_properties(p, para_data) + + # Check for issues after replacements + # Save to a temporary file and reload to avoid modifying the presentation during inventory + # (extract_text_inventory accesses font.color which adds empty elements) + import tempfile + + with tempfile.NamedTemporaryFile(suffix=".pptx", delete=False) as tmp: + tmp_path = Path(tmp.name) + prs.save(str(tmp_path)) + + try: + updated_inventory = extract_text_inventory(tmp_path) + updated_overflow = detect_frame_overflow(updated_inventory) + finally: + tmp_path.unlink() # Clean up temp file + + # Check if any text overflow got worse + overflow_errors = [] + for slide_key, shape_overflows in updated_overflow.items(): + for shape_key, new_overflow in shape_overflows.items(): + # Get original overflow (0 if there was no overflow before) + original = original_overflow.get(slide_key, {}).get(shape_key, 0.0) + + # Error if overflow increased + if new_overflow > original + 0.01: # Small tolerance for rounding + increase = new_overflow - original + overflow_errors.append( + f'{slide_key}/{shape_key}: overflow worsened by {increase:.2f}" ' + f'(was {original:.2f}", now {new_overflow:.2f}")' + ) + + # Collect warnings from updated shapes + warnings = [] + for slide_key, shapes_dict in updated_inventory.items(): + for shape_key, shape_data in shapes_dict.items(): + if shape_data.warnings: + for warning in shape_data.warnings: + warnings.append(f"{slide_key}/{shape_key}: {warning}") + + # Fail if there are any issues + if overflow_errors or warnings: + print("\nERROR: Issues detected in replacement output:") + if overflow_errors: + print("\nText overflow worsened:") + for error in overflow_errors: + print(f" - {error}") + if warnings: + print("\nFormatting warnings:") + for warning in warnings: + print(f" - {warning}") + print("\nPlease fix these issues before saving.") + raise ValueError( + f"Found {len(overflow_errors)} overflow error(s) and {len(warnings)} warning(s)" + ) + + # Save the presentation + prs.save(output_file) + + # Report results + print(f"Saved updated presentation to: {output_file}") + print(f"Processed {len(prs.slides)} slides") + print(f" - Shapes processed: {shapes_processed}") + print(f" - Shapes cleared: {shapes_cleared}") + print(f" - Shapes replaced: {shapes_replaced}") + + +def main(): + """Main entry point for command-line usage.""" + if len(sys.argv) != 4: + print(__doc__) + sys.exit(1) + + input_pptx = Path(sys.argv[1]) + replacements_json = Path(sys.argv[2]) + output_pptx = Path(sys.argv[3]) + + if not input_pptx.exists(): + print(f"Error: Input file '{input_pptx}' not found") + sys.exit(1) + + if not replacements_json.exists(): + print(f"Error: Replacements JSON file '{replacements_json}' not found") + sys.exit(1) + + try: + apply_replacements(str(input_pptx), str(replacements_json), str(output_pptx)) + except Exception as e: + print(f"Error applying replacements: {e}") + import traceback + + traceback.print_exc() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/pptx/scripts/thumbnail.py b/src/flow/skills/pptx/scripts/thumbnail.py new file mode 100755 index 0000000000000000000000000000000000000000..5c7fdf1977e6496cbb9ea83729e7b1df1a36c310 --- /dev/null +++ b/src/flow/skills/pptx/scripts/thumbnail.py @@ -0,0 +1,450 @@ +#!/usr/bin/env python3 +""" +Create thumbnail grids from PowerPoint presentation slides. + +Creates a grid layout of slide thumbnails with configurable columns (max 6). +Each grid contains up to cols×(cols+1) images. For presentations with more +slides, multiple numbered grid files are created automatically. + +The program outputs the names of all files created. + +Output: +- Single grid: {prefix}.jpg (if slides fit in one grid) +- Multiple grids: {prefix}-1.jpg, {prefix}-2.jpg, etc. + +Grid limits by column count: +- 3 cols: max 12 slides per grid (3×4) +- 4 cols: max 20 slides per grid (4×5) +- 5 cols: max 30 slides per grid (5×6) [default] +- 6 cols: max 42 slides per grid (6×7) + +Usage: + python thumbnail.py input.pptx [output_prefix] [--cols N] [--outline-placeholders] + +Examples: + python thumbnail.py presentation.pptx + # Creates: thumbnails.jpg (using default prefix) + # Outputs: + # Created 1 grid(s): + # - thumbnails.jpg + + python thumbnail.py large-deck.pptx grid --cols 4 + # Creates: grid-1.jpg, grid-2.jpg, grid-3.jpg + # Outputs: + # Created 3 grid(s): + # - grid-1.jpg + # - grid-2.jpg + # - grid-3.jpg + + python thumbnail.py template.pptx analysis --outline-placeholders + # Creates thumbnail grids with red outlines around text placeholders +""" + +import argparse +import subprocess +import sys +import tempfile +from pathlib import Path + +from inventory import extract_text_inventory +from PIL import Image, ImageDraw, ImageFont +from pptx import Presentation + +# Constants +THUMBNAIL_WIDTH = 300 # Fixed thumbnail width in pixels +CONVERSION_DPI = 100 # DPI for PDF to image conversion +MAX_COLS = 6 # Maximum number of columns +DEFAULT_COLS = 5 # Default number of columns +JPEG_QUALITY = 95 # JPEG compression quality + +# Grid layout constants +GRID_PADDING = 20 # Padding between thumbnails +BORDER_WIDTH = 2 # Border width around thumbnails +FONT_SIZE_RATIO = 0.12 # Font size as fraction of thumbnail width +LABEL_PADDING_RATIO = 0.4 # Label padding as fraction of font size + + +def main(): + parser = argparse.ArgumentParser( + description="Create thumbnail grids from PowerPoint slides." + ) + parser.add_argument("input", help="Input PowerPoint file (.pptx)") + parser.add_argument( + "output_prefix", + nargs="?", + default="thumbnails", + help="Output prefix for image files (default: thumbnails, will create prefix.jpg or prefix-N.jpg)", + ) + parser.add_argument( + "--cols", + type=int, + default=DEFAULT_COLS, + help=f"Number of columns (default: {DEFAULT_COLS}, max: {MAX_COLS})", + ) + parser.add_argument( + "--outline-placeholders", + action="store_true", + help="Outline text placeholders with a colored border", + ) + + args = parser.parse_args() + + # Validate columns + cols = min(args.cols, MAX_COLS) + if args.cols > MAX_COLS: + print(f"Warning: Columns limited to {MAX_COLS} (requested {args.cols})") + + # Validate input + input_path = Path(args.input) + if not input_path.exists() or input_path.suffix.lower() != ".pptx": + print(f"Error: Invalid PowerPoint file: {args.input}") + sys.exit(1) + + # Construct output path (always JPG) + output_path = Path(f"{args.output_prefix}.jpg") + + print(f"Processing: {args.input}") + + try: + with tempfile.TemporaryDirectory() as temp_dir: + # Get placeholder regions if outlining is enabled + placeholder_regions = None + slide_dimensions = None + if args.outline_placeholders: + print("Extracting placeholder regions...") + placeholder_regions, slide_dimensions = get_placeholder_regions( + input_path + ) + if placeholder_regions: + print(f"Found placeholders on {len(placeholder_regions)} slides") + + # Convert slides to images + slide_images = convert_to_images(input_path, Path(temp_dir), CONVERSION_DPI) + if not slide_images: + print("Error: No slides found") + sys.exit(1) + + print(f"Found {len(slide_images)} slides") + + # Create grids (max cols×(cols+1) images per grid) + grid_files = create_grids( + slide_images, + cols, + THUMBNAIL_WIDTH, + output_path, + placeholder_regions, + slide_dimensions, + ) + + # Print saved files + print(f"Created {len(grid_files)} grid(s):") + for grid_file in grid_files: + print(f" - {grid_file}") + + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + +def create_hidden_slide_placeholder(size): + """Create placeholder image for hidden slides.""" + img = Image.new("RGB", size, color="#F0F0F0") + draw = ImageDraw.Draw(img) + line_width = max(5, min(size) // 100) + draw.line([(0, 0), size], fill="#CCCCCC", width=line_width) + draw.line([(size[0], 0), (0, size[1])], fill="#CCCCCC", width=line_width) + return img + + +def get_placeholder_regions(pptx_path): + """Extract ALL text regions from the presentation. + + Returns a tuple of (placeholder_regions, slide_dimensions). + text_regions is a dict mapping slide indices to lists of text regions. + Each region is a dict with 'left', 'top', 'width', 'height' in inches. + slide_dimensions is a tuple of (width_inches, height_inches). + """ + prs = Presentation(str(pptx_path)) + inventory = extract_text_inventory(pptx_path, prs) + placeholder_regions = {} + + # Get actual slide dimensions in inches (EMU to inches conversion) + slide_width_inches = (prs.slide_width or 9144000) / 914400.0 + slide_height_inches = (prs.slide_height or 5143500) / 914400.0 + + for slide_key, shapes in inventory.items(): + # Extract slide index from "slide-N" format + slide_idx = int(slide_key.split("-")[1]) + regions = [] + + for shape_key, shape_data in shapes.items(): + # The inventory only contains shapes with text, so all shapes should be highlighted + regions.append( + { + "left": shape_data.left, + "top": shape_data.top, + "width": shape_data.width, + "height": shape_data.height, + } + ) + + if regions: + placeholder_regions[slide_idx] = regions + + return placeholder_regions, (slide_width_inches, slide_height_inches) + + +def convert_to_images(pptx_path, temp_dir, dpi): + """Convert PowerPoint to images via PDF, handling hidden slides.""" + # Detect hidden slides + print("Analyzing presentation...") + prs = Presentation(str(pptx_path)) + total_slides = len(prs.slides) + + # Find hidden slides (1-based indexing for display) + hidden_slides = { + idx + 1 + for idx, slide in enumerate(prs.slides) + if slide.element.get("show") == "0" + } + + print(f"Total slides: {total_slides}") + if hidden_slides: + print(f"Hidden slides: {sorted(hidden_slides)}") + + pdf_path = temp_dir / f"{pptx_path.stem}.pdf" + + # Convert to PDF + print("Converting to PDF...") + result = subprocess.run( + [ + "soffice", + "--headless", + "--convert-to", + "pdf", + "--outdir", + str(temp_dir), + str(pptx_path), + ], + capture_output=True, + text=True, + ) + if result.returncode != 0 or not pdf_path.exists(): + raise RuntimeError("PDF conversion failed") + + # Convert PDF to images + print(f"Converting to images at {dpi} DPI...") + result = subprocess.run( + ["pdftoppm", "-jpeg", "-r", str(dpi), str(pdf_path), str(temp_dir / "slide")], + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise RuntimeError("Image conversion failed") + + visible_images = sorted(temp_dir.glob("slide-*.jpg")) + + # Create full list with placeholders for hidden slides + all_images = [] + visible_idx = 0 + + # Get placeholder dimensions from first visible slide + if visible_images: + with Image.open(visible_images[0]) as img: + placeholder_size = img.size + else: + placeholder_size = (1920, 1080) + + for slide_num in range(1, total_slides + 1): + if slide_num in hidden_slides: + # Create placeholder image for hidden slide + placeholder_path = temp_dir / f"hidden-{slide_num:03d}.jpg" + placeholder_img = create_hidden_slide_placeholder(placeholder_size) + placeholder_img.save(placeholder_path, "JPEG") + all_images.append(placeholder_path) + else: + # Use the actual visible slide image + if visible_idx < len(visible_images): + all_images.append(visible_images[visible_idx]) + visible_idx += 1 + + return all_images + + +def create_grids( + image_paths, + cols, + width, + output_path, + placeholder_regions=None, + slide_dimensions=None, +): + """Create multiple thumbnail grids from slide images, max cols×(cols+1) images per grid.""" + # Maximum images per grid is cols × (cols + 1) for better proportions + max_images_per_grid = cols * (cols + 1) + grid_files = [] + + print( + f"Creating grids with {cols} columns (max {max_images_per_grid} images per grid)" + ) + + # Split images into chunks + for chunk_idx, start_idx in enumerate( + range(0, len(image_paths), max_images_per_grid) + ): + end_idx = min(start_idx + max_images_per_grid, len(image_paths)) + chunk_images = image_paths[start_idx:end_idx] + + # Create grid for this chunk + grid = create_grid( + chunk_images, cols, width, start_idx, placeholder_regions, slide_dimensions + ) + + # Generate output filename + if len(image_paths) <= max_images_per_grid: + # Single grid - use base filename without suffix + grid_filename = output_path + else: + # Multiple grids - insert index before extension with dash + stem = output_path.stem + suffix = output_path.suffix + grid_filename = output_path.parent / f"{stem}-{chunk_idx + 1}{suffix}" + + # Save grid + grid_filename.parent.mkdir(parents=True, exist_ok=True) + grid.save(str(grid_filename), quality=JPEG_QUALITY) + grid_files.append(str(grid_filename)) + + return grid_files + + +def create_grid( + image_paths, + cols, + width, + start_slide_num=0, + placeholder_regions=None, + slide_dimensions=None, +): + """Create thumbnail grid from slide images with optional placeholder outlining.""" + font_size = int(width * FONT_SIZE_RATIO) + label_padding = int(font_size * LABEL_PADDING_RATIO) + + # Get dimensions + with Image.open(image_paths[0]) as img: + aspect = img.height / img.width + height = int(width * aspect) + + # Calculate grid size + rows = (len(image_paths) + cols - 1) // cols + grid_w = cols * width + (cols + 1) * GRID_PADDING + grid_h = rows * (height + font_size + label_padding * 2) + (rows + 1) * GRID_PADDING + + # Create grid + grid = Image.new("RGB", (grid_w, grid_h), "white") + draw = ImageDraw.Draw(grid) + + # Load font with size based on thumbnail width + try: + # Use Pillow's default font with size + font = ImageFont.load_default(size=font_size) + except Exception: + # Fall back to basic default font if size parameter not supported + font = ImageFont.load_default() + + # Place thumbnails + for i, img_path in enumerate(image_paths): + row, col = i // cols, i % cols + x = col * width + (col + 1) * GRID_PADDING + y_base = ( + row * (height + font_size + label_padding * 2) + (row + 1) * GRID_PADDING + ) + + # Add label with actual slide number + label = f"{start_slide_num + i}" + bbox = draw.textbbox((0, 0), label, font=font) + text_w = bbox[2] - bbox[0] + draw.text( + (x + (width - text_w) // 2, y_base + label_padding), + label, + fill="black", + font=font, + ) + + # Add thumbnail below label with proportional spacing + y_thumbnail = y_base + label_padding + font_size + label_padding + + with Image.open(img_path) as img: + # Get original dimensions before thumbnail + orig_w, orig_h = img.size + + # Apply placeholder outlines if enabled + if placeholder_regions and (start_slide_num + i) in placeholder_regions: + # Convert to RGBA for transparency support + if img.mode != "RGBA": + img = img.convert("RGBA") + + # Get the regions for this slide + regions = placeholder_regions[start_slide_num + i] + + # Calculate scale factors using actual slide dimensions + if slide_dimensions: + slide_width_inches, slide_height_inches = slide_dimensions + else: + # Fallback: estimate from image size at CONVERSION_DPI + slide_width_inches = orig_w / CONVERSION_DPI + slide_height_inches = orig_h / CONVERSION_DPI + + x_scale = orig_w / slide_width_inches + y_scale = orig_h / slide_height_inches + + # Create a highlight overlay + overlay = Image.new("RGBA", img.size, (255, 255, 255, 0)) + overlay_draw = ImageDraw.Draw(overlay) + + # Highlight each placeholder region + for region in regions: + # Convert from inches to pixels in the original image + px_left = int(region["left"] * x_scale) + px_top = int(region["top"] * y_scale) + px_width = int(region["width"] * x_scale) + px_height = int(region["height"] * y_scale) + + # Draw highlight outline with red color and thick stroke + # Using a bright red outline instead of fill + stroke_width = max( + 5, min(orig_w, orig_h) // 150 + ) # Thicker proportional stroke width + overlay_draw.rectangle( + [(px_left, px_top), (px_left + px_width, px_top + px_height)], + outline=(255, 0, 0, 255), # Bright red, fully opaque + width=stroke_width, + ) + + # Composite the overlay onto the image using alpha blending + img = Image.alpha_composite(img, overlay) + # Convert back to RGB for JPEG saving + img = img.convert("RGB") + + img.thumbnail((width, height), Image.Resampling.LANCZOS) + w, h = img.size + tx = x + (width - w) // 2 + ty = y_thumbnail + (height - h) // 2 + grid.paste(img, (tx, ty)) + + # Add border + if BORDER_WIDTH > 0: + draw.rectangle( + [ + (tx - BORDER_WIDTH, ty - BORDER_WIDTH), + (tx + w + BORDER_WIDTH - 1, ty + h + BORDER_WIDTH - 1), + ], + outline="gray", + width=BORDER_WIDTH, + ) + + return grid + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/skill-creator/LICENSE.txt b/src/flow/skills/skill-creator/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..7a4a3ea2424c09fbe48d455aed1eaa94d9124835 --- /dev/null +++ b/src/flow/skills/skill-creator/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/flow/skills/skill-creator/SKILL.md b/src/flow/skills/skill-creator/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..b7f86598b002c99a6be96026443217b6af3c561d --- /dev/null +++ b/src/flow/skills/skill-creator/SKILL.md @@ -0,0 +1,356 @@ +--- +name: skill-creator +description: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations. +license: Complete terms in LICENSE.txt +--- + +# Skill Creator + +This skill provides guidance for creating effective skills. + +## About Skills + +Skills are modular, self-contained packages that extend Claude's capabilities by providing +specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific +domains or tasks—they transform Claude from a general-purpose agent into a specialized agent +equipped with procedural knowledge that no model can fully possess. + +### What Skills Provide + +1. Specialized workflows - Multi-step procedures for specific domains +2. Tool integrations - Instructions for working with specific file formats or APIs +3. Domain expertise - Company-specific knowledge, schemas, business logic +4. Bundled resources - Scripts, references, and assets for complex and repetitive tasks + +## Core Principles + +### Concise is Key + +The context window is a public good. Skills share the context window with everything else Claude needs: system prompt, conversation history, other Skills' metadata, and the actual user request. + +**Default assumption: Claude is already very smart.** Only add context Claude doesn't already have. Challenge each piece of information: "Does Claude really need this explanation?" and "Does this paragraph justify its token cost?" + +Prefer concise examples over verbose explanations. + +### Set Appropriate Degrees of Freedom + +Match the level of specificity to the task's fragility and variability: + +**High freedom (text-based instructions)**: Use when multiple approaches are valid, decisions depend on context, or heuristics guide the approach. + +**Medium freedom (pseudocode or scripts with parameters)**: Use when a preferred pattern exists, some variation is acceptable, or configuration affects behavior. + +**Low freedom (specific scripts, few parameters)**: Use when operations are fragile and error-prone, consistency is critical, or a specific sequence must be followed. + +Think of Claude as exploring a path: a narrow bridge with cliffs needs specific guardrails (low freedom), while an open field allows many routes (high freedom). + +### Anatomy of a Skill + +Every skill consists of a required SKILL.md file and optional bundled resources: + +``` +skill-name/ +├── SKILL.md (required) +│ ├── YAML frontmatter metadata (required) +│ │ ├── name: (required) +│ │ └── description: (required) +│ └── Markdown instructions (required) +└── Bundled Resources (optional) + ├── scripts/ - Executable code (Python/Bash/etc.) + ├── references/ - Documentation intended to be loaded into context as needed + └── assets/ - Files used in output (templates, icons, fonts, etc.) +``` + +#### SKILL.md (required) + +Every SKILL.md consists of: + +- **Frontmatter** (YAML): Contains `name` and `description` fields. These are the only fields that Claude reads to determine when the skill gets used, thus it is very important to be clear and comprehensive in describing what the skill is, and when it should be used. +- **Body** (Markdown): Instructions and guidance for using the skill. Only loaded AFTER the skill triggers (if at all). + +#### Bundled Resources (optional) + +##### Scripts (`scripts/`) + +Executable code (Python/Bash/etc.) for tasks that require deterministic reliability or are repeatedly rewritten. + +- **When to include**: When the same code is being rewritten repeatedly or deterministic reliability is needed +- **Example**: `scripts/rotate_pdf.py` for PDF rotation tasks +- **Benefits**: Token efficient, deterministic, may be executed without loading into context +- **Note**: Scripts may still need to be read by Claude for patching or environment-specific adjustments + +##### References (`references/`) + +Documentation and reference material intended to be loaded as needed into context to inform Claude's process and thinking. + +- **When to include**: For documentation that Claude should reference while working +- **Examples**: `references/finance.md` for financial schemas, `references/mnda.md` for company NDA template, `references/policies.md` for company policies, `references/api_docs.md` for API specifications +- **Use cases**: Database schemas, API documentation, domain knowledge, company policies, detailed workflow guides +- **Benefits**: Keeps SKILL.md lean, loaded only when Claude determines it's needed +- **Best practice**: If files are large (>10k words), include grep search patterns in SKILL.md +- **Avoid duplication**: Information should live in either SKILL.md or references files, not both. Prefer references files for detailed information unless it's truly core to the skill—this keeps SKILL.md lean while making information discoverable without hogging the context window. Keep only essential procedural instructions and workflow guidance in SKILL.md; move detailed reference material, schemas, and examples to references files. + +##### Assets (`assets/`) + +Files not intended to be loaded into context, but rather used within the output Claude produces. + +- **When to include**: When the skill needs files that will be used in the final output +- **Examples**: `assets/logo.png` for brand assets, `assets/slides.pptx` for PowerPoint templates, `assets/frontend-template/` for HTML/React boilerplate, `assets/font.ttf` for typography +- **Use cases**: Templates, images, icons, boilerplate code, fonts, sample documents that get copied or modified +- **Benefits**: Separates output resources from documentation, enables Claude to use files without loading them into context + +#### What to Not Include in a Skill + +A skill should only contain essential files that directly support its functionality. Do NOT create extraneous documentation or auxiliary files, including: + +- README.md +- INSTALLATION_GUIDE.md +- QUICK_REFERENCE.md +- CHANGELOG.md +- etc. + +The skill should only contain the information needed for an AI agent to do the job at hand. It should not contain auxilary context about the process that went into creating it, setup and testing procedures, user-facing documentation, etc. Creating additional documentation files just adds clutter and confusion. + +### Progressive Disclosure Design Principle + +Skills use a three-level loading system to manage context efficiently: + +1. **Metadata (name + description)** - Always in context (~100 words) +2. **SKILL.md body** - When skill triggers (<5k words) +3. **Bundled resources** - As needed by Claude (Unlimited because scripts can be executed without reading into context window) + +#### Progressive Disclosure Patterns + +Keep SKILL.md body to the essentials and under 500 lines to minimize context bloat. Split content into separate files when approaching this limit. When splitting out content into other files, it is very important to reference them from SKILL.md and describe clearly when to read them, to ensure the reader of the skill knows they exist and when to use them. + +**Key principle:** When a skill supports multiple variations, frameworks, or options, keep only the core workflow and selection guidance in SKILL.md. Move variant-specific details (patterns, examples, configuration) into separate reference files. + +**Pattern 1: High-level guide with references** + +```markdown +# PDF Processing + +## Quick start + +Extract text with pdfplumber: +[code example] + +## Advanced features + +- **Form filling**: See [FORMS.md](FORMS.md) for complete guide +- **API reference**: See [REFERENCE.md](REFERENCE.md) for all methods +- **Examples**: See [EXAMPLES.md](EXAMPLES.md) for common patterns +``` + +Claude loads FORMS.md, REFERENCE.md, or EXAMPLES.md only when needed. + +**Pattern 2: Domain-specific organization** + +For Skills with multiple domains, organize content by domain to avoid loading irrelevant context: + +``` +bigquery-skill/ +├── SKILL.md (overview and navigation) +└── reference/ + ├── finance.md (revenue, billing metrics) + ├── sales.md (opportunities, pipeline) + ├── product.md (API usage, features) + └── marketing.md (campaigns, attribution) +``` + +When a user asks about sales metrics, Claude only reads sales.md. + +Similarly, for skills supporting multiple frameworks or variants, organize by variant: + +``` +cloud-deploy/ +├── SKILL.md (workflow + provider selection) +└── references/ + ├── aws.md (AWS deployment patterns) + ├── gcp.md (GCP deployment patterns) + └── azure.md (Azure deployment patterns) +``` + +When the user chooses AWS, Claude only reads aws.md. + +**Pattern 3: Conditional details** + +Show basic content, link to advanced content: + +```markdown +# DOCX Processing + +## Creating documents + +Use docx-js for new documents. See [DOCX-JS.md](DOCX-JS.md). + +## Editing documents + +For simple edits, modify the XML directly. + +**For tracked changes**: See [REDLINING.md](REDLINING.md) +**For OOXML details**: See [OOXML.md](OOXML.md) +``` + +Claude reads REDLINING.md or OOXML.md only when the user needs those features. + +**Important guidelines:** + +- **Avoid deeply nested references** - Keep references one level deep from SKILL.md. All reference files should link directly from SKILL.md. +- **Structure longer reference files** - For files longer than 100 lines, include a table of contents at the top so Claude can see the full scope when previewing. + +## Skill Creation Process + +Skill creation involves these steps: + +1. Understand the skill with concrete examples +2. Plan reusable skill contents (scripts, references, assets) +3. Initialize the skill (run init_skill.py) +4. Edit the skill (implement resources and write SKILL.md) +5. Package the skill (run package_skill.py) +6. Iterate based on real usage + +Follow these steps in order, skipping only if there is a clear reason why they are not applicable. + +### Step 1: Understanding the Skill with Concrete Examples + +Skip this step only when the skill's usage patterns are already clearly understood. It remains valuable even when working with an existing skill. + +To create an effective skill, clearly understand concrete examples of how the skill will be used. This understanding can come from either direct user examples or generated examples that are validated with user feedback. + +For example, when building an image-editor skill, relevant questions include: + +- "What functionality should the image-editor skill support? Editing, rotating, anything else?" +- "Can you give some examples of how this skill would be used?" +- "I can imagine users asking for things like 'Remove the red-eye from this image' or 'Rotate this image'. Are there other ways you imagine this skill being used?" +- "What would a user say that should trigger this skill?" + +To avoid overwhelming users, avoid asking too many questions in a single message. Start with the most important questions and follow up as needed for better effectiveness. + +Conclude this step when there is a clear sense of the functionality the skill should support. + +### Step 2: Planning the Reusable Skill Contents + +To turn concrete examples into an effective skill, analyze each example by: + +1. Considering how to execute on the example from scratch +2. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly + +Example: When building a `pdf-editor` skill to handle queries like "Help me rotate this PDF," the analysis shows: + +1. Rotating a PDF requires re-writing the same code each time +2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill + +Example: When designing a `frontend-webapp-builder` skill for queries like "Build me a todo app" or "Build me a dashboard to track my steps," the analysis shows: + +1. Writing a frontend webapp requires the same boilerplate HTML/React each time +2. An `assets/hello-world/` template containing the boilerplate HTML/React project files would be helpful to store in the skill + +Example: When building a `big-query` skill to handle queries like "How many users have logged in today?" the analysis shows: + +1. Querying BigQuery requires re-discovering the table schemas and relationships each time +2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill + +To establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets. + +### Step 3: Initializing the Skill + +At this point, it is time to actually create the skill. + +Skip this step only if the skill being developed already exists, and iteration or packaging is needed. In this case, continue to the next step. + +When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable. + +Usage: + +```bash +scripts/init_skill.py --path +``` + +The script: + +- Creates the skill directory at the specified path +- Generates a SKILL.md template with proper frontmatter and TODO placeholders +- Creates example resource directories: `scripts/`, `references/`, and `assets/` +- Adds example files in each directory that can be customized or deleted + +After initialization, customize or remove the generated SKILL.md and example files as needed. + +### Step 4: Edit the Skill + +When editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Claude to use. Include information that would be beneficial and non-obvious to Claude. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Claude instance execute these tasks more effectively. + +#### Learn Proven Design Patterns + +Consult these helpful guides based on your skill's needs: + +- **Multi-step processes**: See references/workflows.md for sequential workflows and conditional logic +- **Specific output formats or quality standards**: See references/output-patterns.md for template and example patterns + +These files contain established best practices for effective skill design. + +#### Start with Reusable Skill Contents + +To begin implementation, start with the reusable resources identified above: `scripts/`, `references/`, and `assets/` files. Note that this step may require user input. For example, when implementing a `brand-guidelines` skill, the user may need to provide brand assets or templates to store in `assets/`, or documentation to store in `references/`. + +Added scripts must be tested by actually running them to ensure there are no bugs and that the output matches what is expected. If there are many similar scripts, only a representative sample needs to be tested to ensure confidence that they all work while balancing time to completion. + +Any example files and directories not needed for the skill should be deleted. The initialization script creates example files in `scripts/`, `references/`, and `assets/` to demonstrate structure, but most skills won't need all of them. + +#### Update SKILL.md + +**Writing Guidelines:** Always use imperative/infinitive form. + +##### Frontmatter + +Write the YAML frontmatter with `name` and `description`: + +- `name`: The skill name +- `description`: This is the primary triggering mechanism for your skill, and helps Claude understand when to use the skill. + - Include both what the Skill does and specific triggers/contexts for when to use it. + - Include all "when to use" information here - Not in the body. The body is only loaded after triggering, so "When to Use This Skill" sections in the body are not helpful to Claude. + - Example description for a `docx` skill: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks" + +Do not include any other fields in YAML frontmatter. + +##### Body + +Write instructions for using the skill and its bundled resources. + +### Step 5: Packaging a Skill + +Once development of the skill is complete, it must be packaged into a distributable .skill file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements: + +```bash +scripts/package_skill.py +``` + +Optional output directory specification: + +```bash +scripts/package_skill.py ./dist +``` + +The packaging script will: + +1. **Validate** the skill automatically, checking: + + - YAML frontmatter format and required fields + - Skill naming conventions and directory structure + - Description completeness and quality + - File organization and resource references + +2. **Package** the skill if validation passes, creating a .skill file named after the skill (e.g., `my-skill.skill`) that includes all files and maintains the proper directory structure for distribution. The .skill file is a zip file with a .skill extension. + +If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again. + +### Step 6: Iterate + +After testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed. + +**Iteration workflow:** + +1. Use the skill on real tasks +2. Notice struggles or inefficiencies +3. Identify how SKILL.md or bundled resources should be updated +4. Implement changes and test again diff --git a/src/flow/skills/skill-creator/references/output-patterns.md b/src/flow/skills/skill-creator/references/output-patterns.md new file mode 100644 index 0000000000000000000000000000000000000000..073ddda5f03989b1731ad29b9df7248279e9eef0 --- /dev/null +++ b/src/flow/skills/skill-creator/references/output-patterns.md @@ -0,0 +1,82 @@ +# Output Patterns + +Use these patterns when skills need to produce consistent, high-quality output. + +## Template Pattern + +Provide templates for output format. Match the level of strictness to your needs. + +**For strict requirements (like API responses or data formats):** + +```markdown +## Report structure + +ALWAYS use this exact template structure: + +# [Analysis Title] + +## Executive summary +[One-paragraph overview of key findings] + +## Key findings +- Finding 1 with supporting data +- Finding 2 with supporting data +- Finding 3 with supporting data + +## Recommendations +1. Specific actionable recommendation +2. Specific actionable recommendation +``` + +**For flexible guidance (when adaptation is useful):** + +```markdown +## Report structure + +Here is a sensible default format, but use your best judgment: + +# [Analysis Title] + +## Executive summary +[Overview] + +## Key findings +[Adapt sections based on what you discover] + +## Recommendations +[Tailor to the specific context] + +Adjust sections as needed for the specific analysis type. +``` + +## Examples Pattern + +For skills where output quality depends on seeing examples, provide input/output pairs: + +```markdown +## Commit message format + +Generate commit messages following these examples: + +**Example 1:** +Input: Added user authentication with JWT tokens +Output: +``` +feat(auth): implement JWT-based authentication + +Add login endpoint and token validation middleware +``` + +**Example 2:** +Input: Fixed bug where dates displayed incorrectly in reports +Output: +``` +fix(reports): correct date formatting in timezone conversion + +Use UTC timestamps consistently across report generation +``` + +Follow this style: type(scope): brief description, then detailed explanation. +``` + +Examples help Claude understand the desired style and level of detail more clearly than descriptions alone. diff --git a/src/flow/skills/skill-creator/references/workflows.md b/src/flow/skills/skill-creator/references/workflows.md new file mode 100644 index 0000000000000000000000000000000000000000..a350c3cc81367fda7acfea9084aaa13a5d83c054 --- /dev/null +++ b/src/flow/skills/skill-creator/references/workflows.md @@ -0,0 +1,28 @@ +# Workflow Patterns + +## Sequential Workflows + +For complex tasks, break operations into clear, sequential steps. It is often helpful to give Claude an overview of the process towards the beginning of SKILL.md: + +```markdown +Filling a PDF form involves these steps: + +1. Analyze the form (run analyze_form.py) +2. Create field mapping (edit fields.json) +3. Validate mapping (run validate_fields.py) +4. Fill the form (run fill_form.py) +5. Verify output (run verify_output.py) +``` + +## Conditional Workflows + +For tasks with branching logic, guide Claude through decision points: + +```markdown +1. Determine the modification type: + **Creating new content?** → Follow "Creation workflow" below + **Editing existing content?** → Follow "Editing workflow" below + +2. Creation workflow: [steps] +3. Editing workflow: [steps] +``` \ No newline at end of file diff --git a/src/flow/skills/skill-creator/scripts/init_skill.py b/src/flow/skills/skill-creator/scripts/init_skill.py new file mode 100755 index 0000000000000000000000000000000000000000..329ad4e5a71546b2c455f8e08c98b32ecdd3c1e3 --- /dev/null +++ b/src/flow/skills/skill-creator/scripts/init_skill.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +""" +Skill Initializer - Creates a new skill from template + +Usage: + init_skill.py --path + +Examples: + init_skill.py my-new-skill --path skills/public + init_skill.py my-api-helper --path skills/private + init_skill.py custom-skill --path /custom/location +""" + +import sys +from pathlib import Path + + +SKILL_TEMPLATE = """--- +name: {skill_name} +description: [TODO: Complete and informative explanation of what the skill does and when to use it. Include WHEN to use this skill - specific scenarios, file types, or tasks that trigger it.] +--- + +# {skill_title} + +## Overview + +[TODO: 1-2 sentences explaining what this skill enables] + +## Structuring This Skill + +[TODO: Choose the structure that best fits this skill's purpose. Common patterns: + +**1. Workflow-Based** (best for sequential processes) +- Works well when there are clear step-by-step procedures +- Example: DOCX skill with "Workflow Decision Tree" → "Reading" → "Creating" → "Editing" +- Structure: ## Overview → ## Workflow Decision Tree → ## Step 1 → ## Step 2... + +**2. Task-Based** (best for tool collections) +- Works well when the skill offers different operations/capabilities +- Example: PDF skill with "Quick Start" → "Merge PDFs" → "Split PDFs" → "Extract Text" +- Structure: ## Overview → ## Quick Start → ## Task Category 1 → ## Task Category 2... + +**3. Reference/Guidelines** (best for standards or specifications) +- Works well for brand guidelines, coding standards, or requirements +- Example: Brand styling with "Brand Guidelines" → "Colors" → "Typography" → "Features" +- Structure: ## Overview → ## Guidelines → ## Specifications → ## Usage... + +**4. Capabilities-Based** (best for integrated systems) +- Works well when the skill provides multiple interrelated features +- Example: Product Management with "Core Capabilities" → numbered capability list +- Structure: ## Overview → ## Core Capabilities → ### 1. Feature → ### 2. Feature... + +Patterns can be mixed and matched as needed. Most skills combine patterns (e.g., start with task-based, add workflow for complex operations). + +Delete this entire "Structuring This Skill" section when done - it's just guidance.] + +## [TODO: Replace with the first main section based on chosen structure] + +[TODO: Add content here. See examples in existing skills: +- Code samples for technical skills +- Decision trees for complex workflows +- Concrete examples with realistic user requests +- References to scripts/templates/references as needed] + +## Resources + +This skill includes example resource directories that demonstrate how to organize different types of bundled resources: + +### scripts/ +Executable code (Python/Bash/etc.) that can be run directly to perform specific operations. + +**Examples from other skills:** +- PDF skill: `fill_fillable_fields.py`, `extract_form_field_info.py` - utilities for PDF manipulation +- DOCX skill: `document.py`, `utilities.py` - Python modules for document processing + +**Appropriate for:** Python scripts, shell scripts, or any executable code that performs automation, data processing, or specific operations. + +**Note:** Scripts may be executed without loading into context, but can still be read by Claude for patching or environment adjustments. + +### references/ +Documentation and reference material intended to be loaded into context to inform Claude's process and thinking. + +**Examples from other skills:** +- Product management: `communication.md`, `context_building.md` - detailed workflow guides +- BigQuery: API reference documentation and query examples +- Finance: Schema documentation, company policies + +**Appropriate for:** In-depth documentation, API references, database schemas, comprehensive guides, or any detailed information that Claude should reference while working. + +### assets/ +Files not intended to be loaded into context, but rather used within the output Claude produces. + +**Examples from other skills:** +- Brand styling: PowerPoint template files (.pptx), logo files +- Frontend builder: HTML/React boilerplate project directories +- Typography: Font files (.ttf, .woff2) + +**Appropriate for:** Templates, boilerplate code, document templates, images, icons, fonts, or any files meant to be copied or used in the final output. + +--- + +**Any unneeded directories can be deleted.** Not every skill requires all three types of resources. +""" + +EXAMPLE_SCRIPT = '''#!/usr/bin/env python3 +""" +Example helper script for {skill_name} + +This is a placeholder script that can be executed directly. +Replace with actual implementation or delete if not needed. + +Example real scripts from other skills: +- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields +- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images +""" + +def main(): + print("This is an example script for {skill_name}") + # TODO: Add actual script logic here + # This could be data processing, file conversion, API calls, etc. + +if __name__ == "__main__": + main() +''' + +EXAMPLE_REFERENCE = """# Reference Documentation for {skill_title} + +This is a placeholder for detailed reference documentation. +Replace with actual reference content or delete if not needed. + +Example real reference docs from other skills: +- product-management/references/communication.md - Comprehensive guide for status updates +- product-management/references/context_building.md - Deep-dive on gathering context +- bigquery/references/ - API references and query examples + +## When Reference Docs Are Useful + +Reference docs are ideal for: +- Comprehensive API documentation +- Detailed workflow guides +- Complex multi-step processes +- Information too lengthy for main SKILL.md +- Content that's only needed for specific use cases + +## Structure Suggestions + +### API Reference Example +- Overview +- Authentication +- Endpoints with examples +- Error codes +- Rate limits + +### Workflow Guide Example +- Prerequisites +- Step-by-step instructions +- Common patterns +- Troubleshooting +- Best practices +""" + +EXAMPLE_ASSET = """# Example Asset File + +This placeholder represents where asset files would be stored. +Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed. + +Asset files are NOT intended to be loaded into context, but rather used within +the output Claude produces. + +Example asset files from other skills: +- Brand guidelines: logo.png, slides_template.pptx +- Frontend builder: hello-world/ directory with HTML/React boilerplate +- Typography: custom-font.ttf, font-family.woff2 +- Data: sample_data.csv, test_dataset.json + +## Common Asset Types + +- Templates: .pptx, .docx, boilerplate directories +- Images: .png, .jpg, .svg, .gif +- Fonts: .ttf, .otf, .woff, .woff2 +- Boilerplate code: Project directories, starter files +- Icons: .ico, .svg +- Data files: .csv, .json, .xml, .yaml + +Note: This is a text placeholder. Actual assets can be any file type. +""" + + +def title_case_skill_name(skill_name): + """Convert hyphenated skill name to Title Case for display.""" + return ' '.join(word.capitalize() for word in skill_name.split('-')) + + +def init_skill(skill_name, path): + """ + Initialize a new skill directory with template SKILL.md. + + Args: + skill_name: Name of the skill + path: Path where the skill directory should be created + + Returns: + Path to created skill directory, or None if error + """ + # Determine skill directory path + skill_dir = Path(path).resolve() / skill_name + + # Check if directory already exists + if skill_dir.exists(): + print(f"❌ Error: Skill directory already exists: {skill_dir}") + return None + + # Create skill directory + try: + skill_dir.mkdir(parents=True, exist_ok=False) + print(f"✅ Created skill directory: {skill_dir}") + except Exception as e: + print(f"❌ Error creating directory: {e}") + return None + + # Create SKILL.md from template + skill_title = title_case_skill_name(skill_name) + skill_content = SKILL_TEMPLATE.format( + skill_name=skill_name, + skill_title=skill_title + ) + + skill_md_path = skill_dir / 'SKILL.md' + try: + skill_md_path.write_text(skill_content) + print("✅ Created SKILL.md") + except Exception as e: + print(f"❌ Error creating SKILL.md: {e}") + return None + + # Create resource directories with example files + try: + # Create scripts/ directory with example script + scripts_dir = skill_dir / 'scripts' + scripts_dir.mkdir(exist_ok=True) + example_script = scripts_dir / 'example.py' + example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name)) + example_script.chmod(0o755) + print("✅ Created scripts/example.py") + + # Create references/ directory with example reference doc + references_dir = skill_dir / 'references' + references_dir.mkdir(exist_ok=True) + example_reference = references_dir / 'api_reference.md' + example_reference.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title)) + print("✅ Created references/api_reference.md") + + # Create assets/ directory with example asset placeholder + assets_dir = skill_dir / 'assets' + assets_dir.mkdir(exist_ok=True) + example_asset = assets_dir / 'example_asset.txt' + example_asset.write_text(EXAMPLE_ASSET) + print("✅ Created assets/example_asset.txt") + except Exception as e: + print(f"❌ Error creating resource directories: {e}") + return None + + # Print next steps + print(f"\n✅ Skill '{skill_name}' initialized successfully at {skill_dir}") + print("\nNext steps:") + print("1. Edit SKILL.md to complete the TODO items and update the description") + print("2. Customize or delete the example files in scripts/, references/, and assets/") + print("3. Run the validator when ready to check the skill structure") + + return skill_dir + + +def main(): + if len(sys.argv) < 4 or sys.argv[2] != '--path': + print("Usage: init_skill.py --path ") + print("\nSkill name requirements:") + print(" - Hyphen-case identifier (e.g., 'data-analyzer')") + print(" - Lowercase letters, digits, and hyphens only") + print(" - Max 40 characters") + print(" - Must match directory name exactly") + print("\nExamples:") + print(" init_skill.py my-new-skill --path skills/public") + print(" init_skill.py my-api-helper --path skills/private") + print(" init_skill.py custom-skill --path /custom/location") + sys.exit(1) + + skill_name = sys.argv[1] + path = sys.argv[3] + + print(f"🚀 Initializing skill: {skill_name}") + print(f" Location: {path}") + print() + + result = init_skill(skill_name, path) + + if result: + sys.exit(0) + else: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/skill-creator/scripts/package_skill.py b/src/flow/skills/skill-creator/scripts/package_skill.py new file mode 100755 index 0000000000000000000000000000000000000000..5cd36cb16e1314f2ab87d50aaedbc9f26925dac1 --- /dev/null +++ b/src/flow/skills/skill-creator/scripts/package_skill.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +""" +Skill Packager - Creates a distributable .skill file of a skill folder + +Usage: + python utils/package_skill.py [output-directory] + +Example: + python utils/package_skill.py skills/public/my-skill + python utils/package_skill.py skills/public/my-skill ./dist +""" + +import sys +import zipfile +from pathlib import Path +from quick_validate import validate_skill + + +def package_skill(skill_path, output_dir=None): + """ + Package a skill folder into a .skill file. + + Args: + skill_path: Path to the skill folder + output_dir: Optional output directory for the .skill file (defaults to current directory) + + Returns: + Path to the created .skill file, or None if error + """ + skill_path = Path(skill_path).resolve() + + # Validate skill folder exists + if not skill_path.exists(): + print(f"❌ Error: Skill folder not found: {skill_path}") + return None + + if not skill_path.is_dir(): + print(f"❌ Error: Path is not a directory: {skill_path}") + return None + + # Validate SKILL.md exists + skill_md = skill_path / "SKILL.md" + if not skill_md.exists(): + print(f"❌ Error: SKILL.md not found in {skill_path}") + return None + + # Run validation before packaging + print("🔍 Validating skill...") + valid, message = validate_skill(skill_path) + if not valid: + print(f"❌ Validation failed: {message}") + print(" Please fix the validation errors before packaging.") + return None + print(f"✅ {message}\n") + + # Determine output location + skill_name = skill_path.name + if output_dir: + output_path = Path(output_dir).resolve() + output_path.mkdir(parents=True, exist_ok=True) + else: + output_path = Path.cwd() + + skill_filename = output_path / f"{skill_name}.skill" + + # Create the .skill file (zip format) + try: + with zipfile.ZipFile(skill_filename, 'w', zipfile.ZIP_DEFLATED) as zipf: + # Walk through the skill directory + for file_path in skill_path.rglob('*'): + if file_path.is_file(): + # Calculate the relative path within the zip + arcname = file_path.relative_to(skill_path.parent) + zipf.write(file_path, arcname) + print(f" Added: {arcname}") + + print(f"\n✅ Successfully packaged skill to: {skill_filename}") + return skill_filename + + except Exception as e: + print(f"❌ Error creating .skill file: {e}") + return None + + +def main(): + if len(sys.argv) < 2: + print("Usage: python utils/package_skill.py [output-directory]") + print("\nExample:") + print(" python utils/package_skill.py skills/public/my-skill") + print(" python utils/package_skill.py skills/public/my-skill ./dist") + sys.exit(1) + + skill_path = sys.argv[1] + output_dir = sys.argv[2] if len(sys.argv) > 2 else None + + print(f"📦 Packaging skill: {skill_path}") + if output_dir: + print(f" Output directory: {output_dir}") + print() + + result = package_skill(skill_path, output_dir) + + if result: + sys.exit(0) + else: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src/flow/skills/skill-creator/scripts/quick_validate.py b/src/flow/skills/skill-creator/scripts/quick_validate.py new file mode 100755 index 0000000000000000000000000000000000000000..d9fbeb75ee10bd8f89c0ee9b6716867125820c7e --- /dev/null +++ b/src/flow/skills/skill-creator/scripts/quick_validate.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +Quick validation script for skills - minimal version +""" + +import sys +import os +import re +import yaml +from pathlib import Path + +def validate_skill(skill_path): + """Basic validation of a skill""" + skill_path = Path(skill_path) + + # Check SKILL.md exists + skill_md = skill_path / 'SKILL.md' + if not skill_md.exists(): + return False, "SKILL.md not found" + + # Read and validate frontmatter + content = skill_md.read_text() + if not content.startswith('---'): + return False, "No YAML frontmatter found" + + # Extract frontmatter + match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL) + if not match: + return False, "Invalid frontmatter format" + + frontmatter_text = match.group(1) + + # Parse YAML frontmatter + try: + frontmatter = yaml.safe_load(frontmatter_text) + if not isinstance(frontmatter, dict): + return False, "Frontmatter must be a YAML dictionary" + except yaml.YAMLError as e: + return False, f"Invalid YAML in frontmatter: {e}" + + # Define allowed properties + ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata'} + + # Check for unexpected properties (excluding nested keys under metadata) + unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES + if unexpected_keys: + return False, ( + f"Unexpected key(s) in SKILL.md frontmatter: {', '.join(sorted(unexpected_keys))}. " + f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}" + ) + + # Check required fields + if 'name' not in frontmatter: + return False, "Missing 'name' in frontmatter" + if 'description' not in frontmatter: + return False, "Missing 'description' in frontmatter" + + # Extract name for validation + name = frontmatter.get('name', '') + if not isinstance(name, str): + return False, f"Name must be a string, got {type(name).__name__}" + name = name.strip() + if name: + # Check naming convention (hyphen-case: lowercase with hyphens) + if not re.match(r'^[a-z0-9-]+$', name): + return False, f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)" + if name.startswith('-') or name.endswith('-') or '--' in name: + return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens" + # Check name length (max 64 characters per spec) + if len(name) > 64: + return False, f"Name is too long ({len(name)} characters). Maximum is 64 characters." + + # Extract and validate description + description = frontmatter.get('description', '') + if not isinstance(description, str): + return False, f"Description must be a string, got {type(description).__name__}" + description = description.strip() + if description: + # Check for angle brackets + if '<' in description or '>' in description: + return False, "Description cannot contain angle brackets (< or >)" + # Check description length (max 1024 characters per spec) + if len(description) > 1024: + return False, f"Description is too long ({len(description)} characters). Maximum is 1024 characters." + + return True, "Skill is valid!" + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python quick_validate.py ") + sys.exit(1) + + valid, message = validate_skill(sys.argv[1]) + print(message) + sys.exit(0 if valid else 1) \ No newline at end of file diff --git a/src/flow/skills/webapp-testing/LICENSE.txt b/src/flow/skills/webapp-testing/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..7a4a3ea2424c09fbe48d455aed1eaa94d9124835 --- /dev/null +++ b/src/flow/skills/webapp-testing/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/flow/skills/webapp-testing/SKILL.md b/src/flow/skills/webapp-testing/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..4726215301db64a0cc4d41fc3219c61f37a30f4a --- /dev/null +++ b/src/flow/skills/webapp-testing/SKILL.md @@ -0,0 +1,96 @@ +--- +name: webapp-testing +description: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. +license: Complete terms in LICENSE.txt +--- + +# Web Application Testing + +To test local web applications, write native Python Playwright scripts. + +**Helper Scripts Available**: +- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers) + +**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window. + +## Decision Tree: Choosing Your Approach + +``` +User task → Is it static HTML? + ├─ Yes → Read HTML file directly to identify selectors + │ ├─ Success → Write Playwright script using selectors + │ └─ Fails/Incomplete → Treat as dynamic (below) + │ + └─ No (dynamic webapp) → Is the server already running? + ├─ No → Run: python scripts/with_server.py --help + │ Then use the helper + write simplified Playwright script + │ + └─ Yes → Reconnaissance-then-action: + 1. Navigate and wait for networkidle + 2. Take screenshot or inspect DOM + 3. Identify selectors from rendered state + 4. Execute actions with discovered selectors +``` + +## Example: Using with_server.py + +To start a server, run `--help` first, then use the helper: + +**Single server:** +```bash +python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py +``` + +**Multiple servers (e.g., backend + frontend):** +```bash +python scripts/with_server.py \ + --server "cd backend && python server.py" --port 3000 \ + --server "cd frontend && npm run dev" --port 5173 \ + -- python your_automation.py +``` + +To create an automation script, include only Playwright logic (servers are managed automatically): +```python +from playwright.sync_api import sync_playwright + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode + page = browser.new_page() + page.goto('http://localhost:5173') # Server already running and ready + page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute + # ... your automation logic + browser.close() +``` + +## Reconnaissance-Then-Action Pattern + +1. **Inspect rendered DOM**: + ```python + page.screenshot(path='/tmp/inspect.png', full_page=True) + content = page.content() + page.locator('button').all() + ``` + +2. **Identify selectors** from inspection results + +3. **Execute actions** using discovered selectors + +## Common Pitfall + +❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps +✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection + +## Best Practices + +- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly. +- Use `sync_playwright()` for synchronous scripts +- Always close the browser when done +- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs +- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()` + +## Reference Files + +- **examples/** - Examples showing common patterns: + - `element_discovery.py` - Discovering buttons, links, and inputs on a page + - `static_html_automation.py` - Using file:// URLs for local HTML + - `console_logging.py` - Capturing console logs during automation \ No newline at end of file diff --git a/src/flow/skills/webapp-testing/examples/console_logging.py b/src/flow/skills/webapp-testing/examples/console_logging.py new file mode 100644 index 0000000000000000000000000000000000000000..9329b5e232ed53d944bfe95e750070117d31b9ab --- /dev/null +++ b/src/flow/skills/webapp-testing/examples/console_logging.py @@ -0,0 +1,35 @@ +from playwright.sync_api import sync_playwright + +# Example: Capturing console logs during browser automation + +url = 'http://localhost:5173' # Replace with your URL + +console_logs = [] + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page(viewport={'width': 1920, 'height': 1080}) + + # Set up console log capture + def handle_console_message(msg): + console_logs.append(f"[{msg.type}] {msg.text}") + print(f"Console: [{msg.type}] {msg.text}") + + page.on("console", handle_console_message) + + # Navigate to page + page.goto(url) + page.wait_for_load_state('networkidle') + + # Interact with the page (triggers console logs) + page.click('text=Dashboard') + page.wait_for_timeout(1000) + + browser.close() + +# Save console logs to file +with open('/mnt/user-data/outputs/console.log', 'w') as f: + f.write('\n'.join(console_logs)) + +print(f"\nCaptured {len(console_logs)} console messages") +print(f"Logs saved to: /mnt/user-data/outputs/console.log") \ No newline at end of file diff --git a/src/flow/skills/webapp-testing/examples/element_discovery.py b/src/flow/skills/webapp-testing/examples/element_discovery.py new file mode 100644 index 0000000000000000000000000000000000000000..917ba72f52421a1b324f93ae8e38af72010a87b9 --- /dev/null +++ b/src/flow/skills/webapp-testing/examples/element_discovery.py @@ -0,0 +1,40 @@ +from playwright.sync_api import sync_playwright + +# Example: Discovering buttons and other elements on a page + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page() + + # Navigate to page and wait for it to fully load + page.goto('http://localhost:5173') + page.wait_for_load_state('networkidle') + + # Discover all buttons on the page + buttons = page.locator('button').all() + print(f"Found {len(buttons)} buttons:") + for i, button in enumerate(buttons): + text = button.inner_text() if button.is_visible() else "[hidden]" + print(f" [{i}] {text}") + + # Discover links + links = page.locator('a[href]').all() + print(f"\nFound {len(links)} links:") + for link in links[:5]: # Show first 5 + text = link.inner_text().strip() + href = link.get_attribute('href') + print(f" - {text} -> {href}") + + # Discover input fields + inputs = page.locator('input, textarea, select').all() + print(f"\nFound {len(inputs)} input fields:") + for input_elem in inputs: + name = input_elem.get_attribute('name') or input_elem.get_attribute('id') or "[unnamed]" + input_type = input_elem.get_attribute('type') or 'text' + print(f" - {name} ({input_type})") + + # Take screenshot for visual reference + page.screenshot(path='/tmp/page_discovery.png', full_page=True) + print("\nScreenshot saved to /tmp/page_discovery.png") + + browser.close() \ No newline at end of file diff --git a/src/flow/skills/webapp-testing/examples/static_html_automation.py b/src/flow/skills/webapp-testing/examples/static_html_automation.py new file mode 100644 index 0000000000000000000000000000000000000000..90bbedcc08cdb345c74fb938ebad02c0baf56603 --- /dev/null +++ b/src/flow/skills/webapp-testing/examples/static_html_automation.py @@ -0,0 +1,33 @@ +from playwright.sync_api import sync_playwright +import os + +# Example: Automating interaction with static HTML files using file:// URLs + +html_file_path = os.path.abspath('path/to/your/file.html') +file_url = f'file://{html_file_path}' + +with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page(viewport={'width': 1920, 'height': 1080}) + + # Navigate to local HTML file + page.goto(file_url) + + # Take screenshot + page.screenshot(path='/mnt/user-data/outputs/static_page.png', full_page=True) + + # Interact with elements + page.click('text=Click Me') + page.fill('#name', 'John Doe') + page.fill('#email', 'john@example.com') + + # Submit form + page.click('button[type="submit"]') + page.wait_for_timeout(500) + + # Take final screenshot + page.screenshot(path='/mnt/user-data/outputs/after_submit.png', full_page=True) + + browser.close() + +print("Static HTML automation completed!") \ No newline at end of file diff --git a/src/flow/skills/webapp-testing/scripts/with_server.py b/src/flow/skills/webapp-testing/scripts/with_server.py new file mode 100755 index 0000000000000000000000000000000000000000..431f2eba16b268b7f3e2ae4daae9db41c0289b6d --- /dev/null +++ b/src/flow/skills/webapp-testing/scripts/with_server.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Start one or more servers, wait for them to be ready, run a command, then clean up. + +Usage: + # Single server + python scripts/with_server.py --server "npm run dev" --port 5173 -- python automation.py + python scripts/with_server.py --server "npm start" --port 3000 -- python test.py + + # Multiple servers + python scripts/with_server.py \ + --server "cd backend && python server.py" --port 3000 \ + --server "cd frontend && npm run dev" --port 5173 \ + -- python test.py +""" + +import subprocess +import socket +import time +import sys +import argparse + +def is_server_ready(port, timeout=30): + """Wait for server to be ready by polling the port.""" + start_time = time.time() + while time.time() - start_time < timeout: + try: + with socket.create_connection(('localhost', port), timeout=1): + return True + except (socket.error, ConnectionRefusedError): + time.sleep(0.5) + return False + + +def main(): + parser = argparse.ArgumentParser(description='Run command with one or more servers') + parser.add_argument('--server', action='append', dest='servers', required=True, help='Server command (can be repeated)') + parser.add_argument('--port', action='append', dest='ports', type=int, required=True, help='Port for each server (must match --server count)') + parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds per server (default: 30)') + parser.add_argument('command', nargs=argparse.REMAINDER, help='Command to run after server(s) ready') + + args = parser.parse_args() + + # Remove the '--' separator if present + if args.command and args.command[0] == '--': + args.command = args.command[1:] + + if not args.command: + print("Error: No command specified to run") + sys.exit(1) + + # Parse server configurations + if len(args.servers) != len(args.ports): + print("Error: Number of --server and --port arguments must match") + sys.exit(1) + + servers = [] + for cmd, port in zip(args.servers, args.ports): + servers.append({'cmd': cmd, 'port': port}) + + server_processes = [] + + try: + # Start all servers + for i, server in enumerate(servers): + print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}") + + # Use shell=True to support commands with cd and && + process = subprocess.Popen( + server['cmd'], + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + server_processes.append(process) + + # Wait for this server to be ready + print(f"Waiting for server on port {server['port']}...") + if not is_server_ready(server['port'], timeout=args.timeout): + raise RuntimeError(f"Server failed to start on port {server['port']} within {args.timeout}s") + + print(f"Server ready on port {server['port']}") + + print(f"\nAll {len(servers)} server(s) ready") + + # Run the command + print(f"Running: {' '.join(args.command)}\n") + result = subprocess.run(args.command) + sys.exit(result.returncode) + + finally: + # Clean up all servers + print(f"\nStopping {len(server_processes)} server(s)...") + for i, process in enumerate(server_processes): + try: + process.terminate() + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + print(f"Server {i+1} stopped") + print("All servers stopped") + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/flow/skills/xlsx/LICENSE.txt b/src/flow/skills/xlsx/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..c55ab42224874608473643de0a85736b7fec0730 --- /dev/null +++ b/src/flow/skills/xlsx/LICENSE.txt @@ -0,0 +1,30 @@ +© 2025 Anthropic, PBC. All rights reserved. + +LICENSE: Use of these materials (including all code, prompts, assets, files, +and other components of this Skill) is governed by your agreement with +Anthropic regarding use of Anthropic's services. If no separate agreement +exists, use is governed by Anthropic's Consumer Terms of Service or +Commercial Terms of Service, as applicable: +https://www.anthropic.com/legal/consumer-terms +https://www.anthropic.com/legal/commercial-terms +Your applicable agreement is referred to as the "Agreement." "Services" are +as defined in the Agreement. + +ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the +contrary, users may not: + +- Extract these materials from the Services or retain copies of these + materials outside the Services +- Reproduce or copy these materials, except for temporary copies created + automatically during authorized use of the Services +- Create derivative works based on these materials +- Distribute, sublicense, or transfer these materials to any third party +- Make, offer to sell, sell, or import any inventions embodied in these + materials +- Reverse engineer, decompile, or disassemble these materials + +The receipt, viewing, or possession of these materials does not convey or +imply any license or right beyond those expressly granted above. + +Anthropic retains all right, title, and interest in these materials, +including all copyrights, patents, and other intellectual property rights. diff --git a/src/flow/skills/xlsx/SKILL.md b/src/flow/skills/xlsx/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..22db189c8b17d48f94f11fa0c45343441239ff40 --- /dev/null +++ b/src/flow/skills/xlsx/SKILL.md @@ -0,0 +1,289 @@ +--- +name: xlsx +description: "Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas" +license: Proprietary. LICENSE.txt has complete terms +--- + +# Requirements for Outputs + +## All Excel files + +### Zero Formula Errors +- Every Excel model MUST be delivered with ZERO formula errors (#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?) + +### Preserve Existing Templates (when updating templates) +- Study and EXACTLY match existing format, style, and conventions when modifying files +- Never impose standardized formatting on files with established patterns +- Existing template conventions ALWAYS override these guidelines + +## Financial models + +### Color Coding Standards +Unless otherwise stated by the user or existing template + +#### Industry-Standard Color Conventions +- **Blue text (RGB: 0,0,255)**: Hardcoded inputs, and numbers users will change for scenarios +- **Black text (RGB: 0,0,0)**: ALL formulas and calculations +- **Green text (RGB: 0,128,0)**: Links pulling from other worksheets within same workbook +- **Red text (RGB: 255,0,0)**: External links to other files +- **Yellow background (RGB: 255,255,0)**: Key assumptions needing attention or cells that need to be updated + +### Number Formatting Standards + +#### Required Format Rules +- **Years**: Format as text strings (e.g., "2024" not "2,024") +- **Currency**: Use $#,##0 format; ALWAYS specify units in headers ("Revenue ($mm)") +- **Zeros**: Use number formatting to make all zeros "-", including percentages (e.g., "$#,##0;($#,##0);-") +- **Percentages**: Default to 0.0% format (one decimal) +- **Multiples**: Format as 0.0x for valuation multiples (EV/EBITDA, P/E) +- **Negative numbers**: Use parentheses (123) not minus -123 + +### Formula Construction Rules + +#### Assumptions Placement +- Place ALL assumptions (growth rates, margins, multiples, etc.) in separate assumption cells +- Use cell references instead of hardcoded values in formulas +- Example: Use =B5*(1+$B$6) instead of =B5*1.05 + +#### Formula Error Prevention +- Verify all cell references are correct +- Check for off-by-one errors in ranges +- Ensure consistent formulas across all projection periods +- Test with edge cases (zero values, negative numbers) +- Verify no unintended circular references + +#### Documentation Requirements for Hardcodes +- Comment or in cells beside (if end of table). Format: "Source: [System/Document], [Date], [Specific Reference], [URL if applicable]" +- Examples: + - "Source: Company 10-K, FY2024, Page 45, Revenue Note, [SEC EDGAR URL]" + - "Source: Company 10-Q, Q2 2025, Exhibit 99.1, [SEC EDGAR URL]" + - "Source: Bloomberg Terminal, 8/15/2025, AAPL US Equity" + - "Source: FactSet, 8/20/2025, Consensus Estimates Screen" + +# XLSX creation, editing, and analysis + +## Overview + +A user may ask you to create, edit, or analyze the contents of an .xlsx file. You have different tools and workflows available for different tasks. + +## Important Requirements + +**LibreOffice Required for Formula Recalculation**: You can assume LibreOffice is installed for recalculating formula values using the `recalc.py` script. The script automatically configures LibreOffice on first run + +## Reading and analyzing data + +### Data analysis with pandas +For data analysis, visualization, and basic operations, use **pandas** which provides powerful data manipulation capabilities: + +```python +import pandas as pd + +# Read Excel +df = pd.read_excel('file.xlsx') # Default: first sheet +all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # All sheets as dict + +# Analyze +df.head() # Preview data +df.info() # Column info +df.describe() # Statistics + +# Write Excel +df.to_excel('output.xlsx', index=False) +``` + +## Excel File Workflows + +## CRITICAL: Use Formulas, Not Hardcoded Values + +**Always use Excel formulas instead of calculating values in Python and hardcoding them.** This ensures the spreadsheet remains dynamic and updateable. + +### ❌ WRONG - Hardcoding Calculated Values +```python +# Bad: Calculating in Python and hardcoding result +total = df['Sales'].sum() +sheet['B10'] = total # Hardcodes 5000 + +# Bad: Computing growth rate in Python +growth = (df.iloc[-1]['Revenue'] - df.iloc[0]['Revenue']) / df.iloc[0]['Revenue'] +sheet['C5'] = growth # Hardcodes 0.15 + +# Bad: Python calculation for average +avg = sum(values) / len(values) +sheet['D20'] = avg # Hardcodes 42.5 +``` + +### ✅ CORRECT - Using Excel Formulas +```python +# Good: Let Excel calculate the sum +sheet['B10'] = '=SUM(B2:B9)' + +# Good: Growth rate as Excel formula +sheet['C5'] = '=(C4-C2)/C2' + +# Good: Average using Excel function +sheet['D20'] = '=AVERAGE(D2:D19)' +``` + +This applies to ALL calculations - totals, percentages, ratios, differences, etc. The spreadsheet should be able to recalculate when source data changes. + +## Common Workflow +1. **Choose tool**: pandas for data, openpyxl for formulas/formatting +2. **Create/Load**: Create new workbook or load existing file +3. **Modify**: Add/edit data, formulas, and formatting +4. **Save**: Write to file +5. **Recalculate formulas (MANDATORY IF USING FORMULAS)**: Use the recalc.py script + ```bash + python recalc.py output.xlsx + ``` +6. **Verify and fix any errors**: + - The script returns JSON with error details + - If `status` is `errors_found`, check `error_summary` for specific error types and locations + - Fix the identified errors and recalculate again + - Common errors to fix: + - `#REF!`: Invalid cell references + - `#DIV/0!`: Division by zero + - `#VALUE!`: Wrong data type in formula + - `#NAME?`: Unrecognized formula name + +### Creating new Excel files + +```python +# Using openpyxl for formulas and formatting +from openpyxl import Workbook +from openpyxl.styles import Font, PatternFill, Alignment + +wb = Workbook() +sheet = wb.active + +# Add data +sheet['A1'] = 'Hello' +sheet['B1'] = 'World' +sheet.append(['Row', 'of', 'data']) + +# Add formula +sheet['B2'] = '=SUM(A1:A10)' + +# Formatting +sheet['A1'].font = Font(bold=True, color='FF0000') +sheet['A1'].fill = PatternFill('solid', start_color='FFFF00') +sheet['A1'].alignment = Alignment(horizontal='center') + +# Column width +sheet.column_dimensions['A'].width = 20 + +wb.save('output.xlsx') +``` + +### Editing existing Excel files + +```python +# Using openpyxl to preserve formulas and formatting +from openpyxl import load_workbook + +# Load existing file +wb = load_workbook('existing.xlsx') +sheet = wb.active # or wb['SheetName'] for specific sheet + +# Working with multiple sheets +for sheet_name in wb.sheetnames: + sheet = wb[sheet_name] + print(f"Sheet: {sheet_name}") + +# Modify cells +sheet['A1'] = 'New Value' +sheet.insert_rows(2) # Insert row at position 2 +sheet.delete_cols(3) # Delete column 3 + +# Add new sheet +new_sheet = wb.create_sheet('NewSheet') +new_sheet['A1'] = 'Data' + +wb.save('modified.xlsx') +``` + +## Recalculating formulas + +Excel files created or modified by openpyxl contain formulas as strings but not calculated values. Use the provided `recalc.py` script to recalculate formulas: + +```bash +python recalc.py [timeout_seconds] +``` + +Example: +```bash +python recalc.py output.xlsx 30 +``` + +The script: +- Automatically sets up LibreOffice macro on first run +- Recalculates all formulas in all sheets +- Scans ALL cells for Excel errors (#REF!, #DIV/0!, etc.) +- Returns JSON with detailed error locations and counts +- Works on both Linux and macOS + +## Formula Verification Checklist + +Quick checks to ensure formulas work correctly: + +### Essential Verification +- [ ] **Test 2-3 sample references**: Verify they pull correct values before building full model +- [ ] **Column mapping**: Confirm Excel columns match (e.g., column 64 = BL, not BK) +- [ ] **Row offset**: Remember Excel rows are 1-indexed (DataFrame row 5 = Excel row 6) + +### Common Pitfalls +- [ ] **NaN handling**: Check for null values with `pd.notna()` +- [ ] **Far-right columns**: FY data often in columns 50+ +- [ ] **Multiple matches**: Search all occurrences, not just first +- [ ] **Division by zero**: Check denominators before using `/` in formulas (#DIV/0!) +- [ ] **Wrong references**: Verify all cell references point to intended cells (#REF!) +- [ ] **Cross-sheet references**: Use correct format (Sheet1!A1) for linking sheets + +### Formula Testing Strategy +- [ ] **Start small**: Test formulas on 2-3 cells before applying broadly +- [ ] **Verify dependencies**: Check all cells referenced in formulas exist +- [ ] **Test edge cases**: Include zero, negative, and very large values + +### Interpreting recalc.py Output +The script returns JSON with error details: +```json +{ + "status": "success", // or "errors_found" + "total_errors": 0, // Total error count + "total_formulas": 42, // Number of formulas in file + "error_summary": { // Only present if errors found + "#REF!": { + "count": 2, + "locations": ["Sheet1!B5", "Sheet1!C10"] + } + } +} +``` + +## Best Practices + +### Library Selection +- **pandas**: Best for data analysis, bulk operations, and simple data export +- **openpyxl**: Best for complex formatting, formulas, and Excel-specific features + +### Working with openpyxl +- Cell indices are 1-based (row=1, column=1 refers to cell A1) +- Use `data_only=True` to read calculated values: `load_workbook('file.xlsx', data_only=True)` +- **Warning**: If opened with `data_only=True` and saved, formulas are replaced with values and permanently lost +- For large files: Use `read_only=True` for reading or `write_only=True` for writing +- Formulas are preserved but not evaluated - use recalc.py to update values + +### Working with pandas +- Specify data types to avoid inference issues: `pd.read_excel('file.xlsx', dtype={'id': str})` +- For large files, read specific columns: `pd.read_excel('file.xlsx', usecols=['A', 'C', 'E'])` +- Handle dates properly: `pd.read_excel('file.xlsx', parse_dates=['date_column'])` + +## Code Style Guidelines +**IMPORTANT**: When generating Python code for Excel operations: +- Write minimal, concise Python code without unnecessary comments +- Avoid verbose variable names and redundant operations +- Avoid unnecessary print statements + +**For Excel files themselves**: +- Add comments to cells with complex formulas or important assumptions +- Document data sources for hardcoded values +- Include notes for key calculations and model sections \ No newline at end of file diff --git a/src/flow/skills/xlsx/recalc.py b/src/flow/skills/xlsx/recalc.py new file mode 100644 index 0000000000000000000000000000000000000000..102e157b0b6392090b2ec3e602ee8fe37fe6c7a3 --- /dev/null +++ b/src/flow/skills/xlsx/recalc.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +""" +Excel Formula Recalculation Script +Recalculates all formulas in an Excel file using LibreOffice +""" + +import json +import sys +import subprocess +import os +import platform +from pathlib import Path +from openpyxl import load_workbook + + +def setup_libreoffice_macro(): + """Setup LibreOffice macro for recalculation if not already configured""" + if platform.system() == 'Darwin': + macro_dir = os.path.expanduser('~/Library/Application Support/LibreOffice/4/user/basic/Standard') + else: + macro_dir = os.path.expanduser('~/.config/libreoffice/4/user/basic/Standard') + + macro_file = os.path.join(macro_dir, 'Module1.xba') + + if os.path.exists(macro_file): + with open(macro_file, 'r') as f: + if 'RecalculateAndSave' in f.read(): + return True + + if not os.path.exists(macro_dir): + subprocess.run(['soffice', '--headless', '--terminate_after_init'], + capture_output=True, timeout=10) + os.makedirs(macro_dir, exist_ok=True) + + macro_content = ''' + + + Sub RecalculateAndSave() + ThisComponent.calculateAll() + ThisComponent.store() + ThisComponent.close(True) + End Sub +''' + + try: + with open(macro_file, 'w') as f: + f.write(macro_content) + return True + except Exception: + return False + + +def recalc(filename, timeout=30): + """ + Recalculate formulas in Excel file and report any errors + + Args: + filename: Path to Excel file + timeout: Maximum time to wait for recalculation (seconds) + + Returns: + dict with error locations and counts + """ + if not Path(filename).exists(): + return {'error': f'File {filename} does not exist'} + + abs_path = str(Path(filename).absolute()) + + if not setup_libreoffice_macro(): + return {'error': 'Failed to setup LibreOffice macro'} + + cmd = [ + 'soffice', '--headless', '--norestore', + 'vnd.sun.star.script:Standard.Module1.RecalculateAndSave?language=Basic&location=application', + abs_path + ] + + # Handle timeout command differences between Linux and macOS + if platform.system() != 'Windows': + timeout_cmd = 'timeout' if platform.system() == 'Linux' else None + if platform.system() == 'Darwin': + # Check if gtimeout is available on macOS + try: + subprocess.run(['gtimeout', '--version'], capture_output=True, timeout=1, check=False) + timeout_cmd = 'gtimeout' + except (FileNotFoundError, subprocess.TimeoutExpired): + pass + + if timeout_cmd: + cmd = [timeout_cmd, str(timeout)] + cmd + + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0 and result.returncode != 124: # 124 is timeout exit code + error_msg = result.stderr or 'Unknown error during recalculation' + if 'Module1' in error_msg or 'RecalculateAndSave' not in error_msg: + return {'error': 'LibreOffice macro not configured properly'} + else: + return {'error': error_msg} + + # Check for Excel errors in the recalculated file - scan ALL cells + try: + wb = load_workbook(filename, data_only=True) + + excel_errors = ['#VALUE!', '#DIV/0!', '#REF!', '#NAME?', '#NULL!', '#NUM!', '#N/A'] + error_details = {err: [] for err in excel_errors} + total_errors = 0 + + for sheet_name in wb.sheetnames: + ws = wb[sheet_name] + # Check ALL rows and columns - no limits + for row in ws.iter_rows(): + for cell in row: + if cell.value is not None and isinstance(cell.value, str): + for err in excel_errors: + if err in cell.value: + location = f"{sheet_name}!{cell.coordinate}" + error_details[err].append(location) + total_errors += 1 + break + + wb.close() + + # Build result summary + result = { + 'status': 'success' if total_errors == 0 else 'errors_found', + 'total_errors': total_errors, + 'error_summary': {} + } + + # Add non-empty error categories + for err_type, locations in error_details.items(): + if locations: + result['error_summary'][err_type] = { + 'count': len(locations), + 'locations': locations[:20] # Show up to 20 locations + } + + # Add formula count for context - also check ALL cells + wb_formulas = load_workbook(filename, data_only=False) + formula_count = 0 + for sheet_name in wb_formulas.sheetnames: + ws = wb_formulas[sheet_name] + for row in ws.iter_rows(): + for cell in row: + if cell.value and isinstance(cell.value, str) and cell.value.startswith('='): + formula_count += 1 + wb_formulas.close() + + result['total_formulas'] = formula_count + + return result + + except Exception as e: + return {'error': str(e)} + + +def main(): + if len(sys.argv) < 2: + print("Usage: python recalc.py [timeout_seconds]") + print("\nRecalculates all formulas in an Excel file using LibreOffice") + print("\nReturns JSON with error details:") + print(" - status: 'success' or 'errors_found'") + print(" - total_errors: Total number of Excel errors found") + print(" - total_formulas: Number of formulas in the file") + print(" - error_summary: Breakdown by error type with locations") + print(" - #VALUE!, #DIV/0!, #REF!, #NAME?, #NULL!, #NUM!, #N/A") + sys.exit(1) + + filename = sys.argv[1] + timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 30 + + result = recalc(filename, timeout) + print(json.dumps(result, indent=2)) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/flow/tools/__init__.py b/src/flow/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ef9f574b7ddbd75079bdd4d4c82fc6c99a3a421 --- /dev/null +++ b/src/flow/tools/__init__.py @@ -0,0 +1,330 @@ +"""Shared tools for Flow agent harnesses. + +This module provides framework-agnostic tool implementations that can be +used with any agent framework (MiniAgent, MAF, LangGraph, etc.). + +The Tool dataclass provides: +- name: Tool name +- description: Human-readable description +- parameters: JSON Schema for parameters +- func: The actual callable (sync or async) +- to_openai_tool(): Convert to OpenAI API format + +Available tools: + +Coding/Filesystem: +- read_file: Read file contents with line numbers +- write_file: Write/create files +- edit_file: Replace text in files (unique match required) +- multi_edit: Multiple edits atomically +- glob_files: Find files by pattern +- grep: Search for text patterns +- ls: List directory contents + +Execution: +- bash: Execute shell commands +- check_processes: Manage background processes +- python_repl: Execute Python code + +Planning: +- think: Explicit reasoning tool +- todo_write: Track task progress +- todo_read: Read current tasks + +Memory: +- memory: Persistent storage across sessions + +Web: +- web_search: Search the web +- web_fetch: Fetch and parse web pages + +Notebooks: +- notebook_edit: Edit Jupyter notebook cells +- notebook_read: Read Jupyter notebooks + +Skills: +- skills: Discover and load domain expertise + +Sub-agents: +- task: Spawn sub-agents for isolated context +""" + +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Any + +from .base import Tool, tool + +# Coding tools +from .coding import ( + read_file, + write_file, + edit_file, + multi_edit, + glob_files, + grep, + ls, +) + +# Execution tools +from .execution import bash, check_processes, python_repl + +# Planning tools +from .planning import think, todo_write, todo_read + +# Memory tools +from .memory import memory, create_memory_tool + +# Web tools +from .web import web_search, web_fetch + +# Notebook tools +from .notebook import notebook_edit, notebook_read + +# Skills tools +from .skills import skills, create_skills_tool + +# Sub-agent tools +from .subagent import task, create_task_tool + +# Workspace management +from .workspace import Workspace, get_workspace, set_workspace, reset_workspace + +# Adapters for framework integration +from .adapters import to_maf_tool, to_openai_tool, tools_to_maf, tools_to_openai + +__all__ = [ + # Base + "Tool", + "tool", + # Coding tools + "read_file", + "write_file", + "edit_file", + "multi_edit", + "glob_files", + "grep", + "ls", + # Execution tools + "bash", + "check_processes", + "python_repl", + # Planning tools + "think", + "todo_write", + "todo_read", + # Memory tools + "memory", + "create_memory_tool", + # Web tools + "web_search", + "web_fetch", + # Notebook tools + "notebook_edit", + "notebook_read", + # Skills tools + "skills", + "create_skills_tool", + # Sub-agent tools + "task", + "create_task_tool", + # Workspace + "Workspace", + "get_workspace", + "set_workspace", + "reset_workspace", + # Adapters + "to_maf_tool", + "to_openai_tool", + "tools_to_maf", + "tools_to_openai", + # Preset functions + "coding_tools", + "planning_tools", + "web_tools", + "all_tools", + "build_tools", +] + +logger = logging.getLogger(__name__) + + +# ============================================================================= +# Tool Presets - Convenient groupings of tools +# ============================================================================= + + +def coding_tools() -> list[Tool]: + """Get the standard coding/filesystem tools. + + Returns: + List of Tool instances for file operations and code search. + + Includes: read_file, write_file, edit_file, multi_edit, glob_files, grep, ls + """ + return [read_file, write_file, edit_file, multi_edit, glob_files, grep, ls] + + +def planning_tools() -> list[Tool]: + """Get the planning and task management tools. + + Returns: + List of Tool instances for reasoning and task tracking. + + Includes: think, todo_write, todo_read + """ + return [think, todo_write, todo_read] + + +def execution_tools() -> list[Tool]: + """Get the execution tools. + + Returns: + List of Tool instances for running code and commands. + + Includes: bash, check_processes, python_repl + """ + return [bash, check_processes, python_repl] + + +def web_tools() -> list[Tool]: + """Get the web research tools. + + Returns: + List of Tool instances for web search and fetch. + + Includes: web_search, web_fetch + """ + return [web_search, web_fetch] + + +def notebook_tools() -> list[Tool]: + """Get the Jupyter notebook tools. + + Returns: + List of Tool instances for notebook manipulation. + + Includes: notebook_edit, notebook_read + """ + return [notebook_edit, notebook_read] + + +def all_tools() -> list[Tool]: + """Get all available tools. + + Returns: + List of all Tool instances. + + Note: This includes the default memory, skills, and task tools. + For custom configurations (e.g., specific workspace), use create_*_tool functions. + """ + return ( + coding_tools() + + planning_tools() + + execution_tools() + + web_tools() + + notebook_tools() + + [memory, skills, task] + ) + + +# ============================================================================= +# build_tools - Build tools from specification dict (for compatibility) +# ============================================================================= + + +def build_tools( + tools_spec: dict[str, dict[str, Any]], + workspace: Path | None = None, + memory_path: Path | None = None, +) -> list[Tool]: + """Build tool instances from a specification dict. + + This is the main entry point for creating tools based on a resolved + tool specification. Returns Tool instances that can be used directly + or converted to framework-specific formats using adapters. + + Args: + tools_spec: Dict mapping tool names to their config dicts. + e.g., {"bash": {"timeout": 60}, "read_file": {}} + workspace: Root directory for file operations (sets global workspace) + memory_path: Directory for persistent memory (deprecated, uses workspace) + + Returns: + List of Tool instances + + Example: + >>> from flow.experiments.models import resolve_tools + >>> tools_spec = resolve_tools("standard") + >>> tools = build_tools(tools_spec, workspace=Path("/my/project")) + >>> for t in tools: + ... print(f"{t.name}: {t.description[:50]}...") + """ + # Set workspace if provided + if workspace: + set_workspace(Workspace(workspace)) + + tools: list[Tool] = [] + + for tool_name, config in tools_spec.items(): + tool_instance = _get_tool_by_name(tool_name, config) + if tool_instance is not None: + tools.append(tool_instance) + + return tools + + +def _get_tool_by_name(name: str, config: dict[str, Any]) -> Tool | None: + """Get a Tool instance by name with optional config. + + Args: + name: Tool name + config: Tool-specific configuration dict + + Returns: + Tool instance or None if unknown tool name + """ + # Mapping from name to Tool instance + tool_map: dict[str, Tool] = { + # Coding tools + "read_file": read_file, + "write_file": write_file, + "edit_file": edit_file, + "multi_edit": multi_edit, + "glob_files": glob_files, + "grep": grep, + "ls": ls, + # Execution tools + "bash": bash, + "check_processes": check_processes, + "python_repl": python_repl, + # Planning tools + "think": think, + "todo_write": todo_write, + "todo_read": todo_read, + # Memory tools + "memory": memory, + # Web tools + "web_search": web_search, + "web_fetch": web_fetch, + # Notebook tools + "notebook_edit": notebook_edit, + "notebook_read": notebook_read, + # Skills tools + "skills": skills, + # Sub-agent tools + "task": task, + } + + if name in tool_map: + return tool_map[name] + + # Handle configurable tools that need factory functions + if name == "skills" and config.get("additional_paths"): + return create_skills_tool(project_path=Path(config["additional_paths"][0])) + + # Unknown tool - log warning and skip + logger.warning(f"Unknown tool name: {name}. Skipping.") + return None diff --git a/src/flow/tools/adapters.py b/src/flow/tools/adapters.py new file mode 100644 index 0000000000000000000000000000000000000000..1f6228ec80f95eb2ed7e43c32be0290015f4b8fb --- /dev/null +++ b/src/flow/tools/adapters.py @@ -0,0 +1,110 @@ +"""Framework adapters for Flow tools. + +This module provides adapters to convert Flow's shared Tool instances +to framework-specific formats (MAF, LangGraph, etc.). + +The adapter pattern allows the same tool implementations to be used +across different agent frameworks without code duplication. +""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +from .base import Tool + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any + +logger = logging.getLogger(__name__) + + +def to_maf_tool(tool: Tool) -> "Callable[..., Any]": + """Convert a Flow Tool to a MAF-decorated function. + + Applies the MAF @tool decorator to the underlying function, + preserving name and description from the Tool instance. + + Args: + tool: A Flow Tool instance + + Returns: + The function wrapped with MAF's @tool decorator + + Example: + from flow.tools import read_file + from flow.tools.adapters import to_maf_tool + + maf_read_file = to_maf_tool(read_file) + """ + try: + from agent_framework import tool as maf_tool + except ImportError: + raise ImportError( + "Microsoft Agent Framework not installed. " + "Install with: pip install agent-framework" + ) + + return maf_tool( + name=tool.name, + description=tool.description, + )(tool.func) + + +def to_openai_tool(tool: Tool) -> dict[str, Any]: + """Convert a Flow Tool to OpenAI function calling format. + + Returns the JSON schema format expected by OpenAI's function calling API. + + Args: + tool: A Flow Tool instance + + Returns: + Dict in OpenAI function calling format + + Example: + from flow.tools import read_file + from flow.tools.adapters import to_openai_tool + + schema = to_openai_tool(read_file) + # {"type": "function", "function": {"name": "read_file", ...}} + """ + return tool.to_openai_tool() + + +def tools_to_maf(tools: list[Tool]) -> "list[Callable[..., Any]]": + """Convert a list of Flow Tools to MAF-decorated functions. + + Args: + tools: List of Flow Tool instances + + Returns: + List of functions wrapped with MAF's @tool decorator + + Example: + from flow.tools import coding_tools + from flow.tools.adapters import tools_to_maf + + maf_tools = tools_to_maf(coding_tools()) + """ + return [to_maf_tool(t) for t in tools] + + +def tools_to_openai(tools: list[Tool]) -> list[dict[str, Any]]: + """Convert a list of Flow Tools to OpenAI function calling format. + + Args: + tools: List of Flow Tool instances + + Returns: + List of dicts in OpenAI function calling format + + Example: + from flow.tools import coding_tools + from flow.tools.adapters import tools_to_openai + + schemas = tools_to_openai(coding_tools()) + """ + return [to_openai_tool(t) for t in tools] diff --git a/src/flow/tools/base.py b/src/flow/tools/base.py new file mode 100644 index 0000000000000000000000000000000000000000..fa8526ed7c903edc2c332bdf45f6c50ab0f70737 --- /dev/null +++ b/src/flow/tools/base.py @@ -0,0 +1,186 @@ +"""Tool definition and @tool decorator for Flow. + +Provides a simple way to define tools that can be called by the LLM. +Tools are framework-agnostic - harnesses convert them to their specific format. + +Example: + @tool + def search(query: Annotated[str, "The search query"]) -> str: + '''Search the web for information.''' + return f"Results for: {query}" +""" + +from dataclasses import dataclass +from typing import Any, Callable, Literal, get_type_hints, get_origin, get_args, Annotated +import inspect + + +@dataclass +class Tool: + """A tool that can be called by the LLM. + + Tools are functions with metadata that allows the LLM to understand + how to call them. + + Attributes: + name: Tool name (used by LLM to invoke) + description: Human-readable description + parameters: JSON Schema for parameters + func: The actual callable + """ + + name: str + description: str + parameters: dict[str, Any] # JSON Schema + func: Callable[..., Any] + + def to_openai_tool(self) -> dict[str, Any]: + """Convert to OpenAI tool format.""" + return { + "type": "function", + "function": { + "name": self.name, + "description": self.description, + "parameters": self.parameters, + }, + } + + async def invoke(self, **kwargs: Any) -> str: + """Execute the tool and return result as string. + + Handles both sync and async functions. + """ + try: + result = self.func(**kwargs) + if inspect.iscoroutine(result): + result = await result + return str(result) if not isinstance(result, str) else result + except Exception as e: + return f"Error executing {self.name}: {str(e)}" + + +def _python_type_to_json_schema(py_type: Any) -> dict[str, Any]: + """Convert a Python type hint to JSON Schema.""" + # Handle None/NoneType + if py_type is None or py_type is type(None): + return {"type": "null"} + + # Handle basic types + if py_type == str: + return {"type": "string"} + if py_type == int: + return {"type": "integer"} + if py_type == float: + return {"type": "number"} + if py_type == bool: + return {"type": "boolean"} + + # Handle dict without type args + if py_type is dict: + return {"type": "object"} + + # Handle Optional (Union with None) + origin = get_origin(py_type) + args = get_args(py_type) + + if origin is list: + if args: + return {"type": "array", "items": _python_type_to_json_schema(args[0])} + return {"type": "array"} + + if origin is dict: + return {"type": "object"} + + # Handle Union types (including Optional) + # In Python 3.10+, Optional[X] is Union[X, None] + if origin is type(int | str): # Union type + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1: + # This is Optional[X] + return _python_type_to_json_schema(non_none_args[0]) + # Multiple types - use anyOf + return {"anyOf": [_python_type_to_json_schema(a) for a in non_none_args]} + + # Handle Literal + if origin is Literal: + return {"type": "string", "enum": list(args)} + + # Default to string + return {"type": "string"} + + +def tool(func: Callable[..., Any]) -> Tool: + """Decorator to convert a function into a Tool. + + Uses type hints and Annotated[] for parameter descriptions. + The function's docstring becomes the tool description. + + Example: + @tool + def search(query: Annotated[str, "The search query"]) -> str: + '''Search the web for information.''' + return f"Results for: {query}" + """ + # Get function signature + sig = inspect.signature(func) + + # Get type hints (with extras for Annotated) + try: + hints = get_type_hints(func, include_extras=True) + except Exception: + hints = {} + + # Build JSON Schema for parameters + properties: dict[str, Any] = {} + required: list[str] = [] + + for param_name, param in sig.parameters.items(): + if param_name in ("self", "cls"): + continue + + # Get the type hint + hint = hints.get(param_name, str) + description = "" + + # Check if it's Annotated + if get_origin(hint) is Annotated: + args = get_args(hint) + actual_type = args[0] + # Look for string descriptions in the annotations + for annotation in args[1:]: + if isinstance(annotation, str): + description = annotation + break + else: + actual_type = hint + + # Convert to JSON Schema + prop_schema = _python_type_to_json_schema(actual_type) + if description: + prop_schema["description"] = description + + properties[param_name] = prop_schema + + # Check if required (no default value) + if param.default is inspect.Parameter.empty: + required.append(param_name) + + # Build the full schema + parameters_schema: dict[str, Any] = { + "type": "object", + "properties": properties, + } + if required: + parameters_schema["required"] = required + + # Get description from docstring + description = func.__doc__ or f"Call the {func.__name__} function" + # Clean up the docstring - take first line/paragraph + description = description.strip().split("\n\n")[0].strip() + + return Tool( + name=func.__name__, + description=description, + parameters=parameters_schema, + func=func, + ) diff --git a/src/flow/tools/coding.py b/src/flow/tools/coding.py new file mode 100644 index 0000000000000000000000000000000000000000..37e346469bd844b5ec0fec580bac7c7c16235f02 --- /dev/null +++ b/src/flow/tools/coding.py @@ -0,0 +1,382 @@ +"""Filesystem tools for Flow. + +Read, write, edit files and search the filesystem. +""" + +import re +from pathlib import Path +from typing import Annotated +import fnmatch + +from .base import tool + + +@tool +def read_file( + path: Annotated[str, "Path to the file to read"], + offset: Annotated[int, "Line number to start from (1-indexed)"] = 1, + limit: Annotated[int, "Maximum number of lines to read"] = 2000, +) -> str: + """Read a file and return its contents with line numbers. + + Returns the file content with line numbers for easy reference. + """ + try: + path_obj = Path(path).expanduser().resolve() + + if not path_obj.exists(): + return f"Error: File not found: {path}" + + if not path_obj.is_file(): + return f"Error: Not a file: {path}" + + with open(path_obj, "r", encoding="utf-8", errors="replace") as f: + lines = f.readlines() + + total_lines = len(lines) + + # Adjust offset to 0-indexed + start = max(0, offset - 1) + end = min(start + limit, total_lines) + + # Format with line numbers + output_lines: list[str] = [] + for i in range(start, end): + line_num = i + 1 + line_content = lines[i].rstrip("\n\r") + output_lines.append(f"{line_num:6d}\t{line_content}") + + result = "\n".join(output_lines) + + # Add metadata + if start > 0 or end < total_lines: + result += f"\n\n[Showing lines {start + 1}-{end} of {total_lines}]" + + return result + + except Exception as e: + return f"Error reading file: {str(e)}" + + +@tool +def write_file( + path: Annotated[str, "Path to write to"], + content: Annotated[str, "Content to write"], + create_dirs: Annotated[bool, "Create parent directories if needed"] = True, +) -> str: + """Write content to a file (creates or overwrites). + + Use this to create new files or completely replace file contents. + For partial edits, use edit_file instead. + """ + try: + path_obj = Path(path).expanduser().resolve() + + if create_dirs: + path_obj.parent.mkdir(parents=True, exist_ok=True) + + with open(path_obj, "w", encoding="utf-8") as f: + f.write(content) + + # Count lines for feedback + line_count = content.count("\n") + (1 if content and not content.endswith("\n") else 0) + + return f"Successfully wrote {len(content)} characters ({line_count} lines) to {path}" + + except Exception as e: + return f"Error writing file: {str(e)}" + + +@tool +def edit_file( + path: Annotated[str, "Path to the file to edit"], + old_string: Annotated[str, "Text to find (must be unique in the file)"], + new_string: Annotated[str, "Text to replace with"], +) -> str: + """Edit a file by replacing old_string with new_string. + + The old_string must appear exactly once in the file. + For multiple replacements, call this tool multiple times. + """ + try: + path_obj = Path(path).expanduser().resolve() + + if not path_obj.exists(): + return f"Error: File not found: {path}" + + with open(path_obj, "r", encoding="utf-8") as f: + content = f.read() + + # Check for unique match + count = content.count(old_string) + + if count == 0: + return f"Error: Could not find the specified text in {path}" + + if count > 1: + return f"Error: Found {count} occurrences of the text. Please provide more context to make it unique." + + # Perform replacement + new_content = content.replace(old_string, new_string, 1) + + with open(path_obj, "w", encoding="utf-8") as f: + f.write(new_content) + + return f"Successfully edited {path}" + + except Exception as e: + return f"Error editing file: {str(e)}" + + +@tool +def glob_files( + pattern: Annotated[str, "Glob pattern (e.g., '**/*.py', 'src/*.ts')"], + path: Annotated[str, "Directory to search in"] = ".", + limit: Annotated[int, "Maximum number of results"] = 100, +) -> str: + """Find files matching a glob pattern. + + Returns a list of matching file paths, sorted by modification time (newest first). + """ + try: + base_path = Path(path).expanduser().resolve() + + if not base_path.exists(): + return f"Error: Directory not found: {path}" + + if not base_path.is_dir(): + return f"Error: Not a directory: {path}" + + # Find matching files + matches = list(base_path.glob(pattern)) + + # Filter to files only and sort by mtime + files = [p for p in matches if p.is_file()] + files.sort(key=lambda p: p.stat().st_mtime, reverse=True) + + # Limit results + files = files[:limit] + + if not files: + return f"No files found matching pattern: {pattern}" + + # Format output + output_lines: list[str] = [] + for f in files: + try: + rel_path = f.relative_to(base_path) + except ValueError: + rel_path = f + output_lines.append(str(rel_path)) + + result = "\n".join(output_lines) + + if len(matches) > limit: + result += f"\n\n[Showing {limit} of {len(matches)} matches]" + + return result + + except Exception as e: + return f"Error searching files: {str(e)}" + + +@tool +def grep( + pattern: Annotated[str, "Regex pattern to search for"], + path: Annotated[str, "File or directory to search"] = ".", + include: Annotated[str, "File pattern to include (e.g., '*.py')"] = "*", + context: Annotated[int, "Lines of context around matches"] = 0, + limit: Annotated[int, "Maximum number of matches to show"] = 50, +) -> str: + """Search for a regex pattern in files. + + Returns matching lines with file paths and line numbers. + """ + try: + base_path = Path(path).expanduser().resolve() + regex = re.compile(pattern) + + matches: list[str] = [] + + if base_path.is_file(): + files = [base_path] + else: + # Find files matching include pattern + files = [ + p for p in base_path.rglob("*") + if p.is_file() and fnmatch.fnmatch(p.name, include) + ] + + for file_path in files: + try: + with open(file_path, "r", encoding="utf-8", errors="replace") as f: + lines = f.readlines() + + for i, line in enumerate(lines): + if regex.search(line): + try: + rel_path = file_path.relative_to(base_path) + except ValueError: + rel_path = file_path + + # Build match with context + match_lines: list[str] = [] + start = max(0, i - context) + end = min(len(lines), i + context + 1) + + for j in range(start, end): + prefix = ">" if j == i else " " + match_lines.append(f"{prefix} {j + 1:4d}: {lines[j].rstrip()}") + + matches.append(f"{rel_path}:\n" + "\n".join(match_lines)) + + if len(matches) >= limit: + break + + except Exception: + continue # Skip files that can't be read + + if len(matches) >= limit: + break + + if not matches: + return f"No matches found for pattern: {pattern}" + + result = "\n\n".join(matches) + + if len(matches) >= limit: + result += f"\n\n[Results limited to {limit} matches]" + + return result + + except re.error as e: + return f"Error: Invalid regex pattern: {str(e)}" + except Exception as e: + return f"Error searching: {str(e)}" + + +@tool +def ls( + path: Annotated[str, "Directory path to list"] = ".", + show_hidden: Annotated[bool, "Include hidden files (starting with .)"] = False, + long_format: Annotated[bool, "Show detailed info (size, modified time)"] = False, +) -> str: + """List files and directories at a path. + + Returns a formatted listing of directory contents. + """ + try: + dir_path = Path(path).expanduser().resolve() + + if not dir_path.exists(): + return f"Error: Path not found: {path}" + + if not dir_path.is_dir(): + return f"Error: Not a directory: {path}" + + entries = list(dir_path.iterdir()) + + if not show_hidden: + entries = [e for e in entries if not e.name.startswith(".")] + + # Sort: directories first, then files, alphabetically + entries.sort(key=lambda e: (not e.is_dir(), e.name.lower())) + + if not entries: + return f"(empty directory: {path})" + + output_lines: list[str] = [] + for entry in entries: + if long_format: + try: + stat = entry.stat() + size = stat.st_size + # Format size + if size < 1024: + size_str = f"{size:>6}B" + elif size < 1024 * 1024: + size_str = f"{size/1024:>6.1f}K" + else: + size_str = f"{size/(1024*1024):>6.1f}M" + + # Format time + from datetime import datetime + mtime = datetime.fromtimestamp(stat.st_mtime) + time_str = mtime.strftime("%Y-%m-%d %H:%M") + + type_char = "d" if entry.is_dir() else "-" + name = entry.name + ("/" if entry.is_dir() else "") + output_lines.append(f"{type_char} {size_str} {time_str} {name}") + except Exception: + output_lines.append(entry.name) + else: + name = entry.name + ("/" if entry.is_dir() else "") + output_lines.append(name) + + return "\n".join(output_lines) + + except Exception as e: + return f"Error listing directory: {str(e)}" + + +@tool +def multi_edit( + path: Annotated[str, "Path to the file to edit"], + edits: Annotated[list[dict[str, str]], "List of edits, each with 'old_string' and 'new_string'"], +) -> str: + """Make multiple edits to a file atomically. + + All edits succeed or fail together. Each edit in the list should have: + - old_string: text to find (must be unique in the current state) + - new_string: text to replace with + + Edits are applied sequentially, so later edits see the result of earlier ones. + """ + try: + path_obj = Path(path).expanduser().resolve() + + if not path_obj.exists(): + return f"Error: File not found: {path}" + + with open(path_obj, "r", encoding="utf-8") as f: + content = f.read() + + # Validate all edits first + for i, edit in enumerate(edits): + if "old_string" not in edit or "new_string" not in edit: + return f"Error: Edit {i+1} missing 'old_string' or 'new_string'" + + # Apply edits sequentially + applied: list[str] = [] + for i, edit in enumerate(edits): + old_str = edit["old_string"] + new_str = edit["new_string"] + + count = content.count(old_str) + + if count == 0: + # Rollback - restore original + return ( + f"Error: Edit {i+1} failed - could not find text.\n" + f"Applied {len(applied)} edit(s) before failure.\n" + f"File unchanged (atomic rollback)." + ) + + if count > 1: + return ( + f"Error: Edit {i+1} failed - found {count} occurrences.\n" + f"Applied {len(applied)} edit(s) before failure.\n" + f"File unchanged (atomic rollback)." + ) + + content = content.replace(old_str, new_str, 1) + applied.append(f"Edit {i+1}: replaced {len(old_str)} chars with {len(new_str)} chars") + + # All edits succeeded - write the file + with open(path_obj, "w", encoding="utf-8") as f: + f.write(content) + + return f"Successfully applied {len(edits)} edit(s) to {path}:\n" + "\n".join(applied) + + except Exception as e: + return f"Error editing file: {str(e)}" diff --git a/src/flow/tools/execution.py b/src/flow/tools/execution.py new file mode 100644 index 0000000000000000000000000000000000000000..122c1f407962153d3fefad02b4334c3b3d3853c0 --- /dev/null +++ b/src/flow/tools/execution.py @@ -0,0 +1,130 @@ +"""Execution tools for Flow. + +Execute shell commands and manage processes. +""" + +import subprocess +from typing import Annotated + +from .base import tool + + +@tool +def bash( + command: Annotated[str, "The bash command to execute"], + timeout: Annotated[int, "Timeout in seconds"] = 120, + cwd: Annotated[str | None, "Working directory for the command"] = None, +) -> str: + """Execute a bash command and return stdout/stderr. + + Use this to run shell commands, scripts, or system utilities. + Be careful with destructive commands. + """ + try: + result = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + timeout=timeout, + cwd=cwd, + ) + + output = result.stdout + if result.stderr: + if output: + output += "\n--- STDERR ---\n" + output += result.stderr + + if result.returncode != 0: + output += f"\n[Exit code: {result.returncode}]" + + return output.strip() if output else "(No output)" + + except subprocess.TimeoutExpired: + return f"Error: Command timed out after {timeout} seconds" + except Exception as e: + return f"Error executing command: {str(e)}" + + +@tool +def check_processes( + action: Annotated[str, "Action: 'list' to show running processes, 'kill' to terminate by PID"] = "list", + pid: Annotated[int | None, "Process ID to kill (for 'kill' action)"] = None, +) -> str: + """Check or manage background processes. + + Actions: + - list: Show running processes + - kill: Terminate a process by PID + """ + import os + import signal + + if action == "list": + try: + # Use ps to list processes + result = subprocess.run( + ["ps", "aux"], + capture_output=True, + text=True, + timeout=10, + ) + return result.stdout if result.stdout else "No processes found" + except Exception as e: + return f"Error listing processes: {str(e)}" + + elif action == "kill": + if pid is None: + return "Error: PID required for 'kill' action" + try: + os.kill(pid, signal.SIGTERM) + return f"Sent SIGTERM to process {pid}" + except ProcessLookupError: + return f"Error: Process {pid} not found" + except PermissionError: + return f"Error: Permission denied to kill process {pid}" + except Exception as e: + return f"Error killing process: {str(e)}" + + else: + return f"Unknown action: {action}. Use 'list' or 'kill'." + + +@tool +def python_repl( + code: Annotated[str, "Python code to execute"], +) -> str: + """Execute Python code in a REPL-like environment. + + Returns the output of the code execution. + """ + import io + from contextlib import redirect_stdout, redirect_stderr + + # Capture stdout and stderr + stdout_capture = io.StringIO() + stderr_capture = io.StringIO() + + # Create execution namespace + namespace: dict[str, object] = {} + + try: + with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture): + exec(code, namespace) + + stdout_output = stdout_capture.getvalue() + stderr_output = stderr_capture.getvalue() + + result = "" + if stdout_output: + result += stdout_output + if stderr_output: + if result: + result += "\n--- STDERR ---\n" + result += stderr_output + + return result.strip() if result else "(No output)" + + except Exception as e: + return f"Error: {type(e).__name__}: {str(e)}" diff --git a/src/flow/tools/memory.py b/src/flow/tools/memory.py new file mode 100644 index 0000000000000000000000000000000000000000..f6c0abc8d3855b8e076b257526049e7ffc5cea0d --- /dev/null +++ b/src/flow/tools/memory.py @@ -0,0 +1,205 @@ +"""Agentic memory tool for Flow. + +Provides persistent memory across agent sessions. This implements the +"Agentic Memory" pattern from context engineering - allowing agents to +selectively persist important information outside the context window. + +Memory is stored in {workspace}/.flow/memory/ as JSON files. + +Usage: + from flow.tools import memory, create_memory_tool + + # Default: uses workspace from current directory + tools = [memory] + + # Or create with explicit workspace + from flow.tools.memory import create_memory_tool + from flow.tools.workspace import Workspace + + my_memory = create_memory_tool(Workspace("/path/to/project")) + tools = [my_memory] +""" + +import uuid +from datetime import datetime +from typing import Annotated, Any, Literal + +from .base import tool, Tool +from .workspace import get_workspace, Workspace + + +# Optional workspace override (for testing or custom setups) +_workspace_override: Workspace | None = None + + +def _get_workspace() -> Workspace: + """Get the workspace for memory storage.""" + if _workspace_override is not None: + return _workspace_override + return get_workspace() + + +def _generate_id() -> str: + """Generate a unique memory ID.""" + return uuid.uuid4().hex[:8] + + +def _search_memories(ws: Workspace, query: str) -> list[dict[str, Any]]: + """Search memories by keyword in content, key, or tags.""" + query_lower = query.lower() + results: list[dict[str, Any]] = [] + + for memory in ws.list_memories(): + # Search in content + if query_lower in memory.get("content", "").lower(): + results.append(memory) + continue + + # Search in tags + tags = memory.get("tags", []) + if any(query_lower in tag.lower() for tag in tags): + results.append(memory) + continue + + # Search in key + if query_lower in memory.get("key", "").lower(): + results.append(memory) + + return results + + +def create_memory_tool(workspace: Workspace | None = None) -> Tool: + """Create a memory tool bound to a workspace. + + Args: + workspace: Workspace to use. If None, uses default workspace. + + Returns: + A Tool instance for memory operations. + """ + def get_ws() -> Workspace: + if workspace is not None: + return workspace + return _get_workspace() + + @tool + def memory( + action: Annotated[ + Literal["store", "recall", "list", "forget"], + "Action: store (save), recall (search), list (show all), forget (delete)" + ], + content: Annotated[ + str, + "For 'store': info to remember. For 'recall': search query. For 'forget': memory ID." + ] = "", + key: Annotated[ + str, + "Optional short identifier (e.g., 'user_preferences', 'project_structure')" + ] = "", + tags: Annotated[ + list[str], + "Optional tags for categorization (e.g., ['important', 'user-info'])" + ] = [], + ) -> str: + """Store and retrieve information across sessions. + + Use this to remember important information that should persist: + - User preferences and requirements + - Key decisions made during the conversation + - Important findings from exploration + - Context that will be needed later + + Memories are stored in {workspace}/.flow/memory/ + + Examples: + memory(action="store", content="User prefers tabs over spaces", key="code_style") + memory(action="recall", content="code style") + memory(action="list") + memory(action="forget", content="abc123") + """ + ws = get_ws() + + if action == "store": + if not content: + return "Error: content is required for 'store' action" + + memory_id = _generate_id() + data = { + "id": memory_id, + "key": key or memory_id, + "content": content, + "tags": tags or [], + "created_at": datetime.now().isoformat(), + } + ws.save_memory(memory_id, data) + + return f"Stored memory '{data['key']}' (id: {memory_id}) in {ws.memory_dir}" + + elif action == "recall": + if not content: + return "Error: content (search query) is required for 'recall' action" + + results = _search_memories(ws, content) + + if not results: + return f"No memories found matching '{content}'" + + output = [f"Found {len(results)} memory(ies) matching '{content}':\n"] + for mem in results: + tags_str = f" [{', '.join(mem['tags'])}]" if mem.get("tags") else "" + output.append(f"- [{mem['id']}] {mem['key']}{tags_str}: {mem['content'][:200]}") + if len(mem['content']) > 200: + output[-1] += "..." + + return "\n".join(output) + + elif action == "list": + memories = ws.list_memories() + + if not memories: + return f"No memories stored in {ws.memory_dir}" + + output = [f"Stored memories ({len(memories)} total):\n"] + for mem in sorted(memories, key=lambda m: m.get("created_at", ""), reverse=True): + tags_str = f" [{', '.join(mem['tags'])}]" if mem.get("tags") else "" + preview = mem['content'][:100] + "..." if len(mem['content']) > 100 else mem['content'] + output.append(f"- [{mem['id']}] {mem['key']}{tags_str}: {preview}") + + return "\n".join(output) + + elif action == "forget": + if not content: + return "Error: content (memory ID) is required for 'forget' action" + + if ws.delete_memory(content): + return f"Deleted memory with id: {content}" + else: + return f"No memory found with id: {content}" + + else: + return f"Unknown action: {action}. Use: store, recall, list, or forget" + + return memory + + +# Default memory tool using default workspace +memory = create_memory_tool() + + +def reset_memory() -> None: + """Clear all memories in the workspace.""" + ws = _get_workspace() + for mem in ws.list_memories(): + ws.delete_memory(mem.get("id", "")) + + +def set_memory_workspace(workspace: Workspace) -> None: + """Set workspace for memory storage (for testing).""" + global _workspace_override + _workspace_override = workspace + + +def clear_memory_workspace_override() -> None: + """Clear workspace override (for testing).""" + global _workspace_override + _workspace_override = None diff --git a/src/flow/tools/notebook.py b/src/flow/tools/notebook.py new file mode 100644 index 0000000000000000000000000000000000000000..a3c2a60e49a96800d3be7417409a2a31e3cdc327 --- /dev/null +++ b/src/flow/tools/notebook.py @@ -0,0 +1,203 @@ +"""Jupyter notebook tools for Flow. + +Edit and manipulate Jupyter notebook cells. +""" + +import json +from pathlib import Path +from typing import Annotated, Any, Literal + +from .base import tool + + +@tool +def notebook_edit( + path: Annotated[str, "Path to the Jupyter notebook (.ipynb)"], + cell_index: Annotated[int, "Index of the cell to edit (0-indexed)"], + new_source: Annotated[str, "New source content for the cell"], + cell_type: Annotated[Literal["code", "markdown"] | None, "Cell type (code or markdown)"] = None, + edit_mode: Annotated[Literal["replace", "insert", "delete"], "Edit mode"] = "replace", +) -> str: + """Edit a Jupyter notebook cell. + + Modes: + - replace: Replace the cell at cell_index with new content + - insert: Insert a new cell at cell_index (shifts existing cells down) + - delete: Delete the cell at cell_index (new_source is ignored) + + For insert mode, cell_type is required. + """ + try: + path_obj = Path(path).expanduser().resolve() + + if not path_obj.exists(): + return f"Error: Notebook not found: {path}" + + if not path_obj.suffix == ".ipynb": + return f"Error: Not a Jupyter notebook (must be .ipynb): {path}" + + # Read notebook + with open(path_obj, "r", encoding="utf-8") as f: + notebook = json.load(f) + + cells = notebook.get("cells", []) + + if edit_mode == "delete": + if cell_index < 0 or cell_index >= len(cells): + return f"Error: Cell index {cell_index} out of range (notebook has {len(cells)} cells)" + + deleted_type = cells[cell_index].get("cell_type", "unknown") + cells.pop(cell_index) + notebook["cells"] = cells + + with open(path_obj, "w", encoding="utf-8") as f: + json.dump(notebook, f, indent=1) + + return f"Successfully deleted {deleted_type} cell at index {cell_index}" + + elif edit_mode == "insert": + if cell_type is None: + return "Error: cell_type is required for insert mode" + + if cell_index < 0 or cell_index > len(cells): + return f"Error: Cell index {cell_index} out of range for insert (0 to {len(cells)})" + + # Create new cell + new_cell: dict[str, Any] = { + "cell_type": cell_type, + "metadata": {}, + "source": new_source.split("\n") if new_source else [], + } + + if cell_type == "code": + new_cell["execution_count"] = None + new_cell["outputs"] = [] + + cells.insert(cell_index, new_cell) + notebook["cells"] = cells + + with open(path_obj, "w", encoding="utf-8") as f: + json.dump(notebook, f, indent=1) + + return f"Successfully inserted {cell_type} cell at index {cell_index}" + + else: # replace + if cell_index < 0 or cell_index >= len(cells): + return f"Error: Cell index {cell_index} out of range (notebook has {len(cells)} cells)" + + cell = cells[cell_index] + + # Update cell type if specified + if cell_type is not None: + cell["cell_type"] = cell_type + if cell_type == "code" and "execution_count" not in cell: + cell["execution_count"] = None + cell["outputs"] = [] + + # Update source + cell["source"] = new_source.split("\n") if new_source else [] + + # Clear outputs for code cells + if cell.get("cell_type") == "code": + cell["outputs"] = [] + cell["execution_count"] = None + + notebook["cells"] = cells + + with open(path_obj, "w", encoding="utf-8") as f: + json.dump(notebook, f, indent=1) + + return f"Successfully replaced cell at index {cell_index}" + + except json.JSONDecodeError as e: + return f"Error: Invalid notebook JSON: {str(e)}" + except Exception as e: + return f"Error editing notebook: {str(e)}" + + +@tool +def notebook_read( + path: Annotated[str, "Path to the Jupyter notebook (.ipynb)"], + cell_index: Annotated[int | None, "Specific cell index to read (None for all)"] = None, + include_outputs: Annotated[bool, "Include cell outputs"] = True, +) -> str: + """Read a Jupyter notebook and display its cells. + + Returns formatted cell contents with indices for easy reference. + """ + try: + path_obj = Path(path).expanduser().resolve() + + if not path_obj.exists(): + return f"Error: Notebook not found: {path}" + + if not path_obj.suffix == ".ipynb": + return f"Error: Not a Jupyter notebook (must be .ipynb): {path}" + + with open(path_obj, "r", encoding="utf-8") as f: + notebook = json.load(f) + + cells = notebook.get("cells", []) + + if not cells: + return f"Notebook is empty: {path}" + + cells_list: list[tuple[int, dict[str, Any]]] + if cell_index is not None: + if cell_index < 0 or cell_index >= len(cells): + return f"Error: Cell index {cell_index} out of range (notebook has {len(cells)} cells)" + cells_list = [(cell_index, cells[cell_index])] + else: + cells_list = list(enumerate(cells)) + + output_parts: list[str] = [] + for idx, cell in cells_list: + cell_type = str(cell.get("cell_type", "unknown")) + source = cell.get("source", []) + + # Source can be a list of lines or a single string + if isinstance(source, list): + source_text = "".join(str(s) for s in source) + else: + source_text = str(source) + + part = f"--- Cell {idx} [{cell_type}] ---\n{source_text}" + + # Include outputs for code cells + if include_outputs and cell_type == "code": + outputs = cell.get("outputs", []) + if outputs and isinstance(outputs, list): + output_texts: list[str] = [] + for output in outputs: + if not isinstance(output, dict): + continue + if output.get("output_type") == "stream": + text_data = output.get("text", []) + text = "".join(str(t) for t in text_data) if isinstance(text_data, list) else str(text_data) + output_texts.append(f"[stdout]\n{text}") + elif output.get("output_type") == "execute_result": + data = output.get("data", {}) + if isinstance(data, dict) and "text/plain" in data: + plain_data = data["text/plain"] + text = "".join(str(t) for t in plain_data) if isinstance(plain_data, list) else str(plain_data) + output_texts.append(f"[result]\n{text}") + elif output.get("output_type") == "error": + ename = str(output.get("ename", "Error")) + evalue = str(output.get("evalue", "")) + output_texts.append(f"[error] {ename}: {evalue}") + + if output_texts: + part += "\n\n" + "\n".join(output_texts) + + output_parts.append(part) + + result = "\n\n".join(output_parts) + if cell_index is None: + result = f"Notebook: {path} ({len(notebook.get('cells', []))} cells)\n\n" + result + + return result + + except json.JSONDecodeError as e: + return f"Error: Invalid notebook JSON: {str(e)}" + except Exception as e: + return f"Error reading notebook: {str(e)}" diff --git a/src/flow/tools/planning.py b/src/flow/tools/planning.py new file mode 100644 index 0000000000000000000000000000000000000000..fce24c917f0f6c70bcc11bbb51c650701b3c4f18 --- /dev/null +++ b/src/flow/tools/planning.py @@ -0,0 +1,161 @@ +"""Planning and task management tools for Flow. + +Tools for reasoning, tracking progress, and managing complex tasks. +Todos are persisted to the workspace's .flow/todos.json file. +""" + +from typing import Annotated, Any + +from .base import tool +from .workspace import get_workspace, Workspace + + +# Optional workspace override (for testing or custom setups) +_workspace_override: Workspace | None = None + + +def _get_workspace() -> Workspace: + """Get the workspace for todo storage.""" + if _workspace_override is not None: + return _workspace_override + return get_workspace() + + +@tool +def think( + thought: Annotated[str, "Your reasoning, analysis, or plan"], +) -> str: + """Think through a problem step by step. + + Use this tool to: + - Break down complex problems before acting + - Plan your approach + - Reason through edge cases + - Reflect on results and adjust strategy + + Your thought is recorded in conversation history for reference. + """ + # The thought is recorded in the tool result, becoming part of context + return "Thought recorded." + + +@tool +def todo_write( + todos: Annotated[ + list[dict[str, Any]], + "List of todo items. Each item needs: content (str), status ('pending'|'in_progress'|'completed'), activeForm (str describing current action)" + ], +) -> str: + """Create or update the task list for this session. + + Use this tool proactively when: + - Starting a complex multi-step task (3+ steps) + - The user provides multiple tasks to complete + - You need to track progress on a larger goal + + Each todo item requires: + - content: What needs to be done (imperative form, e.g., "Fix the login bug") + - status: 'pending', 'in_progress', or 'completed' + - activeForm: Present tense description (e.g., "Fixing the login bug") + + Important: + - Only ONE task should be 'in_progress' at a time + - Mark tasks 'completed' immediately when done (don't batch) + - Keep descriptions concise but specific + + Todos are persisted to {workspace}/.flow/todos.json + + Example: + todo_write(todos=[ + {"content": "Read the config file", "status": "completed", "activeForm": "Reading config"}, + {"content": "Update the API endpoint", "status": "in_progress", "activeForm": "Updating API endpoint"}, + {"content": "Run tests", "status": "pending", "activeForm": "Running tests"}, + ]) + """ + # Validate todos + valid_statuses = {"pending", "in_progress", "completed"} + for i, todo in enumerate(todos): + if "content" not in todo: + return f"Error: Todo {i+1} missing 'content'" + if "status" not in todo: + return f"Error: Todo {i+1} missing 'status'" + if todo["status"] not in valid_statuses: + return f"Error: Todo {i+1} has invalid status '{todo['status']}'. Must be: {valid_statuses}" + if "activeForm" not in todo: + return f"Error: Todo {i+1} missing 'activeForm'" + + # Check only one in_progress + in_progress_count = sum(1 for t in todos if t["status"] == "in_progress") + if in_progress_count > 1: + return f"Error: {in_progress_count} tasks marked 'in_progress'. Only one task should be in progress at a time." + + # Save to workspace + ws = _get_workspace() + ws.save_todos(todos) + + # Format summary + completed = sum(1 for t in todos if t["status"] == "completed") + pending = sum(1 for t in todos if t["status"] == "pending") + in_progress = sum(1 for t in todos if t["status"] == "in_progress") + + current = next((t for t in todos if t["status"] == "in_progress"), None) + current_msg = f"Current: {current['activeForm']}" if current else "No task in progress" + + return f"Todo list updated: {completed} completed, {in_progress} in progress, {pending} pending. {current_msg}" + + +@tool +def todo_read() -> str: + """Read the current todo list. + + Returns the current state of all tasks with their status. + Todos are loaded from {workspace}/.flow/todos.json + """ + ws = _get_workspace() + todos = ws.load_todos() + + if not todos: + return "No todos. Use todo_write to create a task list." + + lines: list[str] = [] + for todo in todos: + status = todo.get("status", "pending") + content = todo.get("content", "") + + if status == "completed": + icon = "✓" + elif status == "in_progress": + icon = "→" + else: + icon = "○" + + lines.append(f"{icon} {content}") + + completed = sum(1 for t in todos if t["status"] == "completed") + total = len(todos) + + return f"Progress: {completed}/{total}\n\n" + "\n".join(lines) + + +def reset_todos() -> None: + """Reset todo state (clears the todos file).""" + ws = _get_workspace() + ws.save_todos([]) + + +def get_todos() -> list[dict[str, Any]]: + """Get current todo state (for testing/inspection).""" + ws = _get_workspace() + return ws.load_todos() + + +def set_planning_workspace(workspace: Workspace) -> None: + """Set workspace for todo storage (for testing).""" + global _workspace_override + _workspace_override = workspace + + +def clear_planning_workspace_override() -> None: + """Clear workspace override (for testing).""" + global _workspace_override + _workspace_override = None diff --git a/src/flow/tools/skills.py b/src/flow/tools/skills.py new file mode 100644 index 0000000000000000000000000000000000000000..8bec26d32f3fbf5891791f09ca241c860d3f7f02 --- /dev/null +++ b/src/flow/tools/skills.py @@ -0,0 +1,237 @@ +"""Skills tool for discovering and loading domain expertise. + +Skills are SKILL.md files with YAML frontmatter following Anthropic's pattern. +Each skill provides domain-specific patterns, best practices, and examples. + +Features: +- Progressive disclosure: List shows only summaries, load fetches full content +- Multiple skill paths: Built-in, user (~/.flow/skills/), and project-local +- Override hierarchy: Project > User > Built-in + +Usage: + from flow.tools import skills, create_skills_tool + + # Default: includes built-in skills + tools = [skills] + + # Or create with custom paths + from flow.tools.skills import create_skills_tool + + custom_skills = create_skills_tool( + builtin_path=Path("./skills"), + user_path=Path.home() / ".flow" / "skills", + ) + tools = [custom_skills] +""" + +import re +from pathlib import Path +from typing import Annotated, Literal + +from .base import tool, Tool + + +def _parse_frontmatter(content: str) -> dict[str, str]: + """Extract YAML frontmatter from SKILL.md. + + Expected format: + --- + name: skill-name + description: What this skill does... + triggers: keyword1, keyword2 # Optional + --- + + # Skill Content... + """ + match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL) + if not match: + return {} + + frontmatter: dict[str, str] = {} + for line in match.group(1).split("\n"): + line = line.strip() + if ":" in line: + key, value = line.split(":", 1) + frontmatter[key.strip()] = value.strip() + + return frontmatter + + +def _get_skill_body(content: str) -> str: + """Extract the body (after frontmatter) from a SKILL.md file.""" + match = re.match(r"^---\s*\n.*?\n---\s*\n", content, re.DOTALL) + if match: + return content[match.end():] + return content + + +def _discover_skills(skills_paths: list[Path]) -> dict[str, tuple[Path, dict[str, str]]]: + """Discover all skills from the given paths. + + Later paths override earlier ones (project > user > built-in). + + Returns: + Dict mapping skill name -> (skill_md_path, frontmatter_metadata) + """ + skills: dict[str, tuple[Path, dict[str, str]]] = {} + + for skills_path in skills_paths: + if not skills_path.exists(): + continue + + for item in skills_path.iterdir(): + if item.is_dir(): + skill_md = item / "SKILL.md" + if skill_md.exists(): + try: + content = skill_md.read_text() + meta = _parse_frontmatter(content) + skill_name = meta.get("name", item.name) + skills[skill_name] = (skill_md, meta) + except Exception: + # Skip broken skills + skills[item.name] = ( + skill_md, + {"name": item.name, "description": "Error reading skill"}, + ) + + return skills + + +def _get_builtin_skills_path() -> Path: + """Get the path to built-in skills that ship with the package.""" + # flow/tools/skills.py -> flow/skills/ + return Path(__file__).parent.parent / "skills" + + +def _get_user_skills_path() -> Path: + """Get the path to user-defined skills.""" + return Path.home() / ".flow" / "skills" + + +def create_skills_tool( + builtin_path: Path | None = None, + user_path: Path | None = None, + project_path: Path | None = None, +) -> Tool: + """Create a skills tool for discovering and loading domain expertise. + + Args: + builtin_path: Path to built-in skills (shipped with package) + user_path: Path to user skills (defaults to ~/.flow/skills/) + project_path: Path to project-local skills (highest priority) + + Returns: + A Tool that can be added to an agent's tool list + + Example: + from flow.tools import create_skills_tool + + skills = create_skills_tool( + project_path=Path("./skills"), + ) + agent = ChatAgent(tools=coding_tools() + [skills]) + """ + # Build list of skill paths (later paths override earlier) + all_paths: list[Path] = [] + + # Built-in skills + if builtin_path: + all_paths.append(builtin_path) + else: + default_builtin = _get_builtin_skills_path() + if default_builtin.exists(): + all_paths.append(default_builtin) + + # User skills + if user_path: + all_paths.append(user_path) + else: + default_user = _get_user_skills_path() + if default_user.exists(): + all_paths.append(default_user) + + # Project-local skills (highest priority) + if project_path: + all_paths.append(project_path) + + @tool + def skills( + action: Annotated[ + Literal["list", "load"], + "'list' for skill summaries (names + descriptions), 'load' for full content", + ], + name: Annotated[ + str, + "Skill name to load (required for 'load' action)", + ] = "", + ) -> str: + """Discover and load skills for domain-specific guidance. + + Use action='list' at the start of complex tasks to see available expertise. + Use action='load' to get full content of a specific skill. + + Skills provide patterns, best practices, and examples for specific domains. + Progressive disclosure: 'list' returns only summaries to save context. + """ + discovered = _discover_skills(all_paths) + + if action == "list": + if not discovered: + paths_str = "\n".join(f" - {p}" for p in all_paths) if all_paths else " (no paths configured)" + return ( + "No skills found.\n\n" + f"Skills are loaded from:\n{paths_str}\n\n" + "Each skill should be a folder with a SKILL.md file containing:\n" + "---\n" + "name: skill-name\n" + "description: What this skill does...\n" + "---\n" + "# Skill content..." + ) + + lines = ["# Available Skills\n"] + lines.append("Use `skills(action='load', name='...')` to load full content.\n") + + for skill_name, (_, meta) in sorted(discovered.items()): + description = meta.get("description", "No description") + triggers = meta.get("triggers", "") + + lines.append(f"### {skill_name}") + lines.append(description) + if triggers: + lines.append(f"_Triggers: {triggers}_") + lines.append("") + + return "\n".join(lines) + + elif action == "load": + if not name: + return "Error: 'name' parameter is required for 'load' action." + + if name not in discovered: + available = sorted(discovered.keys()) + msg = f"Skill '{name}' not found." + if available: + msg += f"\n\nAvailable skills: {', '.join(available)}" + return msg + + skill_md, meta = discovered[name] + try: + content = skill_md.read_text() + skill_name = meta.get("name", name) + + # Return full content (body only, frontmatter already parsed) + body = _get_skill_body(content) + return f"# Skill: {skill_name}\n\n{body}" + except Exception as e: + return f"Error loading skill '{name}': {e}" + + else: + return f"Unknown action: '{action}'. Use 'list' or 'load'." + + return skills + + +# Default skills tool (includes built-in skills from Flow) +skills = create_skills_tool() diff --git a/src/flow/tools/subagent.py b/src/flow/tools/subagent.py new file mode 100644 index 0000000000000000000000000000000000000000..4755548ff213767a18d52e499eb821bc829de817 --- /dev/null +++ b/src/flow/tools/subagent.py @@ -0,0 +1,232 @@ +"""Task tool for context isolation via sub-agents. + +The Task tool spawns sub-agents that run in isolated contexts. +Only summaries cross the boundary back to the coordinator. + +This implements the "Isolation" strategy for context engineering: +- Sub-agent can use many tokens internally (10K+) +- Coordinator only sees the distilled result (few hundred tokens) +- Prevents context pollution from verbose intermediate steps +- Enables parallelization (multiple sub-agents can run concurrently) + +Based on Claude Code's Task tool architecture. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Annotated, Literal, TYPE_CHECKING + +from .base import tool, Tool + +if TYPE_CHECKING: + pass # For future type imports + + +# Agent type configurations +AGENT_TYPES = { + "explore": { + "description": "Fast agent for exploring codebases", + "instructions": """You are a codebase exploration specialist. + +Your role: +1. Quickly find files, code patterns, and answer questions about the codebase +2. Use glob and grep efficiently to locate relevant code +3. Read files to understand implementations +4. Provide concise, accurate summaries + +Guidelines: +- Be thorough but efficient - don't read unnecessary files +- Focus on answering the specific question asked +- Return structured findings (file paths, line numbers, key code snippets) +- Keep your final response under 500 tokens +- Your response will be passed to another agent, so make it self-contained +""", + "tools": ["read_file", "glob_files", "grep", "ls", "think"], + }, + "research": { + "description": "Agent for web research and information gathering", + "instructions": """You are a research assistant. + +Your role: +1. Thoroughly research the given topic using web search and fetch +2. Gather relevant information from multiple sources +3. Synthesize findings into a clear, concise summary +4. Return only the essential information needed + +Guidelines: +- Be thorough in research but concise in response +- Focus on facts and actionable information +- Cite sources when relevant +- Target 200-500 tokens for your final response +- Your response will be passed to another agent, so make it self-contained +""", + "tools": ["web_search", "web_fetch", "think"], + }, + "general": { + "description": "General-purpose agent for complex multi-step tasks", + "instructions": """You are a capable assistant handling a delegated task. + +Your role: +1. Complete the assigned task thoroughly +2. Use available tools as needed +3. Return a clear summary of what was accomplished + +Guidelines: +- Focus on the specific task assigned +- Be thorough but concise in your response +- Include relevant details the coordinator needs +- Target 300-600 tokens for your final response +""", + "tools": None, # Uses coordinator's tools + }, +} + + +def create_task_tool( + coordinator_tools: list[Tool] | None = None, + model: str | None = None, + api_key: str | None = None, + base_url: str | None = None, + token_budget: int = 50_000, + max_iterations: int = 20, +) -> Tool: + """Create a Task tool that spawns sub-agents. + + The Task tool enables context isolation - sub-agents run in their own + context windows, preventing context pollution in the coordinator. + + Args: + coordinator_tools: Tools available to general-purpose sub-agents + model: Model to use for sub-agents + api_key: API key for sub-agents + base_url: Base URL for API + token_budget: Token budget for sub-agents (default 50K) + max_iterations: Max iterations for sub-agents (default 20) + + Returns: + A Tool that can be added to an agent's tool list + + Example: + from flow.tools import coding_tools, create_task_tool + + tools = coding_tools() + [create_task_tool()] + """ + + @tool + async def task( + prompt: Annotated[str, "Detailed description of the task for the sub-agent"], + description: Annotated[str, "Short 3-5 word summary of what the sub-agent will do"], + agent_type: Annotated[ + Literal["explore", "research", "general"], + "Type of sub-agent: 'explore' for codebase search, 'research' for web research, 'general' for other tasks" + ] = "general", + ) -> str: + """Launch a sub-agent to handle a complex task in isolated context. + + Use this tool when: + - You need to explore a codebase without polluting your context + - A task requires many tool calls that would bloat context + - You want to delegate research to a specialist + - The task has clear input/output boundaries + + The sub-agent runs in its own context window: + - It can use many tokens internally + - Only the summary crosses back to you + - Your context stays lean and focused + + Agent types: + - explore: Fast codebase search (glob, grep, read, ls) + - research: Web research (web_search, web_fetch) + - general: All tools available to you + """ + # Lazy imports to avoid circular dependencies + try: + from flow.harness.miniagent.agent import ChatAgent + from flow.harness.miniagent.client import ChatClient, ClientConfig + from flow.harness.miniagent.context import HeadTailStrategy + except ImportError: + return "Error: MiniAgent harness not available. Install flow with miniagent extras." + + # Get agent type config + agent_type_config = AGENT_TYPES.get(agent_type, AGENT_TYPES["general"]) + + # Determine tools for sub-agent + sub_tools: list[Tool] = [] + tool_names = agent_type_config.get("tools") + + if tool_names is None: + # General agent - inherit coordinator's tools (except task itself) + if coordinator_tools: + sub_tools = [t for t in coordinator_tools if t.name != "task"] + else: + # Specific tool set - import from shared tools + from . import ( + read_file, glob_files, grep, ls, + ) + from .planning import think + from .web import web_search, web_fetch + + tool_map = { + "read_file": read_file, + "glob_files": glob_files, + "grep": grep, + "ls": ls, + "think": think, + "web_search": web_search, + "web_fetch": web_fetch, + } + sub_tools = [tool_map[name] for name in tool_names if name in tool_map] + + # Create client for sub-agent + client_config = ClientConfig.from_env() + + # Override with explicit parameters + if model: + client_config.model = model + if api_key: + client_config.api_key = api_key + if base_url: + client_config.endpoint = base_url + + client = ChatClient(client_config) + + # Get instructions from agent type config + instructions_value = agent_type_config.get("instructions") + instructions = instructions_value if isinstance(instructions_value, str) else None + + # Create sub-agent with compaction for its own context + sub_agent = ChatAgent( + client=client, + instructions=instructions, + tools=sub_tools, + context_strategy=HeadTailStrategy(head_ratio=0.2), + token_budget=token_budget, + max_iterations=max_iterations, + ) + + # Run the sub-agent + try: + response = await sub_agent.run(prompt) + + # Format result with metadata + result = response.content or "(No response from sub-agent)" + + # Add usage info for transparency + usage_info = ( + f"\n\n[Sub-agent ({agent_type}): " + f"{response.iterations} iterations, " + f"{response.usage.total_input_tokens} input tokens, " + f"{response.usage.tool_calls} tool calls]" + ) + + return result + usage_info + + except Exception as e: + return f"Sub-agent failed: {str(e)}" + + return task + + +# Default task tool (will use default ChatClient settings) +task = create_task_tool() diff --git a/src/flow/tools/web.py b/src/flow/tools/web.py new file mode 100644 index 0000000000000000000000000000000000000000..f15cae9b027c408af89996fa863917828a88a1f8 --- /dev/null +++ b/src/flow/tools/web.py @@ -0,0 +1,145 @@ +"""Web tools for Flow. + +Search the web and fetch content from URLs. +""" + +import os +from typing import Annotated +from urllib.parse import urlparse + +from .base import tool + + +@tool +def web_search( + query: Annotated[str, "Search query"], + num_results: Annotated[int, "Number of results to return"] = 5, +) -> str: + """Search the web using Google Custom Search API. + + Requires GOOGLE_API_KEY and GOOGLE_CSE_ID environment variables. + Returns a list of search results with titles, URLs, and snippets. + """ + api_key = os.environ.get("GOOGLE_API_KEY") + cse_id = os.environ.get("GOOGLE_CSE_ID") + + if not api_key or not cse_id: + return ( + "Error: Web search requires GOOGLE_API_KEY and GOOGLE_CSE_ID " + "environment variables to be set." + ) + + try: + import httpx + except ImportError: + return "Error: httpx package required. Install with: pip install httpx" + + try: + url = "https://www.googleapis.com/customsearch/v1" + params = { + "key": api_key, + "cx": cse_id, + "q": query, + "num": min(num_results, 10), # API max is 10 + } + + with httpx.Client(timeout=30.0) as client: + response = client.get(url, params=params) + response.raise_for_status() + data = response.json() + + items = data.get("items", []) + + if not items: + return f"No results found for: {query}" + + results: list[str] = [] + for i, item in enumerate(items, 1): + title = item.get("title", "No title") + link = item.get("link", "") + snippet = item.get("snippet", "No description") + results.append(f"{i}. {title}\n {link}\n {snippet}") + + return "\n\n".join(results) + + except Exception as e: + return f"Error performing search: {str(e)}" + + +@tool +def web_fetch( + url: Annotated[str, "URL to fetch"], + format: Annotated[str, "Output format: 'markdown', 'text', or 'html'"] = "markdown", + max_length: Annotated[int, "Maximum content length in characters"] = 50000, +) -> str: + """Fetch and parse content from a URL. + + Returns the page content in the specified format. + Useful for reading documentation, articles, and web pages. + """ + # Validate URL + try: + parsed = urlparse(url) + if not parsed.scheme: + url = "https://" + url + parsed = urlparse(url) + if not parsed.netloc: + return f"Error: Invalid URL: {url}" + except Exception: + return f"Error: Invalid URL format: {url}" + + try: + import httpx + except ImportError: + return "Error: httpx package required. Install with: pip install httpx" + + try: + headers = { + "User-Agent": "Mozilla/5.0 (compatible; FlowAgent/1.0)" + } + + with httpx.Client(timeout=30.0, follow_redirects=True) as client: + response = client.get(url, headers=headers) + response.raise_for_status() + html = response.text + + content: str + if format == "html": + content = html[:max_length] + else: + # Try to extract text/markdown + try: + from bs4 import BeautifulSoup # type: ignore[import-untyped] + import html2text # type: ignore[import-untyped] + + soup = BeautifulSoup(html, "html.parser") + + # Remove script and style elements + for element in soup(["script", "style", "nav", "footer", "header"]): + element.decompose() + + if format == "markdown": + h = html2text.HTML2Text() + h.ignore_links = False + h.ignore_images = True + h.body_width = 0 + content = str(h.handle(str(soup))) + else: + content = str(soup.get_text(separator="\n", strip=True)) + + except ImportError: + # Fallback to basic text extraction + import re + # Remove tags + content = re.sub(r"<[^>]+>", " ", html) + # Clean whitespace + content = re.sub(r"\s+", " ", content).strip() + + # Truncate if needed + if len(content) > max_length: + content = content[:max_length] + "\n\n[Content truncated...]" + + return content + + except Exception as e: + return f"Error fetching URL: {str(e)}" diff --git a/src/flow/tools/workspace.py b/src/flow/tools/workspace.py new file mode 100644 index 0000000000000000000000000000000000000000..fa6d537e64f007d135253dad5812b054b4443456 --- /dev/null +++ b/src/flow/tools/workspace.py @@ -0,0 +1,198 @@ +"""Workspace management for Flow tools. + +Provides a simple convention for where agent-managed data lives: +- Working directory is the workspace (or explicitly set) +- Agent data goes in `{workspace}/.flow/` +- No restrictions on file access - agent can read/write anywhere + +Structure: + {workspace}/ + ├── .flow/ + │ ├── todos.json # Persisted task list + │ ├── memory/ # Memory entries + │ │ ├── {id}.json + │ │ └── ... + │ └── config.json # Optional agent config + └── ... (rest of project) + +Usage: + from flow.tools.workspace import Workspace, get_workspace, set_workspace + + # Use current directory + ws = Workspace() + + # Or explicit path + ws = Workspace("/path/to/project") + + # Get paths + ws.root # /path/to/project + ws.data_dir # /path/to/project/.flow + ws.todos_file # /path/to/project/.flow/todos.json + ws.memory_dir # /path/to/project/.flow/memory +""" + +import json +from pathlib import Path +from typing import Any + + +class Workspace: + """Manages workspace paths and agent data storage. + + The workspace is where the agent operates. Agent-managed data + (todos, memories, etc.) is stored in a `.flow/` subdirectory. + """ + + def __init__(self, root: str | Path | None = None): + """Initialize workspace. + + Args: + root: Workspace root directory. Defaults to current working directory. + """ + if root is None: + root = Path.cwd() + self._root = Path(root).resolve() + + @property + def root(self) -> Path: + """Workspace root directory.""" + return self._root + + @property + def data_dir(self) -> Path: + """Agent data directory (.flow/).""" + return self._root / ".flow" + + @property + def todos_file(self) -> Path: + """Path to todos.json.""" + return self.data_dir / "todos.json" + + @property + def memory_dir(self) -> Path: + """Path to memory/ directory.""" + return self.data_dir / "memory" + + @property + def config_file(self) -> Path: + """Path to config.json.""" + return self.data_dir / "config.json" + + def ensure_data_dir(self) -> Path: + """Create data directory if it doesn't exist.""" + self.data_dir.mkdir(parents=True, exist_ok=True) + return self.data_dir + + def ensure_memory_dir(self) -> Path: + """Create memory directory if it doesn't exist.""" + self.memory_dir.mkdir(parents=True, exist_ok=True) + return self.memory_dir + + # --- Todos --- + + def load_todos(self) -> list[dict[str, Any]]: + """Load todos from workspace.""" + if not self.todos_file.exists(): + return [] + try: + with open(self.todos_file) as f: + return json.load(f) # type: ignore[no-any-return] + except (json.JSONDecodeError, IOError): + return [] + + def save_todos(self, todos: list[dict[str, Any]]) -> None: + """Save todos to workspace.""" + self.ensure_data_dir() + with open(self.todos_file, "w") as f: + json.dump(todos, f, indent=2) + + # --- Memory --- + + def list_memories(self) -> list[dict[str, Any]]: + """List all memory entries.""" + if not self.memory_dir.exists(): + return [] + + memories: list[dict[str, Any]] = [] + for filepath in self.memory_dir.glob("*.json"): + try: + with open(filepath) as f: + memories.append(json.load(f)) + except (json.JSONDecodeError, IOError): + continue + return memories + + def load_memory(self, memory_id: str) -> dict[str, Any] | None: + """Load a specific memory entry.""" + filepath = self.memory_dir / f"{memory_id}.json" + if not filepath.exists(): + return None + try: + with open(filepath) as f: + return json.load(f) # type: ignore[no-any-return] + except (json.JSONDecodeError, IOError): + return None + + def save_memory(self, memory_id: str, data: dict[str, Any]) -> None: + """Save a memory entry.""" + self.ensure_memory_dir() + filepath = self.memory_dir / f"{memory_id}.json" + with open(filepath, "w") as f: + json.dump(data, f, indent=2, default=str) + + def delete_memory(self, memory_id: str) -> bool: + """Delete a memory entry. Returns True if deleted.""" + filepath = self.memory_dir / f"{memory_id}.json" + if filepath.exists(): + filepath.unlink() + return True + return False + + # --- Config --- + + def load_config(self) -> dict[str, Any]: + """Load workspace config.""" + if not self.config_file.exists(): + return {} + try: + with open(self.config_file) as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return {} + + def save_config(self, config: dict[str, Any]) -> None: + """Save workspace config.""" + self.ensure_data_dir() + with open(self.config_file, "w") as f: + json.dump(config, f, indent=2) + + def __repr__(self) -> str: + return f"Workspace({self._root})" + + +# Default workspace (current directory) +_default_workspace: Workspace | None = None + + +def get_workspace() -> Workspace: + """Get the default workspace (creates if needed).""" + global _default_workspace + if _default_workspace is None: + _default_workspace = Workspace() + return _default_workspace + + +def set_workspace(workspace: Workspace | str | Path) -> Workspace: + """Set the default workspace.""" + global _default_workspace + if isinstance(workspace, Workspace): + _default_workspace = workspace + else: + _default_workspace = Workspace(workspace) + return _default_workspace + + +def reset_workspace() -> None: + """Reset default workspace (for testing).""" + global _default_workspace + _default_workspace = None diff --git a/src/flow/ui/api/__init__.py b/src/flow/ui/api/__init__.py index e9f4fc73a940457a6bd041496e0a35d0ce0ff943..16bb55c0510bf1d40be97520ac9c95d0c2019d39 100644 --- a/src/flow/ui/api/__init__.py +++ b/src/flow/ui/api/__init__.py @@ -6,6 +6,8 @@ from .tasks import router as tasks_router from .jobs import router as jobs_router from .runs import router as runs_router from .tests import router as tests_router +from .llm_configs import router as llm_configs_router +from .tools import router as tools_router __all__ = [ "configs_router", @@ -13,4 +15,6 @@ __all__ = [ "jobs_router", "runs_router", "tests_router", + "llm_configs_router", + "tools_router", ] diff --git a/src/flow/ui/api/configs.py b/src/flow/ui/api/configs.py index a5c6cc188604714284d19107beb2ab8a3e6a4863..496cafcc73345129d24d4b59731a019ff7a5c4a3 100644 --- a/src/flow/ui/api/configs.py +++ b/src/flow/ui/api/configs.py @@ -7,7 +7,7 @@ from uuid import UUID from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession -from sqlmodel import desc, select +from sqlmodel import desc, or_, select from flow.experiments.models import Agent, CompactionConfig, GridSearchStrategy @@ -55,10 +55,15 @@ def parse_uuid(id_str: str) -> UUID: @router.get("", response_model=list[AgentResponse]) async def list_configs( include_auto_generated: bool = False, + include_public: bool = False, session: AsyncSession = Depends(get_session), user: Annotated[TokenData | None, Depends(get_current_user)] = None, ) -> list[AgentConfig]: - """List agent configurations for the current user.""" + """List agent configurations. + + By default shows only user's own configs. Set include_public=true to also + include public configs from other users. + """ query = select(AgentConfig) if not include_auto_generated: query = query.where(AgentConfig.is_auto_generated == False) # noqa: E712 @@ -66,7 +71,17 @@ async def list_configs( # Filter by user if auth is enabled if should_filter_by_user(): effective_user_id = get_effective_user_id(user) - query = query.where(AgentConfig.user_id == effective_user_id) + if include_public: + # Show user's own configs OR public configs + query = query.where( + or_( + AgentConfig.user_id == effective_user_id, + AgentConfig.is_public == True, # noqa: E712 + ) + ) + else: + # Show only user's own configs + query = query.where(AgentConfig.user_id == effective_user_id) query = query.order_by(desc(AgentConfig.created_at)) result = await session.execute(query) @@ -98,14 +113,23 @@ async def get_config( session: AsyncSession = Depends(get_session), user: Annotated[TokenData | None, Depends(get_current_user)] = None, ) -> AgentConfig: - """Get a specific agent configuration.""" + """Get a specific agent configuration. + + Accessible if the config is public OR if the user owns it. + """ uuid_id = parse_uuid(config_id) query = select(AgentConfig).where(AgentConfig.id == uuid_id) # Filter by user if auth is enabled if should_filter_by_user(): effective_user_id = get_effective_user_id(user) - query = query.where(AgentConfig.user_id == effective_user_id) + # Allow access if public OR owner + query = query.where( + or_( + AgentConfig.is_public == True, # noqa: E712 + AgentConfig.user_id == effective_user_id, + ) + ) result = await session.execute(query) config = result.scalar_one_or_none() @@ -138,6 +162,7 @@ async def update_config( update_data = data.model_dump(exclude_unset=True) config_fields = [ + "framework", "instructions", "model", "compaction", diff --git a/src/flow/ui/api/jobs.py b/src/flow/ui/api/jobs.py index 2451833dd2748e696bea928d0022b0f91b66b554..2af3fc0db36ad0109106e83d5dc272775b8d6b49 100644 --- a/src/flow/ui/api/jobs.py +++ b/src/flow/ui/api/jobs.py @@ -17,7 +17,7 @@ from ..database import async_session, get_session from ..models.config import AgentConfig from ..models.job import JobStatus, OptimizationJob from ..models.task import TaskModel -from ..schemas import JobCreate, JobResponse +from ..schemas import JobCreate, JobResponse, JobUpdate from ..services.optimizer_service import OptimizerService router = APIRouter(prefix="/jobs", tags=["jobs"]) @@ -38,10 +38,15 @@ def parse_uuid(id_str: str) -> UUID: @router.get("", response_model=list[JobResponse]) async def list_jobs( status: JobStatus | None = None, + include_public: bool = False, session: AsyncSession = Depends(get_session), user: Annotated[TokenData | None, Depends(get_current_user)] = None, ) -> list[OptimizationJob]: - """List all optimization jobs for the current user.""" + """List optimization jobs. + + By default shows only user's own jobs. Set include_public=true to also + include public jobs from other users. + """ query = select(OptimizationJob) if status: query = query.where(OptimizationJob.status == status) @@ -49,7 +54,17 @@ async def list_jobs( # Filter by user if auth is enabled if should_filter_by_user(): effective_user_id = get_effective_user_id(user) - query = query.where(OptimizationJob.user_id == effective_user_id) + if include_public: + # Show user's own jobs OR public jobs + query = query.where( + or_( + OptimizationJob.user_id == effective_user_id, + OptimizationJob.is_public == True, # noqa: E712 + ) + ) + else: + # Show only user's own jobs + query = query.where(OptimizationJob.user_id == effective_user_id) query = query.order_by(desc(OptimizationJob.created_at)) result = await session.execute(query) @@ -90,6 +105,11 @@ async def create_job( if not result.scalar_one_or_none(): raise HTTPException(status_code=400, detail=f"Task {task_id} not found") + # Get display name for creator (use username from token or fallback to user_id) + created_by_name = None + if user: + created_by_name = getattr(user, "name", None) or getattr(user, "preferred_username", None) or user.sub + job = OptimizationJob( name=data.name, candidate_ids=data.candidate_ids, @@ -98,6 +118,7 @@ async def create_job( use_llm_eval=data.use_llm_eval, total_experiments=len(data.candidate_ids) * len(data.task_ids), user_id=effective_user_id, + created_by_name=created_by_name, ) session.add(job) await session.commit() @@ -111,10 +132,45 @@ async def get_job( session: AsyncSession = Depends(get_session), user: Annotated[TokenData | None, Depends(get_current_user)] = None, ) -> OptimizationJob: - """Get a specific optimization job.""" + """Get a specific optimization job. + + Accessible if the job is public OR if the user owns it. + """ uuid_id = parse_uuid(job_id) query = select(OptimizationJob).where(OptimizationJob.id == uuid_id) + if should_filter_by_user(): + effective_user_id = get_effective_user_id(user) + # Allow access if public OR owner + query = query.where( + or_( + OptimizationJob.is_public == True, # noqa: E712 + OptimizationJob.user_id == effective_user_id, + ) + ) + + result = await session.execute(query) + job = result.scalar_one_or_none() + if not job: + raise HTTPException(status_code=404, detail="Job not found") + return job + + +@router.put("/{job_id}", response_model=JobResponse) +async def update_job( + job_id: str, + data: JobUpdate, + session: AsyncSession = Depends(get_session), + user: Annotated[TokenData | None, Depends(get_current_user)] = None, +) -> OptimizationJob: + """Update a job (e.g., toggle public visibility). + + Only the job owner can update. + """ + uuid_id = parse_uuid(job_id) + query = select(OptimizationJob).where(OptimizationJob.id == uuid_id) + + # Only owner can update - strict user filter if should_filter_by_user(): effective_user_id = get_effective_user_id(user) query = query.where(OptimizationJob.user_id == effective_user_id) @@ -123,6 +179,14 @@ async def get_job( job = result.scalar_one_or_none() if not job: raise HTTPException(status_code=404, detail="Job not found") + + # Apply updates + update_data = data.model_dump(exclude_unset=True) + for key, value in update_data.items(): + setattr(job, key, value) + + await session.commit() + await session.refresh(job) return job diff --git a/src/flow/ui/api/llm_configs.py b/src/flow/ui/api/llm_configs.py new file mode 100644 index 0000000000000000000000000000000000000000..4981ed8072abcabfb30442cb4df5aa76917be7bf --- /dev/null +++ b/src/flow/ui/api/llm_configs.py @@ -0,0 +1,307 @@ +# Copyright (c) Microsoft. All rights reserved. +"""LLM configuration API routes.""" + +import time +from datetime import datetime, timezone +from typing import Annotated +from uuid import UUID + +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from sqlmodel import desc, select + +from flow.llm import LLMClientFactory, LLMProvider + +from ..database import get_session +from ..models.llm_config import LLMConfig +from ..schemas.llm_config import ( + LLMConfigCreate, + LLMConfigResponse, + LLMConfigTestResult, + LLMConfigUpdate, +) + +router = APIRouter(prefix="/llm-configs", tags=["llm-configs"]) + + +def parse_uuid(id_str: str) -> UUID: + """Parse a string to UUID, raising 400 if invalid.""" + try: + return UUID(id_str) + except ValueError as e: + raise HTTPException(status_code=400, detail=f"Invalid UUID: {id_str}") from e + + +@router.get("", response_model=list[LLMConfigResponse]) +async def list_llm_configs( + session: AsyncSession = Depends(get_session), +) -> list[LLMConfig]: + """List all LLM configurations.""" + query = select(LLMConfig).order_by(desc(LLMConfig.is_default), desc(LLMConfig.created_at)) + result = await session.execute(query) + return list(result.scalars().all()) + + +@router.post("", response_model=LLMConfigResponse, status_code=201) +async def create_llm_config( + data: LLMConfigCreate, + session: AsyncSession = Depends(get_session), +) -> LLMConfig: + """Create a new LLM configuration.""" + # Check if this is the first config - make it default + count_result = await session.execute(select(LLMConfig)) + is_first = len(list(count_result.scalars().all())) == 0 + + config = LLMConfig( + name=data.name, + provider=data.provider.value, + config_json=data.to_config_json(), + is_default=is_first, + ) + session.add(config) + await session.commit() + await session.refresh(config) + return config + + +@router.get("/default", response_model=LLMConfigResponse) +async def get_default_llm_config( + session: AsyncSession = Depends(get_session), +) -> LLMConfig: + """Get the default LLM configuration.""" + query = select(LLMConfig).where(LLMConfig.is_default == True) # noqa: E712 + result = await session.execute(query) + config = result.scalar_one_or_none() + if not config: + raise HTTPException( + status_code=404, + detail="No default LLM config found. Create one at /api/llm-configs", + ) + return config + + +@router.get("/{config_id}", response_model=LLMConfigResponse) +async def get_llm_config( + config_id: str, + session: AsyncSession = Depends(get_session), +) -> LLMConfig: + """Get a specific LLM configuration.""" + uuid_id = parse_uuid(config_id) + query = select(LLMConfig).where(LLMConfig.id == uuid_id) + result = await session.execute(query) + config = result.scalar_one_or_none() + if not config: + raise HTTPException(status_code=404, detail="LLM config not found") + return config + + +@router.put("/{config_id}", response_model=LLMConfigResponse) +async def update_llm_config( + config_id: str, + data: LLMConfigUpdate, + session: AsyncSession = Depends(get_session), +) -> LLMConfig: + """Update an LLM configuration.""" + uuid_id = parse_uuid(config_id) + query = select(LLMConfig).where(LLMConfig.id == uuid_id) + result = await session.execute(query) + config = result.scalar_one_or_none() + if not config: + raise HTTPException(status_code=404, detail="LLM config not found") + + if data.name is not None: + config.name = data.name + + # Update provider-specific config if provided + config_json = dict(config.config_json) + if data.openai is not None: + config_json["openai"] = data.openai.model_dump(exclude_none=True) + if data.azure_openai is not None: + config_json["azure_openai"] = data.azure_openai.model_dump(exclude_none=True) + if data.anthropic is not None: + config_json["anthropic"] = data.anthropic.model_dump(exclude_none=True) + if data.ollama is not None: + config_json["ollama"] = data.ollama.model_dump(exclude_none=True) + if data.custom is not None: + config_json["custom"] = data.custom.model_dump(exclude_none=True) + + config.config_json = config_json + config.updated_at = datetime.now(timezone.utc) + + await session.commit() + await session.refresh(config) + return config + + +@router.delete("/{config_id}", status_code=204) +async def delete_llm_config( + config_id: str, + session: AsyncSession = Depends(get_session), +) -> None: + """Delete an LLM configuration.""" + uuid_id = parse_uuid(config_id) + query = select(LLMConfig).where(LLMConfig.id == uuid_id) + result = await session.execute(query) + config = result.scalar_one_or_none() + if not config: + raise HTTPException(status_code=404, detail="LLM config not found") + + if config.is_default: + raise HTTPException( + status_code=400, + detail="Cannot delete the default LLM config. Set another as default first.", + ) + + await session.delete(config) + await session.commit() + + +@router.post("/{config_id}/set-default", response_model=LLMConfigResponse) +async def set_default_llm_config( + config_id: str, + session: AsyncSession = Depends(get_session), +) -> LLMConfig: + """Set an LLM configuration as the default.""" + uuid_id = parse_uuid(config_id) + + # Get the config to set as default + query = select(LLMConfig).where(LLMConfig.id == uuid_id) + result = await session.execute(query) + config = result.scalar_one_or_none() + if not config: + raise HTTPException(status_code=404, detail="LLM config not found") + + # Unset current default(s) + current_defaults = await session.execute( + select(LLMConfig).where(LLMConfig.is_default == True) # noqa: E712 + ) + for current in current_defaults.scalars().all(): + current.is_default = False + current.updated_at = datetime.now(timezone.utc) + + # Set new default + config.is_default = True + config.updated_at = datetime.now(timezone.utc) + + await session.commit() + await session.refresh(config) + return config + + +@router.post("/{config_id}/test", response_model=LLMConfigTestResult) +async def test_llm_config( + config_id: str, + session: AsyncSession = Depends(get_session), +) -> LLMConfigTestResult: + """Test an LLM configuration by making a simple API call.""" + uuid_id = parse_uuid(config_id) + query = select(LLMConfig).where(LLMConfig.id == uuid_id) + result = await session.execute(query) + db_config = result.scalar_one_or_none() + if not db_config: + raise HTTPException(status_code=404, detail="LLM config not found") + + try: + llm_config = db_config.to_llm_client_config() + + start_time = time.time() + + # Use OpenAI SDK client for testing (works for OpenAI and Azure) + if llm_config.provider in (LLMProvider.OPENAI, LLMProvider.AZURE_OPENAI, LLMProvider.CUSTOM): + client = LLMClientFactory.create_openai_client(llm_config) + model = LLMClientFactory.get_model_name(llm_config) + + # Make a simple completion request + response = await client.chat.completions.create( + model=model, + messages=[{"role": "user", "content": "Say 'ok' if you can hear me."}], + max_tokens=10, + ) + + latency_ms = (time.time() - start_time) * 1000 + + if response.choices and response.choices[0].message.content: + return LLMConfigTestResult( + success=True, + message=f"Connection successful. Model responded: {response.choices[0].message.content[:50]}", + latency_ms=latency_ms, + ) + else: + return LLMConfigTestResult( + success=False, + message="Connection succeeded but no response content", + latency_ms=latency_ms, + ) + + elif llm_config.provider == LLMProvider.OLLAMA: + # For Ollama, just check if the endpoint is reachable + import httpx + + async with httpx.AsyncClient() as http_client: + if llm_config.ollama: + response = await http_client.get(f"{llm_config.ollama.host}/api/tags") + latency_ms = (time.time() - start_time) * 1000 + if response.status_code == 200: + return LLMConfigTestResult( + success=True, + message="Ollama server is reachable", + latency_ms=latency_ms, + ) + else: + return LLMConfigTestResult( + success=False, + message=f"Ollama server returned status {response.status_code}", + latency_ms=latency_ms, + ) + else: + return LLMConfigTestResult( + success=False, + message="Ollama config is missing", + ) + + elif llm_config.provider == LLMProvider.ANTHROPIC: + # Test Anthropic connection + try: + from anthropic import AsyncAnthropic + except ImportError: + return LLMConfigTestResult( + success=False, + message="anthropic package not installed", + ) + + if llm_config.anthropic: + client = AsyncAnthropic(api_key=llm_config.anthropic.get_api_key()) + response = await client.messages.create( + model=llm_config.anthropic.model_id, + max_tokens=10, + messages=[{"role": "user", "content": "Say 'ok' if you can hear me."}], + ) + latency_ms = (time.time() - start_time) * 1000 + if response.content: + return LLMConfigTestResult( + success=True, + message=f"Connection successful. Response received.", + latency_ms=latency_ms, + ) + return LLMConfigTestResult( + success=False, + message="Anthropic config is missing", + ) + + else: + return LLMConfigTestResult( + success=False, + message=f"Testing not implemented for provider: {llm_config.provider.value}", + ) + + except ValueError as e: + # Missing env var + return LLMConfigTestResult( + success=False, + message=str(e), + ) + except Exception as e: + return LLMConfigTestResult( + success=False, + message=f"Connection failed: {str(e)}", + ) diff --git a/src/flow/ui/api/runs.py b/src/flow/ui/api/runs.py index 777974fd169cd1d600a2a623f63976996a40569f..473c2b812bf6d4f054590a63ecc4ca8411b31861 100644 --- a/src/flow/ui/api/runs.py +++ b/src/flow/ui/api/runs.py @@ -6,7 +6,7 @@ from uuid import UUID from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from sqlmodel import desc, select +from sqlmodel import desc, or_, select from ..auth import TokenData, get_current_user, get_effective_user_id, should_filter_by_user from ..database import get_session @@ -36,7 +36,7 @@ async def list_runs( ) -> list[ExperimentRun]: """List experiment runs with optional filters. - Runs are filtered by their parent job's user_id. + Runs are accessible if the parent job is public OR owned by the user. """ query = select(ExperimentRun) @@ -50,11 +50,14 @@ async def list_runs( if is_pareto is not None: query = query.where(ExperimentRun.is_pareto == is_pareto) - # Filter by user via parent job + # Filter by user via parent job (allow if public OR owner) if should_filter_by_user(): effective_user_id = get_effective_user_id(user) query = query.join(OptimizationJob).where( - OptimizationJob.user_id == effective_user_id + or_( + OptimizationJob.is_public == True, # noqa: E712 + OptimizationJob.user_id == effective_user_id, + ) ) query = query.order_by(desc(ExperimentRun.created_at)) @@ -68,15 +71,21 @@ async def get_run( session: AsyncSession = Depends(get_session), user: Annotated[TokenData | None, Depends(get_current_user)] = None, ) -> dict[str, Any]: - """Get detailed information about a specific run.""" + """Get detailed information about a specific run. + + Accessible if the parent job is public OR owned by the user. + """ uuid_id = parse_uuid(run_id) query = select(ExperimentRun).where(ExperimentRun.id == uuid_id) - # Filter by user via parent job + # Filter by user via parent job (allow if public OR owner) if should_filter_by_user(): effective_user_id = get_effective_user_id(user) query = query.join(OptimizationJob).where( - OptimizationJob.user_id == effective_user_id + or_( + OptimizationJob.is_public == True, # noqa: E712 + OptimizationJob.user_id == effective_user_id, + ) ) result = await session.execute(query) @@ -124,14 +133,22 @@ async def get_job_summary( session: AsyncSession = Depends(get_session), user: Annotated[TokenData | None, Depends(get_current_user)] = None, ) -> dict[str, Any]: - """Get aggregated summary for a job's runs.""" + """Get aggregated summary for a job's runs. + + Accessible if the job is public OR owned by the user. + """ uuid_id = parse_uuid(job_id) - # First verify the job belongs to the user + # Verify the job is accessible (public OR owned) job_query = select(OptimizationJob).where(OptimizationJob.id == uuid_id) if should_filter_by_user(): effective_user_id = get_effective_user_id(user) - job_query = job_query.where(OptimizationJob.user_id == effective_user_id) + job_query = job_query.where( + or_( + OptimizationJob.is_public == True, # noqa: E712 + OptimizationJob.user_id == effective_user_id, + ) + ) job_result = await session.execute(job_query) if not job_result.scalar_one_or_none(): diff --git a/src/flow/ui/api/tasks.py b/src/flow/ui/api/tasks.py index 9432959240eeb6e7fa7a878b48be686dea782a1a..20e167d95697035ab3ae77eb16b6c6f399ae667f 100644 --- a/src/flow/ui/api/tasks.py +++ b/src/flow/ui/api/tasks.py @@ -11,7 +11,7 @@ from sqlmodel import desc, or_, select from ..auth import TokenData, get_current_user, get_effective_user_id, should_filter_by_user from ..database import get_session from ..models.task import TaskModel -from ..schemas import TaskCreate, TaskResponse +from ..schemas import TaskCreate, TaskResponse, TaskUpdate router = APIRouter(prefix="/tasks", tags=["tasks"]) @@ -35,6 +35,7 @@ async def list_tasks( Shows: - Shared tasks (user_id is None, e.g., built-in suites) + - Public tasks (is_public is True) - User's own tasks (when auth is enabled) """ query = select(TaskModel) @@ -44,12 +45,13 @@ async def list_tasks( query = query.where(TaskModel.suite == suite) # Filter by user if auth is enabled - # Show both shared tasks (user_id=None) AND user's own tasks + # Show shared tasks, public tasks, AND user's own tasks if should_filter_by_user(): effective_user_id = get_effective_user_id(user) query = query.where( or_( TaskModel.user_id == None, # noqa: E711 - Shared tasks + TaskModel.is_public == True, # noqa: E712 - Public tasks TaskModel.user_id == effective_user_id, # User's own tasks ) ) @@ -79,23 +81,39 @@ async def create_task( return task +@router.get("/suites", response_model=list[dict]) +async def list_suites() -> list[dict]: + """List all available built-in task suites with metadata.""" + from flow.experiments.types import get_all_suite_info + + suites = get_all_suite_info() + return [ + {"name": s.name, "task_count": s.task_count, "description": s.description} + for s in suites + ] + + @router.get("/{task_id}", response_model=TaskResponse) async def get_task( task_id: str, session: AsyncSession = Depends(get_session), user: Annotated[TokenData | None, Depends(get_current_user)] = None, ) -> TaskModel: - """Get a specific task.""" + """Get a specific task. + + Accessible if the task is shared, public, or owned by the user. + """ uuid_id = parse_uuid(task_id) query = select(TaskModel).where(TaskModel.id == uuid_id) - # Allow access to shared tasks (user_id=None) OR user's own tasks + # Allow access to shared, public, OR user's own tasks if should_filter_by_user(): effective_user_id = get_effective_user_id(user) query = query.where( or_( - TaskModel.user_id == None, # noqa: E711 - TaskModel.user_id == effective_user_id, + TaskModel.user_id == None, # noqa: E711 - Shared + TaskModel.is_public == True, # noqa: E712 - Public + TaskModel.user_id == effective_user_id, # Owner ) ) @@ -106,6 +124,42 @@ async def get_task( return task +@router.put("/{task_id}", response_model=TaskResponse) +async def update_task( + task_id: str, + data: TaskUpdate, + session: AsyncSession = Depends(get_session), + user: Annotated[TokenData | None, Depends(get_current_user)] = None, +) -> TaskModel: + """Update a task (e.g., toggle public visibility). + + Only the task owner can update. Cannot update shared/built-in tasks. + """ + uuid_id = parse_uuid(task_id) + query = select(TaskModel).where(TaskModel.id == uuid_id) + + # Only owner can update - strict user filter + if should_filter_by_user(): + effective_user_id = get_effective_user_id(user) + query = query.where(TaskModel.user_id == effective_user_id) + + result = await session.execute(query) + task = result.scalar_one_or_none() + if not task: + raise HTTPException(status_code=404, detail="Task not found") + + # Apply updates + update_data = data.model_dump(exclude_unset=True) + if "criteria" in update_data and update_data["criteria"] is not None: + task.criteria_json = [c.model_dump() if hasattr(c, "model_dump") else c for c in update_data.pop("criteria")] + for key, value in update_data.items(): + setattr(task, key, value) + + await session.commit() + await session.refresh(task) + return task + + @router.delete("/{task_id}", status_code=204) async def delete_task( task_id: str, diff --git a/src/flow/ui/api/tools.py b/src/flow/ui/api/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..44face76ef4d9fda5faf7301858c497542f76d2f --- /dev/null +++ b/src/flow/ui/api/tools.py @@ -0,0 +1,152 @@ +# Copyright (c) Microsoft. All rights reserved. +"""Tools API routes - serves available tools and presets to the UI.""" + +from typing import Any + +from fastapi import APIRouter +from pydantic import BaseModel + +from flow.experiments.models import TOOL_PRESETS +from flow.tools import ( + read_file, + write_file, + edit_file, + multi_edit, + glob_files, + grep, + ls, + bash, + check_processes, + python_repl, + think, + todo_write, + todo_read, + memory, + web_search, + web_fetch, + notebook_edit, + notebook_read, + skills, + task, + Tool, +) + +router = APIRouter(prefix="/tools", tags=["tools"]) + + +# All available tool instances +ALL_TOOLS: list[Tool] = [ + # Coding tools + read_file, + write_file, + edit_file, + multi_edit, + glob_files, + grep, + ls, + # Execution tools + bash, + check_processes, + python_repl, + # Planning tools + think, + todo_write, + todo_read, + # Memory tools + memory, + # Web tools + web_search, + web_fetch, + # Notebook tools + notebook_edit, + notebook_read, + # Skills tools + skills, + # Sub-agent tools + task, +] + + +class ToolInfo(BaseModel): + """Information about a single tool.""" + + name: str + description: str + category: str + + +class ToolsResponse(BaseModel): + """Response containing all tool information.""" + + tools: list[ToolInfo] + presets: dict[str, list[str]] + + +def _get_tool_category(name: str) -> str: + """Determine the category of a tool by its name.""" + coding_tools = {"read_file", "write_file", "edit_file", "multi_edit", "glob_files", "grep", "ls"} + execution_tools = {"bash", "check_processes", "python_repl"} + planning_tools = {"think", "todo_write", "todo_read"} + memory_tools = {"memory"} + web_tools = {"web_search", "web_fetch"} + notebook_tools = {"notebook_edit", "notebook_read"} + skills_tools = {"skills"} + subagent_tools = {"task"} + + if name in coding_tools: + return "coding" + elif name in execution_tools: + return "execution" + elif name in planning_tools: + return "planning" + elif name in memory_tools: + return "memory" + elif name in web_tools: + return "web" + elif name in notebook_tools: + return "notebook" + elif name in skills_tools: + return "skills" + elif name in subagent_tools: + return "subagent" + else: + return "other" + + +@router.get("", response_model=ToolsResponse) +async def get_tools() -> ToolsResponse: + """Get all available tools and presets. + + Returns tool names, descriptions, categories, and preset configurations. + This endpoint allows the UI to dynamically display available tools + without hardcoding them in the frontend. + """ + tools = [ + ToolInfo( + name=tool.name, + description=tool.description, + category=_get_tool_category(tool.name), + ) + for tool in ALL_TOOLS + ] + + # Convert presets to just tool name lists (UI doesn't need configs) + presets = {name: list(preset.keys()) for name, preset in TOOL_PRESETS.items()} + + return ToolsResponse(tools=tools, presets=presets) + + +@router.get("/{tool_name}") +async def get_tool(tool_name: str) -> ToolInfo: + """Get information about a specific tool.""" + for tool in ALL_TOOLS: + if tool.name == tool_name: + return ToolInfo( + name=tool.name, + description=tool.description, + category=_get_tool_category(tool.name), + ) + + from fastapi import HTTPException + + raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found") diff --git a/src/flow/ui/database.py b/src/flow/ui/database.py index 535c939dd73589e4512336bf23bc2bcc4a7a00ab..229b20c367934b846e50ea071f8ef3d11c09ae9b 100644 --- a/src/flow/ui/database.py +++ b/src/flow/ui/database.py @@ -24,13 +24,17 @@ async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit async def init_db() -> None: """Initialize database tables and run migrations.""" - from flow.ui.models import AgentConfig, ExperimentRun, OptimizationJob, TaskModel, TestRun # noqa: F401 + from flow.ui.models import AgentConfig, ExperimentRun, OptimizationJob, TaskModel, TestRun, LLMConfig # noqa: F401 try: async with engine.begin() as conn: await conn.run_sync(SQLModel.metadata.create_all) - # Run migration for user_id columns (safe to run multiple times) + # Run migrations (safe to run multiple times) await _migrate_user_id_columns(conn) + await _migrate_is_public_columns(conn) + + # Seed default LLM configs based on environment + await _seed_default_llm_configs() except Exception as e: if "already exists" in str(e).lower(): logger.debug("Tables already exist (race condition handled)") @@ -88,6 +92,128 @@ async def _migrate_user_id_columns(conn) -> None: # type: ignore[no-untyped-def ) +async def _migrate_is_public_columns(conn) -> None: # type: ignore[no-untyped-def] + """Add is_public and created_by_name columns if they don't exist. + + This migration is idempotent - safe to run multiple times. + """ + # Check and migrate agent_configs.is_public + try: + await conn.execute(text("SELECT is_public FROM agent_configs LIMIT 1")) + logger.debug("agent_configs.is_public column exists") + except Exception: + logger.info("Adding is_public column to agent_configs") + await conn.execute(text("ALTER TABLE agent_configs ADD COLUMN is_public INTEGER DEFAULT 0")) + + # Check and migrate tasks.is_public + try: + await conn.execute(text("SELECT is_public FROM tasks LIMIT 1")) + logger.debug("tasks.is_public column exists") + except Exception: + logger.info("Adding is_public column to tasks") + await conn.execute(text("ALTER TABLE tasks ADD COLUMN is_public INTEGER DEFAULT 0")) + + # Check and migrate optimization_jobs.is_public + try: + await conn.execute(text("SELECT is_public FROM optimization_jobs LIMIT 1")) + logger.debug("optimization_jobs.is_public column exists") + except Exception: + logger.info("Adding is_public column to optimization_jobs") + await conn.execute(text("ALTER TABLE optimization_jobs ADD COLUMN is_public INTEGER DEFAULT 0")) + + # Check and migrate optimization_jobs.created_by_name + try: + await conn.execute(text("SELECT created_by_name FROM optimization_jobs LIMIT 1")) + logger.debug("optimization_jobs.created_by_name column exists") + except Exception: + logger.info("Adding created_by_name column to optimization_jobs") + await conn.execute(text("ALTER TABLE optimization_jobs ADD COLUMN created_by_name TEXT")) + + +async def _seed_default_llm_configs() -> None: + """Create default LLM configs based on available environment variables. + + This runs on startup and creates configs only if none exist. + Detects which providers are configured via env vars and creates appropriate defaults. + """ + import os + from sqlmodel import select + from flow.ui.models.llm_config import LLMConfig + + async with async_session() as session: + # Check if any configs exist + result = await session.execute(select(LLMConfig).limit(1)) + if result.scalar_one_or_none() is not None: + logger.debug("LLM configs already exist, skipping seeding") + return + + configs_to_create: list[LLMConfig] = [] + first_config = True # First config will be default + + # Check for Azure OpenAI + if os.environ.get("AZURE_OPENAI_ENDPOINT") and os.environ.get("AZURE_OPENAI_API_KEY"): + deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o") + configs_to_create.append(LLMConfig( + name=f"Azure OpenAI ({deployment})", + provider="azure_openai", + is_default=first_config, + config_json={ + "azure_openai": { + "endpoint_env_var": "AZURE_OPENAI_ENDPOINT", + "api_key_env_var": "AZURE_OPENAI_API_KEY", + "deployment": deployment, + "api_version": os.environ.get("AZURE_OPENAI_API_VERSION", "2024-02-15-preview"), + } + }, + )) + first_config = False + logger.info(f"Created default Azure OpenAI config (deployment: {deployment})") + + # Check for OpenAI + if os.environ.get("OPENAI_API_KEY"): + model = os.environ.get("OPENAI_MODEL", "gpt-4o") + configs_to_create.append(LLMConfig( + name=f"OpenAI ({model})", + provider="openai", + is_default=first_config, + config_json={ + "openai": { + "api_key_env_var": "OPENAI_API_KEY", + "model_id": model, + } + }, + )) + first_config = False + logger.info(f"Created default OpenAI config (model: {model})") + + # Check for Anthropic + if os.environ.get("ANTHROPIC_API_KEY"): + model = os.environ.get("ANTHROPIC_MODEL", "claude-3-5-sonnet-20241022") + configs_to_create.append(LLMConfig( + name=f"Anthropic ({model})", + provider="anthropic", + is_default=first_config, + config_json={ + "anthropic": { + "api_key_env_var": "ANTHROPIC_API_KEY", + "model_id": model, + } + }, + )) + first_config = False + logger.info(f"Created default Anthropic config (model: {model})") + + # Add all configs + for config in configs_to_create: + session.add(config) + + if configs_to_create: + await session.commit() + logger.info(f"Seeded {len(configs_to_create)} default LLM config(s)") + else: + logger.debug("No LLM provider env vars found, no default configs created") + + async def get_session() -> AsyncGenerator[AsyncSession, None]: """Get database session.""" async with async_session() as session: diff --git a/src/flow/ui/main.py b/src/flow/ui/main.py index c34d99904c3eeba44ba128143de20d2ea959696d..4ce873bb515624e4865d531f70d59cb8ff6467b4 100644 --- a/src/flow/ui/main.py +++ b/src/flow/ui/main.py @@ -12,7 +12,7 @@ from fastapi.responses import FileResponse from starlette.middleware.base import BaseHTTPMiddleware from .database import init_db -from .api import configs_router, tasks_router, jobs_router, runs_router, tests_router +from .api import configs_router, tasks_router, jobs_router, runs_router, tests_router, llm_configs_router, tools_router from .auth import auth_router, AuthSettings, get_auth_settings, init_auth_settings from .auth.middleware import AuthMiddleware @@ -67,6 +67,8 @@ app.include_router(tasks_router, prefix="/api") app.include_router(jobs_router, prefix="/api") app.include_router(runs_router, prefix="/api") app.include_router(tests_router, prefix="/api") +app.include_router(llm_configs_router, prefix="/api") +app.include_router(tools_router, prefix="/api") # Health check (public, not protected by auth) diff --git a/src/flow/ui/models/__init__.py b/src/flow/ui/models/__init__.py index f6afed68a5d8f13ca89555b75c5d329ece0f4d5d..3897bdb70e806d74e33a08749163b7101728c897 100644 --- a/src/flow/ui/models/__init__.py +++ b/src/flow/ui/models/__init__.py @@ -6,6 +6,7 @@ from .task import TaskModel from .job import OptimizationJob, JobStatus from .run import ExperimentRun from .test_run import TestRun, TestRunStatus +from .llm_config import LLMConfig __all__ = [ "AgentConfig", @@ -15,4 +16,5 @@ __all__ = [ "ExperimentRun", "TestRun", "TestRunStatus", + "LLMConfig", ] diff --git a/src/flow/ui/models/config.py b/src/flow/ui/models/config.py index 891c7ec8bb8c3ea2c0e0b4358fe43bc4e2e53742..92d2b1847d9c583b77d83d3dd56fb0fa21c57289 100644 --- a/src/flow/ui/models/config.py +++ b/src/flow/ui/models/config.py @@ -28,6 +28,9 @@ class AgentConfig(SQLModel, table=True): # Owner of this config (None for legacy data, will be backfilled) user_id: str | None = Field(default=None, index=True) + # Public sharing - if True, anyone can view this config + is_public: bool = Field(default=False) + created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) diff --git a/src/flow/ui/models/job.py b/src/flow/ui/models/job.py index 3f1f2daa59c2c8b75312fbd479a4117b6d65f720..1ac0063739dfcb29c3b3db3ed2622f3e2c33ff60 100644 --- a/src/flow/ui/models/job.py +++ b/src/flow/ui/models/job.py @@ -29,6 +29,11 @@ class OptimizationJob(SQLModel, table=True): # Owner of this job user_id: str | None = Field(default=None, index=True) + # Public sharing - if True, anyone can view this job and its runs + is_public: bool = Field(default=False) + # Display name of creator (for public jobs) + created_by_name: str | None = Field(default=None) + status: JobStatus = Field(default=JobStatus.PENDING) # Job configuration diff --git a/src/flow/ui/models/llm_config.py b/src/flow/ui/models/llm_config.py new file mode 100644 index 0000000000000000000000000000000000000000..1b17112539dacee24b54f434678246d6dab75ce8 --- /dev/null +++ b/src/flow/ui/models/llm_config.py @@ -0,0 +1,101 @@ +# Copyright (c) Microsoft. All rights reserved. +"""LLM configuration database model.""" + +from datetime import datetime, timezone +from typing import Any +from uuid import UUID, uuid4 + +from sqlmodel import Column, Field, JSON, SQLModel + +from flow.llm import ( + AnthropicConfig, + AzureOpenAIConfig, + CustomConfig, + LLMClientConfig, + LLMProvider, + OllamaConfig, + OpenAIConfig, +) + + +class LLMConfig(SQLModel, table=True): + """Stored LLM configuration. + + Stores provider-specific configuration with environment variable references. + Actual secrets (API keys) are never stored - only the env var names. + """ + + __tablename__ = "llm_configs" # type: ignore[assignment] + + id: UUID = Field(default_factory=uuid4, primary_key=True) + name: str = Field(index=True) + provider: str = Field(index=True) # LLMProvider value + is_default: bool = Field(default=False, index=True) + + # Provider-specific config as JSON (env var names, not secrets) + config_json: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON)) + + created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) + updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) + + @property + def config(self) -> dict[str, Any]: + """Alias for config_json used by API response serialization.""" + return self.config_json + + @property + def model_id(self) -> str | None: + """Extract model/deployment ID for display.""" + provider = LLMProvider(self.provider) + match provider: + case LLMProvider.OPENAI: + return self.config_json.get("openai", {}).get("model_id") + case LLMProvider.AZURE_OPENAI: + return self.config_json.get("azure_openai", {}).get("deployment") + case LLMProvider.ANTHROPIC: + return self.config_json.get("anthropic", {}).get("model_id") + case LLMProvider.OLLAMA: + return self.config_json.get("ollama", {}).get("model_id") + case LLMProvider.CUSTOM: + return self.config_json.get("custom", {}).get("model_id") + case _: + return None + + def to_llm_client_config(self) -> LLMClientConfig: + """Convert to LLMClientConfig for use with factory. + + Returns: + LLMClientConfig instance ready for factory methods. + """ + provider = LLMProvider(self.provider) + config_data = self.config_json + + # Build provider-specific config + openai = None + azure_openai = None + anthropic = None + ollama = None + custom = None + + if provider == LLMProvider.OPENAI and "openai" in config_data: + openai = OpenAIConfig(**config_data["openai"]) + elif provider == LLMProvider.AZURE_OPENAI and "azure_openai" in config_data: + azure_openai = AzureOpenAIConfig(**config_data["azure_openai"]) + elif provider == LLMProvider.ANTHROPIC and "anthropic" in config_data: + anthropic = AnthropicConfig(**config_data["anthropic"]) + elif provider == LLMProvider.OLLAMA and "ollama" in config_data: + ollama = OllamaConfig(**config_data["ollama"]) + elif provider == LLMProvider.CUSTOM and "custom" in config_data: + custom = CustomConfig(**config_data["custom"]) + + return LLMClientConfig( + id=str(self.id), + provider=provider, + name=self.name, + is_default=self.is_default, + openai=openai, + azure_openai=azure_openai, + anthropic=anthropic, + ollama=ollama, + custom=custom, + ) diff --git a/src/flow/ui/models/task.py b/src/flow/ui/models/task.py index 2b5fb51e9d04fd6e3a2884c7a006d320827e5447..7e643f7ba00cd60cc38693daa89509264dd828a7 100644 --- a/src/flow/ui/models/task.py +++ b/src/flow/ui/models/task.py @@ -27,6 +27,9 @@ class TaskModel(SQLModel, table=True): # Owner of this task (None = shared/built-in suite task, visible to all) user_id: str | None = Field(default=None, index=True) + # Public sharing - if True, anyone can view this task + is_public: bool = Field(default=False) + created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) @property diff --git a/src/flow/ui/schemas/__init__.py b/src/flow/ui/schemas/__init__.py index 66ceab829839b1cbe1b776623b200b04cd98199e..f3f928c469c6c81c266f97eacd3975470cb48326 100644 --- a/src/flow/ui/schemas/__init__.py +++ b/src/flow/ui/schemas/__init__.py @@ -2,19 +2,32 @@ """Pydantic schemas for API requests/responses.""" from .config import AgentCreate, AgentUpdate, AgentResponse -from .task import TaskCreate, TaskResponse, CriterionSchema -from .job import JobCreate, JobResponse, JobProgress +from .task import TaskCreate, TaskUpdate, TaskResponse, CriterionSchema +from .job import JobCreate, JobUpdate, JobResponse, JobProgress from .run import RunResponse, RunDetailResponse, CriterionResultSchema from .test import TestRunCreate, TestRunResponse, TestRunDetailResponse, TestProgress +from .llm_config import ( + LLMConfigCreate, + LLMConfigUpdate, + LLMConfigResponse, + LLMConfigTestResult, + OpenAIConfigSchema, + AzureOpenAIConfigSchema, + AnthropicConfigSchema, + OllamaConfigSchema, + CustomConfigSchema, +) __all__ = [ "AgentCreate", "AgentUpdate", "AgentResponse", "TaskCreate", + "TaskUpdate", "TaskResponse", "CriterionSchema", "JobCreate", + "JobUpdate", "JobResponse", "JobProgress", "RunResponse", @@ -24,4 +37,14 @@ __all__ = [ "TestRunResponse", "TestRunDetailResponse", "TestProgress", + # LLM Config + "LLMConfigCreate", + "LLMConfigUpdate", + "LLMConfigResponse", + "LLMConfigTestResult", + "OpenAIConfigSchema", + "AzureOpenAIConfigSchema", + "AnthropicConfigSchema", + "OllamaConfigSchema", + "CustomConfigSchema", ] diff --git a/src/flow/ui/schemas/config.py b/src/flow/ui/schemas/config.py index 9eba98febe3199af1e61237c2fca29bcc9adca47..e3c42bc05e368bdf5d3d222cd6a05148e02b14ef 100644 --- a/src/flow/ui/schemas/config.py +++ b/src/flow/ui/schemas/config.py @@ -22,10 +22,16 @@ class AgentCreate(BaseModel): - str: Preset name ("standard", "minimal", "full", "readonly") - list[str]: List of tool names - dict[str, dict]: Full specification with per-tool configs + + Framework can be: + - "maf": Microsoft Agent Framework (default) + - "miniagent": MiniAgent with correct context compaction + - "langgraph": LangGraph (requires extra dependencies) """ name: str description: str = "" + framework: str = "maf" instructions: str | None = None model: str | None = None compaction: CompactionConfigSchema = CompactionConfigSchema() @@ -34,6 +40,7 @@ class AgentCreate(BaseModel): def to_config_json(self) -> dict[str, Any]: """Convert to config JSON for storage (runtime settings only).""" return { + "framework": self.framework, "instructions": self.instructions, "model": self.model, "compaction": self.compaction.model_dump(), @@ -46,10 +53,12 @@ class AgentUpdate(BaseModel): name: str | None = None description: str | None = None + framework: str | None = None instructions: str | None = None model: str | None = None compaction: CompactionConfigSchema | None = None tools: str | list[str] | dict[str, dict[str, Any]] | None = None + is_public: bool | None = None class AgentResponse(BaseModel): @@ -64,6 +73,7 @@ class AgentResponse(BaseModel): is_auto_generated: bool = False job_id: str | None = None user_id: str | None = None + is_public: bool = False created_at: datetime updated_at: datetime diff --git a/src/flow/ui/schemas/job.py b/src/flow/ui/schemas/job.py index cbd267641972663f5db931d9b23c3d2479b2a088..bec22c847facf44f50a1f32c054953d538edeb4b 100644 --- a/src/flow/ui/schemas/job.py +++ b/src/flow/ui/schemas/job.py @@ -19,6 +19,13 @@ class JobCreate(BaseModel): use_llm_eval: bool = False +class JobUpdate(BaseModel): + """Request schema for updating a job.""" + + name: str | None = None + is_public: bool | None = None + + class JobResponse(BaseModel): """Response schema for a job.""" @@ -37,6 +44,8 @@ class JobResponse(BaseModel): total_experiments: int completed_experiments: int user_id: str | None = None + is_public: bool = False + created_by_name: str | None = None created_at: datetime started_at: datetime | None completed_at: datetime | None diff --git a/src/flow/ui/schemas/llm_config.py b/src/flow/ui/schemas/llm_config.py new file mode 100644 index 0000000000000000000000000000000000000000..e8700ab2fcb6e5bd67d1cfaa80fc6a5c7b43f337 --- /dev/null +++ b/src/flow/ui/schemas/llm_config.py @@ -0,0 +1,123 @@ +# Copyright (c) Microsoft. All rights reserved. +"""LLM configuration schemas for API requests/responses.""" + +from datetime import datetime +from typing import Any +from uuid import UUID + +from pydantic import BaseModel, ConfigDict, Field, field_validator + +from flow.llm import LLMProvider + + +class OpenAIConfigSchema(BaseModel): + """OpenAI provider config for API.""" + + api_key_env_var: str = "OPENAI_API_KEY" + model_id: str = "gpt-4o" + base_url: str | None = None + + +class AzureOpenAIConfigSchema(BaseModel): + """Azure OpenAI provider config for API.""" + + endpoint_env_var: str = "AZURE_OPENAI_ENDPOINT" + api_key_env_var: str = "AZURE_OPENAI_API_KEY" + deployment: str + api_version: str = "2024-02-15-preview" + + +class AnthropicConfigSchema(BaseModel): + """Anthropic provider config for API.""" + + api_key_env_var: str = "ANTHROPIC_API_KEY" + model_id: str = "claude-3-5-sonnet-20241022" + + +class OllamaConfigSchema(BaseModel): + """Ollama provider config for API.""" + + host: str = "http://localhost:11434" + model_id: str = "llama3.2" + + +class CustomConfigSchema(BaseModel): + """Custom provider config for API.""" + + base_url: str + api_key_env_var: str = "CUSTOM_API_KEY" + model_id: str + + +class LLMConfigCreate(BaseModel): + """Request schema for creating an LLM configuration.""" + + provider: LLMProvider + name: str = Field(min_length=1, max_length=100) + + # Provider-specific configs (only one should be set based on provider) + openai: OpenAIConfigSchema | None = None + azure_openai: AzureOpenAIConfigSchema | None = None + anthropic: AnthropicConfigSchema | None = None + ollama: OllamaConfigSchema | None = None + custom: CustomConfigSchema | None = None + + def to_config_json(self) -> dict[str, Any]: + """Convert to config JSON for storage.""" + result: dict[str, Any] = {} + + if self.openai: + result["openai"] = self.openai.model_dump(exclude_none=True) + if self.azure_openai: + result["azure_openai"] = self.azure_openai.model_dump(exclude_none=True) + if self.anthropic: + result["anthropic"] = self.anthropic.model_dump(exclude_none=True) + if self.ollama: + result["ollama"] = self.ollama.model_dump(exclude_none=True) + if self.custom: + result["custom"] = self.custom.model_dump(exclude_none=True) + + return result + + +class LLMConfigUpdate(BaseModel): + """Request schema for updating an LLM configuration.""" + + name: str | None = Field(default=None, min_length=1, max_length=100) + + # Provider-specific configs + openai: OpenAIConfigSchema | None = None + azure_openai: AzureOpenAIConfigSchema | None = None + anthropic: AnthropicConfigSchema | None = None + ollama: OllamaConfigSchema | None = None + custom: CustomConfigSchema | None = None + + +class LLMConfigResponse(BaseModel): + """Response schema for an LLM configuration.""" + + model_config = ConfigDict(from_attributes=True) + + id: str + provider: str + name: str + is_default: bool + model_id: str | None = None # Extracted for display + config: dict[str, Any] # Full provider config (no secrets) + created_at: datetime + updated_at: datetime + + @field_validator("id", mode="before") + @classmethod + def convert_uuid(cls, v: UUID | str) -> str: + if isinstance(v, UUID): + return str(v) + return v + + +class LLMConfigTestResult(BaseModel): + """Response schema for testing an LLM configuration.""" + + success: bool + message: str + latency_ms: float | None = None diff --git a/src/flow/ui/schemas/task.py b/src/flow/ui/schemas/task.py index 7b99ec342002a022f57e61cb4c93aa7b183e12af..4a564670c983b214df558784cc3727e3e405c372 100644 --- a/src/flow/ui/schemas/task.py +++ b/src/flow/ui/schemas/task.py @@ -29,6 +29,16 @@ class TaskCreate(BaseModel): return [c.model_dump() for c in self.criteria] +class TaskUpdate(BaseModel): + """Request schema for updating a task.""" + + name: str | None = None + prompt: str | None = None + criteria: list[CriterionSchema] | None = None + category: str | None = None + is_public: bool | None = None + + class TaskResponse(BaseModel): """Response schema for a task.""" @@ -41,6 +51,7 @@ class TaskResponse(BaseModel): category: str suite: str | None user_id: str | None = None + is_public: bool = False created_at: datetime @field_validator("id", mode="before") diff --git a/src/flow/ui/services/optimizer_service.py b/src/flow/ui/services/optimizer_service.py index 1fe30d2d5023451aea37b4c18da370c8923ef4ba..db7f21f93551dd7e77726cc51a46f60912cd719a 100644 --- a/src/flow/ui/services/optimizer_service.py +++ b/src/flow/ui/services/optimizer_service.py @@ -146,6 +146,7 @@ class OptimizerService: "success": task_result.run_result.success, "error": task_result.run_result.error, "spans": task_result.run_result.trace or [], + "criteria_results": task_result.criteria_results, }, is_pareto=summary.is_pareto_optimal, pareto_rank=summary.pareto_rank or 0, @@ -206,6 +207,7 @@ class OptimizerService: agent = Agent( name=db_config.name, + framework=cfg.get("framework", "maf"), instructions=cfg.get("instructions"), model=cfg.get("model"), compaction=compaction, diff --git a/src/flow/ui/services/test_service.py b/src/flow/ui/services/test_service.py index 4dca5544e88559abc32dc1fa06a5128da227788e..6b257decee8e743028474c46432579820a77d4d2 100644 --- a/src/flow/ui/services/test_service.py +++ b/src/flow/ui/services/test_service.py @@ -13,19 +13,18 @@ from uuid import UUID from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExporter, SpanExportResult -from sqlalchemy.ext.asyncio import AsyncSession +from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExportResult from sqlmodel import select -from flow.experiments.models import CompactionConfig +from flow.experiments.models import Agent, CompactionConfig from flow.experiments.metrics import extract_metrics from flow.experiments.trace_collector import FlowTraceCollector -from flow.harness.base import EventType -from flow.harness.maf import MAFHarness -from flow.harness.maf.agent import create_agent +from flow.harness.base import BaseHarness, EventType +from flow.llm import LLMClientConfig from ..database import async_session from ..models.config import AgentConfig +from ..models.llm_config import LLMConfig from ..models.test_run import TestRun, TestRunStatus from ..schemas.test import TestProgress @@ -131,8 +130,21 @@ class TestService: processor = SimpleSpanProcessor(collector) provider.add_span_processor(processor) + # Load LLM config if specified + llm_client_config = None + llm_config_id = agent_config.config_json.get("llm_config_id") + if llm_config_id: + llm_config_result = await session.execute( + select(LLMConfig).where(LLMConfig.id == UUID(llm_config_id)) + ) + llm_config = llm_config_result.scalar_one_or_none() + if llm_config: + llm_client_config = llm_config.to_llm_client_config() + # Create agent from config - harness = self._create_harness_from_config(agent_config, workspace) + harness = self._create_harness_from_config( + agent_config, workspace, llm_client_config + ) # Execute agent with streaming start_time = time.time() @@ -268,41 +280,43 @@ class TestService: logger.debug(f"Failed to clean up workspace: {e}") def _create_harness_from_config( - self, agent_config: AgentConfig, workspace: Path - ) -> MAFHarness: - """Create a MAFHarness from an agent config. + self, + agent_config: AgentConfig, + workspace: Path, + llm_config: LLMClientConfig | None = None, + ) -> BaseHarness: + """Create a harness from an agent config using the registry. Args: agent_config: The agent configuration from database workspace: Workspace directory for agent execution + llm_config: Optional LLM configuration (falls back to env vars) Returns: - Configured MAFHarness + Configured harness """ + # Import harness modules to register them, then use registry + import flow.harness.maf # noqa: F401 + import flow.harness.miniagent # noqa: F401 + from flow.harness import create_harness + cfg = agent_config.config_json # Extract compaction config compaction_data = cfg.get("compaction", {}) - enable_compaction = compaction_data.get("strategy", "head_tail") != "none" - params = compaction_data.get("params", {}) - compaction_head_size = params.get("head_size", 10) - compaction_tail_size = params.get("tail_size", 40) - - # Get tools configuration - tools = cfg.get("tools", "standard") + strategy = compaction_data.get("strategy", "head_tail") + params = compaction_data.get("params", {"head_size": 10, "tail_size": 40}) - # Get instructions - instructions = cfg.get("instructions") + compaction = CompactionConfig(strategy=strategy, params=params) - # Create agent - agent = create_agent( + # Build Agent spec from config + agent = Agent( name=agent_config.name, - instructions=instructions, - tools=tools, - workspace=workspace, - enable_compaction=enable_compaction, - compaction_head_size=compaction_head_size, - compaction_tail_size=compaction_tail_size, + framework=cfg.get("framework", "maf"), + instructions=cfg.get("instructions"), + model=cfg.get("model"), + compaction=compaction, + tools=cfg.get("tools", "standard"), ) - return MAFHarness(agent) + return create_harness(agent, workspace, llm_config=llm_config) diff --git a/src/flow/ui/ui/assets/index-1UhLnuMG.js b/src/flow/ui/ui/assets/index-1UhLnuMG.js new file mode 100644 index 0000000000000000000000000000000000000000..fe9da71de3fb38ab53ae49cd97df282991b6a5d2 --- /dev/null +++ b/src/flow/ui/ui/assets/index-1UhLnuMG.js @@ -0,0 +1,260 @@ +var Au=e=>{throw TypeError(e)};var $i=(e,t,n)=>t.has(e)||Au("Cannot "+n);var y=(e,t,n)=>($i(e,t,"read from private field"),n?n.call(e):t.get(e)),I=(e,t,n)=>t.has(e)?Au("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),R=(e,t,n,r)=>($i(e,t,"write to private field"),r?r.call(e,n):t.set(e,n),n),Q=(e,t,n)=>($i(e,t,"access private method"),n);var la=(e,t,n,r)=>({set _(s){R(e,t,s,n)},get _(){return y(e,t,r)}});function zp(e,t){for(var n=0;nr[s]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const a of s)if(a.type==="childList")for(const l of a.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&r(l)}).observe(document,{childList:!0,subtree:!0});function n(s){const a={};return s.integrity&&(a.integrity=s.integrity),s.referrerPolicy&&(a.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?a.credentials="include":s.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(s){if(s.ep)return;s.ep=!0;const a=n(s);fetch(s.href,a)}})();function Md(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var zd={exports:{}},mi={},Fd={exports:{}},W={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var ea=Symbol.for("react.element"),Fp=Symbol.for("react.portal"),Ip=Symbol.for("react.fragment"),Dp=Symbol.for("react.strict_mode"),Ap=Symbol.for("react.profiler"),$p=Symbol.for("react.provider"),Up=Symbol.for("react.context"),Bp=Symbol.for("react.forward_ref"),Qp=Symbol.for("react.suspense"),Vp=Symbol.for("react.memo"),Hp=Symbol.for("react.lazy"),$u=Symbol.iterator;function Kp(e){return e===null||typeof e!="object"?null:(e=$u&&e[$u]||e["@@iterator"],typeof e=="function"?e:null)}var Id={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Dd=Object.assign,Ad={};function qr(e,t,n){this.props=e,this.context=t,this.refs=Ad,this.updater=n||Id}qr.prototype.isReactComponent={};qr.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};qr.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function $d(){}$d.prototype=qr.prototype;function Oo(e,t,n){this.props=e,this.context=t,this.refs=Ad,this.updater=n||Id}var Lo=Oo.prototype=new $d;Lo.constructor=Oo;Dd(Lo,qr.prototype);Lo.isPureReactComponent=!0;var Uu=Array.isArray,Ud=Object.prototype.hasOwnProperty,Ro={current:null},Bd={key:!0,ref:!0,__self:!0,__source:!0};function Qd(e,t,n){var r,s={},a=null,l=null;if(t!=null)for(r in t.ref!==void 0&&(l=t.ref),t.key!==void 0&&(a=""+t.key),t)Ud.call(t,r)&&!Bd.hasOwnProperty(r)&&(s[r]=t[r]);var o=arguments.length-2;if(o===1)s.children=n;else if(1>>1,A=O[L];if(0>>1;Ls(fe,B))yes(lt,fe)?(O[L]=lt,O[ye]=B,L=ye):(O[L]=fe,O[$]=B,L=$);else if(yes(lt,B))O[L]=lt,O[ye]=B,L=ye;else break e}}return D}function s(O,D){var B=O.sortIndex-D.sortIndex;return B!==0?B:O.id-D.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var l=Date,o=l.now();e.unstable_now=function(){return l.now()-o}}var u=[],c=[],p=1,f=null,h=3,x=!1,w=!1,j=!1,S=typeof setTimeout=="function"?setTimeout:null,v=typeof clearTimeout=="function"?clearTimeout:null,d=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function m(O){for(var D=n(c);D!==null;){if(D.callback===null)r(c);else if(D.startTime<=O)r(c),D.sortIndex=D.expirationTime,t(u,D);else break;D=n(c)}}function g(O){if(j=!1,m(O),!w)if(n(u)!==null)w=!0,Pt(b);else{var D=n(c);D!==null&&He(g,D.startTime-O)}}function b(O,D){w=!1,j&&(j=!1,v(N),N=-1),x=!0;var B=h;try{for(m(D),f=n(u);f!==null&&(!(f.expirationTime>D)||O&&!H());){var L=f.callback;if(typeof L=="function"){f.callback=null,h=f.priorityLevel;var A=L(f.expirationTime<=D);D=e.unstable_now(),typeof A=="function"?f.callback=A:f===n(u)&&r(u),m(D)}else r(u);f=n(u)}if(f!==null)var T=!0;else{var $=n(c);$!==null&&He(g,$.startTime-D),T=!1}return T}finally{f=null,h=B,x=!1}}var _=!1,C=null,N=-1,M=5,P=-1;function H(){return!(e.unstable_now()-PO||125L?(O.sortIndex=B,t(c,O),n(u)===null&&O===n(c)&&(j?(v(N),N=-1):j=!0,He(g,B-L))):(O.sortIndex=A,t(u,O),w||x||(w=!0,Pt(b))),O},e.unstable_shouldYield=H,e.unstable_wrapCallback=function(O){var D=h;return function(){var B=h;h=D;try{return O.apply(this,arguments)}finally{h=B}}}})(qd);Wd.exports=qd;var sm=Wd.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var am=k,Ge=sm;function E(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),yl=Object.prototype.hasOwnProperty,im=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Qu={},Vu={};function lm(e){return yl.call(Vu,e)?!0:yl.call(Qu,e)?!1:im.test(e)?Vu[e]=!0:(Qu[e]=!0,!1)}function om(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function um(e,t,n,r){if(t===null||typeof t>"u"||om(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Ie(e,t,n,r,s,a,l){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=s,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=l}var Ce={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Ce[e]=new Ie(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Ce[t]=new Ie(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Ce[e]=new Ie(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Ce[e]=new Ie(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Ce[e]=new Ie(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Ce[e]=new Ie(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Ce[e]=new Ie(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Ce[e]=new Ie(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Ce[e]=new Ie(e,5,!1,e.toLowerCase(),null,!1,!1)});var Fo=/[\-:]([a-z])/g;function Io(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Fo,Io);Ce[t]=new Ie(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Fo,Io);Ce[t]=new Ie(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Fo,Io);Ce[t]=new Ie(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Ce[e]=new Ie(e,1,!1,e.toLowerCase(),null,!1,!1)});Ce.xlinkHref=new Ie("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Ce[e]=new Ie(e,1,!1,e.toLowerCase(),null,!0,!0)});function Do(e,t,n,r){var s=Ce.hasOwnProperty(t)?Ce[t]:null;(s!==null?s.type!==0:r||!(2o||s[l]!==a[o]){var u=` +`+s[l].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=l&&0<=o);break}}}finally{Qi=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?us(e):""}function cm(e){switch(e.tag){case 5:return us(e.type);case 16:return us("Lazy");case 13:return us("Suspense");case 19:return us("SuspenseList");case 0:case 2:case 15:return e=Vi(e.type,!1),e;case 11:return e=Vi(e.type.render,!1),e;case 1:return e=Vi(e.type,!0),e;default:return""}}function kl(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case cr:return"Fragment";case ur:return"Portal";case gl:return"Profiler";case Ao:return"StrictMode";case jl:return"Suspense";case wl:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Xd:return(e.displayName||"Context")+".Consumer";case Jd:return(e._context.displayName||"Context")+".Provider";case $o:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Uo:return t=e.displayName||null,t!==null?t:kl(e.type)||"Memo";case Gt:t=e._payload,e=e._init;try{return kl(e(t))}catch{}}return null}function dm(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return kl(t);case 8:return t===Ao?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Nn(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Zd(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function fm(e){var t=Zd(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var s=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(l){r=""+l,a.call(this,l)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(l){r=""+l},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function ca(e){e._valueTracker||(e._valueTracker=fm(e))}function ef(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Zd(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Ua(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Sl(e,t){var n=t.checked;return oe({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Ku(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Nn(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function tf(e,t){t=t.checked,t!=null&&Do(e,"checked",t,!1)}function Nl(e,t){tf(e,t);var n=Nn(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?_l(e,t.type,n):t.hasOwnProperty("defaultValue")&&_l(e,t.type,Nn(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Wu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function _l(e,t,n){(t!=="number"||Ua(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var cs=Array.isArray;function wr(e,t,n,r){if(e=e.options,t){t={};for(var s=0;s"+t.valueOf().toString()+"",t=da.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Cs(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var ps={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},hm=["Webkit","ms","Moz","O"];Object.keys(ps).forEach(function(e){hm.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),ps[t]=ps[e]})});function af(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||ps.hasOwnProperty(e)&&ps[e]?(""+t).trim():t+"px"}function lf(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,s=af(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,s):e[n]=s}}var pm=oe({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function El(e,t){if(t){if(pm[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(E(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(E(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(E(61))}if(t.style!=null&&typeof t.style!="object")throw Error(E(62))}}function Pl(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Tl=null;function Bo(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Ol=null,kr=null,Sr=null;function Ju(e){if(e=ra(e)){if(typeof Ol!="function")throw Error(E(280));var t=e.stateNode;t&&(t=ji(t),Ol(e.stateNode,e.type,t))}}function of(e){kr?Sr?Sr.push(e):Sr=[e]:kr=e}function uf(){if(kr){var e=kr,t=Sr;if(Sr=kr=null,Ju(e),t)for(e=0;e>>=0,e===0?32:31-(_m(e)/Cm|0)|0}var fa=64,ha=4194304;function ds(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Ha(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,s=e.suspendedLanes,a=e.pingedLanes,l=n&268435455;if(l!==0){var o=l&~s;o!==0?r=ds(o):(a&=l,a!==0&&(r=ds(a)))}else l=n&~s,l!==0?r=ds(l):a!==0&&(r=ds(a));if(r===0)return 0;if(t!==0&&t!==r&&!(t&s)&&(s=r&-r,a=t&-t,s>=a||s===16&&(a&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function ta(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-mt(t),e[t]=n}function Tm(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=vs),ac=" ",ic=!1;function Pf(e,t){switch(e){case"keyup":return sv.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Tf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var dr=!1;function iv(e,t){switch(e){case"compositionend":return Tf(t);case"keypress":return t.which!==32?null:(ic=!0,ac);case"textInput":return e=t.data,e===ac&&ic?null:e;default:return null}}function lv(e,t){if(dr)return e==="compositionend"||!Jo&&Pf(e,t)?(e=bf(),Oa=Wo=un=null,dr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=cc(n)}}function Mf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Mf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function zf(){for(var e=window,t=Ua();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ua(e.document)}return t}function Xo(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function vv(e){var t=zf(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Mf(n.ownerDocument.documentElement,n)){if(r!==null&&Xo(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var s=n.textContent.length,a=Math.min(r.start,s);r=r.end===void 0?a:Math.min(r.end,s),!e.extend&&a>r&&(s=r,r=a,a=s),s=dc(n,a);var l=dc(n,r);s&&l&&(e.rangeCount!==1||e.anchorNode!==s.node||e.anchorOffset!==s.offset||e.focusNode!==l.node||e.focusOffset!==l.offset)&&(t=t.createRange(),t.setStart(s.node,s.offset),e.removeAllRanges(),a>r?(e.addRange(t),e.extend(l.node,l.offset)):(t.setEnd(l.node,l.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,fr=null,Il=null,ys=null,Dl=!1;function fc(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Dl||fr==null||fr!==Ua(r)||(r=fr,"selectionStart"in r&&Xo(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),ys&&Ls(ys,r)||(ys=r,r=qa(Il,"onSelect"),0mr||(e.current=Vl[mr],Vl[mr]=null,mr--)}function ne(e,t){mr++,Vl[mr]=e.current,e.current=t}var _n={},Oe=bn(_n),Be=bn(!1),Xn=_n;function $r(e,t){var n=e.type.contextTypes;if(!n)return _n;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var s={},a;for(a in n)s[a]=t[a];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=s),s}function Qe(e){return e=e.childContextTypes,e!=null}function Ja(){se(Be),se(Oe)}function gc(e,t,n){if(Oe.current!==_n)throw Error(E(168));ne(Oe,t),ne(Be,n)}function Vf(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var s in r)if(!(s in t))throw Error(E(108,dm(e)||"Unknown",s));return oe({},n,r)}function Xa(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||_n,Xn=Oe.current,ne(Oe,e),ne(Be,Be.current),!0}function jc(e,t,n){var r=e.stateNode;if(!r)throw Error(E(169));n?(e=Vf(e,t,Xn),r.__reactInternalMemoizedMergedChildContext=e,se(Be),se(Oe),ne(Oe,e)):se(Be),ne(Be,n)}var Lt=null,wi=!1,sl=!1;function Hf(e){Lt===null?Lt=[e]:Lt.push(e)}function Ev(e){wi=!0,Hf(e)}function En(){if(!sl&&Lt!==null){sl=!0;var e=0,t=Z;try{var n=Lt;for(Z=1;e>=l,s-=l,It=1<<32-mt(t)+s|n<N?(M=C,C=null):M=C.sibling;var P=h(v,C,m[N],g);if(P===null){C===null&&(C=M);break}e&&C&&P.alternate===null&&t(v,C),d=a(P,d,N),_===null?b=P:_.sibling=P,_=P,C=M}if(N===m.length)return n(v,C),ae&&On(v,N),b;if(C===null){for(;NN?(M=C,C=null):M=C.sibling;var H=h(v,C,P.value,g);if(H===null){C===null&&(C=M);break}e&&C&&H.alternate===null&&t(v,C),d=a(H,d,N),_===null?b=H:_.sibling=H,_=H,C=M}if(P.done)return n(v,C),ae&&On(v,N),b;if(C===null){for(;!P.done;N++,P=m.next())P=f(v,P.value,g),P!==null&&(d=a(P,d,N),_===null?b=P:_.sibling=P,_=P);return ae&&On(v,N),b}for(C=r(v,C);!P.done;N++,P=m.next())P=x(C,v,N,P.value,g),P!==null&&(e&&P.alternate!==null&&C.delete(P.key===null?N:P.key),d=a(P,d,N),_===null?b=P:_.sibling=P,_=P);return e&&C.forEach(function(F){return t(v,F)}),ae&&On(v,N),b}function S(v,d,m,g){if(typeof m=="object"&&m!==null&&m.type===cr&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case ua:e:{for(var b=m.key,_=d;_!==null;){if(_.key===b){if(b=m.type,b===cr){if(_.tag===7){n(v,_.sibling),d=s(_,m.props.children),d.return=v,v=d;break e}}else if(_.elementType===b||typeof b=="object"&&b!==null&&b.$$typeof===Gt&&Sc(b)===_.type){n(v,_.sibling),d=s(_,m.props),d.ref=as(v,_,m),d.return=v,v=d;break e}n(v,_);break}else t(v,_);_=_.sibling}m.type===cr?(d=qn(m.props.children,v.mode,g,m.key),d.return=v,v=d):(g=Aa(m.type,m.key,m.props,null,v.mode,g),g.ref=as(v,d,m),g.return=v,v=g)}return l(v);case ur:e:{for(_=m.key;d!==null;){if(d.key===_)if(d.tag===4&&d.stateNode.containerInfo===m.containerInfo&&d.stateNode.implementation===m.implementation){n(v,d.sibling),d=s(d,m.children||[]),d.return=v,v=d;break e}else{n(v,d);break}else t(v,d);d=d.sibling}d=fl(m,v.mode,g),d.return=v,v=d}return l(v);case Gt:return _=m._init,S(v,d,_(m._payload),g)}if(cs(m))return w(v,d,m,g);if(es(m))return j(v,d,m,g);ja(v,m)}return typeof m=="string"&&m!==""||typeof m=="number"?(m=""+m,d!==null&&d.tag===6?(n(v,d.sibling),d=s(d,m),d.return=v,v=d):(n(v,d),d=dl(m,v.mode,g),d.return=v,v=d),l(v)):n(v,d)}return S}var Br=Gf(!0),Jf=Gf(!1),ei=bn(null),ti=null,yr=null,tu=null;function nu(){tu=yr=ti=null}function ru(e){var t=ei.current;se(ei),e._currentValue=t}function Wl(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function _r(e,t){ti=e,tu=yr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Ue=!0),e.firstContext=null)}function at(e){var t=e._currentValue;if(tu!==e)if(e={context:e,memoizedValue:t,next:null},yr===null){if(ti===null)throw Error(E(308));yr=e,ti.dependencies={lanes:0,firstContext:e}}else yr=yr.next=e;return t}var zn=null;function su(e){zn===null?zn=[e]:zn.push(e)}function Xf(e,t,n,r){var s=t.interleaved;return s===null?(n.next=n,su(t)):(n.next=s.next,s.next=n),t.interleaved=n,Qt(e,r)}function Qt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Jt=!1;function au(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Yf(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function At(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function yn(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,J&2){var s=r.pending;return s===null?t.next=t:(t.next=s.next,s.next=t),r.pending=t,Qt(e,n)}return s=r.interleaved,s===null?(t.next=t,su(r)):(t.next=s.next,s.next=t),r.interleaved=t,Qt(e,n)}function Ra(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Vo(e,n)}}function Nc(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var s=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var l={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};a===null?s=a=l:a=a.next=l,n=n.next}while(n!==null);a===null?s=a=t:a=a.next=t}else s=a=t;n={baseState:r.baseState,firstBaseUpdate:s,lastBaseUpdate:a,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ni(e,t,n,r){var s=e.updateQueue;Jt=!1;var a=s.firstBaseUpdate,l=s.lastBaseUpdate,o=s.shared.pending;if(o!==null){s.shared.pending=null;var u=o,c=u.next;u.next=null,l===null?a=c:l.next=c,l=u;var p=e.alternate;p!==null&&(p=p.updateQueue,o=p.lastBaseUpdate,o!==l&&(o===null?p.firstBaseUpdate=c:o.next=c,p.lastBaseUpdate=u))}if(a!==null){var f=s.baseState;l=0,p=c=u=null,o=a;do{var h=o.lane,x=o.eventTime;if((r&h)===h){p!==null&&(p=p.next={eventTime:x,lane:0,tag:o.tag,payload:o.payload,callback:o.callback,next:null});e:{var w=e,j=o;switch(h=t,x=n,j.tag){case 1:if(w=j.payload,typeof w=="function"){f=w.call(x,f,h);break e}f=w;break e;case 3:w.flags=w.flags&-65537|128;case 0:if(w=j.payload,h=typeof w=="function"?w.call(x,f,h):w,h==null)break e;f=oe({},f,h);break e;case 2:Jt=!0}}o.callback!==null&&o.lane!==0&&(e.flags|=64,h=s.effects,h===null?s.effects=[o]:h.push(o))}else x={eventTime:x,lane:h,tag:o.tag,payload:o.payload,callback:o.callback,next:null},p===null?(c=p=x,u=f):p=p.next=x,l|=h;if(o=o.next,o===null){if(o=s.shared.pending,o===null)break;h=o,o=h.next,h.next=null,s.lastBaseUpdate=h,s.shared.pending=null}}while(!0);if(p===null&&(u=f),s.baseState=u,s.firstBaseUpdate=c,s.lastBaseUpdate=p,t=s.shared.interleaved,t!==null){s=t;do l|=s.lane,s=s.next;while(s!==t)}else a===null&&(s.shared.lanes=0);er|=l,e.lanes=l,e.memoizedState=f}}function _c(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=il.transition;il.transition={};try{e(!1),t()}finally{Z=n,il.transition=r}}function mh(){return it().memoizedState}function Lv(e,t,n){var r=jn(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},vh(e))xh(t,n);else if(n=Xf(e,t,n,r),n!==null){var s=ze();vt(n,e,r,s),yh(n,t,r)}}function Rv(e,t,n){var r=jn(e),s={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(vh(e))xh(t,s);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var l=t.lastRenderedState,o=a(l,n);if(s.hasEagerState=!0,s.eagerState=o,xt(o,l)){var u=t.interleaved;u===null?(s.next=s,su(t)):(s.next=u.next,u.next=s),t.interleaved=s;return}}catch{}finally{}n=Xf(e,t,s,r),n!==null&&(s=ze(),vt(n,e,r,s),yh(n,t,r))}}function vh(e){var t=e.alternate;return e===le||t!==null&&t===le}function xh(e,t){gs=si=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function yh(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Vo(e,n)}}var ai={readContext:at,useCallback:be,useContext:be,useEffect:be,useImperativeHandle:be,useInsertionEffect:be,useLayoutEffect:be,useMemo:be,useReducer:be,useRef:be,useState:be,useDebugValue:be,useDeferredValue:be,useTransition:be,useMutableSource:be,useSyncExternalStore:be,useId:be,unstable_isNewReconciler:!1},Mv={readContext:at,useCallback:function(e,t){return jt().memoizedState=[e,t===void 0?null:t],e},useContext:at,useEffect:bc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,za(4194308,4,ch.bind(null,t,e),n)},useLayoutEffect:function(e,t){return za(4194308,4,e,t)},useInsertionEffect:function(e,t){return za(4,2,e,t)},useMemo:function(e,t){var n=jt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=jt();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Lv.bind(null,le,e),[r.memoizedState,e]},useRef:function(e){var t=jt();return e={current:e},t.memoizedState=e},useState:Cc,useDebugValue:hu,useDeferredValue:function(e){return jt().memoizedState=e},useTransition:function(){var e=Cc(!1),t=e[0];return e=Ov.bind(null,e[1]),jt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=le,s=jt();if(ae){if(n===void 0)throw Error(E(407));n=n()}else{if(n=t(),we===null)throw Error(E(349));Zn&30||nh(r,t,n)}s.memoizedState=n;var a={value:n,getSnapshot:t};return s.queue=a,bc(sh.bind(null,r,a,e),[e]),r.flags|=2048,$s(9,rh.bind(null,r,a,n,t),void 0,null),n},useId:function(){var e=jt(),t=we.identifierPrefix;if(ae){var n=Dt,r=It;n=(r&~(1<<32-mt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Ds++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=l.createElement(n,{is:r.is}):(e=l.createElement(n),n==="select"&&(l=e,r.multiple?l.multiple=!0:r.size&&(l.size=r.size))):e=l.createElementNS(e,n),e[Nt]=t,e[zs]=r,Eh(e,t,!1,!1),t.stateNode=e;e:{switch(l=Pl(n,r),n){case"dialog":re("cancel",e),re("close",e),s=r;break;case"iframe":case"object":case"embed":re("load",e),s=r;break;case"video":case"audio":for(s=0;sHr&&(t.flags|=128,r=!0,is(a,!1),t.lanes=4194304)}else{if(!r)if(e=ri(l),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),is(a,!0),a.tail===null&&a.tailMode==="hidden"&&!l.alternate&&!ae)return Ee(t),null}else 2*he()-a.renderingStartTime>Hr&&n!==1073741824&&(t.flags|=128,r=!0,is(a,!1),t.lanes=4194304);a.isBackwards?(l.sibling=t.child,t.child=l):(n=a.last,n!==null?n.sibling=l:t.child=l,a.last=l)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=he(),t.sibling=null,n=ie.current,ne(ie,r?n&1|2:n&1),t):(Ee(t),null);case 22:case 23:return gu(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Ke&1073741824&&(Ee(t),t.subtreeFlags&6&&(t.flags|=8192)):Ee(t),null;case 24:return null;case 25:return null}throw Error(E(156,t.tag))}function Bv(e,t){switch(Zo(t),t.tag){case 1:return Qe(t.type)&&Ja(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Qr(),se(Be),se(Oe),ou(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return lu(t),null;case 13:if(se(ie),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(E(340));Ur()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return se(ie),null;case 4:return Qr(),null;case 10:return ru(t.type._context),null;case 22:case 23:return gu(),null;case 24:return null;default:return null}}var ka=!1,Te=!1,Qv=typeof WeakSet=="function"?WeakSet:Set,z=null;function gr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){ce(e,t,r)}else n.current=null}function no(e,t,n){try{n()}catch(r){ce(e,t,r)}}var Dc=!1;function Vv(e,t){if(Al=Ka,e=zf(),Xo(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var s=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break e}var l=0,o=-1,u=-1,c=0,p=0,f=e,h=null;t:for(;;){for(var x;f!==n||s!==0&&f.nodeType!==3||(o=l+s),f!==a||r!==0&&f.nodeType!==3||(u=l+r),f.nodeType===3&&(l+=f.nodeValue.length),(x=f.firstChild)!==null;)h=f,f=x;for(;;){if(f===e)break t;if(h===n&&++c===s&&(o=l),h===a&&++p===r&&(u=l),(x=f.nextSibling)!==null)break;f=h,h=f.parentNode}f=x}n=o===-1||u===-1?null:{start:o,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for($l={focusedElem:e,selectionRange:n},Ka=!1,z=t;z!==null;)if(t=z,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,z=e;else for(;z!==null;){t=z;try{var w=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(w!==null){var j=w.memoizedProps,S=w.memoizedState,v=t.stateNode,d=v.getSnapshotBeforeUpdate(t.elementType===t.type?j:ut(t.type,j),S);v.__reactInternalSnapshotBeforeUpdate=d}break;case 3:var m=t.stateNode.containerInfo;m.nodeType===1?m.textContent="":m.nodeType===9&&m.documentElement&&m.removeChild(m.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(E(163))}}catch(g){ce(t,t.return,g)}if(e=t.sibling,e!==null){e.return=t.return,z=e;break}z=t.return}return w=Dc,Dc=!1,w}function js(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var s=r=r.next;do{if((s.tag&e)===e){var a=s.destroy;s.destroy=void 0,a!==void 0&&no(t,n,a)}s=s.next}while(s!==r)}}function Ni(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ro(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Oh(e){var t=e.alternate;t!==null&&(e.alternate=null,Oh(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Nt],delete t[zs],delete t[Ql],delete t[Cv],delete t[bv])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Lh(e){return e.tag===5||e.tag===3||e.tag===4}function Ac(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Lh(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function so(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Ga));else if(r!==4&&(e=e.child,e!==null))for(so(e,t,n),e=e.sibling;e!==null;)so(e,t,n),e=e.sibling}function ao(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ao(e,t,n),e=e.sibling;e!==null;)ao(e,t,n),e=e.sibling}var Se=null,ft=!1;function Wt(e,t,n){for(n=n.child;n!==null;)Rh(e,t,n),n=n.sibling}function Rh(e,t,n){if(_t&&typeof _t.onCommitFiberUnmount=="function")try{_t.onCommitFiberUnmount(vi,n)}catch{}switch(n.tag){case 5:Te||gr(n,t);case 6:var r=Se,s=ft;Se=null,Wt(e,t,n),Se=r,ft=s,Se!==null&&(ft?(e=Se,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Se.removeChild(n.stateNode));break;case 18:Se!==null&&(ft?(e=Se,n=n.stateNode,e.nodeType===8?rl(e.parentNode,n):e.nodeType===1&&rl(e,n),Ts(e)):rl(Se,n.stateNode));break;case 4:r=Se,s=ft,Se=n.stateNode.containerInfo,ft=!0,Wt(e,t,n),Se=r,ft=s;break;case 0:case 11:case 14:case 15:if(!Te&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){s=r=r.next;do{var a=s,l=a.destroy;a=a.tag,l!==void 0&&(a&2||a&4)&&no(n,t,l),s=s.next}while(s!==r)}Wt(e,t,n);break;case 1:if(!Te&&(gr(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(o){ce(n,t,o)}Wt(e,t,n);break;case 21:Wt(e,t,n);break;case 22:n.mode&1?(Te=(r=Te)||n.memoizedState!==null,Wt(e,t,n),Te=r):Wt(e,t,n);break;default:Wt(e,t,n)}}function $c(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Qv),t.forEach(function(r){var s=Zv.bind(null,e,r);n.has(r)||(n.add(r),r.then(s,s))})}}function ot(e,t){var n=t.deletions;if(n!==null)for(var r=0;rs&&(s=l),r&=~a}if(r=s,r=he()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Kv(r/1960))-r,10e?16:e,cn===null)var r=!1;else{if(e=cn,cn=null,oi=0,J&6)throw Error(E(331));var s=J;for(J|=4,z=e.current;z!==null;){var a=z,l=a.child;if(z.flags&16){var o=a.deletions;if(o!==null){for(var u=0;uhe()-xu?Wn(e,0):vu|=n),Ve(e,t)}function Uh(e,t){t===0&&(e.mode&1?(t=ha,ha<<=1,!(ha&130023424)&&(ha=4194304)):t=1);var n=ze();e=Qt(e,t),e!==null&&(ta(e,t,n),Ve(e,n))}function Yv(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Uh(e,n)}function Zv(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,s=e.memoizedState;s!==null&&(n=s.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(E(314))}r!==null&&r.delete(t),Uh(e,n)}var Bh;Bh=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Be.current)Ue=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return Ue=!1,$v(e,t,n);Ue=!!(e.flags&131072)}else Ue=!1,ae&&t.flags&1048576&&Kf(t,Za,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Fa(e,t),e=t.pendingProps;var s=$r(t,Oe.current);_r(t,n),s=cu(null,t,r,e,s,n);var a=du();return t.flags|=1,typeof s=="object"&&s!==null&&typeof s.render=="function"&&s.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Qe(r)?(a=!0,Xa(t)):a=!1,t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,au(t),s.updater=Si,t.stateNode=s,s._reactInternals=t,Gl(t,r,e,n),t=Yl(null,t,r,!0,a,n)):(t.tag=0,ae&&a&&Yo(t),Re(null,t,s,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Fa(e,t),e=t.pendingProps,s=r._init,r=s(r._payload),t.type=r,s=t.tag=tx(r),e=ut(r,e),s){case 0:t=Xl(null,t,r,e,n);break e;case 1:t=zc(null,t,r,e,n);break e;case 11:t=Rc(null,t,r,e,n);break e;case 14:t=Mc(null,t,r,ut(r.type,e),n);break e}throw Error(E(306,r,""))}return t;case 0:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Xl(e,t,r,s,n);case 1:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),zc(e,t,r,s,n);case 3:e:{if(_h(t),e===null)throw Error(E(387));r=t.pendingProps,a=t.memoizedState,s=a.element,Yf(e,t),ni(t,r,null,n);var l=t.memoizedState;if(r=l.element,a.isDehydrated)if(a={element:r,isDehydrated:!1,cache:l.cache,pendingSuspenseBoundaries:l.pendingSuspenseBoundaries,transitions:l.transitions},t.updateQueue.baseState=a,t.memoizedState=a,t.flags&256){s=Vr(Error(E(423)),t),t=Fc(e,t,r,n,s);break e}else if(r!==s){s=Vr(Error(E(424)),t),t=Fc(e,t,r,n,s);break e}else for(We=xn(t.stateNode.containerInfo.firstChild),qe=t,ae=!0,ht=null,n=Jf(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Ur(),r===s){t=Vt(e,t,n);break e}Re(e,t,r,n)}t=t.child}return t;case 5:return Zf(t),e===null&&Kl(t),r=t.type,s=t.pendingProps,a=e!==null?e.memoizedProps:null,l=s.children,Ul(r,s)?l=null:a!==null&&Ul(r,a)&&(t.flags|=32),Nh(e,t),Re(e,t,l,n),t.child;case 6:return e===null&&Kl(t),null;case 13:return Ch(e,t,n);case 4:return iu(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Br(t,null,r,n):Re(e,t,r,n),t.child;case 11:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Rc(e,t,r,s,n);case 7:return Re(e,t,t.pendingProps,n),t.child;case 8:return Re(e,t,t.pendingProps.children,n),t.child;case 12:return Re(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,s=t.pendingProps,a=t.memoizedProps,l=s.value,ne(ei,r._currentValue),r._currentValue=l,a!==null)if(xt(a.value,l)){if(a.children===s.children&&!Be.current){t=Vt(e,t,n);break e}}else for(a=t.child,a!==null&&(a.return=t);a!==null;){var o=a.dependencies;if(o!==null){l=a.child;for(var u=o.firstContext;u!==null;){if(u.context===r){if(a.tag===1){u=At(-1,n&-n),u.tag=2;var c=a.updateQueue;if(c!==null){c=c.shared;var p=c.pending;p===null?u.next=u:(u.next=p.next,p.next=u),c.pending=u}}a.lanes|=n,u=a.alternate,u!==null&&(u.lanes|=n),Wl(a.return,n,t),o.lanes|=n;break}u=u.next}}else if(a.tag===10)l=a.type===t.type?null:a.child;else if(a.tag===18){if(l=a.return,l===null)throw Error(E(341));l.lanes|=n,o=l.alternate,o!==null&&(o.lanes|=n),Wl(l,n,t),l=a.sibling}else l=a.child;if(l!==null)l.return=a;else for(l=a;l!==null;){if(l===t){l=null;break}if(a=l.sibling,a!==null){a.return=l.return,l=a;break}l=l.return}a=l}Re(e,t,s.children,n),t=t.child}return t;case 9:return s=t.type,r=t.pendingProps.children,_r(t,n),s=at(s),r=r(s),t.flags|=1,Re(e,t,r,n),t.child;case 14:return r=t.type,s=ut(r,t.pendingProps),s=ut(r.type,s),Mc(e,t,r,s,n);case 15:return kh(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Fa(e,t),t.tag=1,Qe(r)?(e=!0,Xa(t)):e=!1,_r(t,n),gh(t,r,s),Gl(t,r,s,n),Yl(null,t,r,!0,e,n);case 19:return bh(e,t,n);case 22:return Sh(e,t,n)}throw Error(E(156,t.tag))};function Qh(e,t){return vf(e,t)}function ex(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function nt(e,t,n,r){return new ex(e,t,n,r)}function wu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function tx(e){if(typeof e=="function")return wu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===$o)return 11;if(e===Uo)return 14}return 2}function wn(e,t){var n=e.alternate;return n===null?(n=nt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Aa(e,t,n,r,s,a){var l=2;if(r=e,typeof e=="function")wu(e)&&(l=1);else if(typeof e=="string")l=5;else e:switch(e){case cr:return qn(n.children,s,a,t);case Ao:l=8,s|=8;break;case gl:return e=nt(12,n,t,s|2),e.elementType=gl,e.lanes=a,e;case jl:return e=nt(13,n,t,s),e.elementType=jl,e.lanes=a,e;case wl:return e=nt(19,n,t,s),e.elementType=wl,e.lanes=a,e;case Yd:return Ci(n,s,a,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Jd:l=10;break e;case Xd:l=9;break e;case $o:l=11;break e;case Uo:l=14;break e;case Gt:l=16,r=null;break e}throw Error(E(130,e==null?e:typeof e,""))}return t=nt(l,n,t,s),t.elementType=e,t.type=r,t.lanes=a,t}function qn(e,t,n,r){return e=nt(7,e,r,t),e.lanes=n,e}function Ci(e,t,n,r){return e=nt(22,e,r,t),e.elementType=Yd,e.lanes=n,e.stateNode={isHidden:!1},e}function dl(e,t,n){return e=nt(6,e,null,t),e.lanes=n,e}function fl(e,t,n){return t=nt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function nx(e,t,n,r,s){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Ki(0),this.expirationTimes=Ki(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Ki(0),this.identifierPrefix=r,this.onRecoverableError=s,this.mutableSourceEagerHydrationData=null}function ku(e,t,n,r,s,a,l,o,u){return e=new nx(e,t,n,o,u),t===1?(t=1,a===!0&&(t|=8)):t=0,a=nt(3,null,null,t),e.current=a,a.stateNode=e,a.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},au(a),e}function rx(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Wh)}catch(e){console.error(e)}}Wh(),Kd.exports=Je;var ox=Kd.exports,qc=ox;xl.createRoot=qc.createRoot,xl.hydrateRoot=qc.hydrateRoot;var Xr=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},ux={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},Zt,To,Sd,cx=(Sd=class{constructor(){I(this,Zt,ux);I(this,To,!1)}setTimeoutProvider(e){R(this,Zt,e)}setTimeout(e,t){return y(this,Zt).setTimeout(e,t)}clearTimeout(e){y(this,Zt).clearTimeout(e)}setInterval(e,t){return y(this,Zt).setInterval(e,t)}clearInterval(e){y(this,Zt).clearInterval(e)}},Zt=new WeakMap,To=new WeakMap,Sd),In=new cx;function dx(e){setTimeout(e,0)}var nr=typeof window>"u"||"Deno"in globalThis;function Me(){}function fx(e,t){return typeof e=="function"?e(t):e}function co(e){return typeof e=="number"&&e>=0&&e!==1/0}function qh(e,t){return Math.max(e+(t||0)-Date.now(),0)}function kn(e,t){return typeof e=="function"?e(t):e}function Ze(e,t){return typeof e=="function"?e(t):e}function Gc(e,t){const{type:n="all",exact:r,fetchStatus:s,predicate:a,queryKey:l,stale:o}=e;if(l){if(r){if(t.queryHash!==Cu(l,t.options))return!1}else if(!Bs(t.queryKey,l))return!1}if(n!=="all"){const u=t.isActive();if(n==="active"&&!u||n==="inactive"&&u)return!1}return!(typeof o=="boolean"&&t.isStale()!==o||s&&s!==t.state.fetchStatus||a&&!a(t))}function Jc(e,t){const{exact:n,status:r,predicate:s,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(rr(t.options.mutationKey)!==rr(a))return!1}else if(!Bs(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||s&&!s(t))}function Cu(e,t){return((t==null?void 0:t.queryKeyHashFn)||rr)(e)}function rr(e){return JSON.stringify(e,(t,n)=>fo(n)?Object.keys(n).sort().reduce((r,s)=>(r[s]=n[s],r),{}):n)}function Bs(e,t){return e===t?!0:typeof e!=typeof t?!1:e&&t&&typeof e=="object"&&typeof t=="object"?Object.keys(t).every(n=>Bs(e[n],t[n])):!1}var hx=Object.prototype.hasOwnProperty;function Gh(e,t,n=0){if(e===t)return e;if(n>500)return t;const r=Xc(e)&&Xc(t);if(!r&&!(fo(e)&&fo(t)))return t;const a=(r?e:Object.keys(e)).length,l=r?t:Object.keys(t),o=l.length,u=r?new Array(o):{};let c=0;for(let p=0;p{In.setTimeout(t,e)})}function ho(e,t,n){return typeof n.structuralSharing=="function"?n.structuralSharing(e,t):n.structuralSharing!==!1?Gh(e,t):t}function mx(e,t,n=0){const r=[...e,t];return n&&r.length>n?r.slice(1):r}function vx(e,t,n=0){const r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var bu=Symbol();function Jh(e,t){return!e.queryFn&&(t!=null&&t.initialPromise)?()=>t.initialPromise:!e.queryFn||e.queryFn===bu?()=>Promise.reject(new Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function Eu(e,t){return typeof e=="function"?e(...t):!!e}function xx(e,t,n){let r=!1,s;return Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(s??(s=t()),r||(r=!0,s.aborted?n():s.addEventListener("abort",n,{once:!0})),s)}),e}var Dn,en,br,Nd,yx=(Nd=class extends Xr{constructor(){super();I(this,Dn);I(this,en);I(this,br);R(this,br,t=>{if(!nr&&window.addEventListener){const n=()=>t();return window.addEventListener("visibilitychange",n,!1),()=>{window.removeEventListener("visibilitychange",n)}}})}onSubscribe(){y(this,en)||this.setEventListener(y(this,br))}onUnsubscribe(){var t;this.hasListeners()||((t=y(this,en))==null||t.call(this),R(this,en,void 0))}setEventListener(t){var n;R(this,br,t),(n=y(this,en))==null||n.call(this),R(this,en,t(r=>{typeof r=="boolean"?this.setFocused(r):this.onFocus()}))}setFocused(t){y(this,Dn)!==t&&(R(this,Dn,t),this.onFocus())}onFocus(){const t=this.isFocused();this.listeners.forEach(n=>{n(t)})}isFocused(){var t;return typeof y(this,Dn)=="boolean"?y(this,Dn):((t=globalThis.document)==null?void 0:t.visibilityState)!=="hidden"}},Dn=new WeakMap,en=new WeakMap,br=new WeakMap,Nd),Pu=new yx;function po(){let e,t;const n=new Promise((s,a)=>{e=s,t=a});n.status="pending",n.catch(()=>{});function r(s){Object.assign(n,s),delete n.resolve,delete n.reject}return n.resolve=s=>{r({status:"fulfilled",value:s}),e(s)},n.reject=s=>{r({status:"rejected",reason:s}),t(s)},n}var gx=dx;function jx(){let e=[],t=0,n=o=>{o()},r=o=>{o()},s=gx;const a=o=>{t?e.push(o):s(()=>{n(o)})},l=()=>{const o=e;e=[],o.length&&s(()=>{r(()=>{o.forEach(u=>{n(u)})})})};return{batch:o=>{let u;t++;try{u=o()}finally{t--,t||l()}return u},batchCalls:o=>(...u)=>{a(()=>{o(...u)})},schedule:a,setNotifyFunction:o=>{n=o},setBatchNotifyFunction:o=>{r=o},setScheduler:o=>{s=o}}}var ve=jx(),Er,tn,Pr,_d,wx=(_d=class extends Xr{constructor(){super();I(this,Er,!0);I(this,tn);I(this,Pr);R(this,Pr,t=>{if(!nr&&window.addEventListener){const n=()=>t(!0),r=()=>t(!1);return window.addEventListener("online",n,!1),window.addEventListener("offline",r,!1),()=>{window.removeEventListener("online",n),window.removeEventListener("offline",r)}}})}onSubscribe(){y(this,tn)||this.setEventListener(y(this,Pr))}onUnsubscribe(){var t;this.hasListeners()||((t=y(this,tn))==null||t.call(this),R(this,tn,void 0))}setEventListener(t){var n;R(this,Pr,t),(n=y(this,tn))==null||n.call(this),R(this,tn,t(this.setOnline.bind(this)))}setOnline(t){y(this,Er)!==t&&(R(this,Er,t),this.listeners.forEach(r=>{r(t)}))}isOnline(){return y(this,Er)}},Er=new WeakMap,tn=new WeakMap,Pr=new WeakMap,_d),fi=new wx;function kx(e){return Math.min(1e3*2**e,3e4)}function Xh(e){return(e??"online")==="online"?fi.isOnline():!0}var mo=class extends Error{constructor(e){super("CancelledError"),this.revert=e==null?void 0:e.revert,this.silent=e==null?void 0:e.silent}};function Yh(e){let t=!1,n=0,r;const s=po(),a=()=>s.status!=="pending",l=j=>{var S;if(!a()){const v=new mo(j);h(v),(S=e.onCancel)==null||S.call(e,v)}},o=()=>{t=!0},u=()=>{t=!1},c=()=>Pu.isFocused()&&(e.networkMode==="always"||fi.isOnline())&&e.canRun(),p=()=>Xh(e.networkMode)&&e.canRun(),f=j=>{a()||(r==null||r(),s.resolve(j))},h=j=>{a()||(r==null||r(),s.reject(j))},x=()=>new Promise(j=>{var S;r=v=>{(a()||c())&&j(v)},(S=e.onPause)==null||S.call(e)}).then(()=>{var j;r=void 0,a()||(j=e.onContinue)==null||j.call(e)}),w=()=>{if(a())return;let j;const S=n===0?e.initialPromise:void 0;try{j=S??e.fn()}catch(v){j=Promise.reject(v)}Promise.resolve(j).then(f).catch(v=>{var _;if(a())return;const d=e.retry??(nr?0:3),m=e.retryDelay??kx,g=typeof m=="function"?m(n,v):m,b=d===!0||typeof d=="number"&&nc()?void 0:x()).then(()=>{t?h(v):w()})})};return{promise:s,status:()=>s.status,cancel:l,continue:()=>(r==null||r(),s),cancelRetry:o,continueRetry:u,canStart:p,start:()=>(p()?w():x().then(w),s)}}var An,Cd,Zh=(Cd=class{constructor(){I(this,An)}destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),co(this.gcTime)&&R(this,An,In.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(nr?1/0:5*60*1e3))}clearGcTimeout(){y(this,An)&&(In.clearTimeout(y(this,An)),R(this,An,void 0))}},An=new WeakMap,Cd),$n,Tr,Ye,Un,ge,Gs,Bn,ct,Tt,bd,Sx=(bd=class extends Zh{constructor(t){super();I(this,ct);I(this,$n);I(this,Tr);I(this,Ye);I(this,Un);I(this,ge);I(this,Gs);I(this,Bn);R(this,Bn,!1),R(this,Gs,t.defaultOptions),this.setOptions(t.options),this.observers=[],R(this,Un,t.client),R(this,Ye,y(this,Un).getQueryCache()),this.queryKey=t.queryKey,this.queryHash=t.queryHash,R(this,$n,ed(this.options)),this.state=t.state??y(this,$n),this.scheduleGc()}get meta(){return this.options.meta}get promise(){var t;return(t=y(this,ge))==null?void 0:t.promise}setOptions(t){if(this.options={...y(this,Gs),...t},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){const n=ed(this.options);n.data!==void 0&&(this.setState(Zc(n.data,n.dataUpdatedAt)),R(this,$n,n))}}optionalRemove(){!this.observers.length&&this.state.fetchStatus==="idle"&&y(this,Ye).remove(this)}setData(t,n){const r=ho(this.state.data,t,this.options);return Q(this,ct,Tt).call(this,{data:r,type:"success",dataUpdatedAt:n==null?void 0:n.updatedAt,manual:n==null?void 0:n.manual}),r}setState(t,n){Q(this,ct,Tt).call(this,{type:"setState",state:t,setStateOptions:n})}cancel(t){var r,s;const n=(r=y(this,ge))==null?void 0:r.promise;return(s=y(this,ge))==null||s.cancel(t),n?n.then(Me).catch(Me):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}reset(){this.destroy(),this.setState(y(this,$n))}isActive(){return this.observers.some(t=>Ze(t.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===bu||this.state.dataUpdateCount+this.state.errorUpdateCount===0}isStatic(){return this.getObserversCount()>0?this.observers.some(t=>kn(t.options.staleTime,this)==="static"):!1}isStale(){return this.getObserversCount()>0?this.observers.some(t=>t.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(t=0){return this.state.data===void 0?!0:t==="static"?!1:this.state.isInvalidated?!0:!qh(this.state.dataUpdatedAt,t)}onFocus(){var n;const t=this.observers.find(r=>r.shouldFetchOnWindowFocus());t==null||t.refetch({cancelRefetch:!1}),(n=y(this,ge))==null||n.continue()}onOnline(){var n;const t=this.observers.find(r=>r.shouldFetchOnReconnect());t==null||t.refetch({cancelRefetch:!1}),(n=y(this,ge))==null||n.continue()}addObserver(t){this.observers.includes(t)||(this.observers.push(t),this.clearGcTimeout(),y(this,Ye).notify({type:"observerAdded",query:this,observer:t}))}removeObserver(t){this.observers.includes(t)&&(this.observers=this.observers.filter(n=>n!==t),this.observers.length||(y(this,ge)&&(y(this,Bn)?y(this,ge).cancel({revert:!0}):y(this,ge).cancelRetry()),this.scheduleGc()),y(this,Ye).notify({type:"observerRemoved",query:this,observer:t}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||Q(this,ct,Tt).call(this,{type:"invalidate"})}async fetch(t,n){var u,c,p,f,h,x,w,j,S,v,d,m;if(this.state.fetchStatus!=="idle"&&((u=y(this,ge))==null?void 0:u.status())!=="rejected"){if(this.state.data!==void 0&&(n!=null&&n.cancelRefetch))this.cancel({silent:!0});else if(y(this,ge))return y(this,ge).continueRetry(),y(this,ge).promise}if(t&&this.setOptions(t),!this.options.queryFn){const g=this.observers.find(b=>b.options.queryFn);g&&this.setOptions(g.options)}const r=new AbortController,s=g=>{Object.defineProperty(g,"signal",{enumerable:!0,get:()=>(R(this,Bn,!0),r.signal)})},a=()=>{const g=Jh(this.options,n),_=(()=>{const C={client:y(this,Un),queryKey:this.queryKey,meta:this.meta};return s(C),C})();return R(this,Bn,!1),this.options.persister?this.options.persister(g,_,this):g(_)},o=(()=>{const g={fetchOptions:n,options:this.options,queryKey:this.queryKey,client:y(this,Un),state:this.state,fetchFn:a};return s(g),g})();(c=this.options.behavior)==null||c.onFetch(o,this),R(this,Tr,this.state),(this.state.fetchStatus==="idle"||this.state.fetchMeta!==((p=o.fetchOptions)==null?void 0:p.meta))&&Q(this,ct,Tt).call(this,{type:"fetch",meta:(f=o.fetchOptions)==null?void 0:f.meta}),R(this,ge,Yh({initialPromise:n==null?void 0:n.initialPromise,fn:o.fetchFn,onCancel:g=>{g instanceof mo&&g.revert&&this.setState({...y(this,Tr),fetchStatus:"idle"}),r.abort()},onFail:(g,b)=>{Q(this,ct,Tt).call(this,{type:"failed",failureCount:g,error:b})},onPause:()=>{Q(this,ct,Tt).call(this,{type:"pause"})},onContinue:()=>{Q(this,ct,Tt).call(this,{type:"continue"})},retry:o.options.retry,retryDelay:o.options.retryDelay,networkMode:o.options.networkMode,canRun:()=>!0}));try{const g=await y(this,ge).start();if(g===void 0)throw new Error(`${this.queryHash} data is undefined`);return this.setData(g),(x=(h=y(this,Ye).config).onSuccess)==null||x.call(h,g,this),(j=(w=y(this,Ye).config).onSettled)==null||j.call(w,g,this.state.error,this),g}catch(g){if(g instanceof mo){if(g.silent)return y(this,ge).promise;if(g.revert){if(this.state.data===void 0)throw g;return this.state.data}}throw Q(this,ct,Tt).call(this,{type:"error",error:g}),(v=(S=y(this,Ye).config).onError)==null||v.call(S,g,this),(m=(d=y(this,Ye).config).onSettled)==null||m.call(d,this.state.data,g,this),g}finally{this.scheduleGc()}}},$n=new WeakMap,Tr=new WeakMap,Ye=new WeakMap,Un=new WeakMap,ge=new WeakMap,Gs=new WeakMap,Bn=new WeakMap,ct=new WeakSet,Tt=function(t){const n=r=>{switch(t.type){case"failed":return{...r,fetchFailureCount:t.failureCount,fetchFailureReason:t.error};case"pause":return{...r,fetchStatus:"paused"};case"continue":return{...r,fetchStatus:"fetching"};case"fetch":return{...r,...ep(r.data,this.options),fetchMeta:t.meta??null};case"success":const s={...r,...Zc(t.data,t.dataUpdatedAt),dataUpdateCount:r.dataUpdateCount+1,...!t.manual&&{fetchStatus:"idle",fetchFailureCount:0,fetchFailureReason:null}};return R(this,Tr,t.manual?s:void 0),s;case"error":const a=t.error;return{...r,error:a,errorUpdateCount:r.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:r.fetchFailureCount+1,fetchFailureReason:a,fetchStatus:"idle",status:"error",isInvalidated:!0};case"invalidate":return{...r,isInvalidated:!0};case"setState":return{...r,...t.state}}};this.state=n(this.state),ve.batch(()=>{this.observers.forEach(r=>{r.onQueryUpdate()}),y(this,Ye).notify({query:this,type:"updated",action:t})})},bd);function ep(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:Xh(t.networkMode)?"fetching":"paused",...e===void 0&&{error:null,status:"pending"}}}function Zc(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:"success"}}function ed(e){const t=typeof e.initialData=="function"?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt=="function"?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?"success":"pending",fetchStatus:"idle"}}var De,G,Js,Le,Qn,Or,Rt,nn,Xs,Lr,Rr,Vn,Hn,rn,Mr,Y,hs,vo,xo,yo,go,jo,wo,ko,tp,Ed,Nx=(Ed=class extends Xr{constructor(t,n){super();I(this,Y);I(this,De);I(this,G);I(this,Js);I(this,Le);I(this,Qn);I(this,Or);I(this,Rt);I(this,nn);I(this,Xs);I(this,Lr);I(this,Rr);I(this,Vn);I(this,Hn);I(this,rn);I(this,Mr,new Set);this.options=n,R(this,De,t),R(this,nn,null),R(this,Rt,po()),this.bindMethods(),this.setOptions(n)}bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(y(this,G).addObserver(this),td(y(this,G),this.options)?Q(this,Y,hs).call(this):this.updateResult(),Q(this,Y,go).call(this))}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return So(y(this,G),this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return So(y(this,G),this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,Q(this,Y,jo).call(this),Q(this,Y,wo).call(this),y(this,G).removeObserver(this)}setOptions(t){const n=this.options,r=y(this,G);if(this.options=y(this,De).defaultQueryOptions(t),this.options.enabled!==void 0&&typeof this.options.enabled!="boolean"&&typeof this.options.enabled!="function"&&typeof Ze(this.options.enabled,y(this,G))!="boolean")throw new Error("Expected enabled to be a boolean or a callback that returns a boolean");Q(this,Y,ko).call(this),y(this,G).setOptions(this.options),n._defaulted&&!di(this.options,n)&&y(this,De).getQueryCache().notify({type:"observerOptionsUpdated",query:y(this,G),observer:this});const s=this.hasListeners();s&&nd(y(this,G),r,this.options,n)&&Q(this,Y,hs).call(this),this.updateResult(),s&&(y(this,G)!==r||Ze(this.options.enabled,y(this,G))!==Ze(n.enabled,y(this,G))||kn(this.options.staleTime,y(this,G))!==kn(n.staleTime,y(this,G)))&&Q(this,Y,vo).call(this);const a=Q(this,Y,xo).call(this);s&&(y(this,G)!==r||Ze(this.options.enabled,y(this,G))!==Ze(n.enabled,y(this,G))||a!==y(this,rn))&&Q(this,Y,yo).call(this,a)}getOptimisticResult(t){const n=y(this,De).getQueryCache().build(y(this,De),t),r=this.createResult(n,t);return Cx(this,r)&&(R(this,Le,r),R(this,Or,this.options),R(this,Qn,y(this,G).state)),r}getCurrentResult(){return y(this,Le)}trackResult(t,n){return new Proxy(t,{get:(r,s)=>(this.trackProp(s),n==null||n(s),s==="promise"&&(this.trackProp("data"),!this.options.experimental_prefetchInRender&&y(this,Rt).status==="pending"&&y(this,Rt).reject(new Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(r,s))})}trackProp(t){y(this,Mr).add(t)}getCurrentQuery(){return y(this,G)}refetch({...t}={}){return this.fetch({...t})}fetchOptimistic(t){const n=y(this,De).defaultQueryOptions(t),r=y(this,De).getQueryCache().build(y(this,De),n);return r.fetch().then(()=>this.createResult(r,n))}fetch(t){return Q(this,Y,hs).call(this,{...t,cancelRefetch:t.cancelRefetch??!0}).then(()=>(this.updateResult(),y(this,Le)))}createResult(t,n){var M;const r=y(this,G),s=this.options,a=y(this,Le),l=y(this,Qn),o=y(this,Or),c=t!==r?t.state:y(this,Js),{state:p}=t;let f={...p},h=!1,x;if(n._optimisticResults){const P=this.hasListeners(),H=!P&&td(t,n),F=P&&nd(t,r,n,s);(H||F)&&(f={...f,...ep(p.data,t.options)}),n._optimisticResults==="isRestoring"&&(f.fetchStatus="idle")}let{error:w,errorUpdatedAt:j,status:S}=f;x=f.data;let v=!1;if(n.placeholderData!==void 0&&x===void 0&&S==="pending"){let P;a!=null&&a.isPlaceholderData&&n.placeholderData===(o==null?void 0:o.placeholderData)?(P=a.data,v=!0):P=typeof n.placeholderData=="function"?n.placeholderData((M=y(this,Rr))==null?void 0:M.state.data,y(this,Rr)):n.placeholderData,P!==void 0&&(S="success",x=ho(a==null?void 0:a.data,P,n),h=!0)}if(n.select&&x!==void 0&&!v)if(a&&x===(l==null?void 0:l.data)&&n.select===y(this,Xs))x=y(this,Lr);else try{R(this,Xs,n.select),x=n.select(x),x=ho(a==null?void 0:a.data,x,n),R(this,Lr,x),R(this,nn,null)}catch(P){R(this,nn,P)}y(this,nn)&&(w=y(this,nn),x=y(this,Lr),j=Date.now(),S="error");const d=f.fetchStatus==="fetching",m=S==="pending",g=S==="error",b=m&&d,_=x!==void 0,N={status:S,fetchStatus:f.fetchStatus,isPending:m,isSuccess:S==="success",isError:g,isInitialLoading:b,isLoading:b,data:x,dataUpdatedAt:f.dataUpdatedAt,error:w,errorUpdatedAt:j,failureCount:f.fetchFailureCount,failureReason:f.fetchFailureReason,errorUpdateCount:f.errorUpdateCount,isFetched:f.dataUpdateCount>0||f.errorUpdateCount>0,isFetchedAfterMount:f.dataUpdateCount>c.dataUpdateCount||f.errorUpdateCount>c.errorUpdateCount,isFetching:d,isRefetching:d&&!m,isLoadingError:g&&!_,isPaused:f.fetchStatus==="paused",isPlaceholderData:h,isRefetchError:g&&_,isStale:Tu(t,n),refetch:this.refetch,promise:y(this,Rt),isEnabled:Ze(n.enabled,t)!==!1};if(this.options.experimental_prefetchInRender){const P=N.data!==void 0,H=N.status==="error"&&!P,F=ke=>{H?ke.reject(N.error):P&&ke.resolve(N.data)},U=()=>{const ke=R(this,Rt,N.promise=po());F(ke)},te=y(this,Rt);switch(te.status){case"pending":t.queryHash===r.queryHash&&F(te);break;case"fulfilled":(H||N.data!==te.value)&&U();break;case"rejected":(!H||N.error!==te.reason)&&U();break}}return N}updateResult(){const t=y(this,Le),n=this.createResult(y(this,G),this.options);if(R(this,Qn,y(this,G).state),R(this,Or,this.options),y(this,Qn).data!==void 0&&R(this,Rr,y(this,G)),di(n,t))return;R(this,Le,n);const r=()=>{if(!t)return!0;const{notifyOnChangeProps:s}=this.options,a=typeof s=="function"?s():s;if(a==="all"||!a&&!y(this,Mr).size)return!0;const l=new Set(a??y(this,Mr));return this.options.throwOnError&&l.add("error"),Object.keys(y(this,Le)).some(o=>{const u=o;return y(this,Le)[u]!==t[u]&&l.has(u)})};Q(this,Y,tp).call(this,{listeners:r()})}onQueryUpdate(){this.updateResult(),this.hasListeners()&&Q(this,Y,go).call(this)}},De=new WeakMap,G=new WeakMap,Js=new WeakMap,Le=new WeakMap,Qn=new WeakMap,Or=new WeakMap,Rt=new WeakMap,nn=new WeakMap,Xs=new WeakMap,Lr=new WeakMap,Rr=new WeakMap,Vn=new WeakMap,Hn=new WeakMap,rn=new WeakMap,Mr=new WeakMap,Y=new WeakSet,hs=function(t){Q(this,Y,ko).call(this);let n=y(this,G).fetch(this.options,t);return t!=null&&t.throwOnError||(n=n.catch(Me)),n},vo=function(){Q(this,Y,jo).call(this);const t=kn(this.options.staleTime,y(this,G));if(nr||y(this,Le).isStale||!co(t))return;const r=qh(y(this,Le).dataUpdatedAt,t)+1;R(this,Vn,In.setTimeout(()=>{y(this,Le).isStale||this.updateResult()},r))},xo=function(){return(typeof this.options.refetchInterval=="function"?this.options.refetchInterval(y(this,G)):this.options.refetchInterval)??!1},yo=function(t){Q(this,Y,wo).call(this),R(this,rn,t),!(nr||Ze(this.options.enabled,y(this,G))===!1||!co(y(this,rn))||y(this,rn)===0)&&R(this,Hn,In.setInterval(()=>{(this.options.refetchIntervalInBackground||Pu.isFocused())&&Q(this,Y,hs).call(this)},y(this,rn)))},go=function(){Q(this,Y,vo).call(this),Q(this,Y,yo).call(this,Q(this,Y,xo).call(this))},jo=function(){y(this,Vn)&&(In.clearTimeout(y(this,Vn)),R(this,Vn,void 0))},wo=function(){y(this,Hn)&&(In.clearInterval(y(this,Hn)),R(this,Hn,void 0))},ko=function(){const t=y(this,De).getQueryCache().build(y(this,De),this.options);if(t===y(this,G))return;const n=y(this,G);R(this,G,t),R(this,Js,t.state),this.hasListeners()&&(n==null||n.removeObserver(this),t.addObserver(this))},tp=function(t){ve.batch(()=>{t.listeners&&this.listeners.forEach(n=>{n(y(this,Le))}),y(this,De).getQueryCache().notify({query:y(this,G),type:"observerResultsUpdated"})})},Ed);function _x(e,t){return Ze(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status==="error"&&t.retryOnMount===!1)}function td(e,t){return _x(e,t)||e.state.data!==void 0&&So(e,t,t.refetchOnMount)}function So(e,t,n){if(Ze(t.enabled,e)!==!1&&kn(t.staleTime,e)!=="static"){const r=typeof n=="function"?n(e):n;return r==="always"||r!==!1&&Tu(e,t)}return!1}function nd(e,t,n,r){return(e!==t||Ze(r.enabled,e)===!1)&&(!n.suspense||e.state.status!=="error")&&Tu(e,n)}function Tu(e,t){return Ze(t.enabled,e)!==!1&&e.isStaleByTime(kn(t.staleTime,e))}function Cx(e,t){return!di(e.getCurrentResult(),t)}function rd(e){return{onFetch:(t,n)=>{var p,f,h,x,w;const r=t.options,s=(h=(f=(p=t.fetchOptions)==null?void 0:p.meta)==null?void 0:f.fetchMore)==null?void 0:h.direction,a=((x=t.state.data)==null?void 0:x.pages)||[],l=((w=t.state.data)==null?void 0:w.pageParams)||[];let o={pages:[],pageParams:[]},u=0;const c=async()=>{let j=!1;const S=m=>{xx(m,()=>t.signal,()=>j=!0)},v=Jh(t.options,t.fetchOptions),d=async(m,g,b)=>{if(j)return Promise.reject();if(g==null&&m.pages.length)return Promise.resolve(m);const C=(()=>{const H={client:t.client,queryKey:t.queryKey,pageParam:g,direction:b?"backward":"forward",meta:t.options.meta};return S(H),H})(),N=await v(C),{maxPages:M}=t.options,P=b?vx:mx;return{pages:P(m.pages,N,M),pageParams:P(m.pageParams,g,M)}};if(s&&a.length){const m=s==="backward",g=m?bx:sd,b={pages:a,pageParams:l},_=g(r,b);o=await d(b,_,m)}else{const m=e??a.length;do{const g=u===0?l[0]??r.initialPageParam:sd(r,o);if(u>0&&g==null)break;o=await d(o,g),u++}while(u{var j,S;return(S=(j=t.options).persister)==null?void 0:S.call(j,c,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n)}:t.fetchFn=c}}}function sd(e,{pages:t,pageParams:n}){const r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function bx(e,{pages:t,pageParams:n}){var r;return t.length>0?(r=e.getPreviousPageParam)==null?void 0:r.call(e,t[0],t,n[0],n):void 0}var Ys,wt,Pe,Kn,kt,qt,Pd,Ex=(Pd=class extends Zh{constructor(t){super();I(this,kt);I(this,Ys);I(this,wt);I(this,Pe);I(this,Kn);R(this,Ys,t.client),this.mutationId=t.mutationId,R(this,Pe,t.mutationCache),R(this,wt,[]),this.state=t.state||np(),this.setOptions(t.options),this.scheduleGc()}setOptions(t){this.options=t,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(t){y(this,wt).includes(t)||(y(this,wt).push(t),this.clearGcTimeout(),y(this,Pe).notify({type:"observerAdded",mutation:this,observer:t}))}removeObserver(t){R(this,wt,y(this,wt).filter(n=>n!==t)),this.scheduleGc(),y(this,Pe).notify({type:"observerRemoved",mutation:this,observer:t})}optionalRemove(){y(this,wt).length||(this.state.status==="pending"?this.scheduleGc():y(this,Pe).remove(this))}continue(){var t;return((t=y(this,Kn))==null?void 0:t.continue())??this.execute(this.state.variables)}async execute(t){var l,o,u,c,p,f,h,x,w,j,S,v,d,m,g,b,_,C;const n=()=>{Q(this,kt,qt).call(this,{type:"continue"})},r={client:y(this,Ys),meta:this.options.meta,mutationKey:this.options.mutationKey};R(this,Kn,Yh({fn:()=>this.options.mutationFn?this.options.mutationFn(t,r):Promise.reject(new Error("No mutationFn found")),onFail:(N,M)=>{Q(this,kt,qt).call(this,{type:"failed",failureCount:N,error:M})},onPause:()=>{Q(this,kt,qt).call(this,{type:"pause"})},onContinue:n,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>y(this,Pe).canRun(this)}));const s=this.state.status==="pending",a=!y(this,Kn).canStart();try{if(s)n();else{Q(this,kt,qt).call(this,{type:"pending",variables:t,isPaused:a}),y(this,Pe).config.onMutate&&await y(this,Pe).config.onMutate(t,this,r);const M=await((o=(l=this.options).onMutate)==null?void 0:o.call(l,t,r));M!==this.state.context&&Q(this,kt,qt).call(this,{type:"pending",context:M,variables:t,isPaused:a})}const N=await y(this,Kn).start();return await((c=(u=y(this,Pe).config).onSuccess)==null?void 0:c.call(u,N,t,this.state.context,this,r)),await((f=(p=this.options).onSuccess)==null?void 0:f.call(p,N,t,this.state.context,r)),await((x=(h=y(this,Pe).config).onSettled)==null?void 0:x.call(h,N,null,this.state.variables,this.state.context,this,r)),await((j=(w=this.options).onSettled)==null?void 0:j.call(w,N,null,t,this.state.context,r)),Q(this,kt,qt).call(this,{type:"success",data:N}),N}catch(N){try{await((v=(S=y(this,Pe).config).onError)==null?void 0:v.call(S,N,t,this.state.context,this,r))}catch(M){Promise.reject(M)}try{await((m=(d=this.options).onError)==null?void 0:m.call(d,N,t,this.state.context,r))}catch(M){Promise.reject(M)}try{await((b=(g=y(this,Pe).config).onSettled)==null?void 0:b.call(g,void 0,N,this.state.variables,this.state.context,this,r))}catch(M){Promise.reject(M)}try{await((C=(_=this.options).onSettled)==null?void 0:C.call(_,void 0,N,t,this.state.context,r))}catch(M){Promise.reject(M)}throw Q(this,kt,qt).call(this,{type:"error",error:N}),N}finally{y(this,Pe).runNext(this)}}},Ys=new WeakMap,wt=new WeakMap,Pe=new WeakMap,Kn=new WeakMap,kt=new WeakSet,qt=function(t){const n=r=>{switch(t.type){case"failed":return{...r,failureCount:t.failureCount,failureReason:t.error};case"pause":return{...r,isPaused:!0};case"continue":return{...r,isPaused:!1};case"pending":return{...r,context:t.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:t.isPaused,status:"pending",variables:t.variables,submittedAt:Date.now()};case"success":return{...r,data:t.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...r,data:void 0,error:t.error,failureCount:r.failureCount+1,failureReason:t.error,isPaused:!1,status:"error"}}};this.state=n(this.state),ve.batch(()=>{y(this,wt).forEach(r=>{r.onMutationUpdate(t)}),y(this,Pe).notify({mutation:this,type:"updated",action:t})})},Pd);function np(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}var Mt,dt,Zs,Td,Px=(Td=class extends Xr{constructor(t={}){super();I(this,Mt);I(this,dt);I(this,Zs);this.config=t,R(this,Mt,new Set),R(this,dt,new Map),R(this,Zs,0)}build(t,n,r){const s=new Ex({client:t,mutationCache:this,mutationId:++la(this,Zs)._,options:t.defaultMutationOptions(n),state:r});return this.add(s),s}add(t){y(this,Mt).add(t);const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n);r?r.push(t):y(this,dt).set(n,[t])}this.notify({type:"added",mutation:t})}remove(t){if(y(this,Mt).delete(t)){const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n);if(r)if(r.length>1){const s=r.indexOf(t);s!==-1&&r.splice(s,1)}else r[0]===t&&y(this,dt).delete(n)}}this.notify({type:"removed",mutation:t})}canRun(t){const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n),s=r==null?void 0:r.find(a=>a.state.status==="pending");return!s||s===t}else return!0}runNext(t){var r;const n=_a(t);if(typeof n=="string"){const s=(r=y(this,dt).get(n))==null?void 0:r.find(a=>a!==t&&a.state.isPaused);return(s==null?void 0:s.continue())??Promise.resolve()}else return Promise.resolve()}clear(){ve.batch(()=>{y(this,Mt).forEach(t=>{this.notify({type:"removed",mutation:t})}),y(this,Mt).clear(),y(this,dt).clear()})}getAll(){return Array.from(y(this,Mt))}find(t){const n={exact:!0,...t};return this.getAll().find(r=>Jc(n,r))}findAll(t={}){return this.getAll().filter(n=>Jc(t,n))}notify(t){ve.batch(()=>{this.listeners.forEach(n=>{n(t)})})}resumePausedMutations(){const t=this.getAll().filter(n=>n.state.isPaused);return ve.batch(()=>Promise.all(t.map(n=>n.continue().catch(Me))))}},Mt=new WeakMap,dt=new WeakMap,Zs=new WeakMap,Td);function _a(e){var t;return(t=e.options.scope)==null?void 0:t.id}var zt,sn,Ae,Ft,$t,$a,No,Od,Tx=(Od=class extends Xr{constructor(n,r){super();I(this,$t);I(this,zt);I(this,sn);I(this,Ae);I(this,Ft);R(this,zt,n),this.setOptions(r),this.bindMethods(),Q(this,$t,$a).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(n){var s;const r=this.options;this.options=y(this,zt).defaultMutationOptions(n),di(this.options,r)||y(this,zt).getMutationCache().notify({type:"observerOptionsUpdated",mutation:y(this,Ae),observer:this}),r!=null&&r.mutationKey&&this.options.mutationKey&&rr(r.mutationKey)!==rr(this.options.mutationKey)?this.reset():((s=y(this,Ae))==null?void 0:s.state.status)==="pending"&&y(this,Ae).setOptions(this.options)}onUnsubscribe(){var n;this.hasListeners()||(n=y(this,Ae))==null||n.removeObserver(this)}onMutationUpdate(n){Q(this,$t,$a).call(this),Q(this,$t,No).call(this,n)}getCurrentResult(){return y(this,sn)}reset(){var n;(n=y(this,Ae))==null||n.removeObserver(this),R(this,Ae,void 0),Q(this,$t,$a).call(this),Q(this,$t,No).call(this)}mutate(n,r){var s;return R(this,Ft,r),(s=y(this,Ae))==null||s.removeObserver(this),R(this,Ae,y(this,zt).getMutationCache().build(y(this,zt),this.options)),y(this,Ae).addObserver(this),y(this,Ae).execute(n)}},zt=new WeakMap,sn=new WeakMap,Ae=new WeakMap,Ft=new WeakMap,$t=new WeakSet,$a=function(){var r;const n=((r=y(this,Ae))==null?void 0:r.state)??np();R(this,sn,{...n,isPending:n.status==="pending",isSuccess:n.status==="success",isError:n.status==="error",isIdle:n.status==="idle",mutate:this.mutate,reset:this.reset})},No=function(n){ve.batch(()=>{var r,s,a,l,o,u,c,p;if(y(this,Ft)&&this.hasListeners()){const f=y(this,sn).variables,h=y(this,sn).context,x={client:y(this,zt),meta:this.options.meta,mutationKey:this.options.mutationKey};if((n==null?void 0:n.type)==="success"){try{(s=(r=y(this,Ft)).onSuccess)==null||s.call(r,n.data,f,h,x)}catch(w){Promise.reject(w)}try{(l=(a=y(this,Ft)).onSettled)==null||l.call(a,n.data,null,f,h,x)}catch(w){Promise.reject(w)}}else if((n==null?void 0:n.type)==="error"){try{(u=(o=y(this,Ft)).onError)==null||u.call(o,n.error,f,h,x)}catch(w){Promise.reject(w)}try{(p=(c=y(this,Ft)).onSettled)==null||p.call(c,void 0,n.error,f,h,x)}catch(w){Promise.reject(w)}}}this.listeners.forEach(f=>{f(y(this,sn))})})},Od),St,Ld,Ox=(Ld=class extends Xr{constructor(t={}){super();I(this,St);this.config=t,R(this,St,new Map)}build(t,n,r){const s=n.queryKey,a=n.queryHash??Cu(s,n);let l=this.get(a);return l||(l=new Sx({client:t,queryKey:s,queryHash:a,options:t.defaultQueryOptions(n),state:r,defaultOptions:t.getQueryDefaults(s)}),this.add(l)),l}add(t){y(this,St).has(t.queryHash)||(y(this,St).set(t.queryHash,t),this.notify({type:"added",query:t}))}remove(t){const n=y(this,St).get(t.queryHash);n&&(t.destroy(),n===t&&y(this,St).delete(t.queryHash),this.notify({type:"removed",query:t}))}clear(){ve.batch(()=>{this.getAll().forEach(t=>{this.remove(t)})})}get(t){return y(this,St).get(t)}getAll(){return[...y(this,St).values()]}find(t){const n={exact:!0,...t};return this.getAll().find(r=>Gc(n,r))}findAll(t={}){const n=this.getAll();return Object.keys(t).length>0?n.filter(r=>Gc(t,r)):n}notify(t){ve.batch(()=>{this.listeners.forEach(n=>{n(t)})})}onFocus(){ve.batch(()=>{this.getAll().forEach(t=>{t.onFocus()})})}onOnline(){ve.batch(()=>{this.getAll().forEach(t=>{t.onOnline()})})}},St=new WeakMap,Ld),ue,an,ln,zr,Fr,on,Ir,Dr,Rd,Lx=(Rd=class{constructor(e={}){I(this,ue);I(this,an);I(this,ln);I(this,zr);I(this,Fr);I(this,on);I(this,Ir);I(this,Dr);R(this,ue,e.queryCache||new Ox),R(this,an,e.mutationCache||new Px),R(this,ln,e.defaultOptions||{}),R(this,zr,new Map),R(this,Fr,new Map),R(this,on,0)}mount(){la(this,on)._++,y(this,on)===1&&(R(this,Ir,Pu.subscribe(async e=>{e&&(await this.resumePausedMutations(),y(this,ue).onFocus())})),R(this,Dr,fi.subscribe(async e=>{e&&(await this.resumePausedMutations(),y(this,ue).onOnline())})))}unmount(){var e,t;la(this,on)._--,y(this,on)===0&&((e=y(this,Ir))==null||e.call(this),R(this,Ir,void 0),(t=y(this,Dr))==null||t.call(this),R(this,Dr,void 0))}isFetching(e){return y(this,ue).findAll({...e,fetchStatus:"fetching"}).length}isMutating(e){return y(this,an).findAll({...e,status:"pending"}).length}getQueryData(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=y(this,ue).get(t.queryHash))==null?void 0:n.state.data}ensureQueryData(e){const t=this.defaultQueryOptions(e),n=y(this,ue).build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(kn(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return y(this,ue).findAll(e).map(({queryKey:t,state:n})=>{const r=n.data;return[t,r]})}setQueryData(e,t,n){const r=this.defaultQueryOptions({queryKey:e}),s=y(this,ue).get(r.queryHash),a=s==null?void 0:s.state.data,l=fx(t,a);if(l!==void 0)return y(this,ue).build(this,r).setData(l,{...n,manual:!0})}setQueriesData(e,t,n){return ve.batch(()=>y(this,ue).findAll(e).map(({queryKey:r})=>[r,this.setQueryData(r,t,n)]))}getQueryState(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=y(this,ue).get(t.queryHash))==null?void 0:n.state}removeQueries(e){const t=y(this,ue);ve.batch(()=>{t.findAll(e).forEach(n=>{t.remove(n)})})}resetQueries(e,t){const n=y(this,ue);return ve.batch(()=>(n.findAll(e).forEach(r=>{r.reset()}),this.refetchQueries({type:"active",...e},t)))}cancelQueries(e,t={}){const n={revert:!0,...t},r=ve.batch(()=>y(this,ue).findAll(e).map(s=>s.cancel(n)));return Promise.all(r).then(Me).catch(Me)}invalidateQueries(e,t={}){return ve.batch(()=>(y(this,ue).findAll(e).forEach(n=>{n.invalidate()}),(e==null?void 0:e.refetchType)==="none"?Promise.resolve():this.refetchQueries({...e,type:(e==null?void 0:e.refetchType)??(e==null?void 0:e.type)??"active"},t)))}refetchQueries(e,t={}){const n={...t,cancelRefetch:t.cancelRefetch??!0},r=ve.batch(()=>y(this,ue).findAll(e).filter(s=>!s.isDisabled()&&!s.isStatic()).map(s=>{let a=s.fetch(void 0,n);return n.throwOnError||(a=a.catch(Me)),s.state.fetchStatus==="paused"?Promise.resolve():a}));return Promise.all(r).then(Me)}fetchQuery(e){const t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);const n=y(this,ue).build(this,t);return n.isStaleByTime(kn(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(Me).catch(Me)}fetchInfiniteQuery(e){return e.behavior=rd(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(Me).catch(Me)}ensureInfiniteQueryData(e){return e.behavior=rd(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return fi.isOnline()?y(this,an).resumePausedMutations():Promise.resolve()}getQueryCache(){return y(this,ue)}getMutationCache(){return y(this,an)}getDefaultOptions(){return y(this,ln)}setDefaultOptions(e){R(this,ln,e)}setQueryDefaults(e,t){y(this,zr).set(rr(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){const t=[...y(this,zr).values()],n={};return t.forEach(r=>{Bs(e,r.queryKey)&&Object.assign(n,r.defaultOptions)}),n}setMutationDefaults(e,t){y(this,Fr).set(rr(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){const t=[...y(this,Fr).values()],n={};return t.forEach(r=>{Bs(e,r.mutationKey)&&Object.assign(n,r.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;const t={...y(this,ln).queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=Cu(t.queryKey,t)),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!=="always"),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode="offlineFirst"),t.queryFn===bu&&(t.enabled=!1),t}defaultMutationOptions(e){return e!=null&&e._defaulted?e:{...y(this,ln).mutations,...(e==null?void 0:e.mutationKey)&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){y(this,ue).clear(),y(this,an).clear()}},ue=new WeakMap,an=new WeakMap,ln=new WeakMap,zr=new WeakMap,Fr=new WeakMap,on=new WeakMap,Ir=new WeakMap,Dr=new WeakMap,Rd),rp=k.createContext(void 0),Pn=e=>{const t=k.useContext(rp);if(!t)throw new Error("No QueryClient set, use QueryClientProvider to set one");return t},Rx=({client:e,children:t})=>(k.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),i.jsx(rp.Provider,{value:e,children:t})),sp=k.createContext(!1),Mx=()=>k.useContext(sp);sp.Provider;function zx(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var Fx=k.createContext(zx()),Ix=()=>k.useContext(Fx),Dx=(e,t,n)=>{const r=n!=null&&n.state.error&&typeof e.throwOnError=="function"?Eu(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},Ax=e=>{k.useEffect(()=>{e.clearReset()},[e])},$x=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:s})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(s&&e.data===void 0||Eu(n,[e.error,r])),Ux=e=>{if(e.suspense){const n=s=>s==="static"?s:Math.max(s??1e3,1e3),r=e.staleTime;e.staleTime=typeof r=="function"?(...s)=>n(r(...s)):n(r),typeof e.gcTime=="number"&&(e.gcTime=Math.max(e.gcTime,1e3))}},Bx=(e,t)=>e.isLoading&&e.isFetching&&!t,Qx=(e,t)=>(e==null?void 0:e.suspense)&&t.isPending,ad=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function Vx(e,t,n){var h,x,w,j;const r=Mx(),s=Ix(),a=Pn(),l=a.defaultQueryOptions(e);(x=(h=a.getDefaultOptions().queries)==null?void 0:h._experimental_beforeQuery)==null||x.call(h,l);const o=a.getQueryCache().get(l.queryHash);l._optimisticResults=r?"isRestoring":"optimistic",Ux(l),Dx(l,s,o),Ax(s);const u=!a.getQueryCache().get(l.queryHash),[c]=k.useState(()=>new t(a,l)),p=c.getOptimisticResult(l),f=!r&&e.subscribed!==!1;if(k.useSyncExternalStore(k.useCallback(S=>{const v=f?c.subscribe(ve.batchCalls(S)):Me;return c.updateResult(),v},[c,f]),()=>c.getCurrentResult(),()=>c.getCurrentResult()),k.useEffect(()=>{c.setOptions(l)},[l,c]),Qx(l,p))throw ad(l,c,s);if($x({result:p,errorResetBoundary:s,throwOnError:l.throwOnError,query:o,suspense:l.suspense}))throw p.error;if((j=(w=a.getDefaultOptions().queries)==null?void 0:w._experimental_afterQuery)==null||j.call(w,l,p),l.experimental_prefetchInRender&&!nr&&Bx(p,r)){const S=u?ad(l,c,s):o==null?void 0:o.promise;S==null||S.catch(Me).finally(()=>{c.updateResult()})}return l.notifyOnChangeProps?p:c.trackResult(p)}function _e(e,t){return Vx(e,Nx)}function st(e,t){const n=Pn(),[r]=k.useState(()=>new Tx(n,e));k.useEffect(()=>{r.setOptions(e)},[r,e]);const s=k.useSyncExternalStore(k.useCallback(l=>r.subscribe(ve.batchCalls(l)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),a=k.useCallback((l,o)=>{r.mutate(l,o).catch(Me)},[r]);if(s.error&&Eu(r.options.throwOnError,[s.error]))throw s.error;return{...s,mutate:a,mutateAsync:s.mutate}}/** + * @remix-run/router v1.23.2 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function Qs(){return Qs=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function Ou(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function Kx(){return Math.random().toString(36).substr(2,8)}function ld(e,t){return{usr:e.state,key:e.key,idx:t}}function _o(e,t,n,r){return n===void 0&&(n=null),Qs({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?Yr(t):t,{state:n,key:t&&t.key||r||Kx()})}function hi(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function Yr(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Wx(e,t,n,r){r===void 0&&(r={});let{window:s=document.defaultView,v5Compat:a=!1}=r,l=s.history,o=dn.Pop,u=null,c=p();c==null&&(c=0,l.replaceState(Qs({},l.state,{idx:c}),""));function p(){return(l.state||{idx:null}).idx}function f(){o=dn.Pop;let S=p(),v=S==null?null:S-c;c=S,u&&u({action:o,location:j.location,delta:v})}function h(S,v){o=dn.Push;let d=_o(j.location,S,v);c=p()+1;let m=ld(d,c),g=j.createHref(d);try{l.pushState(m,"",g)}catch(b){if(b instanceof DOMException&&b.name==="DataCloneError")throw b;s.location.assign(g)}a&&u&&u({action:o,location:j.location,delta:1})}function x(S,v){o=dn.Replace;let d=_o(j.location,S,v);c=p();let m=ld(d,c),g=j.createHref(d);l.replaceState(m,"",g),a&&u&&u({action:o,location:j.location,delta:0})}function w(S){let v=s.location.origin!=="null"?s.location.origin:s.location.href,d=typeof S=="string"?S:hi(S);return d=d.replace(/ $/,"%20"),de(v,"No window.location.(origin|href) available to create URL for href: "+d),new URL(d,v)}let j={get action(){return o},get location(){return e(s,l)},listen(S){if(u)throw new Error("A history only accepts one active listener");return s.addEventListener(id,f),u=S,()=>{s.removeEventListener(id,f),u=null}},createHref(S){return t(s,S)},createURL:w,encodeLocation(S){let v=w(S);return{pathname:v.pathname,search:v.search,hash:v.hash}},push:h,replace:x,go(S){return l.go(S)}};return j}var od;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(od||(od={}));function qx(e,t,n){return n===void 0&&(n="/"),Gx(e,t,n)}function Gx(e,t,n,r){let s=typeof t=="string"?Yr(t):t,a=Kr(s.pathname||"/",n);if(a==null)return null;let l=ap(e);Jx(l);let o=null;for(let u=0;o==null&&u{let u={relativePath:o===void 0?a.path||"":o,caseSensitive:a.caseSensitive===!0,childrenIndex:l,route:a};u.relativePath.startsWith("/")&&(de(u.relativePath.startsWith(r),'Absolute route path "'+u.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),u.relativePath=u.relativePath.slice(r.length));let c=Sn([r,u.relativePath]),p=n.concat(u);a.children&&a.children.length>0&&(de(a.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+c+'".')),ap(a.children,t,p,c)),!(a.path==null&&!a.index)&&t.push({path:c,score:ry(c,a.index),routesMeta:p})};return e.forEach((a,l)=>{var o;if(a.path===""||!((o=a.path)!=null&&o.includes("?")))s(a,l);else for(let u of ip(a.path))s(a,l,u)}),t}function ip(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,s=n.endsWith("?"),a=n.replace(/\?$/,"");if(r.length===0)return s?[a,""]:[a];let l=ip(r.join("/")),o=[];return o.push(...l.map(u=>u===""?a:[a,u].join("/"))),s&&o.push(...l),o.map(u=>e.startsWith("/")&&u===""?"/":u)}function Jx(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:sy(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Xx=/^:[\w-]+$/,Yx=3,Zx=2,ey=1,ty=10,ny=-2,ud=e=>e==="*";function ry(e,t){let n=e.split("/"),r=n.length;return n.some(ud)&&(r+=ny),t&&(r+=Zx),n.filter(s=>!ud(s)).reduce((s,a)=>s+(Xx.test(a)?Yx:a===""?ey:ty),r)}function sy(e,t){return e.length===t.length&&e.slice(0,-1).every((r,s)=>r===t[s])?e[e.length-1]-t[t.length-1]:0}function ay(e,t,n){let{routesMeta:r}=e,s={},a="/",l=[];for(let o=0;o{let{paramName:h,isOptional:x}=p;if(h==="*"){let j=o[f]||"";l=a.slice(0,a.length-j.length).replace(/(.)\/+$/,"$1")}const w=o[f];return x&&!w?c[h]=void 0:c[h]=(w||"").replace(/%2F/g,"/"),c},{}),pathname:a,pathnameBase:l,pattern:e}}function iy(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),Ou(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],s="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(l,o,u)=>(r.push({paramName:o,isOptional:u!=null}),u?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),s+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?s+="\\/*$":e!==""&&e!=="/"&&(s+="(?:(?=\\/|$))"),[new RegExp(s,t?void 0:"i"),r]}function ly(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return Ou(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function Kr(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const oy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,uy=e=>oy.test(e);function cy(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:s=""}=typeof e=="string"?Yr(e):e,a;if(n)if(uy(n))a=n;else{if(n.includes("//")){let l=n;n=n.replace(/\/\/+/g,"/"),Ou(!1,"Pathnames cannot have embedded double slashes - normalizing "+(l+" -> "+n))}n.startsWith("/")?a=cd(n.substring(1),"/"):a=cd(n,t)}else a=t;return{pathname:a,search:hy(r),hash:py(s)}}function cd(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(s=>{s===".."?n.length>1&&n.pop():s!=="."&&n.push(s)}),n.length>1?n.join("/"):"/"}function hl(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function dy(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function lp(e,t){let n=dy(e);return t?n.map((r,s)=>s===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function op(e,t,n,r){r===void 0&&(r=!1);let s;typeof e=="string"?s=Yr(e):(s=Qs({},e),de(!s.pathname||!s.pathname.includes("?"),hl("?","pathname","search",s)),de(!s.pathname||!s.pathname.includes("#"),hl("#","pathname","hash",s)),de(!s.search||!s.search.includes("#"),hl("#","search","hash",s)));let a=e===""||s.pathname==="",l=a?"/":s.pathname,o;if(l==null)o=n;else{let f=t.length-1;if(!r&&l.startsWith("..")){let h=l.split("/");for(;h[0]==="..";)h.shift(),f-=1;s.pathname=h.join("/")}o=f>=0?t[f]:"/"}let u=cy(s,o),c=l&&l!=="/"&&l.endsWith("/"),p=(a||l===".")&&n.endsWith("/");return!u.pathname.endsWith("/")&&(c||p)&&(u.pathname+="/"),u}const Sn=e=>e.join("/").replace(/\/\/+/g,"/"),fy=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),hy=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,py=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function my(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const up=["post","put","patch","delete"];new Set(up);const vy=["get",...up];new Set(vy);/** + * React Router v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function Vs(){return Vs=Object.assign?Object.assign.bind():function(e){for(var t=1;t{o.current=!0}),k.useCallback(function(c,p){if(p===void 0&&(p={}),!o.current)return;if(typeof c=="number"){r.go(c);return}let f=op(c,JSON.parse(l),a,p.relative==="path");e==null&&t!=="/"&&(f.pathname=f.pathname==="/"?t:Sn([t,f.pathname])),(p.replace?r.replace:r.push)(f,p.state,p)},[t,r,l,a,e])}const gy=k.createContext(null);function jy(e){let t=k.useContext(Kt).outlet;return t&&k.createElement(gy.Provider,{value:e},t)}function Ri(){let{matches:e}=k.useContext(Kt),t=e[e.length-1];return t?t.params:{}}function Mi(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=k.useContext(Tn),{matches:s}=k.useContext(Kt),{pathname:a}=Zr(),l=JSON.stringify(lp(s,r.v7_relativeSplatPath));return k.useMemo(()=>op(e,JSON.parse(l),a,n==="path"),[e,l,a,n])}function wy(e,t){return ky(e,t)}function ky(e,t,n,r){aa()||de(!1);let{navigator:s}=k.useContext(Tn),{matches:a}=k.useContext(Kt),l=a[a.length-1],o=l?l.params:{};l&&l.pathname;let u=l?l.pathnameBase:"/";l&&l.route;let c=Zr(),p;if(t){var f;let S=typeof t=="string"?Yr(t):t;u==="/"||(f=S.pathname)!=null&&f.startsWith(u)||de(!1),p=S}else p=c;let h=p.pathname||"/",x=h;if(u!=="/"){let S=u.replace(/^\//,"").split("/");x="/"+h.replace(/^\//,"").split("/").slice(S.length).join("/")}let w=qx(e,{pathname:x}),j=by(w&&w.map(S=>Object.assign({},S,{params:Object.assign({},o,S.params),pathname:Sn([u,s.encodeLocation?s.encodeLocation(S.pathname).pathname:S.pathname]),pathnameBase:S.pathnameBase==="/"?u:Sn([u,s.encodeLocation?s.encodeLocation(S.pathnameBase).pathname:S.pathnameBase])})),a,n,r);return t&&j?k.createElement(Li.Provider,{value:{location:Vs({pathname:"/",search:"",hash:"",state:null,key:"default"},p),navigationType:dn.Pop}},j):j}function Sy(){let e=Oy(),t=my(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,s={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return k.createElement(k.Fragment,null,k.createElement("h2",null,"Unexpected Application Error!"),k.createElement("h3",{style:{fontStyle:"italic"}},t),n?k.createElement("pre",{style:s},n):null,null)}const Ny=k.createElement(Sy,null);class _y extends k.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?k.createElement(Kt.Provider,{value:this.props.routeContext},k.createElement(dp.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function Cy(e){let{routeContext:t,match:n,children:r}=e,s=k.useContext(Oi);return s&&s.static&&s.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(s.staticContext._deepestRenderedBoundaryId=n.route.id),k.createElement(Kt.Provider,{value:t},r)}function by(e,t,n,r){var s;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var a;if(!n)return null;if(n.errors)e=n.matches;else if((a=r)!=null&&a.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let l=e,o=(s=n)==null?void 0:s.errors;if(o!=null){let p=l.findIndex(f=>f.route.id&&(o==null?void 0:o[f.route.id])!==void 0);p>=0||de(!1),l=l.slice(0,Math.min(l.length,p+1))}let u=!1,c=-1;if(n&&r&&r.v7_partialHydration)for(let p=0;p=0?l=l.slice(0,c+1):l=[l[0]];break}}}return l.reduceRight((p,f,h)=>{let x,w=!1,j=null,S=null;n&&(x=o&&f.route.id?o[f.route.id]:void 0,j=f.route.errorElement||Ny,u&&(c<0&&h===0?(Ry("route-fallback"),w=!0,S=null):c===h&&(w=!0,S=f.route.hydrateFallbackElement||null)));let v=t.concat(l.slice(0,h+1)),d=()=>{let m;return x?m=j:w?m=S:f.route.Component?m=k.createElement(f.route.Component,null):f.route.element?m=f.route.element:m=p,k.createElement(Cy,{match:f,routeContext:{outlet:p,matches:v,isDataRoute:n!=null},children:m})};return n&&(f.route.ErrorBoundary||f.route.errorElement||h===0)?k.createElement(_y,{location:n.location,revalidation:n.revalidation,component:j,error:x,children:d(),routeContext:{outlet:null,matches:v,isDataRoute:!0}}):d()},null)}var hp=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(hp||{}),pp=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(pp||{});function Ey(e){let t=k.useContext(Oi);return t||de(!1),t}function Py(e){let t=k.useContext(cp);return t||de(!1),t}function Ty(e){let t=k.useContext(Kt);return t||de(!1),t}function mp(e){let t=Ty(),n=t.matches[t.matches.length-1];return n.route.id||de(!1),n.route.id}function Oy(){var e;let t=k.useContext(dp),n=Py(),r=mp();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function Ly(){let{router:e}=Ey(hp.UseNavigateStable),t=mp(pp.UseNavigateStable),n=k.useRef(!1);return fp(()=>{n.current=!0}),k.useCallback(function(s,a){a===void 0&&(a={}),n.current&&(typeof s=="number"?e.navigate(s):e.navigate(s,Vs({fromRouteId:t},a)))},[e,t])}const dd={};function Ry(e,t,n){dd[e]||(dd[e]=!0)}function My(e,t){e==null||e.v7_startTransition,e==null||e.v7_relativeSplatPath}function zy(e){return jy(e.context)}function gt(e){de(!1)}function Fy(e){let{basename:t="/",children:n=null,location:r,navigationType:s=dn.Pop,navigator:a,static:l=!1,future:o}=e;aa()&&de(!1);let u=t.replace(/^\/*/,"/"),c=k.useMemo(()=>({basename:u,navigator:a,static:l,future:Vs({v7_relativeSplatPath:!1},o)}),[u,o,a,l]);typeof r=="string"&&(r=Yr(r));let{pathname:p="/",search:f="",hash:h="",state:x=null,key:w="default"}=r,j=k.useMemo(()=>{let S=Kr(p,u);return S==null?null:{location:{pathname:S,search:f,hash:h,state:x,key:w},navigationType:s}},[u,p,f,h,x,w,s]);return j==null?null:k.createElement(Tn.Provider,{value:c},k.createElement(Li.Provider,{children:n,value:j}))}function Iy(e){let{children:t,location:n}=e;return wy(bo(t),n)}new Promise(()=>{});function bo(e,t){t===void 0&&(t=[]);let n=[];return k.Children.forEach(e,(r,s)=>{if(!k.isValidElement(r))return;let a=[...t,s];if(r.type===k.Fragment){n.push.apply(n,bo(r.props.children,a));return}r.type!==gt&&de(!1),!r.props.index||!r.props.children||de(!1);let l={id:r.props.id||a.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,loader:r.props.loader,action:r.props.action,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(l.children=bo(r.props.children,a)),n.push(l)}),n}/** + * React Router DOM v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function pi(){return pi=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[s]=e[s]);return n}function Dy(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Ay(e,t){return e.button===0&&(!t||t==="_self")&&!Dy(e)}const $y=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],Uy=["aria-current","caseSensitive","className","end","style","to","viewTransition","children"],By="6";try{window.__reactRouterVersion=By}catch{}const Qy=k.createContext({isTransitioning:!1}),Vy="startTransition",fd=Xp[Vy];function Hy(e){let{basename:t,children:n,future:r,window:s}=e,a=k.useRef();a.current==null&&(a.current=Hx({window:s,v5Compat:!0}));let l=a.current,[o,u]=k.useState({action:l.action,location:l.location}),{v7_startTransition:c}=r||{},p=k.useCallback(f=>{c&&fd?fd(()=>u(f)):u(f)},[u,c]);return k.useLayoutEffect(()=>l.listen(p),[l,p]),k.useEffect(()=>My(r),[r]),k.createElement(Fy,{basename:t,children:n,location:o.location,navigationType:o.action,navigator:l,future:r})}const Ky=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",Wy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Hs=k.forwardRef(function(t,n){let{onClick:r,relative:s,reloadDocument:a,replace:l,state:o,target:u,to:c,preventScrollReset:p,viewTransition:f}=t,h=vp(t,$y),{basename:x}=k.useContext(Tn),w,j=!1;if(typeof c=="string"&&Wy.test(c)&&(w=c,Ky))try{let m=new URL(window.location.href),g=c.startsWith("//")?new URL(m.protocol+c):new URL(c),b=Kr(g.pathname,x);g.origin===m.origin&&b!=null?c=b+g.search+g.hash:j=!0}catch{}let S=xy(c,{relative:s}),v=Gy(c,{replace:l,state:o,target:u,preventScrollReset:p,relative:s,viewTransition:f});function d(m){r&&r(m),m.defaultPrevented||v(m)}return k.createElement("a",pi({},h,{href:w||S,onClick:j||a?r:d,ref:n,target:u}))}),hd=k.forwardRef(function(t,n){let{"aria-current":r="page",caseSensitive:s=!1,className:a="",end:l=!1,style:o,to:u,viewTransition:c,children:p}=t,f=vp(t,Uy),h=Mi(u,{relative:f.relative}),x=Zr(),w=k.useContext(cp),{navigator:j,basename:S}=k.useContext(Tn),v=w!=null&&Jy(h)&&c===!0,d=j.encodeLocation?j.encodeLocation(h).pathname:h.pathname,m=x.pathname,g=w&&w.navigation&&w.navigation.location?w.navigation.location.pathname:null;s||(m=m.toLowerCase(),g=g?g.toLowerCase():null,d=d.toLowerCase()),g&&S&&(g=Kr(g,S)||g);const b=d!=="/"&&d.endsWith("/")?d.length-1:d.length;let _=m===d||!l&&m.startsWith(d)&&m.charAt(b)==="/",C=g!=null&&(g===d||!l&&g.startsWith(d)&&g.charAt(d.length)==="/"),N={isActive:_,isPending:C,isTransitioning:v},M=_?r:void 0,P;typeof a=="function"?P=a(N):P=[a,_?"active":null,C?"pending":null,v?"transitioning":null].filter(Boolean).join(" ");let H=typeof o=="function"?o(N):o;return k.createElement(Hs,pi({},f,{"aria-current":M,className:P,ref:n,style:H,to:u,viewTransition:c}),typeof p=="function"?p(N):p)});var Eo;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(Eo||(Eo={}));var pd;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(pd||(pd={}));function qy(e){let t=k.useContext(Oi);return t||de(!1),t}function Gy(e,t){let{target:n,replace:r,state:s,preventScrollReset:a,relative:l,viewTransition:o}=t===void 0?{}:t,u=Et(),c=Zr(),p=Mi(e,{relative:l});return k.useCallback(f=>{if(Ay(f,n)){f.preventDefault();let h=r!==void 0?r:hi(c)===hi(p);u(e,{replace:h,state:s,preventScrollReset:a,relative:l,viewTransition:o})}},[c,u,p,r,s,n,e,a,l,o])}function Jy(e,t){t===void 0&&(t={});let n=k.useContext(Qy);n==null&&de(!1);let{basename:r}=qy(Eo.useViewTransitionState),s=Mi(e,{relative:t.relative});if(!n.isTransitioning)return!1;let a=Kr(n.currentLocation.pathname,r)||n.currentLocation.pathname,l=Kr(n.nextLocation.pathname,r)||n.nextLocation.pathname;return Co(s.pathname,l)!=null||Co(s.pathname,a)!=null}/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */var Xy={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Yy=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().trim(),X=(e,t)=>{const n=k.forwardRef(({color:r="currentColor",size:s=24,strokeWidth:a=2,absoluteStrokeWidth:l,className:o="",children:u,...c},p)=>k.createElement("svg",{ref:p,...Xy,width:s,height:s,stroke:r,strokeWidth:l?Number(a)*24/Number(s):a,className:["lucide",`lucide-${Yy(e)}`,o].join(" "),...c},[...t.map(([f,h])=>k.createElement(f,h)),...Array.isArray(u)?u:[u]]));return n.displayName=`${e}`,n};/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Zy=X("AlertCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const eg=X("ArrowUpDown",[["path",{d:"m21 16-4 4-4-4",key:"f6ql7i"}],["path",{d:"M17 20V4",key:"1ejh1v"}],["path",{d:"m3 8 4-4 4 4",key:"11wl7u"}],["path",{d:"M7 4v16",key:"1glfcx"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const tg=X("BarChart3",[["path",{d:"M3 3v18h18",key:"1s2lah"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ng=X("Bot",[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const rg=X("CheckCircle",[["path",{d:"M22 11.08V12a10 10 0 1 1-5.93-9.14",key:"g774vq"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const sg=X("Check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ag=X("ChevronDown",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ig=X("ChevronLeft",[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ss=X("ChevronRight",[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const lg=X("Clock",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16 14",key:"68esgv"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const og=X("Coins",[["circle",{cx:"8",cy:"8",r:"6",key:"3yglwk"}],["path",{d:"M18.09 10.37A6 6 0 1 1 10.34 18",key:"t5s6rm"}],["path",{d:"M7 6h1v4",key:"1obek4"}],["path",{d:"m16.71 13.88.7.71-2.82 2.82",key:"1rbuyh"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ug=X("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const cg=X("ExternalLink",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const dg=X("GitCompare",[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7",key:"1yeb86"}],["path",{d:"M11 18H8a2 2 0 0 1-2-2V9",key:"19pyzm"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const fg=X("Github",[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const zi=X("Globe",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const hg=X("History",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const pg=X("ListTodo",[["rect",{x:"3",y:"5",width:"6",height:"6",rx:"1",key:"1defrl"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"M13 6h8",key:"15sg57"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 18h8",key:"oe0vm4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const mg=X("List",[["line",{x1:"8",x2:"21",y1:"6",y2:"6",key:"7ey8pc"}],["line",{x1:"8",x2:"21",y1:"12",y2:"12",key:"rjfblc"}],["line",{x1:"8",x2:"21",y1:"18",y2:"18",key:"c3b1m8"}],["line",{x1:"3",x2:"3.01",y1:"6",y2:"6",key:"1g7gq3"}],["line",{x1:"3",x2:"3.01",y1:"12",y2:"12",key:"1pjlvk"}],["line",{x1:"3",x2:"3.01",y1:"18",y2:"18",key:"28t2mc"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Fi=X("Loader2",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const xp=X("Lock",[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const vg=X("LogOut",[["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}],["polyline",{points:"16 17 21 12 16 7",key:"1gabdz"}],["line",{x1:"21",x2:"9",y1:"12",y2:"12",key:"1uyos4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const xg=X("Moon",[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ks=X("Play",[["polygon",{points:"5 3 19 12 5 21 5 3",key:"191637"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const yp=X("Plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const gp=X("Settings",[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",key:"1qme2f"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const yg=X("Share2",[["circle",{cx:"18",cy:"5",r:"3",key:"gq8acd"}],["circle",{cx:"6",cy:"12",r:"3",key:"w7nqdw"}],["circle",{cx:"18",cy:"19",r:"3",key:"1xt0gg"}],["line",{x1:"8.59",x2:"15.42",y1:"13.51",y2:"17.49",key:"47mynk"}],["line",{x1:"15.41",x2:"8.59",y1:"6.51",y2:"10.49",key:"1n3mei"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const gg=X("Square",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jg=X("Sun",[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jp=X("Trash2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const wp=X("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const wg=X("XCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ii=X("Zap",[["polygon",{points:"13 2 3 14 12 14 11 22 21 10 12 10 13 2",key:"45s27k"}]]),kg={},md=e=>{let t;const n=new Set,r=(p,f)=>{const h=typeof p=="function"?p(t):p;if(!Object.is(h,t)){const x=t;t=f??(typeof h!="object"||h===null)?h:Object.assign({},t,h),n.forEach(w=>w(t,x))}},s=()=>t,u={setState:r,getState:s,getInitialState:()=>c,subscribe:p=>(n.add(p),()=>n.delete(p)),destroy:()=>{(kg?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),n.clear()}},c=t=e(r,s,u);return u},Sg=e=>e?md(e):md;var kp={exports:{}},Sp={},Np={exports:{}},_p={};/** + * @license React + * use-sync-external-store-shim.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Wr=k;function Ng(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var _g=typeof Object.is=="function"?Object.is:Ng,Cg=Wr.useState,bg=Wr.useEffect,Eg=Wr.useLayoutEffect,Pg=Wr.useDebugValue;function Tg(e,t){var n=t(),r=Cg({inst:{value:n,getSnapshot:t}}),s=r[0].inst,a=r[1];return Eg(function(){s.value=n,s.getSnapshot=t,pl(s)&&a({inst:s})},[e,n,t]),bg(function(){return pl(s)&&a({inst:s}),e(function(){pl(s)&&a({inst:s})})},[e]),Pg(n),n}function pl(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!_g(e,n)}catch{return!0}}function Og(e,t){return t()}var Lg=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?Og:Tg;_p.useSyncExternalStore=Wr.useSyncExternalStore!==void 0?Wr.useSyncExternalStore:Lg;Np.exports=_p;var Rg=Np.exports;/** + * @license React + * use-sync-external-store-shim/with-selector.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Di=k,Mg=Rg;function zg(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var Fg=typeof Object.is=="function"?Object.is:zg,Ig=Mg.useSyncExternalStore,Dg=Di.useRef,Ag=Di.useEffect,$g=Di.useMemo,Ug=Di.useDebugValue;Sp.useSyncExternalStoreWithSelector=function(e,t,n,r,s){var a=Dg(null);if(a.current===null){var l={hasValue:!1,value:null};a.current=l}else l=a.current;a=$g(function(){function u(x){if(!c){if(c=!0,p=x,x=r(x),s!==void 0&&l.hasValue){var w=l.value;if(s(w,x))return f=w}return f=x}if(w=f,Fg(p,x))return w;var j=r(x);return s!==void 0&&s(w,j)?(p=x,w):(p=x,f=j)}var c=!1,p,f,h=n===void 0?null:n;return[function(){return u(t())},h===null?void 0:function(){return u(h())}]},[t,n,r,s]);var o=Ig(e,a[0],a[1]);return Ag(function(){l.hasValue=!0,l.value=o},[o]),Ug(o),o};kp.exports=Sp;var Bg=kp.exports;const Qg=Md(Bg),Cp={},{useDebugValue:Vg}=zo,{useSyncExternalStoreWithSelector:Hg}=Qg;let vd=!1;const Kg=e=>e;function Wg(e,t=Kg,n){(Cp?"production":void 0)!=="production"&&n&&!vd&&(console.warn("[DEPRECATED] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`. They can be imported from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937"),vd=!0);const r=Hg(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return Vg(r),r}const xd=e=>{(Cp?"production":void 0)!=="production"&&typeof e!="function"&&console.warn("[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.");const t=typeof e=="function"?Sg(e):e,n=(r,s)=>Wg(t,r,s);return Object.assign(n,t),n},Lu=e=>e?xd(e):xd,Ai="/api",Ru="flow_auth_token",Mu="flow_auth_expires",zu="flow_auth_username";function Ws(){const e=localStorage.getItem(Ru),t=localStorage.getItem(Mu);return!e||!t?null:new Date(t)<=new Date?(Gn(),null):e}function yd(e,t,n){localStorage.setItem(Ru,e),localStorage.setItem(Mu,t),localStorage.setItem(zu,n)}function Gn(){localStorage.removeItem(Ru),localStorage.removeItem(Mu),localStorage.removeItem(zu)}function Fu(){return localStorage.getItem(zu)}let sr=null;function qg(e){sr=e}async function V(e,t,n=!1){const r={"Content-Type":"application/json",...t==null?void 0:t.headers};if(!n){const a=Ws();a&&(r.Authorization=`Bearer ${a}`)}const s=await fetch(`${Ai}${e}`,{...t,headers:r});if(s.status===401){Gn(),sr&&sr();const a=await s.json().catch(()=>({detail:"Not authenticated"}));throw new Error(a.detail||"Not authenticated")}if(!s.ok){const a=await s.json().catch(()=>({detail:s.statusText}));throw new Error(a.detail||"API request failed")}if(s.status!==204)return s.json()}const or={getConfig:()=>V("/auth/config",void 0,!0),login:e=>V("/auth/login",{method:"POST",body:JSON.stringify(e)},!0),getGitHubAuthUrl:()=>`${Ai}/auth/github`,getCurrentUser:()=>V("/auth/me"),logout:()=>V("/auth/logout",{method:"POST"})},Jn={list:e=>{const t=new URLSearchParams;e!=null&&e.include_auto_generated&&t.set("include_auto_generated","true"),e!=null&&e.include_public&&t.set("include_public","true");const n=t.toString();return V(`/configs${n?`?${n}`:""}`)},get:e=>V(`/configs/${e}`),create:e=>V("/configs",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>V(`/configs/${e}`,{method:"PUT",body:JSON.stringify(t)}),delete:e=>V(`/configs/${e}`,{method:"DELETE"}),generateCandidates:e=>V("/configs/generate-candidates",{method:"POST",body:JSON.stringify(e)})},fn={list:e=>{const t=new URLSearchParams;e!=null&&e.category&&t.set("category",e.category),e!=null&&e.suite&&t.set("suite",e.suite);const n=t.toString();return V(`/tasks${n?`?${n}`:""}`)},get:e=>V(`/tasks/${e}`),create:e=>V("/tasks",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>V(`/tasks/${e}`,{method:"PUT",body:JSON.stringify(t)}),delete:e=>V(`/tasks/${e}`,{method:"DELETE"}),listSuites:()=>V("/tasks/suites"),importSuite:e=>V(`/tasks/import-suite?suite_name=${encodeURIComponent(e)}`,{method:"POST"})},bt={list:e=>{const t=new URLSearchParams;e!=null&&e.status&&t.set("status",e.status),e!=null&&e.include_public&&t.set("include_public","true");const n=t.toString();return V(`/jobs${n?`?${n}`:""}`)},get:e=>V(`/jobs/${e}`),create:e=>V("/jobs",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>V(`/jobs/${e}`,{method:"PUT",body:JSON.stringify(t)}),start:async function*(e){var o;const t={},n=Ws();n&&(t.Authorization=`Bearer ${n}`);const r=await fetch(`${Ai}/jobs/${e}/start`,{method:"POST",headers:t});if(r.status===401)throw Gn(),sr&&sr(),new Error("Not authenticated");if(!r.ok)throw new Error("Failed to start job");const s=(o=r.body)==null?void 0:o.getReader();if(!s)throw new Error("No response body");const a=new TextDecoder;let l="";for(;;){const{done:u,value:c}=await s.read();if(u)break;l+=a.decode(c,{stream:!0});const p=l.split(` +`);l=p.pop()||"";for(const f of p)f.startsWith("data: ")&&(yield JSON.parse(f.slice(6)))}},cancel:e=>V(`/jobs/${e}/cancel`,{method:"POST"}),delete:e=>V(`/jobs/${e}`,{method:"DELETE"})},Po={list:e=>{const t=new URLSearchParams;e!=null&&e.job_id&&t.set("job_id",e.job_id),e!=null&&e.candidate_name&&t.set("candidate_name",e.candidate_name),e!=null&&e.task_name&&t.set("task_name",e.task_name),(e==null?void 0:e.is_pareto)!==void 0&&t.set("is_pareto",String(e.is_pareto));const n=t.toString();return V(`/runs${n?`?${n}`:""}`)},get:e=>V(`/runs/${e}`),getJobSummary:e=>V(`/runs/job/${e}/summary`)},Ns={list:e=>{const t=new URLSearchParams;e!=null&&e.agent_id&&t.set("agent_id",e.agent_id),e!=null&&e.limit&&t.set("limit",String(e.limit));const n=t.toString();return V(`/tests${n?`?${n}`:""}`)},get:e=>V(`/tests/${e}`),create:e=>V("/tests",{method:"POST",body:JSON.stringify(e)}),start:async function*(e){var o;const t={},n=Ws();n&&(t.Authorization=`Bearer ${n}`);const r=await fetch(`${Ai}/tests/${e}/start`,{method:"POST",headers:t});if(r.status===401)throw Gn(),sr&&sr(),new Error("Not authenticated");if(!r.ok){const u=await r.json().catch(()=>({detail:"Failed to start test"}));throw new Error(u.detail||"Failed to start test")}const s=(o=r.body)==null?void 0:o.getReader();if(!s)throw new Error("No response body");const a=new TextDecoder;let l="";for(;;){const{done:u,value:c}=await s.read();if(u)break;l+=a.decode(c,{stream:!0});const p=l.split(` +`);l=p.pop()||"";for(const f of p)f.startsWith("data: ")&&(yield JSON.parse(f.slice(6)))}},cancel:e=>V(`/tests/${e}/cancel`,{method:"POST"}),delete:e=>V(`/tests/${e}`,{method:"DELETE"})},Gg={list:()=>V("/llm-configs"),get:e=>V(`/llm-configs/${e}`),getDefault:()=>V("/llm-configs/default"),create:e=>V("/llm-configs",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>V(`/llm-configs/${e}`,{method:"PUT",body:JSON.stringify(t)}),delete:e=>V(`/llm-configs/${e}`,{method:"DELETE"}),setDefault:e=>V(`/llm-configs/${e}/set-default`,{method:"POST"}),test:e=>V(`/llm-configs/${e}/test`,{method:"POST"})},Iu=Lu((e,t)=>(qg(()=>{e({isAuthenticated:!1,user:null,error:"Session expired. Please log in again."})}),{authConfig:null,isLoadingConfig:!0,isAuthenticated:!1,isLoading:!1,user:null,error:null,loadAuthConfig:async()=>{e({isLoadingConfig:!0});try{const n=await or.getConfig();if(e({authConfig:n,isLoadingConfig:!1}),n.enabled){const r=Ws(),s=Fu();if(r&&s)try{const a=await or.getCurrentUser();e({isAuthenticated:!0,user:a})}catch{Gn(),e({isAuthenticated:!1,user:null})}}else e({isAuthenticated:!0,user:{username:"anonymous",auth_mode:"none"}})}catch(n){console.error("Failed to load auth config:",n),e({isLoadingConfig:!1,error:"Failed to connect to server"})}},login:async(n,r)=>{e({isLoading:!0,error:null});try{const s=await or.login({username:n,password:r});return yd(s.access_token,s.expires_at,s.username),e({isAuthenticated:!0,isLoading:!1,user:{username:s.username,auth_mode:"basic"}}),!0}catch(s){return e({isLoading:!1,error:s instanceof Error?s.message:"Login failed"}),!1}},loginWithGitHub:()=>{window.location.href=or.getGitHubAuthUrl()},handleOAuthCallback:()=>{const n=new URLSearchParams(window.location.search),r=n.get("auth_error");if(r)return e({error:r}),window.history.replaceState({},"",window.location.pathname),!0;if(n.get("auth_callback")==="true"){const s=n.get("token"),a=n.get("expires_at"),l=n.get("username");return s&&a&&l&&(yd(s,a,l),e({isAuthenticated:!0,user:{username:l,auth_mode:"github"}})),window.history.replaceState({},"",window.location.pathname),!0}return!1},logout:async()=>{try{await or.logout()}catch{}Gn(),e({isAuthenticated:!1,user:null,error:null})},checkAuth:async()=>{const{authConfig:n}=t();if(!(n!=null&&n.enabled)){e({isAuthenticated:!0});return}if(!Ws()){e({isAuthenticated:!1,user:null});return}try{const s=await or.getCurrentUser();e({isAuthenticated:!0,user:s})}catch{Gn(),e({isAuthenticated:!1,user:null})}},clearError:()=>e({error:null})}));function K({variant:e="secondary",size:t="md",className:n="",icon:r,iconRight:s,loading:a=!1,children:l,disabled:o,...u}){const c="font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1.5",p={primary:"bg-[var(--accent)] text-black hover:bg-[#16a34a]",secondary:"bg-[var(--bg-tertiary)] text-[var(--text-primary)] border border-[var(--border)] hover:bg-[var(--border)]",danger:"bg-[var(--error)] text-white hover:bg-red-600",ghost:"text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"},f={sm:"px-2 py-1 text-xs",md:"px-3 py-1.5 text-sm"},h=t==="sm"?14:16;return i.jsxs("button",{className:`${c} ${p[e]} ${f[t]} ${n}`,disabled:o||a,...u,children:[a?i.jsx(Fi,{size:h,className:"animate-spin"}):r?i.jsx(r,{size:h}):null,l,s&&!a&&i.jsx(s,{size:h})]})}const Jg={};function Xg(e,t){let n;try{n=e()}catch{return}return{getItem:s=>{var a;const l=u=>u===null?null:JSON.parse(u,void 0),o=(a=n.getItem(s))!=null?a:null;return o instanceof Promise?o.then(l):l(o)},setItem:(s,a)=>n.setItem(s,JSON.stringify(a,void 0)),removeItem:s=>n.removeItem(s)}}const qs=e=>t=>{try{const n=e(t);return n instanceof Promise?n:{then(r){return qs(r)(n)},catch(r){return this}}}catch(n){return{then(r){return this},catch(r){return qs(r)(n)}}}},Yg=(e,t)=>(n,r,s)=>{let a={getStorage:()=>localStorage,serialize:JSON.stringify,deserialize:JSON.parse,partialize:S=>S,version:0,merge:(S,v)=>({...v,...S}),...t},l=!1;const o=new Set,u=new Set;let c;try{c=a.getStorage()}catch{}if(!c)return e((...S)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...S)},r,s);const p=qs(a.serialize),f=()=>{const S=a.partialize({...r()});let v;const d=p({state:S,version:a.version}).then(m=>c.setItem(a.name,m)).catch(m=>{v=m});if(v)throw v;return d},h=s.setState;s.setState=(S,v)=>{h(S,v),f()};const x=e((...S)=>{n(...S),f()},r,s);let w;const j=()=>{var S;if(!c)return;l=!1,o.forEach(d=>d(r()));const v=((S=a.onRehydrateStorage)==null?void 0:S.call(a,r()))||void 0;return qs(c.getItem.bind(c))(a.name).then(d=>{if(d)return a.deserialize(d)}).then(d=>{if(d)if(typeof d.version=="number"&&d.version!==a.version){if(a.migrate)return a.migrate(d.state,d.version);console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return d.state}).then(d=>{var m;return w=a.merge(d,(m=r())!=null?m:x),n(w,!0),f()}).then(()=>{v==null||v(w,void 0),l=!0,u.forEach(d=>d(w))}).catch(d=>{v==null||v(void 0,d)})};return s.persist={setOptions:S=>{a={...a,...S},S.getStorage&&(c=S.getStorage())},clearStorage:()=>{c==null||c.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>j(),hasHydrated:()=>l,onHydrate:S=>(o.add(S),()=>{o.delete(S)}),onFinishHydration:S=>(u.add(S),()=>{u.delete(S)})},j(),w||x},Zg=(e,t)=>(n,r,s)=>{let a={storage:Xg(()=>localStorage),partialize:j=>j,version:0,merge:(j,S)=>({...S,...j}),...t},l=!1;const o=new Set,u=new Set;let c=a.storage;if(!c)return e((...j)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...j)},r,s);const p=()=>{const j=a.partialize({...r()});return c.setItem(a.name,{state:j,version:a.version})},f=s.setState;s.setState=(j,S)=>{f(j,S),p()};const h=e((...j)=>{n(...j),p()},r,s);s.getInitialState=()=>h;let x;const w=()=>{var j,S;if(!c)return;l=!1,o.forEach(d=>{var m;return d((m=r())!=null?m:h)});const v=((S=a.onRehydrateStorage)==null?void 0:S.call(a,(j=r())!=null?j:h))||void 0;return qs(c.getItem.bind(c))(a.name).then(d=>{if(d)if(typeof d.version=="number"&&d.version!==a.version){if(a.migrate)return[!0,a.migrate(d.state,d.version)];console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return[!1,d.state];return[!1,void 0]}).then(d=>{var m;const[g,b]=d;if(x=a.merge(b,(m=r())!=null?m:h),n(x,!0),g)return p()}).then(()=>{v==null||v(x,void 0),x=r(),l=!0,u.forEach(d=>d(x))}).catch(d=>{v==null||v(void 0,d)})};return s.persist={setOptions:j=>{a={...a,...j},j.storage&&(c=j.storage)},clearStorage:()=>{c==null||c.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>w(),hasHydrated:()=>l,onHydrate:j=>(o.add(j),()=>{o.delete(j)}),onFinishHydration:j=>(u.add(j),()=>{u.delete(j)})},a.skipHydration||w(),x||h},e0=(e,t)=>"getStorage"in t||"serialize"in t||"deserialize"in t?((Jg?"production":void 0)!=="production"&&console.warn("[DEPRECATED] `getStorage`, `serialize` and `deserialize` options are deprecated. Use `storage` option instead."),Yg(e,t)):Zg(e,t),t0=e0,n0=Lu()(t0((e,t)=>({theme:"dark",setTheme:n=>{document.documentElement.setAttribute("data-theme",n),e({theme:n})},toggleTheme:()=>{const n=t().theme==="dark"?"light":"dark";document.documentElement.setAttribute("data-theme",n),e({theme:n})}}),{name:"flow-theme",onRehydrateStorage:()=>e=>{e!=null&&e.theme&&document.documentElement.setAttribute("data-theme",e.theme)}}));function r0(){const{theme:e,toggleTheme:t}=n0();return i.jsx("button",{onClick:t,className:"p-2 rounded-md text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--accent)]","aria-label":`Switch to ${e==="dark"?"light":"dark"} mode`,title:`Switch to ${e==="dark"?"light":"dark"} mode`,children:e==="dark"?i.jsx(jg,{size:16}):i.jsx(xg,{size:16})})}const s0=[{path:"/agents",label:"Agents",icon:ng},{path:"/tasks",label:"Tasks",icon:pg},{path:"/jobs",label:"Jobs",icon:Ks}];function a0(){const e=Zr(),{authConfig:t,user:n,logout:r}=Iu(),s=l=>l==="/agents"?e.pathname==="/"||e.pathname==="/agents":e.pathname.startsWith(l),a=async()=>{await r()};return i.jsxs("div",{className:"min-h-screen flex flex-col",children:[i.jsx("header",{className:"border-b border-[var(--border)] bg-[var(--bg-secondary)]",children:i.jsxs("div",{className:"max-w-7xl mx-auto px-4 py-3 flex items-center justify-between",children:[i.jsxs("div",{className:"flex items-center gap-8",children:[i.jsxs(hd,{to:"/",className:"text-lg font-bold text-[var(--accent)] flex items-center gap-2 hover:opacity-80",children:[i.jsx(Ii,{size:20}),"flow",i.jsx("span",{className:"text-[var(--text-secondary)]",children:"/optimize"})]}),i.jsx("nav",{className:"flex gap-1",children:s0.map(l=>i.jsxs(hd,{to:l.path,className:`px-3 py-1.5 rounded text-sm transition-colors flex items-center gap-2 ${s(l.path)?"bg-[var(--accent)] text-black font-medium":"text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"}`,children:[i.jsx(l.icon,{size:16}),l.label]},l.path))})]}),i.jsxs("div",{className:"flex items-center gap-4",children:[i.jsx(r0,{}),(t==null?void 0:t.enabled)&&n&&i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx(wp,{size:14}),i.jsx("span",{children:n.username})]}),i.jsx(K,{variant:"ghost",size:"sm",icon:vg,onClick:a,title:"Sign out",children:"Sign out"})]})]})]})}),i.jsx("main",{className:"flex-1 bg-[var(--bg-primary)]",children:i.jsx("div",{className:"max-w-7xl mx-auto p-4",children:i.jsx(zy,{})})})]})}const bp=["standard","minimal","full","readonly"],i0=["maf","miniagent","langgraph"],l0={maf:"Microsoft Agent Framework",miniagent:"MiniAgent (correct compaction)",langgraph:"LangGraph"},o0=["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory","sub_agent"],u0={full:["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory","sub_agent"],standard:["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory"],minimal:["read_file","write_file","bash_execute","task_done"],readonly:["read_file","list_directory","grep_search","think","task_done"]},c0={openai:"OpenAI",azure_openai:"Azure OpenAI",anthropic:"Anthropic",ollama:"Ollama",custom:"Custom (OpenAI-compatible)"};function ee({children:e,className:t="",onClick:n,selected:r=!1,selectable:s=!1}){const a="bg-[var(--bg-secondary)] border border-[var(--border)] p-4",l=s?"cursor-pointer hover:border-[var(--accent-dim)] transition-colors":"",o=r?"border-[var(--accent)]":"";return i.jsx("div",{className:`${a} ${l} ${o} ${t}`,onClick:n,children:e})}function q({children:e,variant:t="default"}){const n={default:"bg-[var(--bg-tertiary)] text-[var(--text-primary)] border border-[var(--border)]",success:"bg-green-600 text-white",warning:"bg-yellow-500 text-black",error:"bg-red-600 text-white",info:"bg-blue-600 text-white"};return i.jsx("span",{className:`inline-block px-2 py-0.5 text-xs font-medium rounded ${n[t]}`,children:e})}const d0={pending:"default",running:"info",completed:"success",failed:"error",cancelled:"warning"};function Ep({job:e,onDelete:t}){const n=Et(),r=e.total_experiments>0?e.completed_experiments/e.total_experiments*100:0;return i.jsxs(ee,{className:"cursor-pointer hover:border-[var(--accent-dim)] flex flex-col",onClick:()=>n(`/jobs/${e.id}`),children:[i.jsxs("div",{className:"flex items-start justify-between mb-3",children:[i.jsxs("div",{className:"flex-1 min-w-0",children:[i.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[i.jsx(q,{variant:d0[e.status]||"default",children:e.status}),e.is_public&&i.jsxs(q,{variant:"info",children:[i.jsx(zi,{className:"w-3 h-3 mr-1 inline"}),"Public"]}),e.pareto_frontier.length>0&&i.jsxs(q,{variant:"success",children:[e.pareto_frontier.length," Pareto"]}),e.use_llm_eval&&i.jsx(q,{children:"LLM"})]}),i.jsx("h3",{className:"font-medium mt-2 truncate",title:e.name||`Job ${e.id.slice(0,8)}`,children:e.name||`Job ${e.id.slice(0,8)}`}),i.jsxs("code",{className:"text-xs text-[var(--text-secondary)] font-mono",children:[e.id.slice(0,8),"..."]})]}),t&&i.jsx(K,{variant:"ghost",size:"sm",onClick:s=>{s.stopPropagation(),confirm("Delete this job?")&&t(e.id)},disabled:e.status==="running",children:"×"})]}),(e.status==="running"||e.status==="completed")&&i.jsxs("div",{className:"mb-3",children:[i.jsxs("div",{className:"flex justify-between text-xs text-[var(--text-secondary)] mb-1",children:[i.jsx("span",{children:"Progress"}),i.jsxs("span",{children:[e.completed_experiments,"/",e.total_experiments]})]}),i.jsx("div",{className:"w-full bg-[var(--bg-primary)] h-1.5 rounded-full overflow-hidden",children:i.jsx("div",{className:`h-full transition-all ${e.status==="completed"?"bg-green-500":"bg-[var(--accent)]"}`,style:{width:`${r}%`}})})]}),e.status==="failed"&&e.error&&i.jsx("div",{className:"mb-3 px-2 py-1.5 bg-red-500/10 border border-red-500/30 rounded text-xs text-red-400 line-clamp-2",children:e.error}),i.jsxs("div",{className:"grid grid-cols-3 gap-2 text-center py-2 border-t border-[var(--border)] mt-auto",children:[i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.candidate_ids.length}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"candidates"})]}),i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.task_ids.length}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"tasks"})]}),i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.total_experiments}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"runs"})]})]}),i.jsxs("div",{className:"text-xs text-[var(--text-secondary)] pt-2 border-t border-[var(--border)] flex justify-between items-center",children:[i.jsxs("span",{children:[new Date(e.created_at).toLocaleDateString()," ",new Date(e.created_at).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})]}),e.is_public&&e.created_by_name&&i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["by ",e.created_by_name]})]})]})}function ia({isOpen:e,onClose:t,title:n,children:r}){return k.useEffect(()=>{const s=a=>{a.key==="Escape"&&t()};return e&&(document.addEventListener("keydown",s),document.body.style.overflow="hidden"),()=>{document.removeEventListener("keydown",s),document.body.style.overflow=""}},[e,t]),e?i.jsxs("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[i.jsx("div",{className:"absolute inset-0 bg-black/80",onClick:t}),i.jsxs("div",{className:"relative bg-[var(--bg-secondary)] border border-[var(--border)] max-w-lg w-full mx-4 max-h-[80vh] overflow-y-auto",children:[i.jsxs("div",{className:"sticky top-0 bg-[var(--bg-secondary)] border-b border-[var(--border)] px-4 py-3 flex items-center justify-between",children:[i.jsx("h2",{className:"font-semibold",children:n}),i.jsx("button",{onClick:t,className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"×"})]}),i.jsx("div",{className:"p-4",children:r})]})]}):null}function pt({label:e,className:t="",...n}){return i.jsxs("div",{className:"space-y-1",children:[e&&i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:e}),i.jsx("input",{className:`w-full bg-[var(--bg-primary)] border border-[var(--border)] px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)] ${t}`,...n})]})}function f0({label:e,className:t="",...n}){return i.jsxs("div",{className:"space-y-1",children:[e&&i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:e}),i.jsx("textarea",{className:`w-full bg-[var(--bg-primary)] border border-[var(--border)] px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)] resize-y min-h-[100px] ${t}`,...n})]})}function Rn({label:e,className:t="",...n}){return i.jsxs("label",{className:`flex items-center gap-2 cursor-pointer ${t}`,children:[i.jsx("input",{type:"checkbox",className:"w-4 h-4 bg-[var(--bg-primary)] border border-[var(--border)] accent-[var(--accent)]",...n}),i.jsx("span",{className:"text-sm",children:e})]})}function gd(){const e=Et(),t=Pn(),[n,r]=k.useState(!1),[s,a]=k.useState(null),{data:l=[],isLoading:o}=_e({queryKey:["configs"],queryFn:()=>Jn.list()}),{data:u=[]}=_e({queryKey:["jobs"],queryFn:()=>bt.list()}),c=st({mutationFn:Jn.create,onSuccess:()=>{t.invalidateQueries({queryKey:["configs"]}),r(!1)}}),p=st({mutationFn:Jn.delete,onSuccess:()=>t.invalidateQueries({queryKey:["configs"]})}),f=h=>{const x=u.filter(S=>S.candidate_ids.includes(h)),w=x.filter(S=>S.status==="running").length,j=x.filter(S=>S.status==="completed").length;return{running:w,completed:j,total:x.length}};return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Agents"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:"Define and optimize your agent configurations."})]}),i.jsx(K,{variant:"primary",icon:yp,onClick:()=>r(!0),children:"New Agent"})]}),o?i.jsxs("div",{className:"flex items-center gap-2 text-[var(--text-secondary)]",children:[i.jsx(Fi,{size:16,className:"animate-spin"}),"Loading agents..."]}):l.length===0?i.jsx(h0,{onCreateClick:()=>r(!0)}):i.jsx("div",{className:"grid gap-4 md:grid-cols-2 lg:grid-cols-3",children:l.map(h=>{const x=f(h.id);return i.jsx(m0,{agent:h,stats:x,onClick:()=>e(`/agents/${h.id}`),onOptimize:()=>a(h),onDelete:()=>{confirm(`Delete agent "${h.name}"?`)&&p.mutate(h.id)}},h.id)})}),u.length>0&&i.jsxs("div",{className:"mt-8",children:[i.jsx("h3",{className:"text-lg font-medium mb-4",children:"Recent Optimization Jobs"}),i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4",children:u.slice(0,6).map(h=>i.jsx(Ep,{job:h},h.id))}),u.length>6&&i.jsxs(K,{variant:"ghost",className:"mt-4",onClick:()=>e("/jobs"),children:["View all ",u.length," jobs →"]})]}),i.jsx(v0,{isOpen:n,onClose:()=>r(!1),onSubmit:h=>c.mutate(h),isLoading:c.isPending}),s&&i.jsx(x0,{agent:s,isOpen:!!s,onClose:()=>a(null)})]})}function h0({onCreateClick:e}){return i.jsxs("div",{className:"text-center py-16 border border-dashed border-[var(--border)] rounded-lg",children:[i.jsx("div",{className:"inline-flex items-center justify-center w-12 h-12 rounded-full bg-[var(--bg-tertiary)] mb-4",children:i.jsx(gp,{size:24,className:"text-[var(--text-secondary)]"})}),i.jsx("h3",{className:"text-lg font-medium mb-2",children:"No agents yet"}),i.jsx("p",{className:"text-[var(--text-secondary)] mb-4 max-w-md mx-auto",children:"Create your first agent to start optimizing. Each agent defines instructions, model, compaction strategy, and tool settings."}),i.jsx(K,{variant:"primary",icon:yp,onClick:e,children:"Create Your First Agent"})]})}function p0(e){return typeof e=="string"?`tools: ${e}`:Array.isArray(e)?`tools: [${e.length}]`:typeof e=="object"?`tools: [${Object.keys(e).length}]`:"tools: standard"}function m0({agent:e,stats:t,onClick:n,onOptimize:r,onDelete:s}){const a=e.config.compaction,l=(a==null?void 0:a.strategy)==="head_tail"?`compaction ${a.params.head_size}/${a.params.tail_size}`:(a==null?void 0:a.strategy)==="none"?null:(a==null?void 0:a.strategy)||null,o=p0(e.config.tools),u=e.config.framework||"maf";return i.jsxs(ee,{className:"flex flex-col cursor-pointer hover:border-[var(--accent-dim)] transition-colors",onClick:n,children:[i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-start justify-between mb-3",children:[i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium text-lg",children:e.name}),e.description&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:e.description})]}),i.jsx(K,{variant:"ghost",size:"sm",icon:jp,onClick:c=>{c.stopPropagation(),s()}})]}),i.jsxs("div",{className:"flex flex-wrap gap-1.5 mb-4",children:[i.jsx(q,{variant:u==="miniagent"?"success":"default",children:u}),l&&i.jsx(q,{children:l}),i.jsx(q,{children:o})]}),t.total>0&&i.jsxs("div",{className:"text-xs text-[var(--text-secondary)] mb-3",children:[t.running>0&&i.jsxs("span",{className:"text-[var(--accent)]",children:[t.running," running "]}),t.completed>0&&i.jsxs("span",{children:[t.completed," completed"]})]})]}),i.jsx("div",{className:"flex gap-2",children:i.jsx(K,{variant:"primary",icon:Ii,onClick:c=>{c.stopPropagation(),r()},className:"flex-1",children:"Optimize"})})]})}function v0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){var b,_,C;const s=["read_file","write_file","bash_execute","grep_search","task_done"],{data:a=[]}=_e({queryKey:["llm-configs"],queryFn:()=>Gg.list()}),[l,o]=k.useState({name:"",description:"",instructions:null,model:null,compaction:{strategy:"none",params:{}},tools:s,llm_config_id:null,framework:"maf"}),[u,c]=k.useState(!1),[p,f]=k.useState("custom"),[h,x]=k.useState([...s]),[w,j]=k.useState(!1),S=N=>{if(N.preventDefault(),!l.name.trim())return;const M={...l};p==="custom"&&(M.tools=h),n(M)},v=((b=l.compaction)==null?void 0:b.strategy)!=="none",d=N=>{x(M=>M.includes(N)?M.filter(P=>P!==N):[...M,N])},m=a.find(N=>N.id===l.llm_config_id),g=a.find(N=>N.is_default);return i.jsx(ia,{isOpen:e,onClose:t,title:"Create Agent",children:i.jsxs("form",{onSubmit:S,className:"space-y-4",children:[i.jsx(pt,{label:"Name",value:l.name,onChange:N=>o({...l,name:N.target.value}),placeholder:"e.g., my-coding-agent",required:!0}),i.jsxs("div",{children:[i.jsx("label",{className:"text-sm font-medium block mb-1.5",children:"LLM Configuration"}),a.length===0?i.jsxs("div",{className:"p-3 border border-[var(--border)] rounded bg-[var(--bg-secondary)] text-sm",children:[i.jsx("p",{className:"text-[var(--text-secondary)] mb-2",children:"No LLM configurations found. Create one in Settings first."}),i.jsx("a",{href:"/settings",className:"text-[var(--accent)] hover:underline",children:"Go to Settings →"})]}):i.jsxs(i.Fragment,{children:[i.jsxs("select",{className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",value:l.llm_config_id||"",onChange:N=>o({...l,llm_config_id:N.target.value||null}),children:[i.jsx("option",{value:"",children:g?`Use default (${g.name})`:"Select LLM config..."}),a.map(N=>i.jsxs("option",{value:N.id,children:[N.name," (",c0[N.provider],")",N.is_default?" ★":""]},N.id))]}),m&&i.jsxs("p",{className:"text-xs text-[var(--text-secondary)] mt-1",children:["Model: ",m.model_id||"default"]})]})]}),i.jsxs("div",{children:[i.jsx("label",{className:"text-sm font-medium block mb-1.5",children:"Framework"}),i.jsx("select",{className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",value:l.framework||"maf",onChange:N=>o({...l,framework:N.target.value}),children:i0.map(N=>i.jsx("option",{value:N,children:l0[N]},N))}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-1",children:l.framework==="miniagent"?"MiniAgent applies context compaction correctly before each LLM call.":l.framework==="langgraph"?"LangGraph-based agent with graph workflow support.":"Microsoft Agent Framework - default implementation."})]}),i.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[i.jsx(Rn,{label:"Custom instructions",checked:u,onChange:N=>{c(N.target.checked),N.target.checked||o({...l,instructions:null})}}),i.jsx(Rn,{label:"Enable compaction",checked:v,onChange:N=>o({...l,compaction:N.target.checked?{strategy:"head_tail",params:{head_size:10,tail_size:40}}:{strategy:"none",params:{}}})})]}),u&&i.jsx("textarea",{className:"w-full h-32 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono resize-y",value:l.instructions||"",onChange:N=>o({...l,instructions:N.target.value||null}),placeholder:"System prompt / instructions for the agent..."}),v&&i.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[i.jsx(pt,{label:"Head size",type:"number",value:((_=l.compaction)==null?void 0:_.params.head_size)??10,onChange:N=>o({...l,compaction:{strategy:"head_tail",params:{...l.compaction.params,head_size:parseInt(N.target.value)||10}}}),min:1}),i.jsx(pt,{label:"Tail size",type:"number",value:((C=l.compaction)==null?void 0:C.params.tail_size)??40,onChange:N=>o({...l,compaction:{strategy:"head_tail",params:{...l.compaction.params,tail_size:parseInt(N.target.value)||40}}}),min:1})]}),i.jsxs("div",{className:"space-y-2",children:[i.jsx("label",{className:"text-sm font-medium",children:"Tools"}),i.jsxs("div",{className:"flex gap-4",children:[i.jsxs("label",{className:"flex items-center gap-2",children:[i.jsx("input",{type:"radio",checked:p==="custom",onChange:()=>f("custom"),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:"Custom"})]}),i.jsxs("label",{className:"flex items-center gap-2",children:[i.jsx("input",{type:"radio",checked:p==="preset",onChange:()=>f("preset"),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:"Preset"})]})]}),p==="custom"?i.jsx("div",{className:"grid grid-cols-2 gap-1 p-2 border border-[var(--border)] rounded bg-[var(--bg-secondary)]",children:o0.map(N=>i.jsxs("label",{className:"flex items-center gap-2 p-1 text-sm cursor-pointer hover:bg-[var(--bg-tertiary)]",children:[i.jsx("input",{type:"checkbox",checked:h.includes(N),onChange:()=>d(N),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"font-mono text-xs",children:N})]},N))}):i.jsxs(i.Fragment,{children:[i.jsx("select",{className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",value:typeof l.tools=="string"?l.tools:"standard",onChange:N=>o({...l,tools:N.target.value}),children:bp.map(N=>i.jsx("option",{value:N,children:N},N))}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-1",children:u0[typeof l.tools=="string"?l.tools:"standard"].join(", ")})]})]}),i.jsxs("div",{className:"border-t border-[var(--border)] pt-3",children:[i.jsxs("button",{type:"button",className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors",onClick:()=>j(!w),children:[i.jsx(Ss,{size:14,className:`transition-transform ${w?"rotate-90":""}`}),"More options"]}),w&&i.jsx("div",{className:"mt-3",children:i.jsx(pt,{label:"Description (optional)",value:l.description,onChange:N=>o({...l,description:N.target.value}),placeholder:"Brief description of this agent"})})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(K,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(K,{type:"submit",variant:"primary",disabled:!l.name.trim(),loading:r,children:"Create Agent"})]})]})})}function x0({agent:e,isOpen:t,onClose:n}){var O,D,B;const r=Et(),s=Pn(),[a,l]=k.useState("suite"),[o,u]=k.useState("quick"),[c,p]=k.useState(!1),[f,h]=k.useState([]),[x,w]=k.useState({base_name:e.name,vary_compaction:!0,vary_tools:!0,vary_compaction_head:!1,vary_compaction_tail:!1,tool_presets:["standard","minimal"],compaction_head_values:[5,10,20],compaction_tail_values:[20,40,60]}),[j,S]=k.useState(4),[v,d]=k.useState(!1),[m,g]=k.useState(null),{data:b=[]}=_e({queryKey:["tasks"],queryFn:()=>fn.list()}),_=st({mutationFn:fn.importSuite,onSuccess:L=>{s.invalidateQueries({queryKey:["tasks"]}),h(L.map(A=>A.id))}}),C=st({mutationFn:Jn.generateCandidates,onSuccess:()=>{s.invalidateQueries({queryKey:["configs"]})}}),N=st({mutationFn:async L=>{const A=await bt.create(L);return bt.start(A.id).next(),A},onSuccess:L=>{s.invalidateQueries({queryKey:["jobs"]}),g(L.id),l("success")}}),M=[{value:"quick",label:"Quick",description:"3 fast tasks for rapid testing",tasks:3},{value:"core",label:"Core",description:"5 standard evaluation tasks",tasks:5},{value:"coding",label:"Coding",description:"10 comprehensive coding tasks",tasks:10}],P=()=>{var A,T,$;let L=1;return x.vary_compaction&&(L*=2),x.vary_tools&&(L*=((A=x.tool_presets)==null?void 0:A.length)||3),x.vary_compaction_head&&(L*=((T=x.compaction_head_values)==null?void 0:T.length)||3),x.vary_compaction_tail&&(L*=(($=x.compaction_tail_values)==null?void 0:$.length)||3),L},H=x.vary_compaction||x.vary_tools||x.vary_compaction_head||x.vary_compaction_tail,F=async()=>{l("starting");let L=f,A=[e.id];if(!c)try{L=(await _.mutateAsync(o)).map(ye=>ye.id)}catch(fe){console.error("Failed to import suite:",fe),alert(`Failed to import task suite: ${o}`),l("candidates");return}if(L.length===0){alert("No tasks selected. Please select tasks or choose a task suite."),l("candidates");return}try{A=(await C.mutateAsync({...x,base_name:e.name})).map(ye=>ye.id)}catch(fe){console.error("Failed to generate candidates:",fe),alert("Failed to generate candidates"),l("candidates");return}const T=P(),$={name:`${e.name} optimization (${T} candidates × ${L.length} tasks)`,candidate_ids:A,task_ids:L,parallel:j,use_llm_eval:v};N.mutate($)},U=L=>{h(A=>A.includes(L)?A.filter(T=>T!==L):[...A,L])},te=()=>{l("suite"),g(null),n()},ke=P(),Pt=c?f.length:((O=M.find(L=>L.value===o))==null?void 0:O.tasks)||3,He=ke*Pt;return i.jsx(ia,{isOpen:t,onClose:te,title:`Optimize: ${e.name}`,children:a==="success"&&m?i.jsxs("div",{className:"flex flex-col items-center py-8",children:[i.jsx("div",{className:"w-12 h-12 rounded-full bg-green-500/20 flex items-center justify-center mb-4",children:i.jsx(Ii,{size:24,className:"text-green-500"})}),i.jsx("h3",{className:"text-lg font-medium mb-2",children:"Job Started!"}),i.jsx("p",{className:"text-[var(--text-secondary)] text-center mb-2",children:"Optimization job is now running"}),i.jsxs("code",{className:"text-xs bg-[var(--bg-primary)] px-3 py-1.5 rounded font-mono mb-6",children:["ID: ",m.slice(0,8),"..."]}),i.jsxs("div",{className:"flex gap-3",children:[i.jsx(K,{variant:"secondary",onClick:te,children:"Close"}),i.jsx(K,{variant:"primary",icon:Ks,onClick:()=>{te(),r(`/jobs/${m}`)},children:"View Job"})]})]}):a==="starting"?i.jsxs("div",{className:"flex flex-col items-center py-8",children:[i.jsx(Fi,{size:32,className:"animate-spin text-[var(--accent)] mb-4"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:C.isPending?"Generating candidates...":_.isPending?"Importing tasks...":"Creating optimization job..."})]}):a==="candidates"?i.jsxs("div",{className:"space-y-6",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx("span",{className:"text-[var(--text-primary)]",children:"1. Tasks"}),i.jsx(Ss,{size:14}),i.jsx("span",{className:"text-[var(--accent)] font-medium",children:"2. Candidates"})]}),i.jsxs("div",{className:"space-y-4 p-4 border border-[var(--border)] bg-[var(--bg-secondary)]",children:[i.jsxs("div",{className:"flex items-center justify-between",children:[i.jsx("h4",{className:"font-medium text-sm",children:"Select dimensions to explore:"}),i.jsxs(q,{variant:"info",children:[ke," candidate",ke!==1?"s":""]})]}),!H&&i.jsx("p",{className:"text-xs text-[var(--text-secondary)]",children:"Select at least one dimension to generate candidate variations. Without variations, only the baseline agent will be tested."}),i.jsxs("div",{className:"space-y-2",children:[i.jsx(Rn,{label:"Compaction (on/off)",checked:x.vary_compaction,onChange:L=>w({...x,vary_compaction:L.target.checked})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6 -mt-1",children:"Test with compaction enabled vs disabled"}),i.jsx(Rn,{label:"Tool Presets",checked:x.vary_tools,onChange:L=>w({...x,vary_tools:L.target.checked})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6 -mt-1",children:"Test different tool configurations (standard, minimal, full)"}),x.vary_tools&&i.jsx("div",{className:"ml-6 flex flex-wrap gap-2",children:bp.map(L=>{var A;return i.jsxs("label",{className:"flex items-center gap-1 text-xs",children:[i.jsx("input",{type:"checkbox",checked:(A=x.tool_presets)==null?void 0:A.includes(L),onChange:T=>{const $=x.tool_presets||[];w({...x,tool_presets:T.target.checked?[...$,L]:$.filter(fe=>fe!==L)})},className:"accent-[var(--accent)]"}),i.jsx("span",{children:L})]},L)})})]}),i.jsxs("div",{className:"border-t border-[var(--border)] pt-3 mt-3 space-y-2",children:[i.jsx(Rn,{label:"Compaction Head Size",checked:x.vary_compaction_head,onChange:L=>w({...x,vary_compaction_head:L.target.checked})}),x.vary_compaction_head&&i.jsx(pt,{label:"Head sizes (comma-separated)",value:((D=x.compaction_head_values)==null?void 0:D.join(", "))||"5, 10, 20",onChange:L=>w({...x,compaction_head_values:L.target.value.split(",").map(A=>parseInt(A.trim())).filter(A=>!isNaN(A))})}),i.jsx(Rn,{label:"Compaction Tail Size",checked:x.vary_compaction_tail,onChange:L=>w({...x,vary_compaction_tail:L.target.checked})}),x.vary_compaction_tail&&i.jsx(pt,{label:"Tail sizes (comma-separated)",value:((B=x.compaction_tail_values)==null?void 0:B.join(", "))||"20, 40, 60",onChange:L=>w({...x,compaction_tail_values:L.target.value.split(",").map(A=>parseInt(A.trim())).filter(A=>!isNaN(A))})})]}),i.jsxs("div",{className:"bg-[var(--bg-tertiary)] p-3 rounded text-sm",children:[i.jsxs("div",{className:"flex justify-between",children:[i.jsx("span",{children:"Candidates:"}),i.jsx("span",{className:"font-mono",children:ke})]}),i.jsxs("div",{className:"flex justify-between",children:[i.jsx("span",{children:"Tasks:"}),i.jsx("span",{className:"font-mono",children:Pt})]}),i.jsxs("div",{className:"flex justify-between font-medium border-t border-[var(--border)] pt-2 mt-2",children:[i.jsx("span",{children:"Total experiments:"}),i.jsx("span",{className:"font-mono text-[var(--accent)]",children:He})]})]})]}),i.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[i.jsx(pt,{label:"Parallel Workers",type:"number",value:j,onChange:L=>S(parseInt(L.target.value)||1),min:1,max:16}),i.jsxs("div",{className:"space-y-1",children:[i.jsx(Rn,{label:"Use LLM evaluation",checked:v,onChange:L=>d(L.target.checked)}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6",children:v?"GPT-4o scores task completion (0-1)":"Simple pass/fail based on task success"})]})]}),i.jsxs("div",{className:"flex justify-between gap-2 pt-4 border-t border-[var(--border)]",children:[i.jsx(K,{variant:"secondary",icon:ig,onClick:()=>l("suite"),children:"Back"}),i.jsxs(K,{variant:"primary",icon:Ks,onClick:F,children:["Start Optimization (",He," runs)"]})]})]}):i.jsxs("div",{className:"space-y-6",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx("span",{className:"text-[var(--accent)] font-medium",children:"1. Tasks"}),i.jsx(Ss,{size:14}),i.jsx("span",{children:"2. Candidates"})]}),i.jsxs("div",{children:[i.jsx("label",{className:"text-sm font-medium mb-3 block",children:"Select Task Suite"}),i.jsx("div",{className:"space-y-2",children:M.map(L=>{const A=b.filter(T=>T.suite===L.value).length;return i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer transition-colors ${o===L.value&&!c?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",name:"suite",value:L.value,checked:o===L.value&&!c,onChange:()=>{u(L.value),p(!1)},className:"accent-[var(--accent)]"}),i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"font-medium",children:L.label}),i.jsxs(q,{children:[L.tasks," tasks"]}),A>0&&i.jsxs(q,{variant:"success",children:[A," imported"]})]}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:L.description})]})]},L.value)})})]}),b.length>0&&i.jsxs("div",{children:[i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer transition-colors ${c?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",checked:c,onChange:()=>p(!0),className:"accent-[var(--accent)]"}),i.jsxs("div",{className:"flex-1",children:[i.jsx("span",{className:"font-medium",children:"Custom Selection"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:"Choose specific tasks from your library"})]})]}),c&&i.jsx("div",{className:"mt-3 max-h-48 overflow-y-auto border border-[var(--border)] p-2 space-y-1",children:b.map(L=>i.jsxs("label",{className:"flex items-center gap-2 p-2 hover:bg-[var(--bg-tertiary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:f.includes(L.id),onChange:()=>U(L.id),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:L.name}),L.suite&&i.jsx(q,{children:L.suite})]},L.id))})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4 border-t border-[var(--border)]",children:[i.jsx(K,{variant:"secondary",onClick:n,children:"Cancel"}),i.jsx(K,{variant:"primary",icon:Ss,onClick:()=>l("candidates"),disabled:c&&f.length===0,children:"Next: Candidates"})]})]})})}function y0(e){const t=[],n=r=>r.type==="trace_span"&&typeof r.data=="object"&&r.data!==null?r.data:"span_id"in r?r:null;if(Array.isArray(e.spans)){for(const r of e.spans)if(typeof r=="object"&&r!==null){const s=n(r);s&&t.push(s)}}else if(e.span_id)t.push(e);else for(const r in e){const s=e[r];if(typeof s=="object"&&s!==null){const a=n(s);if(a)t.push(a);else if(Array.isArray(s)){for(const l of s)if(typeof l=="object"&&l!==null){const o=n(l);o&&t.push(o)}}}}return t}function Pp(e){const t=new Map,n=[];for(const s of e)t.set(s.span_id,{span:s,children:[]});for(const s of e){const a=t.get(s.span_id);s.parent_span_id&&t.has(s.parent_span_id)?t.get(s.parent_span_id).children.push(a):n.push(a)}const r=s=>{s.sort((a,l)=>(a.span.start_time||0)-(l.span.start_time||0)),s.forEach(a=>r(a.children))};return r(n),n}function g0(e){return e.includes("Agent")||e.includes("agent")?"bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200":e.includes("chat")||e.includes("Chat")||e.includes("llm")?"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200":e.includes("tool")||e.includes("execute")||e.includes("bash")?"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200":"bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"}function j0(e){return e>=1e3?`${(e/1e3).toFixed(2)}s`:`${e.toFixed(0)}ms`}function Du({node:e,depth:t=0}){var f,h;const[n,r]=k.useState(t<2),[s,a]=k.useState(!1),{span:l}=e,o=e.children.length>0,u=(f=l.attributes)==null?void 0:f["gen_ai.usage.input_tokens"],c=(h=l.attributes)==null?void 0:h["gen_ai.usage.output_tokens"],p=u!==void 0||c!==void 0;return i.jsxs("div",{className:"relative",children:[t>0&&i.jsx("div",{className:"absolute left-0 top-0 bottom-0 border-l-2 border-[var(--border)]",style:{marginLeft:`${(t-1)*16+8}px`}}),i.jsxs("div",{className:"flex items-center gap-2 py-1.5 px-1 hover:bg-[var(--bg-primary)] rounded transition-colors cursor-pointer",style:{paddingLeft:`${t*16}px`},onClick:()=>o?r(!n):a(!s),children:[i.jsx("div",{className:"w-4 h-4 flex items-center justify-center text-[var(--text-secondary)]",children:o?n?"▼":"▶":s?"▼":"▶"}),i.jsx("span",{className:`text-xs px-1.5 py-0.5 rounded font-medium ${g0(l.operation_name)}`,children:l.operation_name.replace("ChatAgent.","").replace("invoke_agent ","")}),l.duration_ms!==void 0&&i.jsx("span",{className:"text-xs text-[var(--text-secondary)] font-mono",children:j0(l.duration_ms)}),p&&i.jsxs("span",{className:"text-xs text-[var(--text-secondary)] font-mono",children:[u!==void 0&&i.jsxs("span",{className:"text-blue-400",children:["↑",String(u)]}),u!==void 0&&c!==void 0&&i.jsx("span",{className:"mx-0.5",children:"/"}),c!==void 0&&i.jsxs("span",{className:"text-green-400",children:["↓",String(c)]})]})]}),s&&!o&&i.jsx("div",{className:"ml-4 mt-1 mb-2 p-2 bg-[var(--bg-primary)] rounded border border-[var(--border)] text-xs",style:{marginLeft:`${t*16+20}px`},children:i.jsxs("div",{className:"space-y-1",children:[l.span_id&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Span ID:"}),i.jsx("span",{className:"font-mono text-xs break-all",children:l.span_id})]}),l.trace_id&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Trace ID:"}),i.jsx("span",{className:"font-mono text-xs break-all",children:l.trace_id})]}),l.status&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Status:"}),i.jsx("span",{className:`px-1.5 py-0.5 rounded text-xs ${l.status==="OK"||l.status==="StatusCode.UNSET"?"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200":"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"}`,children:l.status})]}),Object.keys(l.attributes||{}).length>0&&i.jsxs("div",{className:"mt-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] block mb-1",children:"Attributes:"}),i.jsx("pre",{className:"text-xs bg-[var(--bg-secondary)] border border-[var(--border)] rounded p-2 overflow-auto max-h-32 whitespace-pre-wrap break-all",children:JSON.stringify(l.attributes,null,2)})]})]})}),o&&n&&i.jsx("div",{children:e.children.map((x,w)=>i.jsx(Du,{node:x,depth:t+1},x.span.span_id||w))})]})}function Tp({trace:e}){const[t,n]=k.useState("tree"),r=k.useMemo(()=>y0(e),[e]),s=k.useMemo(()=>Pp(r),[r]);return Object.keys(e).length===0?null:i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-3",children:[i.jsx("h3",{className:"font-medium",children:"Trace Data"}),i.jsxs("div",{className:"flex gap-1",children:[i.jsx("button",{onClick:()=>n("tree"),className:`px-2 py-1 text-xs rounded ${t==="tree"?"bg-[var(--accent)] text-white":"bg-[var(--bg-primary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:"Tree"}),i.jsx("button",{onClick:()=>n("raw"),className:`px-2 py-1 text-xs rounded ${t==="raw"?"bg-[var(--accent)] text-white":"bg-[var(--bg-primary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:"Raw"})]})]}),t==="tree"?r.length>0?i.jsx("div",{className:"border border-[var(--border)] rounded overflow-hidden",children:i.jsxs("div",{className:"p-2",children:[i.jsxs("div",{className:"flex items-center gap-2 mb-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs(q,{variant:"default",children:[r.length," spans"]}),i.jsx("span",{children:"•"}),i.jsx("span",{children:"Click to expand details"})]}),s.map((a,l)=>i.jsx(Du,{node:a,depth:0},a.span.span_id||l))]})}):i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:"No structured spans found. View raw data below."}):i.jsx("pre",{className:"text-xs bg-[var(--bg-primary)] p-3 overflow-x-auto border border-[var(--border)] max-h-96 whitespace-pre-wrap",children:JSON.stringify(e,null,2)})]})}function w0({spans:e,isLive:t=!1}){const n=k.useRef(null),r=k.useMemo(()=>Pp(e),[e]);return k.useEffect(()=>{n.current&&t&&n.current.scrollTo({top:n.current.scrollHeight,behavior:"smooth"})},[e.length,t]),i.jsxs("div",{className:"border border-[var(--border)] rounded overflow-hidden h-full flex flex-col",children:[i.jsxs("div",{className:"flex items-center gap-2 p-2 border-b border-[var(--border)] bg-[var(--bg-secondary)]",children:[i.jsxs(q,{variant:"default",children:[e.length," spans"]}),t&&i.jsx("span",{className:"animate-pulse",children:i.jsx(q,{variant:"info",children:"Live"})})]}),i.jsx("div",{ref:n,className:"flex-1 overflow-auto p-2",children:r.length>0?r.map((s,a)=>i.jsx(Du,{node:s,depth:0},s.span.span_id||a)):i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:t?"Waiting for spans...":"No spans recorded"})})]})}function k0({agent:e}){const[t,n]=k.useState(""),[r,s]=k.useState(null),[a,l]=k.useState("idle"),[o,u]=k.useState(null),[c,p]=k.useState(""),[f,h]=k.useState([]),[x,w]=k.useState(null),[j,S]=k.useState(null),[v,d]=k.useState([]),m=k.useRef(null),{data:g=[]}=_e({queryKey:["tasks"],queryFn:()=>fn.list()});k.useEffect(()=>{m.current&&a==="running"&&(m.current.scrollTop=m.current.scrollHeight)},[c,a]);const b=F=>{if(s(F),F){const U=g.find(te=>te.id===F);U&&n(U.prompt)}},_=async()=>{if(t.trim()){l("running"),p(""),h([]),w(null),S(null),d([]);try{const F=await Ns.create({agent_id:e.id,prompt:t.trim(),task_id:r||void 0});u(F.id);for await(const U of Ns.start(F.id))C(U)}catch(F){S(F instanceof Error?F.message:"Test failed"),l("failed")}}},C=F=>{switch(F.event){case"started":break;case"execution":F.execution_event==="text_delta"&&F.content?p(U=>U+F.content):F.execution_event==="tool_call_start"&&F.tool_name?d(U=>[...U,{name:F.tool_name}]):F.execution_event==="tool_result"&&F.content&&d(U=>{if(U.length>0){const te=[...U];return te[te.length-1]={...te[te.length-1],content:F.content},te}return U});break;case"span":if(F.span){const U=F.span;if(U.data){const te={span_id:U.data.span_id||"",trace_id:U.data.trace_id||"",parent_span_id:U.data.parent_span_id||null,operation_name:U.data.operation_name||"",start_time:U.timestamp?new Date(U.timestamp).getTime():Date.now(),end_time:Date.now(),duration_ms:U.data.duration_ms||0,status:U.data.status||"OK",attributes:U.data.attributes||{}};h(ke=>[...ke,te])}}break;case"complete":l("completed"),F.result&&w(F.result);break;case"error":S(F.message),l("failed");break}},N=async()=>{if(o)try{await Ns.cancel(o)}catch{}l("idle")},M=()=>{l("idle"),u(null),p(""),h([]),w(null),S(null),d([])},P=a==="running",H=a==="completed"||a==="failed";return i.jsxs("div",{className:"h-full flex flex-col",children:[!P&&!H&&i.jsxs("div",{className:"mb-4 space-y-3",children:[i.jsxs("div",{className:"flex gap-3",children:[i.jsxs("div",{className:"flex-1",children:[i.jsx("label",{className:"block text-sm font-medium mb-1",children:"Select Task (optional)"}),i.jsxs("select",{value:r||"",onChange:F=>b(F.target.value||null),className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",children:[i.jsx("option",{value:"",children:"Custom prompt..."}),g.map(F=>i.jsx("option",{value:F.id,children:F.name},F.id))]})]}),i.jsx("div",{className:"flex items-end",children:i.jsx(K,{variant:"primary",icon:Ks,onClick:_,disabled:!t.trim(),children:"Run Test"})})]}),i.jsxs("div",{children:[i.jsx("label",{className:"block text-sm font-medium mb-1",children:"Prompt"}),i.jsx("textarea",{value:t,onChange:F=>n(F.target.value),placeholder:"Enter a test prompt for the agent...",className:"w-full h-32 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono resize-none"})]})]}),(P||H)&&i.jsxs("div",{className:"flex-1 flex flex-col min-h-0",children:[i.jsxs("div",{className:"flex items-center justify-between mb-3",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[P&&i.jsxs(i.Fragment,{children:[i.jsx("span",{className:"animate-pulse",children:i.jsx(q,{variant:"info",children:"Running"})}),i.jsx(K,{variant:"ghost",size:"sm",icon:gg,onClick:N,children:"Cancel"})]}),a==="completed"&&i.jsx(q,{variant:"success",children:"Completed"}),a==="failed"&&i.jsx(q,{variant:"error",children:"Failed"})]}),i.jsxs("div",{className:"flex items-center gap-3",children:[x&&i.jsxs("div",{className:"flex items-center gap-3 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{className:"flex items-center gap-1",children:[i.jsx(lg,{size:12}),x.duration_seconds.toFixed(2),"s"]}),i.jsxs("span",{className:"flex items-center gap-1",children:[i.jsx(og,{size:12}),x.tokens_total," tokens"]}),x.passed!==null&&(x.passed?i.jsx(rg,{size:14,className:"text-green-500"}):i.jsx(wg,{size:14,className:"text-red-500"}))]}),H&&i.jsx(K,{variant:"secondary",size:"sm",onClick:M,children:"New Test"})]})]}),i.jsxs("div",{className:"flex-1 grid grid-cols-2 gap-4 min-h-0",children:[i.jsxs("div",{className:"flex flex-col border border-[var(--border)] rounded overflow-hidden",children:[i.jsx("div",{className:"px-3 py-2 border-b border-[var(--border)] bg-[var(--bg-secondary)] text-sm font-medium",children:"Output"}),i.jsxs("div",{ref:m,className:"flex-1 overflow-auto p-3 font-mono text-sm whitespace-pre-wrap bg-[var(--bg-primary)]",children:[c||(P?"Waiting for response...":"No output"),v.length>0&&i.jsxs("div",{className:"mt-3 space-y-2 border-t border-[var(--border)] pt-3",children:[i.jsx("div",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:"Tool Calls"}),v.map((F,U)=>i.jsxs("div",{className:"p-2 bg-green-500/10 border border-green-500/20 rounded text-xs",children:[i.jsx(q,{variant:"success",children:F.name}),F.content&&i.jsxs("div",{className:"mt-1 text-[var(--text-secondary)] truncate max-w-full",children:[F.content.substring(0,200),F.content.length>200&&"..."]})]},U))]})]})]}),i.jsx(w0,{spans:f,isLive:P})]}),j&&i.jsx("div",{className:"mt-3 p-3 bg-red-500/10 border border-red-500/20 rounded text-sm text-red-400",children:j}),H&&o&&i.jsx("div",{className:"mt-3 flex items-center justify-end pt-3 border-t border-[var(--border)]",children:i.jsx(Hs,{to:`/tests/${o}`,children:i.jsx(K,{variant:"primary",iconRight:cg,children:"View Full Details"})})})]})]})}function S0(){const{agentId:e}=Ri(),t=Et(),n=Pn(),[r,s]=k.useState("overview"),{data:a,isLoading:l}=_e({queryKey:["configs",e],queryFn:()=>Jn.get(e),enabled:!!e}),{data:o=[]}=_e({queryKey:["tests",{agent_id:e}],queryFn:()=>Ns.list({agent_id:e}),enabled:!!e}),{data:u=[]}=_e({queryKey:["jobs"],queryFn:()=>bt.list()}),c=st({mutationFn:f=>Jn.delete(f),onSuccess:()=>{n.invalidateQueries({queryKey:["configs"]}),t("/agents")}}),p=u.filter(f=>f.candidate_ids.includes(e||""));return l?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):a?i.jsxs("div",{className:"h-full flex flex-col",children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t("/agents"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Agents"})}),i.jsxs("div",{className:"flex items-center justify-between",children:[i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:a.name}),a.is_auto_generated&&i.jsx(q,{variant:"info",children:"Auto-generated"})]}),i.jsx("div",{className:"flex gap-2",children:i.jsx(K,{variant:"secondary",icon:jp,onClick:()=>{confirm(`Delete agent "${a.name}"?`)&&c.mutate(a.id)},children:"Delete"})})]}),a.description&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:a.description})]}),i.jsxs("div",{className:"flex gap-1 mb-6 border-b border-[var(--border)]",children:[i.jsx(ml,{active:r==="overview",onClick:()=>s("overview"),icon:i.jsx(gp,{size:16}),children:"Overview"}),i.jsx(ml,{active:r==="test",onClick:()=>s("test"),icon:i.jsx(Ks,{size:16}),children:"Test"}),i.jsx(ml,{active:r==="history",onClick:()=>s("history"),icon:i.jsx(hg,{size:16}),badge:o.length,children:"History"})]}),i.jsxs("div",{className:"flex-1 min-h-0",children:[r==="overview"&&i.jsx(N0,{agent:a,recentTests:o,jobs:p}),r==="test"&&i.jsx(k0,{agent:a}),r==="history"&&i.jsx(_0,{tests:o})]})]}):i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Agent not found"})}function ml({active:e,onClick:t,icon:n,badge:r,children:s}){return i.jsxs("button",{onClick:t,className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${e?"border-[var(--accent)] text-[var(--text-primary)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[n,s,r!==void 0&&r>0&&i.jsx("span",{className:"ml-1 px-1.5 py-0.5 text-xs bg-[var(--bg-tertiary)] rounded",children:r})]})}function N0({agent:e,recentTests:t,jobs:n}){var l,o,u,c,p,f;const r=Et(),s=e.config,a=()=>{const h=s.tools;return typeof h=="string"?h:Array.isArray(h)?h.join(", "):typeof h=="object"?Object.keys(h).join(", "):"standard"};return i.jsxs("div",{className:"space-y-6",children:[i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"Configuration"}),i.jsxs("div",{className:"grid grid-cols-2 gap-4 text-sm",children:[i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Model:"}),i.jsx("div",{className:"font-mono",children:s.model||"default"})]}),i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Compaction:"}),i.jsx("div",{className:"font-mono",children:((l=s.compaction)==null?void 0:l.strategy)==="none"?"disabled":`${(o=s.compaction)==null?void 0:o.strategy} (${((c=(u=s.compaction)==null?void 0:u.params)==null?void 0:c.head_size)||0}/${((f=(p=s.compaction)==null?void 0:p.params)==null?void 0:f.tail_size)||0})`})]}),i.jsxs("div",{className:"col-span-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Tools:"}),i.jsx("div",{className:"font-mono",children:a()})]}),s.instructions&&i.jsxs("div",{className:"col-span-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Instructions:"}),i.jsx("pre",{className:"mt-1 p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-xs whitespace-pre-wrap max-h-32 overflow-auto",children:s.instructions})]})]})]}),i.jsxs(ee,{children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Recent Tests"}),t.length>0&&i.jsxs("span",{className:"text-xs text-[var(--text-secondary)]",children:[t.length," total"]})]}),t.length===0?i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:'No tests yet. Click the "Test" tab to run one.'}):i.jsx("div",{className:"space-y-2",children:t.slice(0,5).map(h=>i.jsxs(Hs,{to:`/tests/${h.id}`,className:"flex items-center justify-between p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx(Op,{status:h.status}),i.jsxs("span",{className:"text-sm truncate max-w-[200px]",children:[h.prompt.slice(0,50),"..."]})]}),i.jsxs("div",{className:"flex items-center gap-3 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[h.duration_seconds.toFixed(1),"s"]}),i.jsxs("span",{children:[h.tokens_total," tok"]})]})]},h.id))})]}),i.jsxs(ee,{children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Optimization Jobs"}),i.jsx(K,{variant:"secondary",size:"sm",icon:Ii,onClick:()=>r("/agents",{state:{optimizeAgent:e}}),children:"New Job"})]}),n.length===0?i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:"No optimization jobs yet."}):i.jsx("div",{className:"space-y-2",children:n.slice(0,5).map(h=>i.jsxs(Hs,{to:`/jobs/${h.id}`,className:"flex items-center justify-between p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx(q,{variant:h.status==="completed"?"success":h.status==="running"?"info":"default",children:h.status}),i.jsx("span",{className:"text-sm",children:h.name})]}),i.jsx("span",{className:"text-xs text-[var(--text-secondary)]",children:new Date(h.created_at).toLocaleDateString()})]},h.id))})]})]})}function _0({tests:e}){return e.length===0?i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"No test history yet. Run a test to see results here."}):i.jsx("div",{className:"space-y-2",children:e.map(t=>i.jsxs(Hs,{to:`/tests/${t.id}`,className:"flex items-center justify-between p-3 bg-[var(--bg-secondary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex-1 min-w-0",children:[i.jsxs("div",{className:"flex items-center gap-2 mb-1",children:[i.jsx(Op,{status:t.status}),t.score!==null&&i.jsxs("span",{className:`text-sm font-medium ${t.passed?"text-green-400":"text-red-400"}`,children:[(t.score*100).toFixed(0),"%"]})]}),i.jsx("p",{className:"text-sm truncate",children:t.prompt})]}),i.jsxs("div",{className:"flex flex-col items-end gap-1 ml-4",children:[i.jsx("span",{className:"text-xs text-[var(--text-secondary)]",children:new Date(t.created_at).toLocaleString()}),i.jsxs("div",{className:"flex items-center gap-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[t.duration_seconds.toFixed(1),"s"]}),i.jsx("span",{children:"•"}),i.jsxs("span",{children:[t.tokens_total," tokens"]})]})]})]},t.id))})}function Op({status:e}){const t={completed:"success",failed:"error",running:"info",cancelled:"warning",pending:"default"};return i.jsx(q,{variant:t[e]||"default",children:e})}const Lp=Lu(e=>({tasks:[],selectedTaskIds:[],setTasks:t=>e({tasks:t}),toggleTaskSelection:t=>e(n=>({selectedTaskIds:n.selectedTaskIds.includes(t)?n.selectedTaskIds.filter(r=>r!==t):[...n.selectedTaskIds,t]})),jobs:[],setJobs:t=>e({jobs:t})}));function C0(){const e=Pn(),[t,n]=k.useState(!1),[r,s]=k.useState(!1),[a,l]=k.useState(new Set(["custom"])),{selectedTaskIds:o,toggleTaskSelection:u,setTasks:c}=Lp(),p=d=>{l(m=>{const g=new Set(m);return g.has(d)?g.delete(d):g.add(d),g})},{data:f=[],isLoading:h}=_e({queryKey:["tasks"],queryFn:()=>fn.list()});k.useEffect(()=>{f.length>0&&c(f)},[f,c]);const x=st({mutationFn:fn.create,onSuccess:()=>{e.invalidateQueries({queryKey:["tasks"]}),n(!1)}}),w=st({mutationFn:fn.importSuite,onSuccess:()=>{e.invalidateQueries({queryKey:["tasks"]}),s(!1)}}),j=st({mutationFn:fn.delete,onSuccess:()=>e.invalidateQueries({queryKey:["tasks"]})}),S=f.reduce((d,m)=>{const g=m.suite||"custom";return d[g]||(d[g]=[]),d[g].push(m),d},{}),v=Object.keys(S).sort((d,m)=>d==="custom"?-1:m==="custom"?1:d.localeCompare(m));return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Tasks"}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:["Define tasks to evaluate agent configurations.",o.length>0&&i.jsxs("span",{className:"ml-2 text-[var(--accent)]",children:[o.length," selected"]})]})]}),i.jsxs("div",{className:"flex gap-2",children:[i.jsx(K,{variant:"secondary",onClick:()=>s(!0),children:"Import Suite"}),i.jsx(K,{variant:"primary",onClick:()=>n(!0),children:"+ New Task"})]})]}),h?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):f.length===0?i.jsx("div",{className:"text-center py-12 text-[var(--text-secondary)]",children:"No tasks yet. Create one or import a built-in suite."}):i.jsx("div",{className:"space-y-4",children:v.map(d=>{const m=S[d],g=a.has(d),b=m.filter(_=>o.includes(_.id)).length;return i.jsxs("div",{children:[i.jsxs("button",{onClick:()=>p(d),className:"flex items-center gap-2 py-2 hover:text-[var(--accent)] transition-colors",children:[g?i.jsx(ag,{size:16,className:"text-[var(--text-secondary)]"}):i.jsx(Ss,{size:16,className:"text-[var(--text-secondary)]"}),i.jsx("h3",{className:"text-sm font-medium uppercase tracking-wide",children:d==="custom"?"Custom Tasks":`${d} Suite`}),i.jsx(q,{variant:d==="custom"?"default":"info",children:m.length}),b>0&&i.jsxs(q,{variant:"success",children:[b," selected"]})]}),g&&i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 mt-2",children:m.map(_=>i.jsx(ee,{selectable:!0,selected:o.includes(_.id),onClick:()=>u(_.id),children:i.jsxs("div",{className:"flex flex-col h-full",children:[i.jsxs("div",{className:"flex items-start justify-between gap-2",children:[i.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[i.jsx("span",{className:"font-medium",children:_.name}),o.includes(_.id)&&i.jsx(q,{variant:"success",children:"Selected"}),_.category&&_.category!=="default"&&i.jsx(q,{variant:"default",children:_.category})]}),i.jsx(K,{variant:"ghost",size:"sm",onClick:C=>{C.stopPropagation(),confirm("Delete this task?")&&j.mutate(_.id)},children:"Delete"})]}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-2 line-clamp-3 flex-1",children:_.prompt}),_.criteria.length>0&&i.jsx("div",{className:"flex gap-1 mt-2 flex-wrap",children:_.criteria.map(C=>i.jsx(q,{variant:"default",children:C.name},C.name))})]})},_.id))})]},d)})}),i.jsx(b0,{isOpen:t,onClose:()=>n(!1),onSubmit:d=>x.mutate(d),isLoading:x.isPending}),i.jsx(E0,{isOpen:r,onClose:()=>s(!1),onSubmit:d=>w.mutate(d),isLoading:w.isPending})]})}function b0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){const[s,a]=k.useState({name:"",prompt:"",criteria:[],category:"default"}),l=()=>{a({...s,criteria:[...s.criteria,{name:"",instruction:"",weight:1}]})},o=(p,f)=>{const h=[...s.criteria];h[p]={...h[p],...f},a({...s,criteria:h})},u=p=>{a({...s,criteria:s.criteria.filter((f,h)=>h!==p)})},c=p=>{p.preventDefault(),!(!s.name.trim()||!s.prompt.trim())&&n({...s,criteria:s.criteria.filter(f=>f.name.trim()&&f.instruction.trim())})};return i.jsx(ia,{isOpen:e,onClose:t,title:"Create Task",children:i.jsxs("form",{onSubmit:c,className:"space-y-4",children:[i.jsx(pt,{label:"Name",value:s.name,onChange:p=>a({...s,name:p.target.value}),placeholder:"e.g., fizzbuzz",required:!0}),i.jsx(f0,{label:"Prompt",value:s.prompt,onChange:p=>a({...s,prompt:p.target.value}),placeholder:"The task description for the agent...",required:!0}),i.jsx(pt,{label:"Category",value:s.category,onChange:p=>a({...s,category:p.target.value}),placeholder:"e.g., coding, research"}),i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsx("label",{className:"text-sm text-[var(--text-secondary)]",children:"Evaluation Criteria"}),i.jsx(K,{type:"button",variant:"ghost",size:"sm",onClick:l,children:"+ Add"})]}),i.jsx("div",{className:"space-y-2",children:s.criteria.map((p,f)=>i.jsxs("div",{className:"flex gap-2 items-start",children:[i.jsx(pt,{value:p.name,onChange:h=>o(f,{name:h.target.value}),placeholder:"Name",className:"w-32"}),i.jsx(pt,{value:p.instruction,onChange:h=>o(f,{instruction:h.target.value}),placeholder:"Instruction",className:"flex-1"}),i.jsx(K,{type:"button",variant:"ghost",size:"sm",onClick:()=>u(f),children:"×"})]},f))})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(K,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(K,{type:"submit",variant:"primary",disabled:r||!s.name.trim()||!s.prompt.trim(),children:r?"Creating...":"Create"})]})]})})}function E0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){const[s,a]=k.useState(""),{data:l=[],isLoading:o}=_e({queryKey:["suites"],queryFn:()=>fn.listSuites(),enabled:e});return k.useEffect(()=>{l.length>0&&!s&&a(l[0].name)},[l,s]),i.jsx(ia,{isOpen:e,onClose:t,title:"Import Task Suite",children:i.jsxs("div",{className:"space-y-4",children:[i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:"Import a built-in task suite for evaluation."}),o?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading suites..."}):l.length===0?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"No suites available."}):i.jsx("div",{className:"space-y-2 max-h-80 overflow-y-auto",children:l.map(u=>i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer ${s===u.name?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",name:"suite",value:u.name,checked:s===u.name,onChange:()=>a(u.name),className:"accent-[var(--accent)]"}),i.jsxs("span",{className:"capitalize",children:[u.name.replace(/_/g," ")," (",u.task_count," tasks) - ",u.description]})]},u.name))}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(K,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(K,{variant:"primary",onClick:()=>n(s),disabled:r||!s,children:r?"Importing...":"Import"})]})]})})}function P0(){const e=Et(),t=Pn(),[n,r]=k.useState(!1),{setJobs:s}=Lp(),a=Fu(),{data:l=[],isLoading:o}=_e({queryKey:["jobs",n],queryFn:()=>bt.list({include_public:n}),refetchInterval:5e3});k.useEffect(()=>{l.length>0&&s(l)},[l,s]);const u=st({mutationFn:bt.delete,onSuccess:()=>t.invalidateQueries({queryKey:["jobs"]})});return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Optimization Jobs"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:"View and manage optimization experiments. Start new jobs from the Agents page."})]}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:n,onChange:c=>r(c.target.checked),className:"rounded border-[var(--border)]"}),i.jsx(zi,{className:"w-4 h-4"}),"Show public"]}),i.jsx(K,{variant:"secondary",onClick:()=>e("/agents"),children:"Go to Agents"})]})]}),o?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):l.length===0?i.jsx("div",{className:"text-center py-12 text-[var(--text-secondary)]",children:"No jobs yet. Go to Agents page to start an optimization."}):i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4",children:l.map(c=>{const p=!c.user_id||c.user_id==="anonymous"||a&&c.created_by_name===a;return i.jsx(Ep,{job:c,onDelete:p?f=>u.mutate(f):void 0},c.id)})})]})}function T0(e,t=!0){return Math.abs(e)<10?"text-[var(--text-secondary)]":(t?e<0:e>0)?"text-green-400":"text-red-400"}function O0(e){return`${e>0?"+":""}${e.toFixed(1)}%`}function Rp(e,t){return t===0?0:(e-t)/t*100}function os({label:e,values:t,baselineIndex:n,formatter:r,isLowerBetter:s=!0}){const a=t[n];return i.jsxs("tr",{className:"border-b border-[var(--border)] last:border-0",children:[i.jsx("td",{className:"py-2 pr-4 text-[var(--text-secondary)] text-sm",children:e}),t.map((l,o)=>{const u=Rp(l,a),c=o===n;return i.jsxs("td",{className:"py-2 px-4 text-right",children:[i.jsx("div",{className:"font-mono",children:r(l)}),!c&&i.jsx("div",{className:`text-xs ${T0(u,s)}`,children:O0(u)}),c&&i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"(baseline)"})]},o)})]})}function L0({runs:e,baselineRunId:t}){const n=k.useMemo(()=>{if(t){const a=e.findIndex(l=>l.id===t);if(a>=0)return a}return 0},[e,t]);if(e.length<2)return null;const r=Math.min(...e.map(a=>a.tokens_total)),s=Math.max(...e.map(a=>a.score));return i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-4",children:"Candidate Comparison"}),i.jsx("div",{className:"overflow-x-auto",children:i.jsxs("table",{className:"w-full text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"border-b border-[var(--border)]",children:[i.jsx("th",{className:"pb-2 pr-4 text-left text-[var(--text-secondary)] font-medium",children:"Metric"}),e.map((a,l)=>i.jsx("th",{className:"pb-2 px-4 text-right",children:i.jsxs("div",{className:"flex items-center justify-end gap-2",children:[i.jsx("span",{className:"font-medium",children:a.candidate_name}),a.is_pareto&&i.jsx(q,{variant:"success",children:"Pareto"}),l===n&&i.jsx(q,{variant:"info",children:"Base"})]})},a.id))]})}),i.jsxs("tbody",{children:[i.jsx(os,{label:"Total Tokens",values:e.map(a=>a.tokens_total),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Input Tokens",values:e.map(a=>a.tokens_input),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Output Tokens",values:e.map(a=>a.tokens_output),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Duration",values:e.map(a=>a.duration_seconds),baselineIndex:n,formatter:a=>`${a.toFixed(1)}s`,isLowerBetter:!0}),i.jsx(os,{label:"Score",values:e.map(a=>a.score*100),baselineIndex:n,formatter:a=>`${a.toFixed(1)}%`,isLowerBetter:!1})]})]})}),i.jsxs("div",{className:"mt-4 pt-4 border-t border-[var(--border)]",children:[i.jsx("h4",{className:"text-sm font-medium mb-2 text-[var(--text-secondary)]",children:"Key Insights"}),i.jsxs("ul",{className:"text-sm space-y-1 text-[var(--text-secondary)]",children:[e.map(a=>{const l=Rp(a.tokens_total,e[n].tokens_total);return a.tokens_total===r&&l<-5?i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-green-400",children:"✓"}),i.jsxs("span",{children:[i.jsx("strong",{children:a.candidate_name})," uses ",Math.abs(l).toFixed(0),"% fewer tokens"]})]},`token-${a.id}`):null}),e.map(a=>a.score===s&&a.passed?i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-green-400",children:"✓"}),i.jsxs("span",{children:[i.jsx("strong",{children:a.candidate_name})," achieved highest score (",(a.score*100).toFixed(0),"%)"]})]},`score-${a.id}`):null),e.filter(a=>a.is_pareto).length>0&&i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-purple-400",children:"★"}),i.jsxs("span",{children:["Pareto-optimal candidates:"," ",e.filter(a=>a.is_pareto).map(a=>a.candidate_name).join(", ")]})]})]})]}),i.jsxs("div",{className:"mt-4 pt-4 border-t border-[var(--border)]",children:[i.jsx("h4",{className:"text-sm font-medium mb-3 text-[var(--text-secondary)]",children:"Token Efficiency"}),i.jsx("div",{className:"space-y-2",children:e.map(a=>{const l=a.tokens_total/e[n].tokens_total*100,o=a.tokens_total<=r;return i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("div",{className:"w-24 text-sm truncate",title:a.candidate_name,children:a.candidate_name}),i.jsx("div",{className:"flex-1 h-6 bg-[var(--bg-primary)] rounded overflow-hidden",children:i.jsx("div",{className:`h-full transition-all duration-300 ${o?"bg-green-500":"bg-blue-500"}`,style:{width:`${Math.min(l,100)}%`}})}),i.jsx("div",{className:"w-20 text-right font-mono text-sm",children:a.tokens_total.toLocaleString()})]},a.id)})})]})]})}function R0({summaries:e,height:t=350}){const n=k.useRef(null),[r,s]=k.useState(600),[a,l]=k.useState("tokens"),[o,u]=k.useState(null),[c,p]=k.useState({x:0,y:0});k.useEffect(()=>{const _=()=>{n.current&&s(n.current.clientWidth)};return _(),window.addEventListener("resize",_),()=>window.removeEventListener("resize",_)},[]);const f={top:30,right:30,bottom:50,left:60},h=r-f.left-f.right,x=t-f.top-f.bottom,w=_=>a==="tokens"?_.avg_tokens:_.avg_duration,{xScale:j,yScale:S,xTicks:v,yTicks:d,paretoLine:m}=k.useMemo(()=>{if(e.length===0||h<=0)return{xScale:()=>0,yScale:()=>0,xTicks:[],yTicks:[],paretoLine:[]};const _=e.map(w),C=e.map(O=>O.avg_score),N=Math.min(..._)*.9,M=Math.max(..._)*1.1,P=Math.min(...C,.5),H=Math.min(Math.max(...C)*1.05,1),F=O=>(O-N)/(M-N)*h,U=O=>x-(O-P)/(H-P)*x,te=Array.from({length:5},(O,D)=>N+D/4*(M-N)),ke=Array.from({length:5},(O,D)=>P+D/4*(H-P)),He=e.filter(O=>O.is_pareto).sort((O,D)=>w(O)-w(D)).map(O=>({x:F(w(O)),y:U(O.avg_score)}));return{xScale:F,yScale:U,xTicks:te,yTicks:ke,paretoLine:He}},[e,h,x,a]);if(e.length===0)return i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"No data to display"});const g=_=>a==="tokens"?_>=1e6?`${(_/1e6).toFixed(1)}M`:_>=1e3?`${(_/1e3).toFixed(0)}K`:_.toFixed(0):`${_.toFixed(1)}s`,b=(_,C)=>{var M;const N=(M=n.current)==null?void 0:M.getBoundingClientRect();N&&p({x:C.clientX-N.left,y:C.clientY-N.top}),u(_)};return i.jsxs("div",{ref:n,className:"w-full relative",children:[i.jsx("div",{className:"flex justify-end mb-2",children:i.jsxs("div",{className:"inline-flex rounded border border-[var(--border)] text-xs",children:[i.jsx("button",{className:`px-3 py-1 ${a==="tokens"?"bg-[var(--accent)] text-black":"text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,onClick:()=>l("tokens"),children:"Tokens"}),i.jsx("button",{className:`px-3 py-1 ${a==="duration"?"bg-[var(--accent)] text-black":"text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,onClick:()=>l("duration"),children:"Latency"})]})}),i.jsx("svg",{width:r,height:t,className:"font-mono text-xs",children:i.jsxs("g",{transform:`translate(${f.left}, ${f.top})`,children:[v.map((_,C)=>i.jsx("line",{x1:j(_),y1:0,x2:j(_),y2:x,stroke:"var(--border)",strokeDasharray:"2,2"},`x-grid-${C}`)),d.map((_,C)=>i.jsx("line",{x1:0,y1:S(_),x2:h,y2:S(_),stroke:"var(--border)",strokeDasharray:"2,2"},`y-grid-${C}`)),m.length>1&&i.jsx("polyline",{points:m.map(_=>`${_.x},${_.y}`).join(" "),fill:"none",stroke:"var(--accent)",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),e.slice().sort((_,C)=>(_.is_pareto?1:0)-(C.is_pareto?1:0)).map(_=>{const C=j(w(_)),N=S(_.avg_score),M=_.is_pareto,P=(o==null?void 0:o.candidate_name)===_.candidate_name;return i.jsxs("g",{onMouseEnter:H=>b(_,H),onMouseLeave:()=>u(null),children:[i.jsx("circle",{cx:C,cy:N,r:P?10:M?8:6,fill:M?"var(--accent)":"transparent",stroke:P?"var(--text-primary)":M?"var(--accent)":"var(--text-secondary)",strokeWidth:P?3:2,className:"cursor-pointer transition-all"}),M&&!P&&i.jsx("text",{x:C,y:N-12,textAnchor:"middle",fill:"var(--text-primary)",fontSize:10,className:"pointer-events-none",children:_.candidate_name.replace(/^baseline_/,"").slice(0,15)})]},_.candidate_name)}),i.jsx("line",{x1:0,y1:x,x2:h,y2:x,stroke:"var(--text-secondary)"}),v.map((_,C)=>i.jsxs("g",{transform:`translate(${j(_)}, ${x})`,children:[i.jsx("line",{y2:5,stroke:"var(--text-secondary)"}),i.jsx("text",{y:20,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:10,children:g(_)})]},`x-tick-${C}`)),i.jsx("text",{x:h/2,y:x+40,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:11,children:a==="tokens"?"Tokens (cost)":"Duration (latency)"}),i.jsx("line",{x1:0,y1:0,x2:0,y2:x,stroke:"var(--text-secondary)"}),d.map((_,C)=>i.jsxs("g",{transform:`translate(0, ${S(_)})`,children:[i.jsx("line",{x2:-5,stroke:"var(--text-secondary)"}),i.jsxs("text",{x:-10,textAnchor:"end",dominantBaseline:"middle",fill:"var(--text-secondary)",fontSize:10,children:[(_*100).toFixed(0),"%"]})]},`y-tick-${C}`)),i.jsx("text",{transform:`translate(-45, ${x/2}) rotate(-90)`,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:11,children:"Score (quality)"})]})}),o&&i.jsxs("div",{className:"absolute z-10 bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-lg p-3 text-sm pointer-events-none",style:{left:Math.min(c.x+15,r-200),top:c.y-10,maxWidth:220},children:[i.jsx("div",{className:"font-medium text-[var(--text-primary)] truncate mb-2",children:o.candidate_name.replace(/^baseline_/,"")}),i.jsxs("div",{className:"grid grid-cols-2 gap-x-4 gap-y-1 text-xs",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Score:"}),i.jsxs("span",{className:"text-right font-medium",children:[(o.avg_score*100).toFixed(1),"%"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Tokens:"}),i.jsxs("span",{className:"text-right",children:[(o.avg_tokens/1e3).toFixed(1),"K"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Duration:"}),i.jsxs("span",{className:"text-right",children:[o.avg_duration.toFixed(1),"s"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Pass rate:"}),i.jsxs("span",{className:"text-right",children:[o.passed_runs,"/",o.total_runs]})]}),o.is_pareto&&i.jsx("div",{className:"mt-2 pt-2 border-t border-[var(--border)] text-xs text-[var(--accent)]",children:"Pareto optimal"})]})]})}function M0(e=2e3){const[t,n]=k.useState(!1),[r,s]=k.useState(null),a=k.useCallback(async o=>{try{return await navigator.clipboard.writeText(o),n(!0),s(null),setTimeout(()=>n(!1),e),!0}catch{return s("Failed to copy to clipboard"),n(!1),!1}},[e]),l=k.useCallback(()=>{n(!1),s(null)},[]);return{copy:a,copied:t,error:r,reset:l}}function z0({isOpen:e,onClose:t,title:n,itemId:r,itemType:s,isPublic:a,createdByName:l,onTogglePublic:o}){const[u,c]=k.useState(!1),{copy:p,copied:f}=M0(),h=`${window.location.origin}/${s}s/${r}`,x=async()=>{c(!0);try{await o(!a)}finally{c(!1)}},w=()=>{p(h)};return i.jsx(ia,{isOpen:e,onClose:t,title:n,children:i.jsxs("div",{className:"space-y-4",children:[i.jsxs("div",{className:"flex items-center justify-between p-3 bg-[var(--bg-tertiary)] rounded",children:[i.jsxs("div",{className:"flex items-center gap-3",children:[a?i.jsx(zi,{className:"w-5 h-5 text-[var(--accent)]"}):i.jsx(xp,{className:"w-5 h-5 text-[var(--text-secondary)]"}),i.jsxs("div",{children:[i.jsx("div",{className:"font-medium",children:a?"Public":"Private"}),i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:a?"Anyone with the link can view":"Only you can access"})]})]}),i.jsx("button",{onClick:x,disabled:u,className:`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${a?"bg-[var(--accent)]":"bg-[var(--border)]"} ${u?"opacity-50 cursor-not-allowed":""}`,children:i.jsx("span",{className:`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${a?"translate-x-6":"translate-x-1"}`})})]}),a&&i.jsxs("div",{className:"space-y-2",children:[i.jsx("label",{className:"text-sm text-[var(--text-secondary)]",children:"Share link"}),i.jsxs("div",{className:"flex gap-2",children:[i.jsx("input",{type:"text",readOnly:!0,value:h,className:"flex-1 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono"}),i.jsx(K,{variant:"secondary",onClick:w,children:f?i.jsxs(i.Fragment,{children:[i.jsx(sg,{className:"w-4 h-4 mr-1"}),"Copied"]}):i.jsxs(i.Fragment,{children:[i.jsx(ug,{className:"w-4 h-4 mr-1"}),"Copy"]})})]})]}),l&&i.jsxs("div",{className:"text-sm text-[var(--text-secondary)]",children:["Created by ",i.jsx("span",{className:"text-[var(--text-primary)]",children:l})]}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)] pt-2 border-t border-[var(--border)]",children:a?i.jsxs(i.Fragment,{children:[i.jsxs("p",{children:["Public ",s,"s can be viewed by anyone with the link."]}),i.jsxs("p",{className:"mt-1",children:["Only you can edit or delete this ",s,"."]})]}):i.jsxs("p",{children:["Make this ",s," public to share it with others."]})})]})})}function F0({job:e,onUpdate:t}){const[n,r]=k.useState(!1),s=async a=>{await bt.update(e.id,{is_public:a}),t()};return i.jsxs(i.Fragment,{children:[i.jsxs(K,{variant:"secondary",size:"sm",onClick:()=>r(!0),title:e.is_public?"Sharing settings":"Share this job",children:[i.jsx(yg,{className:"w-4 h-4 mr-1"}),e.is_public?"Shared":"Share"]}),i.jsx(z0,{isOpen:n,onClose:()=>r(!1),title:"Share Job",itemId:e.id,itemType:"job",isPublic:e.is_public,createdByName:e.created_by_name,onTogglePublic:s})]})}function I0(){const{jobId:e}=Ri(),t=Et(),n=Pn(),[r,s]=k.useState(null),[a,l]=k.useState(!1),[o,u]=k.useState(null),[c,p]=k.useState([]),[f,h]=k.useState(null),[x,w]=k.useState(null),[j,S]=k.useState("results"),[v,d]=k.useState("score"),[m,g]=k.useState("desc"),[b,_]=k.useState(!1),{data:C,isLoading:N}=_e({queryKey:["jobs",e],queryFn:()=>bt.get(e),enabled:!!e,refetchInterval:a?2e3:!1}),{data:M=[]}=_e({queryKey:["runs",e],queryFn:()=>Po.list({job_id:e}),enabled:!!e,refetchInterval:a?2e3:!1}),{data:P}=_e({queryKey:["job-summary",e],queryFn:()=>Po.getJobSummary(e),enabled:!!e&&(C==null?void 0:C.status)==="completed"}),H=st({mutationFn:async()=>{l(!0),p([]),h(null),w(null);for await(const T of bt.start(e))s(T),T.current_candidate&&T.current_task&&h($=>($&&($.candidate!==T.current_candidate||$.task!==T.current_task)&&p(fe=>[...fe,{candidate_name:$.candidate,task_name:$.task,completed_at:Date.now()}]),{candidate:T.current_candidate,task:T.current_task})),T.event==="error"&&(w(T.message),l(!1),n.invalidateQueries({queryKey:["jobs",e]})),T.event==="complete"&&(h($=>($&&p(fe=>[...fe,{candidate_name:$.candidate,task_name:$.task,completed_at:Date.now()}]),null)),l(!1),n.invalidateQueries({queryKey:["jobs",e]}),n.invalidateQueries({queryKey:["runs",e]}),n.invalidateQueries({queryKey:["job-summary",e]}))}}),F=st({mutationFn:()=>bt.cancel(e),onSuccess:()=>{l(!1),n.invalidateQueries({queryKey:["jobs",e]})}});k.useEffect(()=>{(C==null?void 0:C.status)==="running"&&l(!0)},[C==null?void 0:C.status]);const U=k.useMemo(()=>{const T=new Map;for(const $ of M)T.has($.task_name)||T.set($.task_name,[]),T.get($.task_name).push($);return T},[M]),te=k.useMemo(()=>Array.from(U.keys()),[U]),ke=k.useMemo(()=>{if(!(P!=null&&P.candidate_summaries))return[];let T=[...P.candidate_summaries];return b&&(T=T.filter($=>$.is_pareto)),T.sort(($,fe)=>{let ye,lt;switch(v){case"score":ye=$.avg_score,lt=fe.avg_score;break;case"tokens":ye=$.avg_tokens,lt=fe.avg_tokens;break;case"duration":ye=$.avg_duration,lt=fe.avg_duration;break;case"pass_rate":ye=$.passed_runs/$.total_runs,lt=fe.passed_runs/fe.total_runs;break}return m==="desc"?lt-ye:ye-lt}),T},[P,v,m,b]),Pt=T=>{v===T?g(m==="desc"?"asc":"desc"):(d(T),g(T==="tokens"||T==="duration"?"asc":"desc"))},He=({label:T,sortKeyVal:$})=>i.jsx("th",{className:"pb-2 cursor-pointer hover:text-[var(--text-primary)] select-none",onClick:()=>Pt($),children:i.jsxs("div",{className:"flex items-center gap-1",children:[T,v===$&&i.jsx(eg,{size:12,className:m==="asc"?"rotate-180":""})]})});if(N)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."});if(!C)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Job not found"});const O=Fu(),D=!C.user_id||C.user_id==="anonymous"||O&&C.created_by_name===O,B=C.is_public&&!D,L=()=>{n.invalidateQueries({queryKey:["jobs",e]})},A=T=>{const $={pending:"default",running:"info",completed:"success",failed:"error",cancelled:"warning"};return i.jsx(q,{variant:$[T]||"default",children:T})};return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("button",{onClick:()=>t("/jobs"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Jobs"}),i.jsx("h2",{className:"text-xl font-bold",children:C.name||`Job ${C.id.slice(0,8)}`}),A(C.status),C.is_public&&i.jsxs(q,{variant:"info",children:[i.jsx(zi,{className:"w-3 h-3 mr-1 inline"}),"Public"]})]}),i.jsxs("div",{className:"flex items-center gap-3 mt-1",children:[i.jsxs("code",{className:"text-xs bg-[var(--bg-primary)] px-2 py-0.5 rounded font-mono text-[var(--text-secondary)]",children:[C.id.slice(0,8),"..."]}),i.jsxs("span",{className:"text-sm text-[var(--text-secondary)]",children:[C.candidate_ids.length," candidates × ",C.task_ids.length," tasks = ",C.total_experiments," experiments"]}),C.is_public&&C.created_by_name&&i.jsxs("span",{className:"text-sm text-[var(--text-secondary)]",children:["Created by ",i.jsx("span",{className:"text-[var(--text-primary)]",children:C.created_by_name})]})]})]}),i.jsxs("div",{className:"flex gap-2",children:[D&&i.jsx(F0,{job:C,onUpdate:L}),B&&i.jsx(q,{variant:"default",children:"View Only"}),D&&C.status==="pending"&&i.jsx(K,{variant:"primary",onClick:()=>H.mutate(),disabled:H.isPending,children:H.isPending?"Starting...":"Start"}),D&&C.status==="running"&&i.jsx(K,{variant:"danger",onClick:()=>F.mutate(),disabled:F.isPending,children:"Cancel"})]})]}),(x||C.error)&&i.jsx(ee,{className:"mb-6 border-red-500/50 bg-red-500/10",children:i.jsxs("div",{className:"flex items-start gap-3",children:[i.jsx("div",{className:"w-5 h-5 rounded-full bg-red-500 flex items-center justify-center text-white text-xs font-bold flex-shrink-0 mt-0.5",children:"!"}),i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium text-red-400",children:"Error"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:x||C.error})]})]})}),(a||r)&&i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsx("span",{className:"font-medium",children:"Progress"}),i.jsxs("span",{className:"text-[var(--accent)]",children:[(r==null?void 0:r.completed)||C.completed_experiments,"/",(r==null?void 0:r.total)||C.total_experiments]})]}),i.jsx("div",{className:"w-full bg-[var(--bg-primary)] h-2 mb-2",children:i.jsx("div",{className:"h-full bg-[var(--accent)] transition-all",style:{width:`${((r==null?void 0:r.completed)||C.completed_experiments)/((r==null?void 0:r.total)||C.total_experiments)*100}%`}})}),(r==null?void 0:r.message)&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:r.message}),a&&i.jsxs("div",{className:"mt-4 border-t border-[var(--border)] pt-4",children:[(r==null?void 0:r.current_candidate)&&(r==null?void 0:r.current_task)&&i.jsxs("div",{className:"mb-3",children:[i.jsx("span",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:"Currently Running"}),i.jsxs("div",{className:"flex items-center gap-2 mt-1 px-3 py-2 bg-blue-500/10 border border-blue-500/30 rounded",children:[i.jsx("div",{className:"w-2 h-2 bg-blue-400 rounded-full animate-pulse"}),i.jsx("span",{className:"font-medium",children:r.current_candidate}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{children:r.current_task})]})]}),c.length>0&&i.jsxs("div",{children:[i.jsxs("span",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:["Completed (",c.length,")"]}),i.jsx("div",{className:"mt-1 max-h-40 overflow-y-auto space-y-1",children:c.map((T,$)=>i.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 bg-green-500/10 border border-green-500/30 rounded text-sm",children:[i.jsx("div",{className:"w-2 h-2 bg-green-400 rounded-full"}),i.jsx("span",{className:"font-medium",children:T.candidate_name}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{children:T.task_name})]},`${T.candidate_name}-${T.task_name}-${$}`))})]})]})]}),(C.status==="completed"||M.length>0)&&i.jsxs("div",{className:"flex gap-1 mb-6 border-b border-[var(--border)]",children:[i.jsxs("button",{onClick:()=>S("results"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="results"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(tg,{size:16}),"Results"]}),i.jsxs("button",{onClick:()=>S("compare"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="compare"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(dg,{size:16}),"Compare"]}),i.jsxs("button",{onClick:()=>S("runs"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="runs"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(mg,{size:16}),"Runs (",M.length,")"]})]}),j==="results"&&i.jsxs(i.Fragment,{children:[P&&P.candidate_summaries.length>1&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("div",{className:"flex items-start justify-between mb-4",children:i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium",children:"Pareto Frontier"}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:["Candidates on the frontier (connected line) are ",i.jsx("strong",{children:"Pareto optimal"})," - no other candidate beats them on both score AND cost."]})]})}),i.jsxs("div",{className:"mb-4 p-3 bg-[var(--bg-primary)] rounded border border-[var(--border)] text-xs text-[var(--text-secondary)]",children:[i.jsx("strong",{className:"text-[var(--text-primary)]",children:"How Pareto optimal is calculated:"})," A candidate is Pareto optimal if there's no other candidate that has both a higher score AND lower cost. For example, if Candidate A has 95% score at 50K tokens and Candidate B has 90% score at 40K tokens, both are Pareto optimal - A is better on score, B is better on cost. But if Candidate C has 85% score at 60K tokens, it's ",i.jsx("em",{children:"not"})," Pareto optimal because B beats it on both metrics."]}),i.jsx(R0,{summaries:P.candidate_summaries,height:350})]}),P&&i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Results Summary"}),i.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:b,onChange:T=>_(T.target.checked),className:"rounded border-[var(--border)]"}),"Pareto only"]})]}),i.jsx("div",{className:"overflow-x-auto",children:i.jsxs("table",{className:"w-full text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"text-left text-[var(--text-secondary)] border-b border-[var(--border)]",children:[i.jsx("th",{className:"pb-2",children:"Candidate"}),i.jsx(He,{label:"Score",sortKeyVal:"score"}),i.jsx(He,{label:"Tokens",sortKeyVal:"tokens"}),i.jsx(He,{label:"Duration",sortKeyVal:"duration"}),i.jsx(He,{label:"Pass Rate",sortKeyVal:"pass_rate"}),i.jsx("th",{className:"pb-2",children:"Pareto"})]})}),i.jsx("tbody",{children:ke.map((T,$)=>i.jsxs("tr",{className:`border-b border-[var(--border)] ${$===0?"bg-[var(--accent)]/10":""}`,children:[i.jsxs("td",{className:"py-2 font-medium",children:[$===0&&i.jsx("span",{className:"text-[var(--accent)] mr-1",children:"#1"}),T.candidate_name.replace(/^baseline_/,"")]}),i.jsxs("td",{className:"py-2",children:[(T.avg_score*100).toFixed(1),"%"]}),i.jsxs("td",{className:"py-2",children:[(T.avg_tokens/1e3).toFixed(1),"K"]}),i.jsxs("td",{className:"py-2",children:[T.avg_duration.toFixed(1),"s"]}),i.jsxs("td",{className:"py-2",children:[T.passed_runs,"/",T.total_runs]}),i.jsx("td",{className:"py-2",children:T.is_pareto&&i.jsx(q,{variant:"success",children:"Pareto"})})]},T.candidate_name))})]})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-3",children:"Click column headers to sort. The #1 ranked candidate is highlighted based on your sort criteria."})]}),!P&&i.jsx(ee,{children:i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Results will appear here after the job completes.":"No results yet. Start the job to see results."})})]}),j==="compare"&&i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"Compare Candidates by Task"}),te.length>0?i.jsxs(i.Fragment,{children:[i.jsx("div",{className:"flex flex-wrap gap-2 mb-4",children:te.map(T=>i.jsx("button",{onClick:()=>u(o===T?null:T),className:`px-3 py-1 text-sm rounded border transition-colors ${o===T?"bg-[var(--accent)] text-white border-[var(--accent)]":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:T},T))}),o&&U.get(o)?i.jsx(L0,{runs:U.get(o).map(T=>({id:T.id,candidate_name:T.candidate_name,tokens_input:0,tokens_output:0,tokens_total:T.tokens_total,duration_seconds:T.duration_seconds,score:T.score,passed:T.passed,is_pareto:T.is_pareto}))}):i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"Select a task above to compare how different candidates performed on it."})]}):i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Comparison data will appear here after runs complete.":"No runs yet. Start the job to compare candidates."})]}),j==="runs"&&i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"All Experiment Runs"}),M.length===0?i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Runs will appear here as they complete. See progress above for live status.":"No runs yet. Start the job to see results."}):i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3",children:M.map(T=>i.jsxs("div",{className:"p-3 bg-[var(--bg-primary)] rounded border border-[var(--border)] cursor-pointer hover:border-[var(--accent-dim)] transition-colors",onClick:()=>t(`/runs/${T.id}`),children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsxs("span",{className:`text-lg font-bold ${T.passed?"text-green-400":"text-red-400"}`,children:[(T.score*100).toFixed(0),"%"]}),T.is_pareto&&i.jsx(q,{variant:"success",children:"Pareto"})]}),i.jsx("div",{className:"text-sm font-medium truncate",title:T.candidate_name,children:T.candidate_name.replace(/^baseline_/,"")}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)] truncate",children:T.task_name}),i.jsxs("div",{className:"flex items-center gap-3 mt-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[(T.tokens_total/1e3).toFixed(1),"K tokens"]}),i.jsxs("span",{children:[T.duration_seconds.toFixed(1),"s"]})]})]},T.id))})]})]})}const hn={input:"bg-blue-500",output:"bg-emerald-500",inputText:"text-blue-400",outputText:"text-emerald-400"};function jd(e){return e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function wd({input:e,output:t,maxValue:n,height:r=24,showLabels:s=!0}){const a=e+t;if(a===0)return i.jsx("div",{className:"flex items-center gap-2 w-full",children:i.jsx("div",{className:"rounded bg-[var(--bg-primary)] flex-1",style:{height:`${r}px`}})});const l=n>0?a/n*100:100;return i.jsxs("div",{className:"flex items-center gap-3 w-full",children:[i.jsx("div",{className:"relative rounded overflow-hidden bg-[var(--bg-primary)] flex-1",style:{height:`${r}px`},children:i.jsxs("div",{className:"h-full flex transition-all duration-300",style:{width:`${l}%`},children:[i.jsx("div",{className:`h-full ${hn.input} transition-all`,style:{width:`${e/a*100}%`},title:`Input: ${e.toLocaleString()} tokens`}),i.jsx("div",{className:`h-full ${hn.output} transition-all`,style:{width:`${t/a*100}%`},title:`Output: ${t.toLocaleString()} tokens`})]})}),s&&i.jsxs("div",{className:"flex items-center gap-1 text-xs font-mono text-[var(--text-secondary)] min-w-[90px] justify-end",children:[i.jsxs("span",{className:hn.inputText,children:["↑",jd(e)]}),i.jsx("span",{children:"/"}),i.jsxs("span",{className:hn.outputText,children:["↓",jd(t)]})]})]})}function vl({label:e,value:t,color:n="default"}){const r={default:"text-[var(--text-primary)]",input:hn.inputText,output:hn.outputText}[n];return i.jsxs("div",{className:"flex-1 p-3 bg-[var(--bg-primary)] border border-[var(--border)] rounded",children:[i.jsx("div",{className:"text-xs text-[var(--text-secondary)] mb-1",children:e}),i.jsx("div",{className:`font-mono text-lg font-bold ${r}`,children:t})]})}function Mp({tokensInput:e,tokensOutput:t,tokensTotal:n,turns:r}){const s=n>0?Math.round(e/n*100):0,a=n>0?Math.round(t/n*100):0,l=k.useMemo(()=>{if(!r||r.length===0)return null;let u=0,c=0;return r.map(p=>(u+=p.input,c+=p.output,{input:u,output:c,total:u+c}))},[r]),o=l?Math.max(...l.map(u=>u.total)):n;return i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-4",children:"Token Usage"}),i.jsx("div",{className:"mb-4",children:i.jsx(wd,{input:e,output:t,maxValue:n,height:32})}),i.jsxs("div",{className:"flex items-center gap-6 text-xs mb-4",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("div",{className:`w-3 h-3 rounded ${hn.input}`}),i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["Input (",s,"%)"]})]}),i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("div",{className:`w-3 h-3 rounded ${hn.output}`}),i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["Output (",a,"%)"]})]})]}),i.jsxs("div",{className:"flex gap-3 mb-4",children:[i.jsx(vl,{label:"Input Tokens",value:e.toLocaleString(),color:"input"}),i.jsx(vl,{label:"Output Tokens",value:t.toLocaleString(),color:"output"}),i.jsx(vl,{label:"Total Tokens",value:n.toLocaleString()})]}),l&&l.length>1&&i.jsxs("div",{className:"border-t border-[var(--border)] pt-4",children:[i.jsxs("h4",{className:"text-sm font-medium mb-3 text-[var(--text-secondary)]",children:["Token Accumulation (",r.length," turns)"]}),i.jsx("div",{className:"space-y-2",children:r.map((u,c)=>i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("div",{className:"w-6 h-6 rounded-full bg-[var(--bg-primary)] border border-[var(--border)] flex items-center justify-center text-xs font-medium",children:c+1}),i.jsx("div",{className:"flex-1",children:i.jsx(wd,{input:l[c].input,output:l[c].output,maxValue:o,height:16})})]},c))})]}),i.jsx("div",{className:"mt-4 text-xs text-[var(--text-secondary)] border-t border-[var(--border)] pt-3",children:"Token usage affects API cost. Input tokens are typically cheaper than output tokens."})]})}function D0(){const{runId:e}=Ri(),t=Et(),{data:n,isLoading:r}=_e({queryKey:["runs",e],queryFn:()=>Po.get(e),enabled:!!e});return r?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):n?i.jsxs("div",{children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t(`/jobs/${n.job_id}`),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Job"})}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:n.candidate_name}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{className:"text-lg",children:n.task_name}),n.is_pareto&&i.jsx(q,{variant:"success",children:"Pareto Optimal"})]})]}),i.jsxs("div",{className:"grid grid-cols-4 gap-4 mb-6",children:[i.jsx(Ca,{label:"Score",value:`${(n.score*100).toFixed(1)}%`,status:n.passed?"success":"error"}),i.jsx(Ca,{label:"Total Tokens",value:n.tokens_total.toLocaleString()}),i.jsx(Ca,{label:"Duration",value:`${n.duration_seconds.toFixed(1)}s`}),i.jsx(Ca,{label:"Status",value:n.passed?"Passed":"Failed",status:n.passed?"success":"error"})]}),i.jsx(Mp,{tokensInput:n.tokens_input,tokensOutput:n.tokens_output,tokensTotal:n.tokens_total}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Evaluation"}),n.reasoning&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mb-4",children:n.reasoning}),n.criteria_results.length>0&&i.jsx("div",{className:"space-y-2",children:n.criteria_results.map(s=>i.jsx("div",{className:"flex items-start justify-between p-3 bg-[var(--bg-primary)] border border-[var(--border)]",children:i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"font-medium",children:s.name}),i.jsxs(q,{variant:s.passed?"success":"error",children:[(s.score*100).toFixed(0),"%"]})]}),s.reasoning&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:s.reasoning})]})},s.name))})]}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Agent Output"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)]",children:n.output||"(no output)"})]}),n.files_created.length>0&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Files Created"}),i.jsx("div",{className:"space-y-1",children:n.files_created.map(s=>i.jsx("div",{className:"text-sm font-mono text-[var(--text-secondary)]",children:s},s))})]}),Object.keys(n.trace).length>0&&i.jsx(Tp,{trace:n.trace})]}):i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Run not found"})}function Ca({label:e,value:t,status:n}){const r={success:"text-green-400",error:"text-red-400"};return i.jsxs(ee,{children:[i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:e}),i.jsx("div",{className:`text-xl font-bold ${n?r[n]:""}`,children:t})]})}function A0(){const{testId:e}=Ri(),t=Et(),{data:n,isLoading:r}=_e({queryKey:["tests",e],queryFn:()=>Ns.get(e),enabled:!!e}),{data:s}=_e({queryKey:["configs",n==null?void 0:n.agent_id],queryFn:()=>Jn.get(n.agent_id),enabled:!!(n!=null&&n.agent_id)});if(r)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."});if(!n)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Test not found"});const a={completed:"success",failed:"error",running:"info",pending:"default",cancelled:"warning"};return i.jsxs("div",{children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t("/agents"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Agents"})}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:"Test Run"}),s&&i.jsxs(i.Fragment,{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"•"}),i.jsx("span",{className:"text-lg",children:s.name})]}),i.jsx(q,{variant:a[n.status]||"default",children:n.status})]}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1 font-mono",children:["ID: ",n.id.slice(0,8),"..."]})]}),i.jsxs("div",{className:"grid grid-cols-4 gap-4 mb-6",children:[i.jsx(ba,{label:"Duration",value:`${n.duration_seconds.toFixed(2)}s`}),i.jsx(ba,{label:"Total Tokens",value:n.tokens_total.toLocaleString()}),n.score!==null&&i.jsx(ba,{label:"Score",value:`${(n.score*100).toFixed(1)}%`,status:n.passed?"success":"error"}),i.jsx(ba,{label:"Status",value:n.status.charAt(0).toUpperCase()+n.status.slice(1),status:n.status==="completed"?"success":n.status==="failed"?"error":void 0})]}),i.jsx(Mp,{tokensInput:n.tokens_input,tokensOutput:n.tokens_output,tokensTotal:n.tokens_total}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Prompt"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)] font-mono",children:n.prompt})]}),n.error&&i.jsxs(ee,{className:"mb-6 border-red-500/30 bg-red-500/5",children:[i.jsx("h3",{className:"font-medium mb-3 text-red-400",children:"Error"}),i.jsx("pre",{className:"text-sm text-red-300 overflow-x-auto whitespace-pre-wrap",children:n.error})]}),n.reasoning&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Evaluation"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:n.reasoning})]}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Agent Output"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)]",children:n.output||"(no output)"})]}),n.files_created&&n.files_created.length>0&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Files Created"}),i.jsx("div",{className:"space-y-1",children:n.files_created.map(l=>i.jsx("div",{className:"text-sm font-mono text-[var(--text-secondary)]",children:l},l))})]}),n.trace&&Object.keys(n.trace).length>0&&i.jsx(Tp,{trace:n.trace}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Timestamps"}),i.jsxs("div",{className:"grid grid-cols-3 gap-4 text-sm",children:[i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Created:"}),i.jsx("div",{className:"font-mono",children:new Date(n.created_at).toLocaleString()})]}),n.started_at&&i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Started:"}),i.jsx("div",{className:"font-mono",children:new Date(n.started_at).toLocaleString()})]}),n.completed_at&&i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Completed:"}),i.jsx("div",{className:"font-mono",children:new Date(n.completed_at).toLocaleString()})]})]})]})]})}function ba({label:e,value:t,status:n}){const r={success:"text-green-400",error:"text-red-400"};return i.jsxs(ee,{children:[i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:e}),i.jsx("div",{className:`text-xl font-bold ${n?r[n]:""}`,children:t})]})}function $0(){const{authConfig:e,isLoading:t,error:n,login:r,loginWithGitHub:s,clearError:a}=Iu(),[l,o]=k.useState(""),[u,c]=k.useState("");k.useEffect(()=>{n&&a()},[l,u]);const p=async h=>{h.preventDefault(),!(!l||!u)&&await r(l,u)},f=()=>{s()};return i.jsx("div",{className:"min-h-screen bg-[var(--bg-primary)] flex items-center justify-center p-4",children:i.jsxs("div",{className:"w-full max-w-md",children:[i.jsxs("div",{className:"text-center mb-8",children:[i.jsx("h1",{className:"text-2xl font-bold text-[var(--text-primary)] mb-2",children:"Flow"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:"Sign in to access the optimization dashboard"})]}),i.jsxs("div",{className:"bg-[var(--bg-secondary)] border border-[var(--border)] p-6 space-y-6",children:[n&&i.jsxs("div",{className:"flex items-start gap-3 p-3 bg-[var(--error)]/10 border border-[var(--error)]/20 text-[var(--error)]",children:[i.jsx(Zy,{size:18,className:"mt-0.5 flex-shrink-0"}),i.jsx("p",{className:"text-sm",children:n})]}),(e==null?void 0:e.mode)==="basic"&&i.jsxs("form",{onSubmit:p,className:"space-y-4",children:[i.jsxs("div",{className:"space-y-1",children:[i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:"Username"}),i.jsxs("div",{className:"relative",children:[i.jsx(wp,{size:16,className:"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)]"}),i.jsx("input",{type:"text",value:l,onChange:h=>o(h.target.value),className:"w-full bg-[var(--bg-primary)] border border-[var(--border)] pl-10 pr-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]",placeholder:"Enter username",autoComplete:"username",autoFocus:!0})]})]}),i.jsxs("div",{className:"space-y-1",children:[i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:"Password"}),i.jsxs("div",{className:"relative",children:[i.jsx(xp,{size:16,className:"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)]"}),i.jsx("input",{type:"password",value:u,onChange:h=>c(h.target.value),className:"w-full bg-[var(--bg-primary)] border border-[var(--border)] pl-10 pr-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]",placeholder:"Enter password",autoComplete:"current-password"})]})]}),i.jsx(K,{type:"submit",variant:"primary",className:"w-full justify-center",loading:t,disabled:!l||!u,children:"Sign In"})]}),(e==null?void 0:e.mode)==="github"&&i.jsxs("div",{className:"space-y-4",children:[i.jsx("p",{className:"text-sm text-[var(--text-secondary)] text-center",children:"Sign in with your GitHub account to continue"}),i.jsx(K,{onClick:f,variant:"secondary",className:"w-full justify-center",icon:fg,children:"Continue with GitHub"})]})]})]})})}function U0({children:e}){const{authConfig:t,isLoadingConfig:n,isAuthenticated:r,loadAuthConfig:s,handleOAuthCallback:a}=Iu();return k.useEffect(()=>{s()},[s]),k.useEffect(()=>{n||a()},[n,a]),n?i.jsx("div",{className:"min-h-screen bg-[var(--bg-primary)] flex items-center justify-center",children:i.jsxs("div",{className:"text-center",children:[i.jsx(Fi,{className:"w-8 h-8 animate-spin text-[var(--accent)] mx-auto mb-4"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:"Loading..."})]})}):t!=null&&t.enabled&&!r?i.jsx($0,{}):i.jsx(i.Fragment,{children:e})}function B0(){return i.jsx(Hy,{children:i.jsx(U0,{children:i.jsx(Iy,{children:i.jsxs(gt,{path:"/",element:i.jsx(a0,{}),children:[i.jsx(gt,{index:!0,element:i.jsx(gd,{})}),i.jsx(gt,{path:"agents",element:i.jsx(gd,{})}),i.jsx(gt,{path:"agents/:agentId",element:i.jsx(S0,{})}),i.jsx(gt,{path:"tasks",element:i.jsx(C0,{})}),i.jsx(gt,{path:"jobs",element:i.jsx(P0,{})}),i.jsx(gt,{path:"jobs/:jobId",element:i.jsx(I0,{})}),i.jsx(gt,{path:"runs/:runId",element:i.jsx(D0,{})}),i.jsx(gt,{path:"tests/:testId",element:i.jsx(A0,{})})]})})})})}const kd=localStorage.getItem("flow-theme");if(kd)try{const{state:e}=JSON.parse(kd);e!=null&&e.theme&&document.documentElement.setAttribute("data-theme",e.theme)}catch{}const Q0=new Lx({defaultOptions:{queries:{staleTime:5e3,refetchOnWindowFocus:!1}}});xl.createRoot(document.getElementById("root")).render(i.jsx(zo.StrictMode,{children:i.jsx(Rx,{client:Q0,children:i.jsx(B0,{})})})); diff --git a/src/flow/ui/ui/assets/index-BXe1ouql.css b/src/flow/ui/ui/assets/index-BXe1ouql.css new file mode 100644 index 0000000000000000000000000000000000000000..3336225bb930319444924786fbb3e6e1d04afc7a --- /dev/null +++ b/src/flow/ui/ui/assets/index-BXe1ouql.css @@ -0,0 +1 @@ +*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-3{left:.75rem}.top-0{top:0}.top-1\/2{top:50%}.z-10{z-index:10}.z-50{z-index:50}.col-span-2{grid-column:span 2 / span 2}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-80{max-height:20rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-0{min-height:0px}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-11{width:2.75rem}.w-12{width:3rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-\[200px\]{max-width:200px}.max-w-full{max-width:100%}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-1{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-6{--tw-translate-x: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize-y{resize:vertical}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/20{border-color:#22c55e33}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/20{border-color:#ef444433}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.border-transparent{border-color:transparent}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--border\)\]{background-color:var(--border)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-500\/5{background-color:#ef44440d}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-50{opacity:.5}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}} diff --git a/src/flow/ui/ui/assets/index-DNZOGIaU.js b/src/flow/ui/ui/assets/index-DNZOGIaU.js new file mode 100644 index 0000000000000000000000000000000000000000..3ea4bc0a164dc74f564f5ac2b664119d6f35bd6d --- /dev/null +++ b/src/flow/ui/ui/assets/index-DNZOGIaU.js @@ -0,0 +1,260 @@ +var Au=e=>{throw TypeError(e)};var $i=(e,t,n)=>t.has(e)||Au("Cannot "+n);var y=(e,t,n)=>($i(e,t,"read from private field"),n?n.call(e):t.get(e)),I=(e,t,n)=>t.has(e)?Au("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),R=(e,t,n,r)=>($i(e,t,"write to private field"),r?r.call(e,n):t.set(e,n),n),Q=(e,t,n)=>($i(e,t,"access private method"),n);var la=(e,t,n,r)=>({set _(s){R(e,t,s,n)},get _(){return y(e,t,r)}});function zp(e,t){for(var n=0;nr[s]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const a of s)if(a.type==="childList")for(const l of a.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&r(l)}).observe(document,{childList:!0,subtree:!0});function n(s){const a={};return s.integrity&&(a.integrity=s.integrity),s.referrerPolicy&&(a.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?a.credentials="include":s.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(s){if(s.ep)return;s.ep=!0;const a=n(s);fetch(s.href,a)}})();function Md(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var zd={exports:{}},mi={},Fd={exports:{}},W={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var ea=Symbol.for("react.element"),Fp=Symbol.for("react.portal"),Ip=Symbol.for("react.fragment"),Dp=Symbol.for("react.strict_mode"),Ap=Symbol.for("react.profiler"),$p=Symbol.for("react.provider"),Up=Symbol.for("react.context"),Bp=Symbol.for("react.forward_ref"),Qp=Symbol.for("react.suspense"),Vp=Symbol.for("react.memo"),Hp=Symbol.for("react.lazy"),$u=Symbol.iterator;function Kp(e){return e===null||typeof e!="object"?null:(e=$u&&e[$u]||e["@@iterator"],typeof e=="function"?e:null)}var Id={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Dd=Object.assign,Ad={};function qr(e,t,n){this.props=e,this.context=t,this.refs=Ad,this.updater=n||Id}qr.prototype.isReactComponent={};qr.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};qr.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function $d(){}$d.prototype=qr.prototype;function Oo(e,t,n){this.props=e,this.context=t,this.refs=Ad,this.updater=n||Id}var Lo=Oo.prototype=new $d;Lo.constructor=Oo;Dd(Lo,qr.prototype);Lo.isPureReactComponent=!0;var Uu=Array.isArray,Ud=Object.prototype.hasOwnProperty,Ro={current:null},Bd={key:!0,ref:!0,__self:!0,__source:!0};function Qd(e,t,n){var r,s={},a=null,l=null;if(t!=null)for(r in t.ref!==void 0&&(l=t.ref),t.key!==void 0&&(a=""+t.key),t)Ud.call(t,r)&&!Bd.hasOwnProperty(r)&&(s[r]=t[r]);var o=arguments.length-2;if(o===1)s.children=n;else if(1>>1,A=O[L];if(0>>1;Ls(fe,B))yes(lt,fe)?(O[L]=lt,O[ye]=B,L=ye):(O[L]=fe,O[$]=B,L=$);else if(yes(lt,B))O[L]=lt,O[ye]=B,L=ye;else break e}}return D}function s(O,D){var B=O.sortIndex-D.sortIndex;return B!==0?B:O.id-D.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var l=Date,o=l.now();e.unstable_now=function(){return l.now()-o}}var u=[],c=[],p=1,f=null,h=3,x=!1,w=!1,j=!1,S=typeof setTimeout=="function"?setTimeout:null,v=typeof clearTimeout=="function"?clearTimeout:null,d=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function m(O){for(var D=n(c);D!==null;){if(D.callback===null)r(c);else if(D.startTime<=O)r(c),D.sortIndex=D.expirationTime,t(u,D);else break;D=n(c)}}function g(O){if(j=!1,m(O),!w)if(n(u)!==null)w=!0,Pt(b);else{var D=n(c);D!==null&&He(g,D.startTime-O)}}function b(O,D){w=!1,j&&(j=!1,v(N),N=-1),x=!0;var B=h;try{for(m(D),f=n(u);f!==null&&(!(f.expirationTime>D)||O&&!H());){var L=f.callback;if(typeof L=="function"){f.callback=null,h=f.priorityLevel;var A=L(f.expirationTime<=D);D=e.unstable_now(),typeof A=="function"?f.callback=A:f===n(u)&&r(u),m(D)}else r(u);f=n(u)}if(f!==null)var T=!0;else{var $=n(c);$!==null&&He(g,$.startTime-D),T=!1}return T}finally{f=null,h=B,x=!1}}var _=!1,C=null,N=-1,M=5,P=-1;function H(){return!(e.unstable_now()-PO||125L?(O.sortIndex=B,t(c,O),n(u)===null&&O===n(c)&&(j?(v(N),N=-1):j=!0,He(g,B-L))):(O.sortIndex=A,t(u,O),w||x||(w=!0,Pt(b))),O},e.unstable_shouldYield=H,e.unstable_wrapCallback=function(O){var D=h;return function(){var B=h;h=D;try{return O.apply(this,arguments)}finally{h=B}}}})(qd);Wd.exports=qd;var sm=Wd.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var am=k,Ge=sm;function E(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),yl=Object.prototype.hasOwnProperty,im=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Qu={},Vu={};function lm(e){return yl.call(Vu,e)?!0:yl.call(Qu,e)?!1:im.test(e)?Vu[e]=!0:(Qu[e]=!0,!1)}function om(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function um(e,t,n,r){if(t===null||typeof t>"u"||om(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Ie(e,t,n,r,s,a,l){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=s,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=l}var Ce={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Ce[e]=new Ie(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Ce[t]=new Ie(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Ce[e]=new Ie(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Ce[e]=new Ie(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Ce[e]=new Ie(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Ce[e]=new Ie(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Ce[e]=new Ie(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Ce[e]=new Ie(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Ce[e]=new Ie(e,5,!1,e.toLowerCase(),null,!1,!1)});var Fo=/[\-:]([a-z])/g;function Io(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Fo,Io);Ce[t]=new Ie(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Fo,Io);Ce[t]=new Ie(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Fo,Io);Ce[t]=new Ie(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Ce[e]=new Ie(e,1,!1,e.toLowerCase(),null,!1,!1)});Ce.xlinkHref=new Ie("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Ce[e]=new Ie(e,1,!1,e.toLowerCase(),null,!0,!0)});function Do(e,t,n,r){var s=Ce.hasOwnProperty(t)?Ce[t]:null;(s!==null?s.type!==0:r||!(2o||s[l]!==a[o]){var u=` +`+s[l].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=l&&0<=o);break}}}finally{Qi=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?us(e):""}function cm(e){switch(e.tag){case 5:return us(e.type);case 16:return us("Lazy");case 13:return us("Suspense");case 19:return us("SuspenseList");case 0:case 2:case 15:return e=Vi(e.type,!1),e;case 11:return e=Vi(e.type.render,!1),e;case 1:return e=Vi(e.type,!0),e;default:return""}}function kl(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case cr:return"Fragment";case ur:return"Portal";case gl:return"Profiler";case Ao:return"StrictMode";case jl:return"Suspense";case wl:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Xd:return(e.displayName||"Context")+".Consumer";case Jd:return(e._context.displayName||"Context")+".Provider";case $o:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Uo:return t=e.displayName||null,t!==null?t:kl(e.type)||"Memo";case Gt:t=e._payload,e=e._init;try{return kl(e(t))}catch{}}return null}function dm(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return kl(t);case 8:return t===Ao?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Nn(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Zd(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function fm(e){var t=Zd(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var s=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(l){r=""+l,a.call(this,l)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(l){r=""+l},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function ca(e){e._valueTracker||(e._valueTracker=fm(e))}function ef(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Zd(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Ua(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Sl(e,t){var n=t.checked;return oe({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Ku(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Nn(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function tf(e,t){t=t.checked,t!=null&&Do(e,"checked",t,!1)}function Nl(e,t){tf(e,t);var n=Nn(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?_l(e,t.type,n):t.hasOwnProperty("defaultValue")&&_l(e,t.type,Nn(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Wu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function _l(e,t,n){(t!=="number"||Ua(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var cs=Array.isArray;function wr(e,t,n,r){if(e=e.options,t){t={};for(var s=0;s"+t.valueOf().toString()+"",t=da.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Cs(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var ps={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},hm=["Webkit","ms","Moz","O"];Object.keys(ps).forEach(function(e){hm.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),ps[t]=ps[e]})});function af(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||ps.hasOwnProperty(e)&&ps[e]?(""+t).trim():t+"px"}function lf(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,s=af(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,s):e[n]=s}}var pm=oe({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function El(e,t){if(t){if(pm[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(E(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(E(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(E(61))}if(t.style!=null&&typeof t.style!="object")throw Error(E(62))}}function Pl(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Tl=null;function Bo(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Ol=null,kr=null,Sr=null;function Ju(e){if(e=ra(e)){if(typeof Ol!="function")throw Error(E(280));var t=e.stateNode;t&&(t=ji(t),Ol(e.stateNode,e.type,t))}}function of(e){kr?Sr?Sr.push(e):Sr=[e]:kr=e}function uf(){if(kr){var e=kr,t=Sr;if(Sr=kr=null,Ju(e),t)for(e=0;e>>=0,e===0?32:31-(_m(e)/Cm|0)|0}var fa=64,ha=4194304;function ds(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Ha(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,s=e.suspendedLanes,a=e.pingedLanes,l=n&268435455;if(l!==0){var o=l&~s;o!==0?r=ds(o):(a&=l,a!==0&&(r=ds(a)))}else l=n&~s,l!==0?r=ds(l):a!==0&&(r=ds(a));if(r===0)return 0;if(t!==0&&t!==r&&!(t&s)&&(s=r&-r,a=t&-t,s>=a||s===16&&(a&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function ta(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-mt(t),e[t]=n}function Tm(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=vs),ac=" ",ic=!1;function Pf(e,t){switch(e){case"keyup":return sv.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Tf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var dr=!1;function iv(e,t){switch(e){case"compositionend":return Tf(t);case"keypress":return t.which!==32?null:(ic=!0,ac);case"textInput":return e=t.data,e===ac&&ic?null:e;default:return null}}function lv(e,t){if(dr)return e==="compositionend"||!Jo&&Pf(e,t)?(e=bf(),Oa=Wo=un=null,dr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=cc(n)}}function Mf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Mf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function zf(){for(var e=window,t=Ua();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ua(e.document)}return t}function Xo(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function vv(e){var t=zf(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Mf(n.ownerDocument.documentElement,n)){if(r!==null&&Xo(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var s=n.textContent.length,a=Math.min(r.start,s);r=r.end===void 0?a:Math.min(r.end,s),!e.extend&&a>r&&(s=r,r=a,a=s),s=dc(n,a);var l=dc(n,r);s&&l&&(e.rangeCount!==1||e.anchorNode!==s.node||e.anchorOffset!==s.offset||e.focusNode!==l.node||e.focusOffset!==l.offset)&&(t=t.createRange(),t.setStart(s.node,s.offset),e.removeAllRanges(),a>r?(e.addRange(t),e.extend(l.node,l.offset)):(t.setEnd(l.node,l.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,fr=null,Il=null,ys=null,Dl=!1;function fc(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Dl||fr==null||fr!==Ua(r)||(r=fr,"selectionStart"in r&&Xo(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),ys&&Ls(ys,r)||(ys=r,r=qa(Il,"onSelect"),0mr||(e.current=Vl[mr],Vl[mr]=null,mr--)}function ne(e,t){mr++,Vl[mr]=e.current,e.current=t}var _n={},Oe=bn(_n),Be=bn(!1),Xn=_n;function $r(e,t){var n=e.type.contextTypes;if(!n)return _n;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var s={},a;for(a in n)s[a]=t[a];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=s),s}function Qe(e){return e=e.childContextTypes,e!=null}function Ja(){se(Be),se(Oe)}function gc(e,t,n){if(Oe.current!==_n)throw Error(E(168));ne(Oe,t),ne(Be,n)}function Vf(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var s in r)if(!(s in t))throw Error(E(108,dm(e)||"Unknown",s));return oe({},n,r)}function Xa(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||_n,Xn=Oe.current,ne(Oe,e),ne(Be,Be.current),!0}function jc(e,t,n){var r=e.stateNode;if(!r)throw Error(E(169));n?(e=Vf(e,t,Xn),r.__reactInternalMemoizedMergedChildContext=e,se(Be),se(Oe),ne(Oe,e)):se(Be),ne(Be,n)}var Lt=null,wi=!1,sl=!1;function Hf(e){Lt===null?Lt=[e]:Lt.push(e)}function Ev(e){wi=!0,Hf(e)}function En(){if(!sl&&Lt!==null){sl=!0;var e=0,t=Z;try{var n=Lt;for(Z=1;e>=l,s-=l,It=1<<32-mt(t)+s|n<N?(M=C,C=null):M=C.sibling;var P=h(v,C,m[N],g);if(P===null){C===null&&(C=M);break}e&&C&&P.alternate===null&&t(v,C),d=a(P,d,N),_===null?b=P:_.sibling=P,_=P,C=M}if(N===m.length)return n(v,C),ae&&On(v,N),b;if(C===null){for(;NN?(M=C,C=null):M=C.sibling;var H=h(v,C,P.value,g);if(H===null){C===null&&(C=M);break}e&&C&&H.alternate===null&&t(v,C),d=a(H,d,N),_===null?b=H:_.sibling=H,_=H,C=M}if(P.done)return n(v,C),ae&&On(v,N),b;if(C===null){for(;!P.done;N++,P=m.next())P=f(v,P.value,g),P!==null&&(d=a(P,d,N),_===null?b=P:_.sibling=P,_=P);return ae&&On(v,N),b}for(C=r(v,C);!P.done;N++,P=m.next())P=x(C,v,N,P.value,g),P!==null&&(e&&P.alternate!==null&&C.delete(P.key===null?N:P.key),d=a(P,d,N),_===null?b=P:_.sibling=P,_=P);return e&&C.forEach(function(F){return t(v,F)}),ae&&On(v,N),b}function S(v,d,m,g){if(typeof m=="object"&&m!==null&&m.type===cr&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case ua:e:{for(var b=m.key,_=d;_!==null;){if(_.key===b){if(b=m.type,b===cr){if(_.tag===7){n(v,_.sibling),d=s(_,m.props.children),d.return=v,v=d;break e}}else if(_.elementType===b||typeof b=="object"&&b!==null&&b.$$typeof===Gt&&Sc(b)===_.type){n(v,_.sibling),d=s(_,m.props),d.ref=as(v,_,m),d.return=v,v=d;break e}n(v,_);break}else t(v,_);_=_.sibling}m.type===cr?(d=qn(m.props.children,v.mode,g,m.key),d.return=v,v=d):(g=Aa(m.type,m.key,m.props,null,v.mode,g),g.ref=as(v,d,m),g.return=v,v=g)}return l(v);case ur:e:{for(_=m.key;d!==null;){if(d.key===_)if(d.tag===4&&d.stateNode.containerInfo===m.containerInfo&&d.stateNode.implementation===m.implementation){n(v,d.sibling),d=s(d,m.children||[]),d.return=v,v=d;break e}else{n(v,d);break}else t(v,d);d=d.sibling}d=fl(m,v.mode,g),d.return=v,v=d}return l(v);case Gt:return _=m._init,S(v,d,_(m._payload),g)}if(cs(m))return w(v,d,m,g);if(es(m))return j(v,d,m,g);ja(v,m)}return typeof m=="string"&&m!==""||typeof m=="number"?(m=""+m,d!==null&&d.tag===6?(n(v,d.sibling),d=s(d,m),d.return=v,v=d):(n(v,d),d=dl(m,v.mode,g),d.return=v,v=d),l(v)):n(v,d)}return S}var Br=Gf(!0),Jf=Gf(!1),ei=bn(null),ti=null,yr=null,tu=null;function nu(){tu=yr=ti=null}function ru(e){var t=ei.current;se(ei),e._currentValue=t}function Wl(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function _r(e,t){ti=e,tu=yr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Ue=!0),e.firstContext=null)}function at(e){var t=e._currentValue;if(tu!==e)if(e={context:e,memoizedValue:t,next:null},yr===null){if(ti===null)throw Error(E(308));yr=e,ti.dependencies={lanes:0,firstContext:e}}else yr=yr.next=e;return t}var zn=null;function su(e){zn===null?zn=[e]:zn.push(e)}function Xf(e,t,n,r){var s=t.interleaved;return s===null?(n.next=n,su(t)):(n.next=s.next,s.next=n),t.interleaved=n,Qt(e,r)}function Qt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Jt=!1;function au(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Yf(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function At(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function yn(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,J&2){var s=r.pending;return s===null?t.next=t:(t.next=s.next,s.next=t),r.pending=t,Qt(e,n)}return s=r.interleaved,s===null?(t.next=t,su(r)):(t.next=s.next,s.next=t),r.interleaved=t,Qt(e,n)}function Ra(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Vo(e,n)}}function Nc(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var s=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var l={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};a===null?s=a=l:a=a.next=l,n=n.next}while(n!==null);a===null?s=a=t:a=a.next=t}else s=a=t;n={baseState:r.baseState,firstBaseUpdate:s,lastBaseUpdate:a,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ni(e,t,n,r){var s=e.updateQueue;Jt=!1;var a=s.firstBaseUpdate,l=s.lastBaseUpdate,o=s.shared.pending;if(o!==null){s.shared.pending=null;var u=o,c=u.next;u.next=null,l===null?a=c:l.next=c,l=u;var p=e.alternate;p!==null&&(p=p.updateQueue,o=p.lastBaseUpdate,o!==l&&(o===null?p.firstBaseUpdate=c:o.next=c,p.lastBaseUpdate=u))}if(a!==null){var f=s.baseState;l=0,p=c=u=null,o=a;do{var h=o.lane,x=o.eventTime;if((r&h)===h){p!==null&&(p=p.next={eventTime:x,lane:0,tag:o.tag,payload:o.payload,callback:o.callback,next:null});e:{var w=e,j=o;switch(h=t,x=n,j.tag){case 1:if(w=j.payload,typeof w=="function"){f=w.call(x,f,h);break e}f=w;break e;case 3:w.flags=w.flags&-65537|128;case 0:if(w=j.payload,h=typeof w=="function"?w.call(x,f,h):w,h==null)break e;f=oe({},f,h);break e;case 2:Jt=!0}}o.callback!==null&&o.lane!==0&&(e.flags|=64,h=s.effects,h===null?s.effects=[o]:h.push(o))}else x={eventTime:x,lane:h,tag:o.tag,payload:o.payload,callback:o.callback,next:null},p===null?(c=p=x,u=f):p=p.next=x,l|=h;if(o=o.next,o===null){if(o=s.shared.pending,o===null)break;h=o,o=h.next,h.next=null,s.lastBaseUpdate=h,s.shared.pending=null}}while(!0);if(p===null&&(u=f),s.baseState=u,s.firstBaseUpdate=c,s.lastBaseUpdate=p,t=s.shared.interleaved,t!==null){s=t;do l|=s.lane,s=s.next;while(s!==t)}else a===null&&(s.shared.lanes=0);er|=l,e.lanes=l,e.memoizedState=f}}function _c(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=il.transition;il.transition={};try{e(!1),t()}finally{Z=n,il.transition=r}}function mh(){return it().memoizedState}function Lv(e,t,n){var r=jn(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},vh(e))xh(t,n);else if(n=Xf(e,t,n,r),n!==null){var s=ze();vt(n,e,r,s),yh(n,t,r)}}function Rv(e,t,n){var r=jn(e),s={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(vh(e))xh(t,s);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var l=t.lastRenderedState,o=a(l,n);if(s.hasEagerState=!0,s.eagerState=o,xt(o,l)){var u=t.interleaved;u===null?(s.next=s,su(t)):(s.next=u.next,u.next=s),t.interleaved=s;return}}catch{}finally{}n=Xf(e,t,s,r),n!==null&&(s=ze(),vt(n,e,r,s),yh(n,t,r))}}function vh(e){var t=e.alternate;return e===le||t!==null&&t===le}function xh(e,t){gs=si=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function yh(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Vo(e,n)}}var ai={readContext:at,useCallback:be,useContext:be,useEffect:be,useImperativeHandle:be,useInsertionEffect:be,useLayoutEffect:be,useMemo:be,useReducer:be,useRef:be,useState:be,useDebugValue:be,useDeferredValue:be,useTransition:be,useMutableSource:be,useSyncExternalStore:be,useId:be,unstable_isNewReconciler:!1},Mv={readContext:at,useCallback:function(e,t){return jt().memoizedState=[e,t===void 0?null:t],e},useContext:at,useEffect:bc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,za(4194308,4,ch.bind(null,t,e),n)},useLayoutEffect:function(e,t){return za(4194308,4,e,t)},useInsertionEffect:function(e,t){return za(4,2,e,t)},useMemo:function(e,t){var n=jt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=jt();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Lv.bind(null,le,e),[r.memoizedState,e]},useRef:function(e){var t=jt();return e={current:e},t.memoizedState=e},useState:Cc,useDebugValue:hu,useDeferredValue:function(e){return jt().memoizedState=e},useTransition:function(){var e=Cc(!1),t=e[0];return e=Ov.bind(null,e[1]),jt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=le,s=jt();if(ae){if(n===void 0)throw Error(E(407));n=n()}else{if(n=t(),we===null)throw Error(E(349));Zn&30||nh(r,t,n)}s.memoizedState=n;var a={value:n,getSnapshot:t};return s.queue=a,bc(sh.bind(null,r,a,e),[e]),r.flags|=2048,$s(9,rh.bind(null,r,a,n,t),void 0,null),n},useId:function(){var e=jt(),t=we.identifierPrefix;if(ae){var n=Dt,r=It;n=(r&~(1<<32-mt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Ds++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=l.createElement(n,{is:r.is}):(e=l.createElement(n),n==="select"&&(l=e,r.multiple?l.multiple=!0:r.size&&(l.size=r.size))):e=l.createElementNS(e,n),e[Nt]=t,e[zs]=r,Eh(e,t,!1,!1),t.stateNode=e;e:{switch(l=Pl(n,r),n){case"dialog":re("cancel",e),re("close",e),s=r;break;case"iframe":case"object":case"embed":re("load",e),s=r;break;case"video":case"audio":for(s=0;sHr&&(t.flags|=128,r=!0,is(a,!1),t.lanes=4194304)}else{if(!r)if(e=ri(l),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),is(a,!0),a.tail===null&&a.tailMode==="hidden"&&!l.alternate&&!ae)return Ee(t),null}else 2*he()-a.renderingStartTime>Hr&&n!==1073741824&&(t.flags|=128,r=!0,is(a,!1),t.lanes=4194304);a.isBackwards?(l.sibling=t.child,t.child=l):(n=a.last,n!==null?n.sibling=l:t.child=l,a.last=l)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=he(),t.sibling=null,n=ie.current,ne(ie,r?n&1|2:n&1),t):(Ee(t),null);case 22:case 23:return gu(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Ke&1073741824&&(Ee(t),t.subtreeFlags&6&&(t.flags|=8192)):Ee(t),null;case 24:return null;case 25:return null}throw Error(E(156,t.tag))}function Bv(e,t){switch(Zo(t),t.tag){case 1:return Qe(t.type)&&Ja(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Qr(),se(Be),se(Oe),ou(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return lu(t),null;case 13:if(se(ie),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(E(340));Ur()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return se(ie),null;case 4:return Qr(),null;case 10:return ru(t.type._context),null;case 22:case 23:return gu(),null;case 24:return null;default:return null}}var ka=!1,Te=!1,Qv=typeof WeakSet=="function"?WeakSet:Set,z=null;function gr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){ce(e,t,r)}else n.current=null}function no(e,t,n){try{n()}catch(r){ce(e,t,r)}}var Dc=!1;function Vv(e,t){if(Al=Ka,e=zf(),Xo(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var s=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break e}var l=0,o=-1,u=-1,c=0,p=0,f=e,h=null;t:for(;;){for(var x;f!==n||s!==0&&f.nodeType!==3||(o=l+s),f!==a||r!==0&&f.nodeType!==3||(u=l+r),f.nodeType===3&&(l+=f.nodeValue.length),(x=f.firstChild)!==null;)h=f,f=x;for(;;){if(f===e)break t;if(h===n&&++c===s&&(o=l),h===a&&++p===r&&(u=l),(x=f.nextSibling)!==null)break;f=h,h=f.parentNode}f=x}n=o===-1||u===-1?null:{start:o,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for($l={focusedElem:e,selectionRange:n},Ka=!1,z=t;z!==null;)if(t=z,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,z=e;else for(;z!==null;){t=z;try{var w=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(w!==null){var j=w.memoizedProps,S=w.memoizedState,v=t.stateNode,d=v.getSnapshotBeforeUpdate(t.elementType===t.type?j:ut(t.type,j),S);v.__reactInternalSnapshotBeforeUpdate=d}break;case 3:var m=t.stateNode.containerInfo;m.nodeType===1?m.textContent="":m.nodeType===9&&m.documentElement&&m.removeChild(m.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(E(163))}}catch(g){ce(t,t.return,g)}if(e=t.sibling,e!==null){e.return=t.return,z=e;break}z=t.return}return w=Dc,Dc=!1,w}function js(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var s=r=r.next;do{if((s.tag&e)===e){var a=s.destroy;s.destroy=void 0,a!==void 0&&no(t,n,a)}s=s.next}while(s!==r)}}function Ni(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ro(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Oh(e){var t=e.alternate;t!==null&&(e.alternate=null,Oh(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Nt],delete t[zs],delete t[Ql],delete t[Cv],delete t[bv])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Lh(e){return e.tag===5||e.tag===3||e.tag===4}function Ac(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Lh(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function so(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Ga));else if(r!==4&&(e=e.child,e!==null))for(so(e,t,n),e=e.sibling;e!==null;)so(e,t,n),e=e.sibling}function ao(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ao(e,t,n),e=e.sibling;e!==null;)ao(e,t,n),e=e.sibling}var Se=null,ft=!1;function Wt(e,t,n){for(n=n.child;n!==null;)Rh(e,t,n),n=n.sibling}function Rh(e,t,n){if(_t&&typeof _t.onCommitFiberUnmount=="function")try{_t.onCommitFiberUnmount(vi,n)}catch{}switch(n.tag){case 5:Te||gr(n,t);case 6:var r=Se,s=ft;Se=null,Wt(e,t,n),Se=r,ft=s,Se!==null&&(ft?(e=Se,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Se.removeChild(n.stateNode));break;case 18:Se!==null&&(ft?(e=Se,n=n.stateNode,e.nodeType===8?rl(e.parentNode,n):e.nodeType===1&&rl(e,n),Ts(e)):rl(Se,n.stateNode));break;case 4:r=Se,s=ft,Se=n.stateNode.containerInfo,ft=!0,Wt(e,t,n),Se=r,ft=s;break;case 0:case 11:case 14:case 15:if(!Te&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){s=r=r.next;do{var a=s,l=a.destroy;a=a.tag,l!==void 0&&(a&2||a&4)&&no(n,t,l),s=s.next}while(s!==r)}Wt(e,t,n);break;case 1:if(!Te&&(gr(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(o){ce(n,t,o)}Wt(e,t,n);break;case 21:Wt(e,t,n);break;case 22:n.mode&1?(Te=(r=Te)||n.memoizedState!==null,Wt(e,t,n),Te=r):Wt(e,t,n);break;default:Wt(e,t,n)}}function $c(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Qv),t.forEach(function(r){var s=Zv.bind(null,e,r);n.has(r)||(n.add(r),r.then(s,s))})}}function ot(e,t){var n=t.deletions;if(n!==null)for(var r=0;rs&&(s=l),r&=~a}if(r=s,r=he()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Kv(r/1960))-r,10e?16:e,cn===null)var r=!1;else{if(e=cn,cn=null,oi=0,J&6)throw Error(E(331));var s=J;for(J|=4,z=e.current;z!==null;){var a=z,l=a.child;if(z.flags&16){var o=a.deletions;if(o!==null){for(var u=0;uhe()-xu?Wn(e,0):vu|=n),Ve(e,t)}function Uh(e,t){t===0&&(e.mode&1?(t=ha,ha<<=1,!(ha&130023424)&&(ha=4194304)):t=1);var n=ze();e=Qt(e,t),e!==null&&(ta(e,t,n),Ve(e,n))}function Yv(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Uh(e,n)}function Zv(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,s=e.memoizedState;s!==null&&(n=s.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(E(314))}r!==null&&r.delete(t),Uh(e,n)}var Bh;Bh=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Be.current)Ue=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return Ue=!1,$v(e,t,n);Ue=!!(e.flags&131072)}else Ue=!1,ae&&t.flags&1048576&&Kf(t,Za,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Fa(e,t),e=t.pendingProps;var s=$r(t,Oe.current);_r(t,n),s=cu(null,t,r,e,s,n);var a=du();return t.flags|=1,typeof s=="object"&&s!==null&&typeof s.render=="function"&&s.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Qe(r)?(a=!0,Xa(t)):a=!1,t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,au(t),s.updater=Si,t.stateNode=s,s._reactInternals=t,Gl(t,r,e,n),t=Yl(null,t,r,!0,a,n)):(t.tag=0,ae&&a&&Yo(t),Re(null,t,s,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Fa(e,t),e=t.pendingProps,s=r._init,r=s(r._payload),t.type=r,s=t.tag=tx(r),e=ut(r,e),s){case 0:t=Xl(null,t,r,e,n);break e;case 1:t=zc(null,t,r,e,n);break e;case 11:t=Rc(null,t,r,e,n);break e;case 14:t=Mc(null,t,r,ut(r.type,e),n);break e}throw Error(E(306,r,""))}return t;case 0:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Xl(e,t,r,s,n);case 1:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),zc(e,t,r,s,n);case 3:e:{if(_h(t),e===null)throw Error(E(387));r=t.pendingProps,a=t.memoizedState,s=a.element,Yf(e,t),ni(t,r,null,n);var l=t.memoizedState;if(r=l.element,a.isDehydrated)if(a={element:r,isDehydrated:!1,cache:l.cache,pendingSuspenseBoundaries:l.pendingSuspenseBoundaries,transitions:l.transitions},t.updateQueue.baseState=a,t.memoizedState=a,t.flags&256){s=Vr(Error(E(423)),t),t=Fc(e,t,r,n,s);break e}else if(r!==s){s=Vr(Error(E(424)),t),t=Fc(e,t,r,n,s);break e}else for(We=xn(t.stateNode.containerInfo.firstChild),qe=t,ae=!0,ht=null,n=Jf(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Ur(),r===s){t=Vt(e,t,n);break e}Re(e,t,r,n)}t=t.child}return t;case 5:return Zf(t),e===null&&Kl(t),r=t.type,s=t.pendingProps,a=e!==null?e.memoizedProps:null,l=s.children,Ul(r,s)?l=null:a!==null&&Ul(r,a)&&(t.flags|=32),Nh(e,t),Re(e,t,l,n),t.child;case 6:return e===null&&Kl(t),null;case 13:return Ch(e,t,n);case 4:return iu(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Br(t,null,r,n):Re(e,t,r,n),t.child;case 11:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Rc(e,t,r,s,n);case 7:return Re(e,t,t.pendingProps,n),t.child;case 8:return Re(e,t,t.pendingProps.children,n),t.child;case 12:return Re(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,s=t.pendingProps,a=t.memoizedProps,l=s.value,ne(ei,r._currentValue),r._currentValue=l,a!==null)if(xt(a.value,l)){if(a.children===s.children&&!Be.current){t=Vt(e,t,n);break e}}else for(a=t.child,a!==null&&(a.return=t);a!==null;){var o=a.dependencies;if(o!==null){l=a.child;for(var u=o.firstContext;u!==null;){if(u.context===r){if(a.tag===1){u=At(-1,n&-n),u.tag=2;var c=a.updateQueue;if(c!==null){c=c.shared;var p=c.pending;p===null?u.next=u:(u.next=p.next,p.next=u),c.pending=u}}a.lanes|=n,u=a.alternate,u!==null&&(u.lanes|=n),Wl(a.return,n,t),o.lanes|=n;break}u=u.next}}else if(a.tag===10)l=a.type===t.type?null:a.child;else if(a.tag===18){if(l=a.return,l===null)throw Error(E(341));l.lanes|=n,o=l.alternate,o!==null&&(o.lanes|=n),Wl(l,n,t),l=a.sibling}else l=a.child;if(l!==null)l.return=a;else for(l=a;l!==null;){if(l===t){l=null;break}if(a=l.sibling,a!==null){a.return=l.return,l=a;break}l=l.return}a=l}Re(e,t,s.children,n),t=t.child}return t;case 9:return s=t.type,r=t.pendingProps.children,_r(t,n),s=at(s),r=r(s),t.flags|=1,Re(e,t,r,n),t.child;case 14:return r=t.type,s=ut(r,t.pendingProps),s=ut(r.type,s),Mc(e,t,r,s,n);case 15:return kh(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Fa(e,t),t.tag=1,Qe(r)?(e=!0,Xa(t)):e=!1,_r(t,n),gh(t,r,s),Gl(t,r,s,n),Yl(null,t,r,!0,e,n);case 19:return bh(e,t,n);case 22:return Sh(e,t,n)}throw Error(E(156,t.tag))};function Qh(e,t){return vf(e,t)}function ex(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function nt(e,t,n,r){return new ex(e,t,n,r)}function wu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function tx(e){if(typeof e=="function")return wu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===$o)return 11;if(e===Uo)return 14}return 2}function wn(e,t){var n=e.alternate;return n===null?(n=nt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Aa(e,t,n,r,s,a){var l=2;if(r=e,typeof e=="function")wu(e)&&(l=1);else if(typeof e=="string")l=5;else e:switch(e){case cr:return qn(n.children,s,a,t);case Ao:l=8,s|=8;break;case gl:return e=nt(12,n,t,s|2),e.elementType=gl,e.lanes=a,e;case jl:return e=nt(13,n,t,s),e.elementType=jl,e.lanes=a,e;case wl:return e=nt(19,n,t,s),e.elementType=wl,e.lanes=a,e;case Yd:return Ci(n,s,a,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Jd:l=10;break e;case Xd:l=9;break e;case $o:l=11;break e;case Uo:l=14;break e;case Gt:l=16,r=null;break e}throw Error(E(130,e==null?e:typeof e,""))}return t=nt(l,n,t,s),t.elementType=e,t.type=r,t.lanes=a,t}function qn(e,t,n,r){return e=nt(7,e,r,t),e.lanes=n,e}function Ci(e,t,n,r){return e=nt(22,e,r,t),e.elementType=Yd,e.lanes=n,e.stateNode={isHidden:!1},e}function dl(e,t,n){return e=nt(6,e,null,t),e.lanes=n,e}function fl(e,t,n){return t=nt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function nx(e,t,n,r,s){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Ki(0),this.expirationTimes=Ki(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Ki(0),this.identifierPrefix=r,this.onRecoverableError=s,this.mutableSourceEagerHydrationData=null}function ku(e,t,n,r,s,a,l,o,u){return e=new nx(e,t,n,o,u),t===1?(t=1,a===!0&&(t|=8)):t=0,a=nt(3,null,null,t),e.current=a,a.stateNode=e,a.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},au(a),e}function rx(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Wh)}catch(e){console.error(e)}}Wh(),Kd.exports=Je;var ox=Kd.exports,qc=ox;xl.createRoot=qc.createRoot,xl.hydrateRoot=qc.hydrateRoot;var Xr=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},ux={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},Zt,To,Sd,cx=(Sd=class{constructor(){I(this,Zt,ux);I(this,To,!1)}setTimeoutProvider(e){R(this,Zt,e)}setTimeout(e,t){return y(this,Zt).setTimeout(e,t)}clearTimeout(e){y(this,Zt).clearTimeout(e)}setInterval(e,t){return y(this,Zt).setInterval(e,t)}clearInterval(e){y(this,Zt).clearInterval(e)}},Zt=new WeakMap,To=new WeakMap,Sd),In=new cx;function dx(e){setTimeout(e,0)}var nr=typeof window>"u"||"Deno"in globalThis;function Me(){}function fx(e,t){return typeof e=="function"?e(t):e}function co(e){return typeof e=="number"&&e>=0&&e!==1/0}function qh(e,t){return Math.max(e+(t||0)-Date.now(),0)}function kn(e,t){return typeof e=="function"?e(t):e}function Ze(e,t){return typeof e=="function"?e(t):e}function Gc(e,t){const{type:n="all",exact:r,fetchStatus:s,predicate:a,queryKey:l,stale:o}=e;if(l){if(r){if(t.queryHash!==Cu(l,t.options))return!1}else if(!Bs(t.queryKey,l))return!1}if(n!=="all"){const u=t.isActive();if(n==="active"&&!u||n==="inactive"&&u)return!1}return!(typeof o=="boolean"&&t.isStale()!==o||s&&s!==t.state.fetchStatus||a&&!a(t))}function Jc(e,t){const{exact:n,status:r,predicate:s,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(rr(t.options.mutationKey)!==rr(a))return!1}else if(!Bs(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||s&&!s(t))}function Cu(e,t){return((t==null?void 0:t.queryKeyHashFn)||rr)(e)}function rr(e){return JSON.stringify(e,(t,n)=>fo(n)?Object.keys(n).sort().reduce((r,s)=>(r[s]=n[s],r),{}):n)}function Bs(e,t){return e===t?!0:typeof e!=typeof t?!1:e&&t&&typeof e=="object"&&typeof t=="object"?Object.keys(t).every(n=>Bs(e[n],t[n])):!1}var hx=Object.prototype.hasOwnProperty;function Gh(e,t,n=0){if(e===t)return e;if(n>500)return t;const r=Xc(e)&&Xc(t);if(!r&&!(fo(e)&&fo(t)))return t;const a=(r?e:Object.keys(e)).length,l=r?t:Object.keys(t),o=l.length,u=r?new Array(o):{};let c=0;for(let p=0;p{In.setTimeout(t,e)})}function ho(e,t,n){return typeof n.structuralSharing=="function"?n.structuralSharing(e,t):n.structuralSharing!==!1?Gh(e,t):t}function mx(e,t,n=0){const r=[...e,t];return n&&r.length>n?r.slice(1):r}function vx(e,t,n=0){const r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var bu=Symbol();function Jh(e,t){return!e.queryFn&&(t!=null&&t.initialPromise)?()=>t.initialPromise:!e.queryFn||e.queryFn===bu?()=>Promise.reject(new Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function Eu(e,t){return typeof e=="function"?e(...t):!!e}function xx(e,t,n){let r=!1,s;return Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(s??(s=t()),r||(r=!0,s.aborted?n():s.addEventListener("abort",n,{once:!0})),s)}),e}var Dn,en,br,Nd,yx=(Nd=class extends Xr{constructor(){super();I(this,Dn);I(this,en);I(this,br);R(this,br,t=>{if(!nr&&window.addEventListener){const n=()=>t();return window.addEventListener("visibilitychange",n,!1),()=>{window.removeEventListener("visibilitychange",n)}}})}onSubscribe(){y(this,en)||this.setEventListener(y(this,br))}onUnsubscribe(){var t;this.hasListeners()||((t=y(this,en))==null||t.call(this),R(this,en,void 0))}setEventListener(t){var n;R(this,br,t),(n=y(this,en))==null||n.call(this),R(this,en,t(r=>{typeof r=="boolean"?this.setFocused(r):this.onFocus()}))}setFocused(t){y(this,Dn)!==t&&(R(this,Dn,t),this.onFocus())}onFocus(){const t=this.isFocused();this.listeners.forEach(n=>{n(t)})}isFocused(){var t;return typeof y(this,Dn)=="boolean"?y(this,Dn):((t=globalThis.document)==null?void 0:t.visibilityState)!=="hidden"}},Dn=new WeakMap,en=new WeakMap,br=new WeakMap,Nd),Pu=new yx;function po(){let e,t;const n=new Promise((s,a)=>{e=s,t=a});n.status="pending",n.catch(()=>{});function r(s){Object.assign(n,s),delete n.resolve,delete n.reject}return n.resolve=s=>{r({status:"fulfilled",value:s}),e(s)},n.reject=s=>{r({status:"rejected",reason:s}),t(s)},n}var gx=dx;function jx(){let e=[],t=0,n=o=>{o()},r=o=>{o()},s=gx;const a=o=>{t?e.push(o):s(()=>{n(o)})},l=()=>{const o=e;e=[],o.length&&s(()=>{r(()=>{o.forEach(u=>{n(u)})})})};return{batch:o=>{let u;t++;try{u=o()}finally{t--,t||l()}return u},batchCalls:o=>(...u)=>{a(()=>{o(...u)})},schedule:a,setNotifyFunction:o=>{n=o},setBatchNotifyFunction:o=>{r=o},setScheduler:o=>{s=o}}}var ve=jx(),Er,tn,Pr,_d,wx=(_d=class extends Xr{constructor(){super();I(this,Er,!0);I(this,tn);I(this,Pr);R(this,Pr,t=>{if(!nr&&window.addEventListener){const n=()=>t(!0),r=()=>t(!1);return window.addEventListener("online",n,!1),window.addEventListener("offline",r,!1),()=>{window.removeEventListener("online",n),window.removeEventListener("offline",r)}}})}onSubscribe(){y(this,tn)||this.setEventListener(y(this,Pr))}onUnsubscribe(){var t;this.hasListeners()||((t=y(this,tn))==null||t.call(this),R(this,tn,void 0))}setEventListener(t){var n;R(this,Pr,t),(n=y(this,tn))==null||n.call(this),R(this,tn,t(this.setOnline.bind(this)))}setOnline(t){y(this,Er)!==t&&(R(this,Er,t),this.listeners.forEach(r=>{r(t)}))}isOnline(){return y(this,Er)}},Er=new WeakMap,tn=new WeakMap,Pr=new WeakMap,_d),fi=new wx;function kx(e){return Math.min(1e3*2**e,3e4)}function Xh(e){return(e??"online")==="online"?fi.isOnline():!0}var mo=class extends Error{constructor(e){super("CancelledError"),this.revert=e==null?void 0:e.revert,this.silent=e==null?void 0:e.silent}};function Yh(e){let t=!1,n=0,r;const s=po(),a=()=>s.status!=="pending",l=j=>{var S;if(!a()){const v=new mo(j);h(v),(S=e.onCancel)==null||S.call(e,v)}},o=()=>{t=!0},u=()=>{t=!1},c=()=>Pu.isFocused()&&(e.networkMode==="always"||fi.isOnline())&&e.canRun(),p=()=>Xh(e.networkMode)&&e.canRun(),f=j=>{a()||(r==null||r(),s.resolve(j))},h=j=>{a()||(r==null||r(),s.reject(j))},x=()=>new Promise(j=>{var S;r=v=>{(a()||c())&&j(v)},(S=e.onPause)==null||S.call(e)}).then(()=>{var j;r=void 0,a()||(j=e.onContinue)==null||j.call(e)}),w=()=>{if(a())return;let j;const S=n===0?e.initialPromise:void 0;try{j=S??e.fn()}catch(v){j=Promise.reject(v)}Promise.resolve(j).then(f).catch(v=>{var _;if(a())return;const d=e.retry??(nr?0:3),m=e.retryDelay??kx,g=typeof m=="function"?m(n,v):m,b=d===!0||typeof d=="number"&&nc()?void 0:x()).then(()=>{t?h(v):w()})})};return{promise:s,status:()=>s.status,cancel:l,continue:()=>(r==null||r(),s),cancelRetry:o,continueRetry:u,canStart:p,start:()=>(p()?w():x().then(w),s)}}var An,Cd,Zh=(Cd=class{constructor(){I(this,An)}destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),co(this.gcTime)&&R(this,An,In.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(nr?1/0:5*60*1e3))}clearGcTimeout(){y(this,An)&&(In.clearTimeout(y(this,An)),R(this,An,void 0))}},An=new WeakMap,Cd),$n,Tr,Ye,Un,ge,Gs,Bn,ct,Tt,bd,Sx=(bd=class extends Zh{constructor(t){super();I(this,ct);I(this,$n);I(this,Tr);I(this,Ye);I(this,Un);I(this,ge);I(this,Gs);I(this,Bn);R(this,Bn,!1),R(this,Gs,t.defaultOptions),this.setOptions(t.options),this.observers=[],R(this,Un,t.client),R(this,Ye,y(this,Un).getQueryCache()),this.queryKey=t.queryKey,this.queryHash=t.queryHash,R(this,$n,ed(this.options)),this.state=t.state??y(this,$n),this.scheduleGc()}get meta(){return this.options.meta}get promise(){var t;return(t=y(this,ge))==null?void 0:t.promise}setOptions(t){if(this.options={...y(this,Gs),...t},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){const n=ed(this.options);n.data!==void 0&&(this.setState(Zc(n.data,n.dataUpdatedAt)),R(this,$n,n))}}optionalRemove(){!this.observers.length&&this.state.fetchStatus==="idle"&&y(this,Ye).remove(this)}setData(t,n){const r=ho(this.state.data,t,this.options);return Q(this,ct,Tt).call(this,{data:r,type:"success",dataUpdatedAt:n==null?void 0:n.updatedAt,manual:n==null?void 0:n.manual}),r}setState(t,n){Q(this,ct,Tt).call(this,{type:"setState",state:t,setStateOptions:n})}cancel(t){var r,s;const n=(r=y(this,ge))==null?void 0:r.promise;return(s=y(this,ge))==null||s.cancel(t),n?n.then(Me).catch(Me):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}reset(){this.destroy(),this.setState(y(this,$n))}isActive(){return this.observers.some(t=>Ze(t.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===bu||this.state.dataUpdateCount+this.state.errorUpdateCount===0}isStatic(){return this.getObserversCount()>0?this.observers.some(t=>kn(t.options.staleTime,this)==="static"):!1}isStale(){return this.getObserversCount()>0?this.observers.some(t=>t.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(t=0){return this.state.data===void 0?!0:t==="static"?!1:this.state.isInvalidated?!0:!qh(this.state.dataUpdatedAt,t)}onFocus(){var n;const t=this.observers.find(r=>r.shouldFetchOnWindowFocus());t==null||t.refetch({cancelRefetch:!1}),(n=y(this,ge))==null||n.continue()}onOnline(){var n;const t=this.observers.find(r=>r.shouldFetchOnReconnect());t==null||t.refetch({cancelRefetch:!1}),(n=y(this,ge))==null||n.continue()}addObserver(t){this.observers.includes(t)||(this.observers.push(t),this.clearGcTimeout(),y(this,Ye).notify({type:"observerAdded",query:this,observer:t}))}removeObserver(t){this.observers.includes(t)&&(this.observers=this.observers.filter(n=>n!==t),this.observers.length||(y(this,ge)&&(y(this,Bn)?y(this,ge).cancel({revert:!0}):y(this,ge).cancelRetry()),this.scheduleGc()),y(this,Ye).notify({type:"observerRemoved",query:this,observer:t}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||Q(this,ct,Tt).call(this,{type:"invalidate"})}async fetch(t,n){var u,c,p,f,h,x,w,j,S,v,d,m;if(this.state.fetchStatus!=="idle"&&((u=y(this,ge))==null?void 0:u.status())!=="rejected"){if(this.state.data!==void 0&&(n!=null&&n.cancelRefetch))this.cancel({silent:!0});else if(y(this,ge))return y(this,ge).continueRetry(),y(this,ge).promise}if(t&&this.setOptions(t),!this.options.queryFn){const g=this.observers.find(b=>b.options.queryFn);g&&this.setOptions(g.options)}const r=new AbortController,s=g=>{Object.defineProperty(g,"signal",{enumerable:!0,get:()=>(R(this,Bn,!0),r.signal)})},a=()=>{const g=Jh(this.options,n),_=(()=>{const C={client:y(this,Un),queryKey:this.queryKey,meta:this.meta};return s(C),C})();return R(this,Bn,!1),this.options.persister?this.options.persister(g,_,this):g(_)},o=(()=>{const g={fetchOptions:n,options:this.options,queryKey:this.queryKey,client:y(this,Un),state:this.state,fetchFn:a};return s(g),g})();(c=this.options.behavior)==null||c.onFetch(o,this),R(this,Tr,this.state),(this.state.fetchStatus==="idle"||this.state.fetchMeta!==((p=o.fetchOptions)==null?void 0:p.meta))&&Q(this,ct,Tt).call(this,{type:"fetch",meta:(f=o.fetchOptions)==null?void 0:f.meta}),R(this,ge,Yh({initialPromise:n==null?void 0:n.initialPromise,fn:o.fetchFn,onCancel:g=>{g instanceof mo&&g.revert&&this.setState({...y(this,Tr),fetchStatus:"idle"}),r.abort()},onFail:(g,b)=>{Q(this,ct,Tt).call(this,{type:"failed",failureCount:g,error:b})},onPause:()=>{Q(this,ct,Tt).call(this,{type:"pause"})},onContinue:()=>{Q(this,ct,Tt).call(this,{type:"continue"})},retry:o.options.retry,retryDelay:o.options.retryDelay,networkMode:o.options.networkMode,canRun:()=>!0}));try{const g=await y(this,ge).start();if(g===void 0)throw new Error(`${this.queryHash} data is undefined`);return this.setData(g),(x=(h=y(this,Ye).config).onSuccess)==null||x.call(h,g,this),(j=(w=y(this,Ye).config).onSettled)==null||j.call(w,g,this.state.error,this),g}catch(g){if(g instanceof mo){if(g.silent)return y(this,ge).promise;if(g.revert){if(this.state.data===void 0)throw g;return this.state.data}}throw Q(this,ct,Tt).call(this,{type:"error",error:g}),(v=(S=y(this,Ye).config).onError)==null||v.call(S,g,this),(m=(d=y(this,Ye).config).onSettled)==null||m.call(d,this.state.data,g,this),g}finally{this.scheduleGc()}}},$n=new WeakMap,Tr=new WeakMap,Ye=new WeakMap,Un=new WeakMap,ge=new WeakMap,Gs=new WeakMap,Bn=new WeakMap,ct=new WeakSet,Tt=function(t){const n=r=>{switch(t.type){case"failed":return{...r,fetchFailureCount:t.failureCount,fetchFailureReason:t.error};case"pause":return{...r,fetchStatus:"paused"};case"continue":return{...r,fetchStatus:"fetching"};case"fetch":return{...r,...ep(r.data,this.options),fetchMeta:t.meta??null};case"success":const s={...r,...Zc(t.data,t.dataUpdatedAt),dataUpdateCount:r.dataUpdateCount+1,...!t.manual&&{fetchStatus:"idle",fetchFailureCount:0,fetchFailureReason:null}};return R(this,Tr,t.manual?s:void 0),s;case"error":const a=t.error;return{...r,error:a,errorUpdateCount:r.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:r.fetchFailureCount+1,fetchFailureReason:a,fetchStatus:"idle",status:"error",isInvalidated:!0};case"invalidate":return{...r,isInvalidated:!0};case"setState":return{...r,...t.state}}};this.state=n(this.state),ve.batch(()=>{this.observers.forEach(r=>{r.onQueryUpdate()}),y(this,Ye).notify({query:this,type:"updated",action:t})})},bd);function ep(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:Xh(t.networkMode)?"fetching":"paused",...e===void 0&&{error:null,status:"pending"}}}function Zc(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:"success"}}function ed(e){const t=typeof e.initialData=="function"?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt=="function"?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?"success":"pending",fetchStatus:"idle"}}var De,G,Js,Le,Qn,Or,Rt,nn,Xs,Lr,Rr,Vn,Hn,rn,Mr,Y,hs,vo,xo,yo,go,jo,wo,ko,tp,Ed,Nx=(Ed=class extends Xr{constructor(t,n){super();I(this,Y);I(this,De);I(this,G);I(this,Js);I(this,Le);I(this,Qn);I(this,Or);I(this,Rt);I(this,nn);I(this,Xs);I(this,Lr);I(this,Rr);I(this,Vn);I(this,Hn);I(this,rn);I(this,Mr,new Set);this.options=n,R(this,De,t),R(this,nn,null),R(this,Rt,po()),this.bindMethods(),this.setOptions(n)}bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(y(this,G).addObserver(this),td(y(this,G),this.options)?Q(this,Y,hs).call(this):this.updateResult(),Q(this,Y,go).call(this))}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return So(y(this,G),this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return So(y(this,G),this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,Q(this,Y,jo).call(this),Q(this,Y,wo).call(this),y(this,G).removeObserver(this)}setOptions(t){const n=this.options,r=y(this,G);if(this.options=y(this,De).defaultQueryOptions(t),this.options.enabled!==void 0&&typeof this.options.enabled!="boolean"&&typeof this.options.enabled!="function"&&typeof Ze(this.options.enabled,y(this,G))!="boolean")throw new Error("Expected enabled to be a boolean or a callback that returns a boolean");Q(this,Y,ko).call(this),y(this,G).setOptions(this.options),n._defaulted&&!di(this.options,n)&&y(this,De).getQueryCache().notify({type:"observerOptionsUpdated",query:y(this,G),observer:this});const s=this.hasListeners();s&&nd(y(this,G),r,this.options,n)&&Q(this,Y,hs).call(this),this.updateResult(),s&&(y(this,G)!==r||Ze(this.options.enabled,y(this,G))!==Ze(n.enabled,y(this,G))||kn(this.options.staleTime,y(this,G))!==kn(n.staleTime,y(this,G)))&&Q(this,Y,vo).call(this);const a=Q(this,Y,xo).call(this);s&&(y(this,G)!==r||Ze(this.options.enabled,y(this,G))!==Ze(n.enabled,y(this,G))||a!==y(this,rn))&&Q(this,Y,yo).call(this,a)}getOptimisticResult(t){const n=y(this,De).getQueryCache().build(y(this,De),t),r=this.createResult(n,t);return Cx(this,r)&&(R(this,Le,r),R(this,Or,this.options),R(this,Qn,y(this,G).state)),r}getCurrentResult(){return y(this,Le)}trackResult(t,n){return new Proxy(t,{get:(r,s)=>(this.trackProp(s),n==null||n(s),s==="promise"&&(this.trackProp("data"),!this.options.experimental_prefetchInRender&&y(this,Rt).status==="pending"&&y(this,Rt).reject(new Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(r,s))})}trackProp(t){y(this,Mr).add(t)}getCurrentQuery(){return y(this,G)}refetch({...t}={}){return this.fetch({...t})}fetchOptimistic(t){const n=y(this,De).defaultQueryOptions(t),r=y(this,De).getQueryCache().build(y(this,De),n);return r.fetch().then(()=>this.createResult(r,n))}fetch(t){return Q(this,Y,hs).call(this,{...t,cancelRefetch:t.cancelRefetch??!0}).then(()=>(this.updateResult(),y(this,Le)))}createResult(t,n){var M;const r=y(this,G),s=this.options,a=y(this,Le),l=y(this,Qn),o=y(this,Or),c=t!==r?t.state:y(this,Js),{state:p}=t;let f={...p},h=!1,x;if(n._optimisticResults){const P=this.hasListeners(),H=!P&&td(t,n),F=P&&nd(t,r,n,s);(H||F)&&(f={...f,...ep(p.data,t.options)}),n._optimisticResults==="isRestoring"&&(f.fetchStatus="idle")}let{error:w,errorUpdatedAt:j,status:S}=f;x=f.data;let v=!1;if(n.placeholderData!==void 0&&x===void 0&&S==="pending"){let P;a!=null&&a.isPlaceholderData&&n.placeholderData===(o==null?void 0:o.placeholderData)?(P=a.data,v=!0):P=typeof n.placeholderData=="function"?n.placeholderData((M=y(this,Rr))==null?void 0:M.state.data,y(this,Rr)):n.placeholderData,P!==void 0&&(S="success",x=ho(a==null?void 0:a.data,P,n),h=!0)}if(n.select&&x!==void 0&&!v)if(a&&x===(l==null?void 0:l.data)&&n.select===y(this,Xs))x=y(this,Lr);else try{R(this,Xs,n.select),x=n.select(x),x=ho(a==null?void 0:a.data,x,n),R(this,Lr,x),R(this,nn,null)}catch(P){R(this,nn,P)}y(this,nn)&&(w=y(this,nn),x=y(this,Lr),j=Date.now(),S="error");const d=f.fetchStatus==="fetching",m=S==="pending",g=S==="error",b=m&&d,_=x!==void 0,N={status:S,fetchStatus:f.fetchStatus,isPending:m,isSuccess:S==="success",isError:g,isInitialLoading:b,isLoading:b,data:x,dataUpdatedAt:f.dataUpdatedAt,error:w,errorUpdatedAt:j,failureCount:f.fetchFailureCount,failureReason:f.fetchFailureReason,errorUpdateCount:f.errorUpdateCount,isFetched:f.dataUpdateCount>0||f.errorUpdateCount>0,isFetchedAfterMount:f.dataUpdateCount>c.dataUpdateCount||f.errorUpdateCount>c.errorUpdateCount,isFetching:d,isRefetching:d&&!m,isLoadingError:g&&!_,isPaused:f.fetchStatus==="paused",isPlaceholderData:h,isRefetchError:g&&_,isStale:Tu(t,n),refetch:this.refetch,promise:y(this,Rt),isEnabled:Ze(n.enabled,t)!==!1};if(this.options.experimental_prefetchInRender){const P=N.data!==void 0,H=N.status==="error"&&!P,F=ke=>{H?ke.reject(N.error):P&&ke.resolve(N.data)},U=()=>{const ke=R(this,Rt,N.promise=po());F(ke)},te=y(this,Rt);switch(te.status){case"pending":t.queryHash===r.queryHash&&F(te);break;case"fulfilled":(H||N.data!==te.value)&&U();break;case"rejected":(!H||N.error!==te.reason)&&U();break}}return N}updateResult(){const t=y(this,Le),n=this.createResult(y(this,G),this.options);if(R(this,Qn,y(this,G).state),R(this,Or,this.options),y(this,Qn).data!==void 0&&R(this,Rr,y(this,G)),di(n,t))return;R(this,Le,n);const r=()=>{if(!t)return!0;const{notifyOnChangeProps:s}=this.options,a=typeof s=="function"?s():s;if(a==="all"||!a&&!y(this,Mr).size)return!0;const l=new Set(a??y(this,Mr));return this.options.throwOnError&&l.add("error"),Object.keys(y(this,Le)).some(o=>{const u=o;return y(this,Le)[u]!==t[u]&&l.has(u)})};Q(this,Y,tp).call(this,{listeners:r()})}onQueryUpdate(){this.updateResult(),this.hasListeners()&&Q(this,Y,go).call(this)}},De=new WeakMap,G=new WeakMap,Js=new WeakMap,Le=new WeakMap,Qn=new WeakMap,Or=new WeakMap,Rt=new WeakMap,nn=new WeakMap,Xs=new WeakMap,Lr=new WeakMap,Rr=new WeakMap,Vn=new WeakMap,Hn=new WeakMap,rn=new WeakMap,Mr=new WeakMap,Y=new WeakSet,hs=function(t){Q(this,Y,ko).call(this);let n=y(this,G).fetch(this.options,t);return t!=null&&t.throwOnError||(n=n.catch(Me)),n},vo=function(){Q(this,Y,jo).call(this);const t=kn(this.options.staleTime,y(this,G));if(nr||y(this,Le).isStale||!co(t))return;const r=qh(y(this,Le).dataUpdatedAt,t)+1;R(this,Vn,In.setTimeout(()=>{y(this,Le).isStale||this.updateResult()},r))},xo=function(){return(typeof this.options.refetchInterval=="function"?this.options.refetchInterval(y(this,G)):this.options.refetchInterval)??!1},yo=function(t){Q(this,Y,wo).call(this),R(this,rn,t),!(nr||Ze(this.options.enabled,y(this,G))===!1||!co(y(this,rn))||y(this,rn)===0)&&R(this,Hn,In.setInterval(()=>{(this.options.refetchIntervalInBackground||Pu.isFocused())&&Q(this,Y,hs).call(this)},y(this,rn)))},go=function(){Q(this,Y,vo).call(this),Q(this,Y,yo).call(this,Q(this,Y,xo).call(this))},jo=function(){y(this,Vn)&&(In.clearTimeout(y(this,Vn)),R(this,Vn,void 0))},wo=function(){y(this,Hn)&&(In.clearInterval(y(this,Hn)),R(this,Hn,void 0))},ko=function(){const t=y(this,De).getQueryCache().build(y(this,De),this.options);if(t===y(this,G))return;const n=y(this,G);R(this,G,t),R(this,Js,t.state),this.hasListeners()&&(n==null||n.removeObserver(this),t.addObserver(this))},tp=function(t){ve.batch(()=>{t.listeners&&this.listeners.forEach(n=>{n(y(this,Le))}),y(this,De).getQueryCache().notify({query:y(this,G),type:"observerResultsUpdated"})})},Ed);function _x(e,t){return Ze(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status==="error"&&t.retryOnMount===!1)}function td(e,t){return _x(e,t)||e.state.data!==void 0&&So(e,t,t.refetchOnMount)}function So(e,t,n){if(Ze(t.enabled,e)!==!1&&kn(t.staleTime,e)!=="static"){const r=typeof n=="function"?n(e):n;return r==="always"||r!==!1&&Tu(e,t)}return!1}function nd(e,t,n,r){return(e!==t||Ze(r.enabled,e)===!1)&&(!n.suspense||e.state.status!=="error")&&Tu(e,n)}function Tu(e,t){return Ze(t.enabled,e)!==!1&&e.isStaleByTime(kn(t.staleTime,e))}function Cx(e,t){return!di(e.getCurrentResult(),t)}function rd(e){return{onFetch:(t,n)=>{var p,f,h,x,w;const r=t.options,s=(h=(f=(p=t.fetchOptions)==null?void 0:p.meta)==null?void 0:f.fetchMore)==null?void 0:h.direction,a=((x=t.state.data)==null?void 0:x.pages)||[],l=((w=t.state.data)==null?void 0:w.pageParams)||[];let o={pages:[],pageParams:[]},u=0;const c=async()=>{let j=!1;const S=m=>{xx(m,()=>t.signal,()=>j=!0)},v=Jh(t.options,t.fetchOptions),d=async(m,g,b)=>{if(j)return Promise.reject();if(g==null&&m.pages.length)return Promise.resolve(m);const C=(()=>{const H={client:t.client,queryKey:t.queryKey,pageParam:g,direction:b?"backward":"forward",meta:t.options.meta};return S(H),H})(),N=await v(C),{maxPages:M}=t.options,P=b?vx:mx;return{pages:P(m.pages,N,M),pageParams:P(m.pageParams,g,M)}};if(s&&a.length){const m=s==="backward",g=m?bx:sd,b={pages:a,pageParams:l},_=g(r,b);o=await d(b,_,m)}else{const m=e??a.length;do{const g=u===0?l[0]??r.initialPageParam:sd(r,o);if(u>0&&g==null)break;o=await d(o,g),u++}while(u{var j,S;return(S=(j=t.options).persister)==null?void 0:S.call(j,c,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n)}:t.fetchFn=c}}}function sd(e,{pages:t,pageParams:n}){const r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function bx(e,{pages:t,pageParams:n}){var r;return t.length>0?(r=e.getPreviousPageParam)==null?void 0:r.call(e,t[0],t,n[0],n):void 0}var Ys,wt,Pe,Kn,kt,qt,Pd,Ex=(Pd=class extends Zh{constructor(t){super();I(this,kt);I(this,Ys);I(this,wt);I(this,Pe);I(this,Kn);R(this,Ys,t.client),this.mutationId=t.mutationId,R(this,Pe,t.mutationCache),R(this,wt,[]),this.state=t.state||np(),this.setOptions(t.options),this.scheduleGc()}setOptions(t){this.options=t,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(t){y(this,wt).includes(t)||(y(this,wt).push(t),this.clearGcTimeout(),y(this,Pe).notify({type:"observerAdded",mutation:this,observer:t}))}removeObserver(t){R(this,wt,y(this,wt).filter(n=>n!==t)),this.scheduleGc(),y(this,Pe).notify({type:"observerRemoved",mutation:this,observer:t})}optionalRemove(){y(this,wt).length||(this.state.status==="pending"?this.scheduleGc():y(this,Pe).remove(this))}continue(){var t;return((t=y(this,Kn))==null?void 0:t.continue())??this.execute(this.state.variables)}async execute(t){var l,o,u,c,p,f,h,x,w,j,S,v,d,m,g,b,_,C;const n=()=>{Q(this,kt,qt).call(this,{type:"continue"})},r={client:y(this,Ys),meta:this.options.meta,mutationKey:this.options.mutationKey};R(this,Kn,Yh({fn:()=>this.options.mutationFn?this.options.mutationFn(t,r):Promise.reject(new Error("No mutationFn found")),onFail:(N,M)=>{Q(this,kt,qt).call(this,{type:"failed",failureCount:N,error:M})},onPause:()=>{Q(this,kt,qt).call(this,{type:"pause"})},onContinue:n,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>y(this,Pe).canRun(this)}));const s=this.state.status==="pending",a=!y(this,Kn).canStart();try{if(s)n();else{Q(this,kt,qt).call(this,{type:"pending",variables:t,isPaused:a}),y(this,Pe).config.onMutate&&await y(this,Pe).config.onMutate(t,this,r);const M=await((o=(l=this.options).onMutate)==null?void 0:o.call(l,t,r));M!==this.state.context&&Q(this,kt,qt).call(this,{type:"pending",context:M,variables:t,isPaused:a})}const N=await y(this,Kn).start();return await((c=(u=y(this,Pe).config).onSuccess)==null?void 0:c.call(u,N,t,this.state.context,this,r)),await((f=(p=this.options).onSuccess)==null?void 0:f.call(p,N,t,this.state.context,r)),await((x=(h=y(this,Pe).config).onSettled)==null?void 0:x.call(h,N,null,this.state.variables,this.state.context,this,r)),await((j=(w=this.options).onSettled)==null?void 0:j.call(w,N,null,t,this.state.context,r)),Q(this,kt,qt).call(this,{type:"success",data:N}),N}catch(N){try{await((v=(S=y(this,Pe).config).onError)==null?void 0:v.call(S,N,t,this.state.context,this,r))}catch(M){Promise.reject(M)}try{await((m=(d=this.options).onError)==null?void 0:m.call(d,N,t,this.state.context,r))}catch(M){Promise.reject(M)}try{await((b=(g=y(this,Pe).config).onSettled)==null?void 0:b.call(g,void 0,N,this.state.variables,this.state.context,this,r))}catch(M){Promise.reject(M)}try{await((C=(_=this.options).onSettled)==null?void 0:C.call(_,void 0,N,t,this.state.context,r))}catch(M){Promise.reject(M)}throw Q(this,kt,qt).call(this,{type:"error",error:N}),N}finally{y(this,Pe).runNext(this)}}},Ys=new WeakMap,wt=new WeakMap,Pe=new WeakMap,Kn=new WeakMap,kt=new WeakSet,qt=function(t){const n=r=>{switch(t.type){case"failed":return{...r,failureCount:t.failureCount,failureReason:t.error};case"pause":return{...r,isPaused:!0};case"continue":return{...r,isPaused:!1};case"pending":return{...r,context:t.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:t.isPaused,status:"pending",variables:t.variables,submittedAt:Date.now()};case"success":return{...r,data:t.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...r,data:void 0,error:t.error,failureCount:r.failureCount+1,failureReason:t.error,isPaused:!1,status:"error"}}};this.state=n(this.state),ve.batch(()=>{y(this,wt).forEach(r=>{r.onMutationUpdate(t)}),y(this,Pe).notify({mutation:this,type:"updated",action:t})})},Pd);function np(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}var Mt,dt,Zs,Td,Px=(Td=class extends Xr{constructor(t={}){super();I(this,Mt);I(this,dt);I(this,Zs);this.config=t,R(this,Mt,new Set),R(this,dt,new Map),R(this,Zs,0)}build(t,n,r){const s=new Ex({client:t,mutationCache:this,mutationId:++la(this,Zs)._,options:t.defaultMutationOptions(n),state:r});return this.add(s),s}add(t){y(this,Mt).add(t);const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n);r?r.push(t):y(this,dt).set(n,[t])}this.notify({type:"added",mutation:t})}remove(t){if(y(this,Mt).delete(t)){const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n);if(r)if(r.length>1){const s=r.indexOf(t);s!==-1&&r.splice(s,1)}else r[0]===t&&y(this,dt).delete(n)}}this.notify({type:"removed",mutation:t})}canRun(t){const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n),s=r==null?void 0:r.find(a=>a.state.status==="pending");return!s||s===t}else return!0}runNext(t){var r;const n=_a(t);if(typeof n=="string"){const s=(r=y(this,dt).get(n))==null?void 0:r.find(a=>a!==t&&a.state.isPaused);return(s==null?void 0:s.continue())??Promise.resolve()}else return Promise.resolve()}clear(){ve.batch(()=>{y(this,Mt).forEach(t=>{this.notify({type:"removed",mutation:t})}),y(this,Mt).clear(),y(this,dt).clear()})}getAll(){return Array.from(y(this,Mt))}find(t){const n={exact:!0,...t};return this.getAll().find(r=>Jc(n,r))}findAll(t={}){return this.getAll().filter(n=>Jc(t,n))}notify(t){ve.batch(()=>{this.listeners.forEach(n=>{n(t)})})}resumePausedMutations(){const t=this.getAll().filter(n=>n.state.isPaused);return ve.batch(()=>Promise.all(t.map(n=>n.continue().catch(Me))))}},Mt=new WeakMap,dt=new WeakMap,Zs=new WeakMap,Td);function _a(e){var t;return(t=e.options.scope)==null?void 0:t.id}var zt,sn,Ae,Ft,$t,$a,No,Od,Tx=(Od=class extends Xr{constructor(n,r){super();I(this,$t);I(this,zt);I(this,sn);I(this,Ae);I(this,Ft);R(this,zt,n),this.setOptions(r),this.bindMethods(),Q(this,$t,$a).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(n){var s;const r=this.options;this.options=y(this,zt).defaultMutationOptions(n),di(this.options,r)||y(this,zt).getMutationCache().notify({type:"observerOptionsUpdated",mutation:y(this,Ae),observer:this}),r!=null&&r.mutationKey&&this.options.mutationKey&&rr(r.mutationKey)!==rr(this.options.mutationKey)?this.reset():((s=y(this,Ae))==null?void 0:s.state.status)==="pending"&&y(this,Ae).setOptions(this.options)}onUnsubscribe(){var n;this.hasListeners()||(n=y(this,Ae))==null||n.removeObserver(this)}onMutationUpdate(n){Q(this,$t,$a).call(this),Q(this,$t,No).call(this,n)}getCurrentResult(){return y(this,sn)}reset(){var n;(n=y(this,Ae))==null||n.removeObserver(this),R(this,Ae,void 0),Q(this,$t,$a).call(this),Q(this,$t,No).call(this)}mutate(n,r){var s;return R(this,Ft,r),(s=y(this,Ae))==null||s.removeObserver(this),R(this,Ae,y(this,zt).getMutationCache().build(y(this,zt),this.options)),y(this,Ae).addObserver(this),y(this,Ae).execute(n)}},zt=new WeakMap,sn=new WeakMap,Ae=new WeakMap,Ft=new WeakMap,$t=new WeakSet,$a=function(){var r;const n=((r=y(this,Ae))==null?void 0:r.state)??np();R(this,sn,{...n,isPending:n.status==="pending",isSuccess:n.status==="success",isError:n.status==="error",isIdle:n.status==="idle",mutate:this.mutate,reset:this.reset})},No=function(n){ve.batch(()=>{var r,s,a,l,o,u,c,p;if(y(this,Ft)&&this.hasListeners()){const f=y(this,sn).variables,h=y(this,sn).context,x={client:y(this,zt),meta:this.options.meta,mutationKey:this.options.mutationKey};if((n==null?void 0:n.type)==="success"){try{(s=(r=y(this,Ft)).onSuccess)==null||s.call(r,n.data,f,h,x)}catch(w){Promise.reject(w)}try{(l=(a=y(this,Ft)).onSettled)==null||l.call(a,n.data,null,f,h,x)}catch(w){Promise.reject(w)}}else if((n==null?void 0:n.type)==="error"){try{(u=(o=y(this,Ft)).onError)==null||u.call(o,n.error,f,h,x)}catch(w){Promise.reject(w)}try{(p=(c=y(this,Ft)).onSettled)==null||p.call(c,void 0,n.error,f,h,x)}catch(w){Promise.reject(w)}}}this.listeners.forEach(f=>{f(y(this,sn))})})},Od),St,Ld,Ox=(Ld=class extends Xr{constructor(t={}){super();I(this,St);this.config=t,R(this,St,new Map)}build(t,n,r){const s=n.queryKey,a=n.queryHash??Cu(s,n);let l=this.get(a);return l||(l=new Sx({client:t,queryKey:s,queryHash:a,options:t.defaultQueryOptions(n),state:r,defaultOptions:t.getQueryDefaults(s)}),this.add(l)),l}add(t){y(this,St).has(t.queryHash)||(y(this,St).set(t.queryHash,t),this.notify({type:"added",query:t}))}remove(t){const n=y(this,St).get(t.queryHash);n&&(t.destroy(),n===t&&y(this,St).delete(t.queryHash),this.notify({type:"removed",query:t}))}clear(){ve.batch(()=>{this.getAll().forEach(t=>{this.remove(t)})})}get(t){return y(this,St).get(t)}getAll(){return[...y(this,St).values()]}find(t){const n={exact:!0,...t};return this.getAll().find(r=>Gc(n,r))}findAll(t={}){const n=this.getAll();return Object.keys(t).length>0?n.filter(r=>Gc(t,r)):n}notify(t){ve.batch(()=>{this.listeners.forEach(n=>{n(t)})})}onFocus(){ve.batch(()=>{this.getAll().forEach(t=>{t.onFocus()})})}onOnline(){ve.batch(()=>{this.getAll().forEach(t=>{t.onOnline()})})}},St=new WeakMap,Ld),ue,an,ln,zr,Fr,on,Ir,Dr,Rd,Lx=(Rd=class{constructor(e={}){I(this,ue);I(this,an);I(this,ln);I(this,zr);I(this,Fr);I(this,on);I(this,Ir);I(this,Dr);R(this,ue,e.queryCache||new Ox),R(this,an,e.mutationCache||new Px),R(this,ln,e.defaultOptions||{}),R(this,zr,new Map),R(this,Fr,new Map),R(this,on,0)}mount(){la(this,on)._++,y(this,on)===1&&(R(this,Ir,Pu.subscribe(async e=>{e&&(await this.resumePausedMutations(),y(this,ue).onFocus())})),R(this,Dr,fi.subscribe(async e=>{e&&(await this.resumePausedMutations(),y(this,ue).onOnline())})))}unmount(){var e,t;la(this,on)._--,y(this,on)===0&&((e=y(this,Ir))==null||e.call(this),R(this,Ir,void 0),(t=y(this,Dr))==null||t.call(this),R(this,Dr,void 0))}isFetching(e){return y(this,ue).findAll({...e,fetchStatus:"fetching"}).length}isMutating(e){return y(this,an).findAll({...e,status:"pending"}).length}getQueryData(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=y(this,ue).get(t.queryHash))==null?void 0:n.state.data}ensureQueryData(e){const t=this.defaultQueryOptions(e),n=y(this,ue).build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(kn(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return y(this,ue).findAll(e).map(({queryKey:t,state:n})=>{const r=n.data;return[t,r]})}setQueryData(e,t,n){const r=this.defaultQueryOptions({queryKey:e}),s=y(this,ue).get(r.queryHash),a=s==null?void 0:s.state.data,l=fx(t,a);if(l!==void 0)return y(this,ue).build(this,r).setData(l,{...n,manual:!0})}setQueriesData(e,t,n){return ve.batch(()=>y(this,ue).findAll(e).map(({queryKey:r})=>[r,this.setQueryData(r,t,n)]))}getQueryState(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=y(this,ue).get(t.queryHash))==null?void 0:n.state}removeQueries(e){const t=y(this,ue);ve.batch(()=>{t.findAll(e).forEach(n=>{t.remove(n)})})}resetQueries(e,t){const n=y(this,ue);return ve.batch(()=>(n.findAll(e).forEach(r=>{r.reset()}),this.refetchQueries({type:"active",...e},t)))}cancelQueries(e,t={}){const n={revert:!0,...t},r=ve.batch(()=>y(this,ue).findAll(e).map(s=>s.cancel(n)));return Promise.all(r).then(Me).catch(Me)}invalidateQueries(e,t={}){return ve.batch(()=>(y(this,ue).findAll(e).forEach(n=>{n.invalidate()}),(e==null?void 0:e.refetchType)==="none"?Promise.resolve():this.refetchQueries({...e,type:(e==null?void 0:e.refetchType)??(e==null?void 0:e.type)??"active"},t)))}refetchQueries(e,t={}){const n={...t,cancelRefetch:t.cancelRefetch??!0},r=ve.batch(()=>y(this,ue).findAll(e).filter(s=>!s.isDisabled()&&!s.isStatic()).map(s=>{let a=s.fetch(void 0,n);return n.throwOnError||(a=a.catch(Me)),s.state.fetchStatus==="paused"?Promise.resolve():a}));return Promise.all(r).then(Me)}fetchQuery(e){const t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);const n=y(this,ue).build(this,t);return n.isStaleByTime(kn(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(Me).catch(Me)}fetchInfiniteQuery(e){return e.behavior=rd(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(Me).catch(Me)}ensureInfiniteQueryData(e){return e.behavior=rd(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return fi.isOnline()?y(this,an).resumePausedMutations():Promise.resolve()}getQueryCache(){return y(this,ue)}getMutationCache(){return y(this,an)}getDefaultOptions(){return y(this,ln)}setDefaultOptions(e){R(this,ln,e)}setQueryDefaults(e,t){y(this,zr).set(rr(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){const t=[...y(this,zr).values()],n={};return t.forEach(r=>{Bs(e,r.queryKey)&&Object.assign(n,r.defaultOptions)}),n}setMutationDefaults(e,t){y(this,Fr).set(rr(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){const t=[...y(this,Fr).values()],n={};return t.forEach(r=>{Bs(e,r.mutationKey)&&Object.assign(n,r.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;const t={...y(this,ln).queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=Cu(t.queryKey,t)),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!=="always"),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode="offlineFirst"),t.queryFn===bu&&(t.enabled=!1),t}defaultMutationOptions(e){return e!=null&&e._defaulted?e:{...y(this,ln).mutations,...(e==null?void 0:e.mutationKey)&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){y(this,ue).clear(),y(this,an).clear()}},ue=new WeakMap,an=new WeakMap,ln=new WeakMap,zr=new WeakMap,Fr=new WeakMap,on=new WeakMap,Ir=new WeakMap,Dr=new WeakMap,Rd),rp=k.createContext(void 0),Pn=e=>{const t=k.useContext(rp);if(!t)throw new Error("No QueryClient set, use QueryClientProvider to set one");return t},Rx=({client:e,children:t})=>(k.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),i.jsx(rp.Provider,{value:e,children:t})),sp=k.createContext(!1),Mx=()=>k.useContext(sp);sp.Provider;function zx(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var Fx=k.createContext(zx()),Ix=()=>k.useContext(Fx),Dx=(e,t,n)=>{const r=n!=null&&n.state.error&&typeof e.throwOnError=="function"?Eu(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},Ax=e=>{k.useEffect(()=>{e.clearReset()},[e])},$x=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:s})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(s&&e.data===void 0||Eu(n,[e.error,r])),Ux=e=>{if(e.suspense){const n=s=>s==="static"?s:Math.max(s??1e3,1e3),r=e.staleTime;e.staleTime=typeof r=="function"?(...s)=>n(r(...s)):n(r),typeof e.gcTime=="number"&&(e.gcTime=Math.max(e.gcTime,1e3))}},Bx=(e,t)=>e.isLoading&&e.isFetching&&!t,Qx=(e,t)=>(e==null?void 0:e.suspense)&&t.isPending,ad=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function Vx(e,t,n){var h,x,w,j;const r=Mx(),s=Ix(),a=Pn(),l=a.defaultQueryOptions(e);(x=(h=a.getDefaultOptions().queries)==null?void 0:h._experimental_beforeQuery)==null||x.call(h,l);const o=a.getQueryCache().get(l.queryHash);l._optimisticResults=r?"isRestoring":"optimistic",Ux(l),Dx(l,s,o),Ax(s);const u=!a.getQueryCache().get(l.queryHash),[c]=k.useState(()=>new t(a,l)),p=c.getOptimisticResult(l),f=!r&&e.subscribed!==!1;if(k.useSyncExternalStore(k.useCallback(S=>{const v=f?c.subscribe(ve.batchCalls(S)):Me;return c.updateResult(),v},[c,f]),()=>c.getCurrentResult(),()=>c.getCurrentResult()),k.useEffect(()=>{c.setOptions(l)},[l,c]),Qx(l,p))throw ad(l,c,s);if($x({result:p,errorResetBoundary:s,throwOnError:l.throwOnError,query:o,suspense:l.suspense}))throw p.error;if((j=(w=a.getDefaultOptions().queries)==null?void 0:w._experimental_afterQuery)==null||j.call(w,l,p),l.experimental_prefetchInRender&&!nr&&Bx(p,r)){const S=u?ad(l,c,s):o==null?void 0:o.promise;S==null||S.catch(Me).finally(()=>{c.updateResult()})}return l.notifyOnChangeProps?p:c.trackResult(p)}function _e(e,t){return Vx(e,Nx)}function st(e,t){const n=Pn(),[r]=k.useState(()=>new Tx(n,e));k.useEffect(()=>{r.setOptions(e)},[r,e]);const s=k.useSyncExternalStore(k.useCallback(l=>r.subscribe(ve.batchCalls(l)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),a=k.useCallback((l,o)=>{r.mutate(l,o).catch(Me)},[r]);if(s.error&&Eu(r.options.throwOnError,[s.error]))throw s.error;return{...s,mutate:a,mutateAsync:s.mutate}}/** + * @remix-run/router v1.23.2 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function Qs(){return Qs=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function Ou(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function Kx(){return Math.random().toString(36).substr(2,8)}function ld(e,t){return{usr:e.state,key:e.key,idx:t}}function _o(e,t,n,r){return n===void 0&&(n=null),Qs({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?Yr(t):t,{state:n,key:t&&t.key||r||Kx()})}function hi(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function Yr(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Wx(e,t,n,r){r===void 0&&(r={});let{window:s=document.defaultView,v5Compat:a=!1}=r,l=s.history,o=dn.Pop,u=null,c=p();c==null&&(c=0,l.replaceState(Qs({},l.state,{idx:c}),""));function p(){return(l.state||{idx:null}).idx}function f(){o=dn.Pop;let S=p(),v=S==null?null:S-c;c=S,u&&u({action:o,location:j.location,delta:v})}function h(S,v){o=dn.Push;let d=_o(j.location,S,v);c=p()+1;let m=ld(d,c),g=j.createHref(d);try{l.pushState(m,"",g)}catch(b){if(b instanceof DOMException&&b.name==="DataCloneError")throw b;s.location.assign(g)}a&&u&&u({action:o,location:j.location,delta:1})}function x(S,v){o=dn.Replace;let d=_o(j.location,S,v);c=p();let m=ld(d,c),g=j.createHref(d);l.replaceState(m,"",g),a&&u&&u({action:o,location:j.location,delta:0})}function w(S){let v=s.location.origin!=="null"?s.location.origin:s.location.href,d=typeof S=="string"?S:hi(S);return d=d.replace(/ $/,"%20"),de(v,"No window.location.(origin|href) available to create URL for href: "+d),new URL(d,v)}let j={get action(){return o},get location(){return e(s,l)},listen(S){if(u)throw new Error("A history only accepts one active listener");return s.addEventListener(id,f),u=S,()=>{s.removeEventListener(id,f),u=null}},createHref(S){return t(s,S)},createURL:w,encodeLocation(S){let v=w(S);return{pathname:v.pathname,search:v.search,hash:v.hash}},push:h,replace:x,go(S){return l.go(S)}};return j}var od;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(od||(od={}));function qx(e,t,n){return n===void 0&&(n="/"),Gx(e,t,n)}function Gx(e,t,n,r){let s=typeof t=="string"?Yr(t):t,a=Kr(s.pathname||"/",n);if(a==null)return null;let l=ap(e);Jx(l);let o=null;for(let u=0;o==null&&u{let u={relativePath:o===void 0?a.path||"":o,caseSensitive:a.caseSensitive===!0,childrenIndex:l,route:a};u.relativePath.startsWith("/")&&(de(u.relativePath.startsWith(r),'Absolute route path "'+u.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),u.relativePath=u.relativePath.slice(r.length));let c=Sn([r,u.relativePath]),p=n.concat(u);a.children&&a.children.length>0&&(de(a.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+c+'".')),ap(a.children,t,p,c)),!(a.path==null&&!a.index)&&t.push({path:c,score:ry(c,a.index),routesMeta:p})};return e.forEach((a,l)=>{var o;if(a.path===""||!((o=a.path)!=null&&o.includes("?")))s(a,l);else for(let u of ip(a.path))s(a,l,u)}),t}function ip(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,s=n.endsWith("?"),a=n.replace(/\?$/,"");if(r.length===0)return s?[a,""]:[a];let l=ip(r.join("/")),o=[];return o.push(...l.map(u=>u===""?a:[a,u].join("/"))),s&&o.push(...l),o.map(u=>e.startsWith("/")&&u===""?"/":u)}function Jx(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:sy(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Xx=/^:[\w-]+$/,Yx=3,Zx=2,ey=1,ty=10,ny=-2,ud=e=>e==="*";function ry(e,t){let n=e.split("/"),r=n.length;return n.some(ud)&&(r+=ny),t&&(r+=Zx),n.filter(s=>!ud(s)).reduce((s,a)=>s+(Xx.test(a)?Yx:a===""?ey:ty),r)}function sy(e,t){return e.length===t.length&&e.slice(0,-1).every((r,s)=>r===t[s])?e[e.length-1]-t[t.length-1]:0}function ay(e,t,n){let{routesMeta:r}=e,s={},a="/",l=[];for(let o=0;o{let{paramName:h,isOptional:x}=p;if(h==="*"){let j=o[f]||"";l=a.slice(0,a.length-j.length).replace(/(.)\/+$/,"$1")}const w=o[f];return x&&!w?c[h]=void 0:c[h]=(w||"").replace(/%2F/g,"/"),c},{}),pathname:a,pathnameBase:l,pattern:e}}function iy(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),Ou(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],s="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(l,o,u)=>(r.push({paramName:o,isOptional:u!=null}),u?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),s+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?s+="\\/*$":e!==""&&e!=="/"&&(s+="(?:(?=\\/|$))"),[new RegExp(s,t?void 0:"i"),r]}function ly(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return Ou(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function Kr(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const oy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,uy=e=>oy.test(e);function cy(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:s=""}=typeof e=="string"?Yr(e):e,a;if(n)if(uy(n))a=n;else{if(n.includes("//")){let l=n;n=n.replace(/\/\/+/g,"/"),Ou(!1,"Pathnames cannot have embedded double slashes - normalizing "+(l+" -> "+n))}n.startsWith("/")?a=cd(n.substring(1),"/"):a=cd(n,t)}else a=t;return{pathname:a,search:hy(r),hash:py(s)}}function cd(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(s=>{s===".."?n.length>1&&n.pop():s!=="."&&n.push(s)}),n.length>1?n.join("/"):"/"}function hl(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function dy(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function lp(e,t){let n=dy(e);return t?n.map((r,s)=>s===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function op(e,t,n,r){r===void 0&&(r=!1);let s;typeof e=="string"?s=Yr(e):(s=Qs({},e),de(!s.pathname||!s.pathname.includes("?"),hl("?","pathname","search",s)),de(!s.pathname||!s.pathname.includes("#"),hl("#","pathname","hash",s)),de(!s.search||!s.search.includes("#"),hl("#","search","hash",s)));let a=e===""||s.pathname==="",l=a?"/":s.pathname,o;if(l==null)o=n;else{let f=t.length-1;if(!r&&l.startsWith("..")){let h=l.split("/");for(;h[0]==="..";)h.shift(),f-=1;s.pathname=h.join("/")}o=f>=0?t[f]:"/"}let u=cy(s,o),c=l&&l!=="/"&&l.endsWith("/"),p=(a||l===".")&&n.endsWith("/");return!u.pathname.endsWith("/")&&(c||p)&&(u.pathname+="/"),u}const Sn=e=>e.join("/").replace(/\/\/+/g,"/"),fy=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),hy=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,py=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function my(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const up=["post","put","patch","delete"];new Set(up);const vy=["get",...up];new Set(vy);/** + * React Router v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function Vs(){return Vs=Object.assign?Object.assign.bind():function(e){for(var t=1;t{o.current=!0}),k.useCallback(function(c,p){if(p===void 0&&(p={}),!o.current)return;if(typeof c=="number"){r.go(c);return}let f=op(c,JSON.parse(l),a,p.relative==="path");e==null&&t!=="/"&&(f.pathname=f.pathname==="/"?t:Sn([t,f.pathname])),(p.replace?r.replace:r.push)(f,p.state,p)},[t,r,l,a,e])}const gy=k.createContext(null);function jy(e){let t=k.useContext(Kt).outlet;return t&&k.createElement(gy.Provider,{value:e},t)}function Ri(){let{matches:e}=k.useContext(Kt),t=e[e.length-1];return t?t.params:{}}function Mi(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=k.useContext(Tn),{matches:s}=k.useContext(Kt),{pathname:a}=Zr(),l=JSON.stringify(lp(s,r.v7_relativeSplatPath));return k.useMemo(()=>op(e,JSON.parse(l),a,n==="path"),[e,l,a,n])}function wy(e,t){return ky(e,t)}function ky(e,t,n,r){aa()||de(!1);let{navigator:s}=k.useContext(Tn),{matches:a}=k.useContext(Kt),l=a[a.length-1],o=l?l.params:{};l&&l.pathname;let u=l?l.pathnameBase:"/";l&&l.route;let c=Zr(),p;if(t){var f;let S=typeof t=="string"?Yr(t):t;u==="/"||(f=S.pathname)!=null&&f.startsWith(u)||de(!1),p=S}else p=c;let h=p.pathname||"/",x=h;if(u!=="/"){let S=u.replace(/^\//,"").split("/");x="/"+h.replace(/^\//,"").split("/").slice(S.length).join("/")}let w=qx(e,{pathname:x}),j=by(w&&w.map(S=>Object.assign({},S,{params:Object.assign({},o,S.params),pathname:Sn([u,s.encodeLocation?s.encodeLocation(S.pathname).pathname:S.pathname]),pathnameBase:S.pathnameBase==="/"?u:Sn([u,s.encodeLocation?s.encodeLocation(S.pathnameBase).pathname:S.pathnameBase])})),a,n,r);return t&&j?k.createElement(Li.Provider,{value:{location:Vs({pathname:"/",search:"",hash:"",state:null,key:"default"},p),navigationType:dn.Pop}},j):j}function Sy(){let e=Oy(),t=my(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,s={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return k.createElement(k.Fragment,null,k.createElement("h2",null,"Unexpected Application Error!"),k.createElement("h3",{style:{fontStyle:"italic"}},t),n?k.createElement("pre",{style:s},n):null,null)}const Ny=k.createElement(Sy,null);class _y extends k.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?k.createElement(Kt.Provider,{value:this.props.routeContext},k.createElement(dp.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function Cy(e){let{routeContext:t,match:n,children:r}=e,s=k.useContext(Oi);return s&&s.static&&s.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(s.staticContext._deepestRenderedBoundaryId=n.route.id),k.createElement(Kt.Provider,{value:t},r)}function by(e,t,n,r){var s;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var a;if(!n)return null;if(n.errors)e=n.matches;else if((a=r)!=null&&a.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let l=e,o=(s=n)==null?void 0:s.errors;if(o!=null){let p=l.findIndex(f=>f.route.id&&(o==null?void 0:o[f.route.id])!==void 0);p>=0||de(!1),l=l.slice(0,Math.min(l.length,p+1))}let u=!1,c=-1;if(n&&r&&r.v7_partialHydration)for(let p=0;p=0?l=l.slice(0,c+1):l=[l[0]];break}}}return l.reduceRight((p,f,h)=>{let x,w=!1,j=null,S=null;n&&(x=o&&f.route.id?o[f.route.id]:void 0,j=f.route.errorElement||Ny,u&&(c<0&&h===0?(Ry("route-fallback"),w=!0,S=null):c===h&&(w=!0,S=f.route.hydrateFallbackElement||null)));let v=t.concat(l.slice(0,h+1)),d=()=>{let m;return x?m=j:w?m=S:f.route.Component?m=k.createElement(f.route.Component,null):f.route.element?m=f.route.element:m=p,k.createElement(Cy,{match:f,routeContext:{outlet:p,matches:v,isDataRoute:n!=null},children:m})};return n&&(f.route.ErrorBoundary||f.route.errorElement||h===0)?k.createElement(_y,{location:n.location,revalidation:n.revalidation,component:j,error:x,children:d(),routeContext:{outlet:null,matches:v,isDataRoute:!0}}):d()},null)}var hp=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(hp||{}),pp=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(pp||{});function Ey(e){let t=k.useContext(Oi);return t||de(!1),t}function Py(e){let t=k.useContext(cp);return t||de(!1),t}function Ty(e){let t=k.useContext(Kt);return t||de(!1),t}function mp(e){let t=Ty(),n=t.matches[t.matches.length-1];return n.route.id||de(!1),n.route.id}function Oy(){var e;let t=k.useContext(dp),n=Py(),r=mp();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function Ly(){let{router:e}=Ey(hp.UseNavigateStable),t=mp(pp.UseNavigateStable),n=k.useRef(!1);return fp(()=>{n.current=!0}),k.useCallback(function(s,a){a===void 0&&(a={}),n.current&&(typeof s=="number"?e.navigate(s):e.navigate(s,Vs({fromRouteId:t},a)))},[e,t])}const dd={};function Ry(e,t,n){dd[e]||(dd[e]=!0)}function My(e,t){e==null||e.v7_startTransition,e==null||e.v7_relativeSplatPath}function zy(e){return jy(e.context)}function gt(e){de(!1)}function Fy(e){let{basename:t="/",children:n=null,location:r,navigationType:s=dn.Pop,navigator:a,static:l=!1,future:o}=e;aa()&&de(!1);let u=t.replace(/^\/*/,"/"),c=k.useMemo(()=>({basename:u,navigator:a,static:l,future:Vs({v7_relativeSplatPath:!1},o)}),[u,o,a,l]);typeof r=="string"&&(r=Yr(r));let{pathname:p="/",search:f="",hash:h="",state:x=null,key:w="default"}=r,j=k.useMemo(()=>{let S=Kr(p,u);return S==null?null:{location:{pathname:S,search:f,hash:h,state:x,key:w},navigationType:s}},[u,p,f,h,x,w,s]);return j==null?null:k.createElement(Tn.Provider,{value:c},k.createElement(Li.Provider,{children:n,value:j}))}function Iy(e){let{children:t,location:n}=e;return wy(bo(t),n)}new Promise(()=>{});function bo(e,t){t===void 0&&(t=[]);let n=[];return k.Children.forEach(e,(r,s)=>{if(!k.isValidElement(r))return;let a=[...t,s];if(r.type===k.Fragment){n.push.apply(n,bo(r.props.children,a));return}r.type!==gt&&de(!1),!r.props.index||!r.props.children||de(!1);let l={id:r.props.id||a.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,loader:r.props.loader,action:r.props.action,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(l.children=bo(r.props.children,a)),n.push(l)}),n}/** + * React Router DOM v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function pi(){return pi=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[s]=e[s]);return n}function Dy(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Ay(e,t){return e.button===0&&(!t||t==="_self")&&!Dy(e)}const $y=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],Uy=["aria-current","caseSensitive","className","end","style","to","viewTransition","children"],By="6";try{window.__reactRouterVersion=By}catch{}const Qy=k.createContext({isTransitioning:!1}),Vy="startTransition",fd=Xp[Vy];function Hy(e){let{basename:t,children:n,future:r,window:s}=e,a=k.useRef();a.current==null&&(a.current=Hx({window:s,v5Compat:!0}));let l=a.current,[o,u]=k.useState({action:l.action,location:l.location}),{v7_startTransition:c}=r||{},p=k.useCallback(f=>{c&&fd?fd(()=>u(f)):u(f)},[u,c]);return k.useLayoutEffect(()=>l.listen(p),[l,p]),k.useEffect(()=>My(r),[r]),k.createElement(Fy,{basename:t,children:n,location:o.location,navigationType:o.action,navigator:l,future:r})}const Ky=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",Wy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Hs=k.forwardRef(function(t,n){let{onClick:r,relative:s,reloadDocument:a,replace:l,state:o,target:u,to:c,preventScrollReset:p,viewTransition:f}=t,h=vp(t,$y),{basename:x}=k.useContext(Tn),w,j=!1;if(typeof c=="string"&&Wy.test(c)&&(w=c,Ky))try{let m=new URL(window.location.href),g=c.startsWith("//")?new URL(m.protocol+c):new URL(c),b=Kr(g.pathname,x);g.origin===m.origin&&b!=null?c=b+g.search+g.hash:j=!0}catch{}let S=xy(c,{relative:s}),v=Gy(c,{replace:l,state:o,target:u,preventScrollReset:p,relative:s,viewTransition:f});function d(m){r&&r(m),m.defaultPrevented||v(m)}return k.createElement("a",pi({},h,{href:w||S,onClick:j||a?r:d,ref:n,target:u}))}),hd=k.forwardRef(function(t,n){let{"aria-current":r="page",caseSensitive:s=!1,className:a="",end:l=!1,style:o,to:u,viewTransition:c,children:p}=t,f=vp(t,Uy),h=Mi(u,{relative:f.relative}),x=Zr(),w=k.useContext(cp),{navigator:j,basename:S}=k.useContext(Tn),v=w!=null&&Jy(h)&&c===!0,d=j.encodeLocation?j.encodeLocation(h).pathname:h.pathname,m=x.pathname,g=w&&w.navigation&&w.navigation.location?w.navigation.location.pathname:null;s||(m=m.toLowerCase(),g=g?g.toLowerCase():null,d=d.toLowerCase()),g&&S&&(g=Kr(g,S)||g);const b=d!=="/"&&d.endsWith("/")?d.length-1:d.length;let _=m===d||!l&&m.startsWith(d)&&m.charAt(b)==="/",C=g!=null&&(g===d||!l&&g.startsWith(d)&&g.charAt(d.length)==="/"),N={isActive:_,isPending:C,isTransitioning:v},M=_?r:void 0,P;typeof a=="function"?P=a(N):P=[a,_?"active":null,C?"pending":null,v?"transitioning":null].filter(Boolean).join(" ");let H=typeof o=="function"?o(N):o;return k.createElement(Hs,pi({},f,{"aria-current":M,className:P,ref:n,style:H,to:u,viewTransition:c}),typeof p=="function"?p(N):p)});var Eo;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(Eo||(Eo={}));var pd;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(pd||(pd={}));function qy(e){let t=k.useContext(Oi);return t||de(!1),t}function Gy(e,t){let{target:n,replace:r,state:s,preventScrollReset:a,relative:l,viewTransition:o}=t===void 0?{}:t,u=Et(),c=Zr(),p=Mi(e,{relative:l});return k.useCallback(f=>{if(Ay(f,n)){f.preventDefault();let h=r!==void 0?r:hi(c)===hi(p);u(e,{replace:h,state:s,preventScrollReset:a,relative:l,viewTransition:o})}},[c,u,p,r,s,n,e,a,l,o])}function Jy(e,t){t===void 0&&(t={});let n=k.useContext(Qy);n==null&&de(!1);let{basename:r}=qy(Eo.useViewTransitionState),s=Mi(e,{relative:t.relative});if(!n.isTransitioning)return!1;let a=Kr(n.currentLocation.pathname,r)||n.currentLocation.pathname,l=Kr(n.nextLocation.pathname,r)||n.nextLocation.pathname;return Co(s.pathname,l)!=null||Co(s.pathname,a)!=null}/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */var Xy={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Yy=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().trim(),X=(e,t)=>{const n=k.forwardRef(({color:r="currentColor",size:s=24,strokeWidth:a=2,absoluteStrokeWidth:l,className:o="",children:u,...c},p)=>k.createElement("svg",{ref:p,...Xy,width:s,height:s,stroke:r,strokeWidth:l?Number(a)*24/Number(s):a,className:["lucide",`lucide-${Yy(e)}`,o].join(" "),...c},[...t.map(([f,h])=>k.createElement(f,h)),...Array.isArray(u)?u:[u]]));return n.displayName=`${e}`,n};/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Zy=X("AlertCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const eg=X("ArrowUpDown",[["path",{d:"m21 16-4 4-4-4",key:"f6ql7i"}],["path",{d:"M17 20V4",key:"1ejh1v"}],["path",{d:"m3 8 4-4 4 4",key:"11wl7u"}],["path",{d:"M7 4v16",key:"1glfcx"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const tg=X("BarChart3",[["path",{d:"M3 3v18h18",key:"1s2lah"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ng=X("Bot",[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const rg=X("CheckCircle",[["path",{d:"M22 11.08V12a10 10 0 1 1-5.93-9.14",key:"g774vq"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const sg=X("Check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ag=X("ChevronDown",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ig=X("ChevronLeft",[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ss=X("ChevronRight",[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const lg=X("Clock",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16 14",key:"68esgv"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const og=X("Coins",[["circle",{cx:"8",cy:"8",r:"6",key:"3yglwk"}],["path",{d:"M18.09 10.37A6 6 0 1 1 10.34 18",key:"t5s6rm"}],["path",{d:"M7 6h1v4",key:"1obek4"}],["path",{d:"m16.71 13.88.7.71-2.82 2.82",key:"1rbuyh"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ug=X("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const cg=X("ExternalLink",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const dg=X("GitCompare",[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7",key:"1yeb86"}],["path",{d:"M11 18H8a2 2 0 0 1-2-2V9",key:"19pyzm"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const fg=X("Github",[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const zi=X("Globe",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const hg=X("History",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const pg=X("ListTodo",[["rect",{x:"3",y:"5",width:"6",height:"6",rx:"1",key:"1defrl"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"M13 6h8",key:"15sg57"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 18h8",key:"oe0vm4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const mg=X("List",[["line",{x1:"8",x2:"21",y1:"6",y2:"6",key:"7ey8pc"}],["line",{x1:"8",x2:"21",y1:"12",y2:"12",key:"rjfblc"}],["line",{x1:"8",x2:"21",y1:"18",y2:"18",key:"c3b1m8"}],["line",{x1:"3",x2:"3.01",y1:"6",y2:"6",key:"1g7gq3"}],["line",{x1:"3",x2:"3.01",y1:"12",y2:"12",key:"1pjlvk"}],["line",{x1:"3",x2:"3.01",y1:"18",y2:"18",key:"28t2mc"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Fi=X("Loader2",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const xp=X("Lock",[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const vg=X("LogOut",[["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}],["polyline",{points:"16 17 21 12 16 7",key:"1gabdz"}],["line",{x1:"21",x2:"9",y1:"12",y2:"12",key:"1uyos4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const xg=X("Moon",[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ks=X("Play",[["polygon",{points:"5 3 19 12 5 21 5 3",key:"191637"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const yp=X("Plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const gp=X("Settings",[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",key:"1qme2f"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const yg=X("Share2",[["circle",{cx:"18",cy:"5",r:"3",key:"gq8acd"}],["circle",{cx:"6",cy:"12",r:"3",key:"w7nqdw"}],["circle",{cx:"18",cy:"19",r:"3",key:"1xt0gg"}],["line",{x1:"8.59",x2:"15.42",y1:"13.51",y2:"17.49",key:"47mynk"}],["line",{x1:"15.41",x2:"8.59",y1:"6.51",y2:"10.49",key:"1n3mei"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const gg=X("Square",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jg=X("Sun",[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jp=X("Trash2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const wp=X("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const wg=X("XCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ii=X("Zap",[["polygon",{points:"13 2 3 14 12 14 11 22 21 10 12 10 13 2",key:"45s27k"}]]),kg={},md=e=>{let t;const n=new Set,r=(p,f)=>{const h=typeof p=="function"?p(t):p;if(!Object.is(h,t)){const x=t;t=f??(typeof h!="object"||h===null)?h:Object.assign({},t,h),n.forEach(w=>w(t,x))}},s=()=>t,u={setState:r,getState:s,getInitialState:()=>c,subscribe:p=>(n.add(p),()=>n.delete(p)),destroy:()=>{(kg?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),n.clear()}},c=t=e(r,s,u);return u},Sg=e=>e?md(e):md;var kp={exports:{}},Sp={},Np={exports:{}},_p={};/** + * @license React + * use-sync-external-store-shim.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Wr=k;function Ng(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var _g=typeof Object.is=="function"?Object.is:Ng,Cg=Wr.useState,bg=Wr.useEffect,Eg=Wr.useLayoutEffect,Pg=Wr.useDebugValue;function Tg(e,t){var n=t(),r=Cg({inst:{value:n,getSnapshot:t}}),s=r[0].inst,a=r[1];return Eg(function(){s.value=n,s.getSnapshot=t,pl(s)&&a({inst:s})},[e,n,t]),bg(function(){return pl(s)&&a({inst:s}),e(function(){pl(s)&&a({inst:s})})},[e]),Pg(n),n}function pl(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!_g(e,n)}catch{return!0}}function Og(e,t){return t()}var Lg=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?Og:Tg;_p.useSyncExternalStore=Wr.useSyncExternalStore!==void 0?Wr.useSyncExternalStore:Lg;Np.exports=_p;var Rg=Np.exports;/** + * @license React + * use-sync-external-store-shim/with-selector.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Di=k,Mg=Rg;function zg(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var Fg=typeof Object.is=="function"?Object.is:zg,Ig=Mg.useSyncExternalStore,Dg=Di.useRef,Ag=Di.useEffect,$g=Di.useMemo,Ug=Di.useDebugValue;Sp.useSyncExternalStoreWithSelector=function(e,t,n,r,s){var a=Dg(null);if(a.current===null){var l={hasValue:!1,value:null};a.current=l}else l=a.current;a=$g(function(){function u(x){if(!c){if(c=!0,p=x,x=r(x),s!==void 0&&l.hasValue){var w=l.value;if(s(w,x))return f=w}return f=x}if(w=f,Fg(p,x))return w;var j=r(x);return s!==void 0&&s(w,j)?(p=x,w):(p=x,f=j)}var c=!1,p,f,h=n===void 0?null:n;return[function(){return u(t())},h===null?void 0:function(){return u(h())}]},[t,n,r,s]);var o=Ig(e,a[0],a[1]);return Ag(function(){l.hasValue=!0,l.value=o},[o]),Ug(o),o};kp.exports=Sp;var Bg=kp.exports;const Qg=Md(Bg),Cp={},{useDebugValue:Vg}=zo,{useSyncExternalStoreWithSelector:Hg}=Qg;let vd=!1;const Kg=e=>e;function Wg(e,t=Kg,n){(Cp?"production":void 0)!=="production"&&n&&!vd&&(console.warn("[DEPRECATED] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`. They can be imported from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937"),vd=!0);const r=Hg(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return Vg(r),r}const xd=e=>{(Cp?"production":void 0)!=="production"&&typeof e!="function"&&console.warn("[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.");const t=typeof e=="function"?Sg(e):e,n=(r,s)=>Wg(t,r,s);return Object.assign(n,t),n},Lu=e=>e?xd(e):xd,Ai="/api",Ru="flow_auth_token",Mu="flow_auth_expires",zu="flow_auth_username";function Ws(){const e=localStorage.getItem(Ru),t=localStorage.getItem(Mu);return!e||!t?null:new Date(t)<=new Date?(Gn(),null):e}function yd(e,t,n){localStorage.setItem(Ru,e),localStorage.setItem(Mu,t),localStorage.setItem(zu,n)}function Gn(){localStorage.removeItem(Ru),localStorage.removeItem(Mu),localStorage.removeItem(zu)}function Fu(){return localStorage.getItem(zu)}let sr=null;function qg(e){sr=e}async function V(e,t,n=!1){const r={"Content-Type":"application/json",...t==null?void 0:t.headers};if(!n){const a=Ws();a&&(r.Authorization=`Bearer ${a}`)}const s=await fetch(`${Ai}${e}`,{...t,headers:r});if(s.status===401){Gn(),sr&&sr();const a=await s.json().catch(()=>({detail:"Not authenticated"}));throw new Error(a.detail||"Not authenticated")}if(!s.ok){const a=await s.json().catch(()=>({detail:s.statusText}));throw new Error(a.detail||"API request failed")}if(s.status!==204)return s.json()}const or={getConfig:()=>V("/auth/config",void 0,!0),login:e=>V("/auth/login",{method:"POST",body:JSON.stringify(e)},!0),getGitHubAuthUrl:()=>`${Ai}/auth/github`,getCurrentUser:()=>V("/auth/me"),logout:()=>V("/auth/logout",{method:"POST"})},Jn={list:e=>{const t=new URLSearchParams;e!=null&&e.include_auto_generated&&t.set("include_auto_generated","true"),e!=null&&e.include_public&&t.set("include_public","true");const n=t.toString();return V(`/configs${n?`?${n}`:""}`)},get:e=>V(`/configs/${e}`),create:e=>V("/configs",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>V(`/configs/${e}`,{method:"PUT",body:JSON.stringify(t)}),delete:e=>V(`/configs/${e}`,{method:"DELETE"}),generateCandidates:e=>V("/configs/generate-candidates",{method:"POST",body:JSON.stringify(e)})},fn={list:e=>{const t=new URLSearchParams;e!=null&&e.category&&t.set("category",e.category),e!=null&&e.suite&&t.set("suite",e.suite);const n=t.toString();return V(`/tasks${n?`?${n}`:""}`)},get:e=>V(`/tasks/${e}`),create:e=>V("/tasks",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>V(`/tasks/${e}`,{method:"PUT",body:JSON.stringify(t)}),delete:e=>V(`/tasks/${e}`,{method:"DELETE"}),listSuites:()=>V("/tasks/suites"),importSuite:e=>V(`/tasks/import-suite?suite_name=${encodeURIComponent(e)}`,{method:"POST"})},bt={list:e=>{const t=new URLSearchParams;e!=null&&e.status&&t.set("status",e.status),e!=null&&e.include_public&&t.set("include_public","true");const n=t.toString();return V(`/jobs${n?`?${n}`:""}`)},get:e=>V(`/jobs/${e}`),create:e=>V("/jobs",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>V(`/jobs/${e}`,{method:"PUT",body:JSON.stringify(t)}),start:async function*(e){var o;const t={},n=Ws();n&&(t.Authorization=`Bearer ${n}`);const r=await fetch(`${Ai}/jobs/${e}/start`,{method:"POST",headers:t});if(r.status===401)throw Gn(),sr&&sr(),new Error("Not authenticated");if(!r.ok)throw new Error("Failed to start job");const s=(o=r.body)==null?void 0:o.getReader();if(!s)throw new Error("No response body");const a=new TextDecoder;let l="";for(;;){const{done:u,value:c}=await s.read();if(u)break;l+=a.decode(c,{stream:!0});const p=l.split(` +`);l=p.pop()||"";for(const f of p)f.startsWith("data: ")&&(yield JSON.parse(f.slice(6)))}},cancel:e=>V(`/jobs/${e}/cancel`,{method:"POST"}),delete:e=>V(`/jobs/${e}`,{method:"DELETE"})},Po={list:e=>{const t=new URLSearchParams;e!=null&&e.job_id&&t.set("job_id",e.job_id),e!=null&&e.candidate_name&&t.set("candidate_name",e.candidate_name),e!=null&&e.task_name&&t.set("task_name",e.task_name),(e==null?void 0:e.is_pareto)!==void 0&&t.set("is_pareto",String(e.is_pareto));const n=t.toString();return V(`/runs${n?`?${n}`:""}`)},get:e=>V(`/runs/${e}`),getJobSummary:e=>V(`/runs/job/${e}/summary`)},Ns={list:e=>{const t=new URLSearchParams;e!=null&&e.agent_id&&t.set("agent_id",e.agent_id),e!=null&&e.limit&&t.set("limit",String(e.limit));const n=t.toString();return V(`/tests${n?`?${n}`:""}`)},get:e=>V(`/tests/${e}`),create:e=>V("/tests",{method:"POST",body:JSON.stringify(e)}),start:async function*(e){var o;const t={},n=Ws();n&&(t.Authorization=`Bearer ${n}`);const r=await fetch(`${Ai}/tests/${e}/start`,{method:"POST",headers:t});if(r.status===401)throw Gn(),sr&&sr(),new Error("Not authenticated");if(!r.ok){const u=await r.json().catch(()=>({detail:"Failed to start test"}));throw new Error(u.detail||"Failed to start test")}const s=(o=r.body)==null?void 0:o.getReader();if(!s)throw new Error("No response body");const a=new TextDecoder;let l="";for(;;){const{done:u,value:c}=await s.read();if(u)break;l+=a.decode(c,{stream:!0});const p=l.split(` +`);l=p.pop()||"";for(const f of p)f.startsWith("data: ")&&(yield JSON.parse(f.slice(6)))}},cancel:e=>V(`/tests/${e}/cancel`,{method:"POST"}),delete:e=>V(`/tests/${e}`,{method:"DELETE"})},Gg={list:()=>V("/llm-configs"),get:e=>V(`/llm-configs/${e}`),getDefault:()=>V("/llm-configs/default"),create:e=>V("/llm-configs",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>V(`/llm-configs/${e}`,{method:"PUT",body:JSON.stringify(t)}),delete:e=>V(`/llm-configs/${e}`,{method:"DELETE"}),setDefault:e=>V(`/llm-configs/${e}/set-default`,{method:"POST"}),test:e=>V(`/llm-configs/${e}/test`,{method:"POST"})},Iu=Lu((e,t)=>(qg(()=>{e({isAuthenticated:!1,user:null,error:"Session expired. Please log in again."})}),{authConfig:null,isLoadingConfig:!0,isAuthenticated:!1,isLoading:!1,user:null,error:null,loadAuthConfig:async()=>{e({isLoadingConfig:!0});try{const n=await or.getConfig();if(e({authConfig:n,isLoadingConfig:!1}),n.enabled){const r=Ws(),s=Fu();if(r&&s)try{const a=await or.getCurrentUser();e({isAuthenticated:!0,user:a})}catch{Gn(),e({isAuthenticated:!1,user:null})}}else e({isAuthenticated:!0,user:{username:"anonymous",auth_mode:"none"}})}catch(n){console.error("Failed to load auth config:",n),e({isLoadingConfig:!1,error:"Failed to connect to server"})}},login:async(n,r)=>{e({isLoading:!0,error:null});try{const s=await or.login({username:n,password:r});return yd(s.access_token,s.expires_at,s.username),e({isAuthenticated:!0,isLoading:!1,user:{username:s.username,auth_mode:"basic"}}),!0}catch(s){return e({isLoading:!1,error:s instanceof Error?s.message:"Login failed"}),!1}},loginWithGitHub:()=>{window.location.href=or.getGitHubAuthUrl()},handleOAuthCallback:()=>{const n=new URLSearchParams(window.location.search),r=n.get("auth_error");if(r)return e({error:r}),window.history.replaceState({},"",window.location.pathname),!0;if(n.get("auth_callback")==="true"){const s=n.get("token"),a=n.get("expires_at"),l=n.get("username");return s&&a&&l&&(yd(s,a,l),e({isAuthenticated:!0,user:{username:l,auth_mode:"github"}})),window.history.replaceState({},"",window.location.pathname),!0}return!1},logout:async()=>{try{await or.logout()}catch{}Gn(),e({isAuthenticated:!1,user:null,error:null})},checkAuth:async()=>{const{authConfig:n}=t();if(!(n!=null&&n.enabled)){e({isAuthenticated:!0});return}if(!Ws()){e({isAuthenticated:!1,user:null});return}try{const s=await or.getCurrentUser();e({isAuthenticated:!0,user:s})}catch{Gn(),e({isAuthenticated:!1,user:null})}},clearError:()=>e({error:null})}));function K({variant:e="secondary",size:t="md",className:n="",icon:r,iconRight:s,loading:a=!1,children:l,disabled:o,...u}){const c="font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1.5",p={primary:"bg-[var(--accent)] text-black hover:bg-[#16a34a]",secondary:"bg-[var(--bg-tertiary)] text-[var(--text-primary)] border border-[var(--border)] hover:bg-[var(--border)]",danger:"bg-[var(--error)] text-white hover:bg-red-600",ghost:"text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"},f={sm:"px-2 py-1 text-xs",md:"px-3 py-1.5 text-sm"},h=t==="sm"?14:16;return i.jsxs("button",{className:`${c} ${p[e]} ${f[t]} ${n}`,disabled:o||a,...u,children:[a?i.jsx(Fi,{size:h,className:"animate-spin"}):r?i.jsx(r,{size:h}):null,l,s&&!a&&i.jsx(s,{size:h})]})}const Jg={};function Xg(e,t){let n;try{n=e()}catch{return}return{getItem:s=>{var a;const l=u=>u===null?null:JSON.parse(u,void 0),o=(a=n.getItem(s))!=null?a:null;return o instanceof Promise?o.then(l):l(o)},setItem:(s,a)=>n.setItem(s,JSON.stringify(a,void 0)),removeItem:s=>n.removeItem(s)}}const qs=e=>t=>{try{const n=e(t);return n instanceof Promise?n:{then(r){return qs(r)(n)},catch(r){return this}}}catch(n){return{then(r){return this},catch(r){return qs(r)(n)}}}},Yg=(e,t)=>(n,r,s)=>{let a={getStorage:()=>localStorage,serialize:JSON.stringify,deserialize:JSON.parse,partialize:S=>S,version:0,merge:(S,v)=>({...v,...S}),...t},l=!1;const o=new Set,u=new Set;let c;try{c=a.getStorage()}catch{}if(!c)return e((...S)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...S)},r,s);const p=qs(a.serialize),f=()=>{const S=a.partialize({...r()});let v;const d=p({state:S,version:a.version}).then(m=>c.setItem(a.name,m)).catch(m=>{v=m});if(v)throw v;return d},h=s.setState;s.setState=(S,v)=>{h(S,v),f()};const x=e((...S)=>{n(...S),f()},r,s);let w;const j=()=>{var S;if(!c)return;l=!1,o.forEach(d=>d(r()));const v=((S=a.onRehydrateStorage)==null?void 0:S.call(a,r()))||void 0;return qs(c.getItem.bind(c))(a.name).then(d=>{if(d)return a.deserialize(d)}).then(d=>{if(d)if(typeof d.version=="number"&&d.version!==a.version){if(a.migrate)return a.migrate(d.state,d.version);console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return d.state}).then(d=>{var m;return w=a.merge(d,(m=r())!=null?m:x),n(w,!0),f()}).then(()=>{v==null||v(w,void 0),l=!0,u.forEach(d=>d(w))}).catch(d=>{v==null||v(void 0,d)})};return s.persist={setOptions:S=>{a={...a,...S},S.getStorage&&(c=S.getStorage())},clearStorage:()=>{c==null||c.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>j(),hasHydrated:()=>l,onHydrate:S=>(o.add(S),()=>{o.delete(S)}),onFinishHydration:S=>(u.add(S),()=>{u.delete(S)})},j(),w||x},Zg=(e,t)=>(n,r,s)=>{let a={storage:Xg(()=>localStorage),partialize:j=>j,version:0,merge:(j,S)=>({...S,...j}),...t},l=!1;const o=new Set,u=new Set;let c=a.storage;if(!c)return e((...j)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...j)},r,s);const p=()=>{const j=a.partialize({...r()});return c.setItem(a.name,{state:j,version:a.version})},f=s.setState;s.setState=(j,S)=>{f(j,S),p()};const h=e((...j)=>{n(...j),p()},r,s);s.getInitialState=()=>h;let x;const w=()=>{var j,S;if(!c)return;l=!1,o.forEach(d=>{var m;return d((m=r())!=null?m:h)});const v=((S=a.onRehydrateStorage)==null?void 0:S.call(a,(j=r())!=null?j:h))||void 0;return qs(c.getItem.bind(c))(a.name).then(d=>{if(d)if(typeof d.version=="number"&&d.version!==a.version){if(a.migrate)return[!0,a.migrate(d.state,d.version)];console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return[!1,d.state];return[!1,void 0]}).then(d=>{var m;const[g,b]=d;if(x=a.merge(b,(m=r())!=null?m:h),n(x,!0),g)return p()}).then(()=>{v==null||v(x,void 0),x=r(),l=!0,u.forEach(d=>d(x))}).catch(d=>{v==null||v(void 0,d)})};return s.persist={setOptions:j=>{a={...a,...j},j.storage&&(c=j.storage)},clearStorage:()=>{c==null||c.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>w(),hasHydrated:()=>l,onHydrate:j=>(o.add(j),()=>{o.delete(j)}),onFinishHydration:j=>(u.add(j),()=>{u.delete(j)})},a.skipHydration||w(),x||h},e0=(e,t)=>"getStorage"in t||"serialize"in t||"deserialize"in t?((Jg?"production":void 0)!=="production"&&console.warn("[DEPRECATED] `getStorage`, `serialize` and `deserialize` options are deprecated. Use `storage` option instead."),Yg(e,t)):Zg(e,t),t0=e0,n0=Lu()(t0((e,t)=>({theme:"dark",setTheme:n=>{document.documentElement.setAttribute("data-theme",n),e({theme:n})},toggleTheme:()=>{const n=t().theme==="dark"?"light":"dark";document.documentElement.setAttribute("data-theme",n),e({theme:n})}}),{name:"flow-theme",onRehydrateStorage:()=>e=>{e!=null&&e.theme&&document.documentElement.setAttribute("data-theme",e.theme)}}));function r0(){const{theme:e,toggleTheme:t}=n0();return i.jsx("button",{onClick:t,className:"p-2 rounded-md text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--accent)]","aria-label":`Switch to ${e==="dark"?"light":"dark"} mode`,title:`Switch to ${e==="dark"?"light":"dark"} mode`,children:e==="dark"?i.jsx(jg,{size:16}):i.jsx(xg,{size:16})})}const s0=[{path:"/agents",label:"Agents",icon:ng},{path:"/tasks",label:"Tasks",icon:pg},{path:"/jobs",label:"Jobs",icon:Ks}];function a0(){const e=Zr(),{authConfig:t,user:n,logout:r}=Iu(),s=l=>l==="/agents"?e.pathname==="/"||e.pathname==="/agents":e.pathname.startsWith(l),a=async()=>{await r()};return i.jsxs("div",{className:"min-h-screen flex flex-col",children:[i.jsx("header",{className:"border-b border-[var(--border)] bg-[var(--bg-secondary)]",children:i.jsxs("div",{className:"max-w-7xl mx-auto px-4 py-3 flex items-center justify-between",children:[i.jsxs("div",{className:"flex items-center gap-8",children:[i.jsxs(hd,{to:"/",className:"text-lg font-bold text-[var(--accent)] flex items-center gap-2 hover:opacity-80",children:[i.jsx(Ii,{size:20}),"flow",i.jsx("span",{className:"text-[var(--text-secondary)]",children:"/optimize"})]}),i.jsx("nav",{className:"flex gap-1",children:s0.map(l=>i.jsxs(hd,{to:l.path,className:`px-3 py-1.5 rounded text-sm transition-colors flex items-center gap-2 ${s(l.path)?"bg-[var(--accent)] text-black font-medium":"text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"}`,children:[i.jsx(l.icon,{size:16}),l.label]},l.path))})]}),i.jsxs("div",{className:"flex items-center gap-4",children:[i.jsx(r0,{}),(t==null?void 0:t.enabled)&&n&&i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx(wp,{size:14}),i.jsx("span",{children:n.username})]}),i.jsx(K,{variant:"ghost",size:"sm",icon:vg,onClick:a,title:"Sign out",children:"Sign out"})]})]})]})}),i.jsx("main",{className:"flex-1 bg-[var(--bg-primary)]",children:i.jsx("div",{className:"max-w-7xl mx-auto p-4",children:i.jsx(zy,{})})})]})}const bp=["standard","minimal","full","readonly"],i0=["maf","miniagent","langgraph"],l0={maf:"Microsoft Agent Framework",miniagent:"MiniAgent",langgraph:"LangGraph"},o0=["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory","sub_agent"],u0={full:["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory","sub_agent"],standard:["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory"],minimal:["read_file","write_file","bash_execute","task_done"],readonly:["read_file","list_directory","grep_search","think","task_done"]},c0={openai:"OpenAI",azure_openai:"Azure OpenAI",anthropic:"Anthropic",ollama:"Ollama",custom:"Custom (OpenAI-compatible)"};function ee({children:e,className:t="",onClick:n,selected:r=!1,selectable:s=!1}){const a="bg-[var(--bg-secondary)] border border-[var(--border)] p-4",l=s?"cursor-pointer hover:border-[var(--accent-dim)] transition-colors":"",o=r?"border-[var(--accent)]":"";return i.jsx("div",{className:`${a} ${l} ${o} ${t}`,onClick:n,children:e})}function q({children:e,variant:t="default"}){const n={default:"bg-[var(--bg-tertiary)] text-[var(--text-primary)] border border-[var(--border)]",success:"bg-green-600 text-white",warning:"bg-yellow-500 text-black",error:"bg-red-600 text-white",info:"bg-blue-600 text-white"};return i.jsx("span",{className:`inline-block px-2 py-0.5 text-xs font-medium rounded ${n[t]}`,children:e})}const d0={pending:"default",running:"info",completed:"success",failed:"error",cancelled:"warning"};function Ep({job:e,onDelete:t}){const n=Et(),r=e.total_experiments>0?e.completed_experiments/e.total_experiments*100:0;return i.jsxs(ee,{className:"cursor-pointer hover:border-[var(--accent-dim)] flex flex-col",onClick:()=>n(`/jobs/${e.id}`),children:[i.jsxs("div",{className:"flex items-start justify-between mb-3",children:[i.jsxs("div",{className:"flex-1 min-w-0",children:[i.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[i.jsx(q,{variant:d0[e.status]||"default",children:e.status}),e.is_public&&i.jsxs(q,{variant:"info",children:[i.jsx(zi,{className:"w-3 h-3 mr-1 inline"}),"Public"]}),e.pareto_frontier.length>0&&i.jsxs(q,{variant:"success",children:[e.pareto_frontier.length," Pareto"]}),e.use_llm_eval&&i.jsx(q,{children:"LLM"})]}),i.jsx("h3",{className:"font-medium mt-2 truncate",title:e.name||`Job ${e.id.slice(0,8)}`,children:e.name||`Job ${e.id.slice(0,8)}`}),i.jsxs("code",{className:"text-xs text-[var(--text-secondary)] font-mono",children:[e.id.slice(0,8),"..."]})]}),t&&i.jsx(K,{variant:"ghost",size:"sm",onClick:s=>{s.stopPropagation(),confirm("Delete this job?")&&t(e.id)},disabled:e.status==="running",children:"×"})]}),(e.status==="running"||e.status==="completed")&&i.jsxs("div",{className:"mb-3",children:[i.jsxs("div",{className:"flex justify-between text-xs text-[var(--text-secondary)] mb-1",children:[i.jsx("span",{children:"Progress"}),i.jsxs("span",{children:[e.completed_experiments,"/",e.total_experiments]})]}),i.jsx("div",{className:"w-full bg-[var(--bg-primary)] h-1.5 rounded-full overflow-hidden",children:i.jsx("div",{className:`h-full transition-all ${e.status==="completed"?"bg-green-500":"bg-[var(--accent)]"}`,style:{width:`${r}%`}})})]}),e.status==="failed"&&e.error&&i.jsx("div",{className:"mb-3 px-2 py-1.5 bg-red-500/10 border border-red-500/30 rounded text-xs text-red-400 line-clamp-2",children:e.error}),i.jsxs("div",{className:"grid grid-cols-3 gap-2 text-center py-2 border-t border-[var(--border)] mt-auto",children:[i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.candidate_ids.length}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"candidates"})]}),i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.task_ids.length}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"tasks"})]}),i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.total_experiments}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"runs"})]})]}),i.jsxs("div",{className:"text-xs text-[var(--text-secondary)] pt-2 border-t border-[var(--border)] flex justify-between items-center",children:[i.jsxs("span",{children:[new Date(e.created_at).toLocaleDateString()," ",new Date(e.created_at).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})]}),e.is_public&&e.created_by_name&&i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["by ",e.created_by_name]})]})]})}function ia({isOpen:e,onClose:t,title:n,children:r}){return k.useEffect(()=>{const s=a=>{a.key==="Escape"&&t()};return e&&(document.addEventListener("keydown",s),document.body.style.overflow="hidden"),()=>{document.removeEventListener("keydown",s),document.body.style.overflow=""}},[e,t]),e?i.jsxs("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[i.jsx("div",{className:"absolute inset-0 bg-black/80",onClick:t}),i.jsxs("div",{className:"relative bg-[var(--bg-secondary)] border border-[var(--border)] max-w-lg w-full mx-4 max-h-[80vh] overflow-y-auto",children:[i.jsxs("div",{className:"sticky top-0 bg-[var(--bg-secondary)] border-b border-[var(--border)] px-4 py-3 flex items-center justify-between",children:[i.jsx("h2",{className:"font-semibold",children:n}),i.jsx("button",{onClick:t,className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"×"})]}),i.jsx("div",{className:"p-4",children:r})]})]}):null}function pt({label:e,className:t="",...n}){return i.jsxs("div",{className:"space-y-1",children:[e&&i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:e}),i.jsx("input",{className:`w-full bg-[var(--bg-primary)] border border-[var(--border)] px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)] ${t}`,...n})]})}function f0({label:e,className:t="",...n}){return i.jsxs("div",{className:"space-y-1",children:[e&&i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:e}),i.jsx("textarea",{className:`w-full bg-[var(--bg-primary)] border border-[var(--border)] px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)] resize-y min-h-[100px] ${t}`,...n})]})}function Rn({label:e,className:t="",...n}){return i.jsxs("label",{className:`flex items-center gap-2 cursor-pointer ${t}`,children:[i.jsx("input",{type:"checkbox",className:"w-4 h-4 bg-[var(--bg-primary)] border border-[var(--border)] accent-[var(--accent)]",...n}),i.jsx("span",{className:"text-sm",children:e})]})}function gd(){const e=Et(),t=Pn(),[n,r]=k.useState(!1),[s,a]=k.useState(null),{data:l=[],isLoading:o}=_e({queryKey:["configs"],queryFn:()=>Jn.list()}),{data:u=[]}=_e({queryKey:["jobs"],queryFn:()=>bt.list()}),c=st({mutationFn:Jn.create,onSuccess:()=>{t.invalidateQueries({queryKey:["configs"]}),r(!1)}}),p=st({mutationFn:Jn.delete,onSuccess:()=>t.invalidateQueries({queryKey:["configs"]})}),f=h=>{const x=u.filter(S=>S.candidate_ids.includes(h)),w=x.filter(S=>S.status==="running").length,j=x.filter(S=>S.status==="completed").length;return{running:w,completed:j,total:x.length}};return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Agents"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:"Define and optimize your agent configurations."})]}),i.jsx(K,{variant:"primary",icon:yp,onClick:()=>r(!0),children:"New Agent"})]}),o?i.jsxs("div",{className:"flex items-center gap-2 text-[var(--text-secondary)]",children:[i.jsx(Fi,{size:16,className:"animate-spin"}),"Loading agents..."]}):l.length===0?i.jsx(h0,{onCreateClick:()=>r(!0)}):i.jsx("div",{className:"grid gap-4 md:grid-cols-2 lg:grid-cols-3",children:l.map(h=>{const x=f(h.id);return i.jsx(m0,{agent:h,stats:x,onClick:()=>e(`/agents/${h.id}`),onOptimize:()=>a(h),onDelete:()=>{confirm(`Delete agent "${h.name}"?`)&&p.mutate(h.id)}},h.id)})}),u.length>0&&i.jsxs("div",{className:"mt-8",children:[i.jsx("h3",{className:"text-lg font-medium mb-4",children:"Recent Optimization Jobs"}),i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4",children:u.slice(0,6).map(h=>i.jsx(Ep,{job:h},h.id))}),u.length>6&&i.jsxs(K,{variant:"ghost",className:"mt-4",onClick:()=>e("/jobs"),children:["View all ",u.length," jobs →"]})]}),i.jsx(v0,{isOpen:n,onClose:()=>r(!1),onSubmit:h=>c.mutate(h),isLoading:c.isPending}),s&&i.jsx(x0,{agent:s,isOpen:!!s,onClose:()=>a(null)})]})}function h0({onCreateClick:e}){return i.jsxs("div",{className:"text-center py-16 border border-dashed border-[var(--border)] rounded-lg",children:[i.jsx("div",{className:"inline-flex items-center justify-center w-12 h-12 rounded-full bg-[var(--bg-tertiary)] mb-4",children:i.jsx(gp,{size:24,className:"text-[var(--text-secondary)]"})}),i.jsx("h3",{className:"text-lg font-medium mb-2",children:"No agents yet"}),i.jsx("p",{className:"text-[var(--text-secondary)] mb-4 max-w-md mx-auto",children:"Create your first agent to start optimizing. Each agent defines instructions, model, compaction strategy, and tool settings."}),i.jsx(K,{variant:"primary",icon:yp,onClick:e,children:"Create Your First Agent"})]})}function p0(e){return typeof e=="string"?`tools: ${e}`:Array.isArray(e)?`tools: [${e.length}]`:typeof e=="object"?`tools: [${Object.keys(e).length}]`:"tools: standard"}function m0({agent:e,stats:t,onClick:n,onOptimize:r,onDelete:s}){const a=e.config.compaction,l=(a==null?void 0:a.strategy)==="head_tail"?`compaction ${a.params.head_size}/${a.params.tail_size}`:(a==null?void 0:a.strategy)==="none"?null:(a==null?void 0:a.strategy)||null,o=p0(e.config.tools),u=e.config.framework||"maf";return i.jsxs(ee,{className:"flex flex-col cursor-pointer hover:border-[var(--accent-dim)] transition-colors",onClick:n,children:[i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-start justify-between mb-3",children:[i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium text-lg",children:e.name}),e.description&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:e.description})]}),i.jsx(K,{variant:"ghost",size:"sm",icon:jp,onClick:c=>{c.stopPropagation(),s()}})]}),i.jsxs("div",{className:"flex flex-wrap gap-1.5 mb-4",children:[i.jsx(q,{variant:u==="miniagent"?"success":"default",children:u}),l&&i.jsx(q,{children:l}),i.jsx(q,{children:o})]}),t.total>0&&i.jsxs("div",{className:"text-xs text-[var(--text-secondary)] mb-3",children:[t.running>0&&i.jsxs("span",{className:"text-[var(--accent)]",children:[t.running," running "]}),t.completed>0&&i.jsxs("span",{children:[t.completed," completed"]})]})]}),i.jsx("div",{className:"flex gap-2",children:i.jsx(K,{variant:"primary",icon:Ii,onClick:c=>{c.stopPropagation(),r()},className:"flex-1",children:"Optimize"})})]})}function v0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){var b,_,C;const s=["read_file","write_file","bash_execute","grep_search","task_done"],{data:a=[]}=_e({queryKey:["llm-configs"],queryFn:()=>Gg.list()}),[l,o]=k.useState({name:"",description:"",instructions:null,model:null,compaction:{strategy:"none",params:{}},tools:s,llm_config_id:null,framework:"maf"}),[u,c]=k.useState(!1),[p,f]=k.useState("custom"),[h,x]=k.useState([...s]),[w,j]=k.useState(!1),S=N=>{if(N.preventDefault(),!l.name.trim())return;const M={...l};p==="custom"&&(M.tools=h),n(M)},v=((b=l.compaction)==null?void 0:b.strategy)!=="none",d=N=>{x(M=>M.includes(N)?M.filter(P=>P!==N):[...M,N])},m=a.find(N=>N.id===l.llm_config_id),g=a.find(N=>N.is_default);return i.jsx(ia,{isOpen:e,onClose:t,title:"Create Agent",children:i.jsxs("form",{onSubmit:S,className:"space-y-4",children:[i.jsx(pt,{label:"Name",value:l.name,onChange:N=>o({...l,name:N.target.value}),placeholder:"e.g., my-coding-agent",required:!0}),i.jsxs("div",{children:[i.jsx("label",{className:"text-sm font-medium block mb-1.5",children:"LLM Configuration"}),a.length===0?i.jsxs("div",{className:"p-3 border border-[var(--border)] rounded bg-[var(--bg-secondary)] text-sm",children:[i.jsx("p",{className:"text-[var(--text-secondary)] mb-2",children:"No LLM configurations found. Create one in Settings first."}),i.jsx("a",{href:"/settings",className:"text-[var(--accent)] hover:underline",children:"Go to Settings →"})]}):i.jsxs(i.Fragment,{children:[i.jsxs("select",{className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",value:l.llm_config_id||"",onChange:N=>o({...l,llm_config_id:N.target.value||null}),children:[i.jsx("option",{value:"",children:g?`Use default (${g.name})`:"Select LLM config..."}),a.map(N=>i.jsxs("option",{value:N.id,children:[N.name," (",c0[N.provider],")",N.is_default?" ★":""]},N.id))]}),m&&i.jsxs("p",{className:"text-xs text-[var(--text-secondary)] mt-1",children:["Model: ",m.model_id||"default"]})]})]}),i.jsxs("div",{children:[i.jsx("label",{className:"text-sm font-medium block mb-1.5",children:"Framework"}),i.jsx("select",{className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",value:l.framework||"maf",onChange:N=>o({...l,framework:N.target.value}),children:i0.map(N=>i.jsx("option",{value:N,children:l0[N]},N))}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-1",children:l.framework==="miniagent"?"Lightweight agent with token-aware context management.":l.framework==="langgraph"?"Graph-based workflows with state management.":"Default agent implementation."})]}),i.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[i.jsx(Rn,{label:"Custom instructions",checked:u,onChange:N=>{c(N.target.checked),N.target.checked||o({...l,instructions:null})}}),i.jsx(Rn,{label:"Enable compaction",checked:v,onChange:N=>o({...l,compaction:N.target.checked?{strategy:"head_tail",params:{head_size:10,tail_size:40}}:{strategy:"none",params:{}}})})]}),u&&i.jsx("textarea",{className:"w-full h-32 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono resize-y",value:l.instructions||"",onChange:N=>o({...l,instructions:N.target.value||null}),placeholder:"System prompt / instructions for the agent..."}),v&&i.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[i.jsx(pt,{label:"Head size",type:"number",value:((_=l.compaction)==null?void 0:_.params.head_size)??10,onChange:N=>o({...l,compaction:{strategy:"head_tail",params:{...l.compaction.params,head_size:parseInt(N.target.value)||10}}}),min:1}),i.jsx(pt,{label:"Tail size",type:"number",value:((C=l.compaction)==null?void 0:C.params.tail_size)??40,onChange:N=>o({...l,compaction:{strategy:"head_tail",params:{...l.compaction.params,tail_size:parseInt(N.target.value)||40}}}),min:1})]}),i.jsxs("div",{className:"space-y-2",children:[i.jsx("label",{className:"text-sm font-medium",children:"Tools"}),i.jsxs("div",{className:"flex gap-4",children:[i.jsxs("label",{className:"flex items-center gap-2",children:[i.jsx("input",{type:"radio",checked:p==="custom",onChange:()=>f("custom"),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:"Custom"})]}),i.jsxs("label",{className:"flex items-center gap-2",children:[i.jsx("input",{type:"radio",checked:p==="preset",onChange:()=>f("preset"),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:"Preset"})]})]}),p==="custom"?i.jsx("div",{className:"grid grid-cols-2 gap-1 p-2 border border-[var(--border)] rounded bg-[var(--bg-secondary)]",children:o0.map(N=>i.jsxs("label",{className:"flex items-center gap-2 p-1 text-sm cursor-pointer hover:bg-[var(--bg-tertiary)]",children:[i.jsx("input",{type:"checkbox",checked:h.includes(N),onChange:()=>d(N),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"font-mono text-xs",children:N})]},N))}):i.jsxs(i.Fragment,{children:[i.jsx("select",{className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",value:typeof l.tools=="string"?l.tools:"standard",onChange:N=>o({...l,tools:N.target.value}),children:bp.map(N=>i.jsx("option",{value:N,children:N},N))}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-1",children:u0[typeof l.tools=="string"?l.tools:"standard"].join(", ")})]})]}),i.jsxs("div",{className:"border-t border-[var(--border)] pt-3",children:[i.jsxs("button",{type:"button",className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors",onClick:()=>j(!w),children:[i.jsx(Ss,{size:14,className:`transition-transform ${w?"rotate-90":""}`}),"More options"]}),w&&i.jsx("div",{className:"mt-3",children:i.jsx(pt,{label:"Description (optional)",value:l.description,onChange:N=>o({...l,description:N.target.value}),placeholder:"Brief description of this agent"})})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(K,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(K,{type:"submit",variant:"primary",disabled:!l.name.trim(),loading:r,children:"Create Agent"})]})]})})}function x0({agent:e,isOpen:t,onClose:n}){var O,D,B;const r=Et(),s=Pn(),[a,l]=k.useState("suite"),[o,u]=k.useState("quick"),[c,p]=k.useState(!1),[f,h]=k.useState([]),[x,w]=k.useState({base_name:e.name,vary_compaction:!0,vary_tools:!0,vary_compaction_head:!1,vary_compaction_tail:!1,tool_presets:["standard","minimal"],compaction_head_values:[5,10,20],compaction_tail_values:[20,40,60]}),[j,S]=k.useState(4),[v,d]=k.useState(!1),[m,g]=k.useState(null),{data:b=[]}=_e({queryKey:["tasks"],queryFn:()=>fn.list()}),_=st({mutationFn:fn.importSuite,onSuccess:L=>{s.invalidateQueries({queryKey:["tasks"]}),h(L.map(A=>A.id))}}),C=st({mutationFn:Jn.generateCandidates,onSuccess:()=>{s.invalidateQueries({queryKey:["configs"]})}}),N=st({mutationFn:async L=>{const A=await bt.create(L);return bt.start(A.id).next(),A},onSuccess:L=>{s.invalidateQueries({queryKey:["jobs"]}),g(L.id),l("success")}}),M=[{value:"quick",label:"Quick",description:"3 fast tasks for rapid testing",tasks:3},{value:"core",label:"Core",description:"5 standard evaluation tasks",tasks:5},{value:"coding",label:"Coding",description:"10 comprehensive coding tasks",tasks:10}],P=()=>{var A,T,$;let L=1;return x.vary_compaction&&(L*=2),x.vary_tools&&(L*=((A=x.tool_presets)==null?void 0:A.length)||3),x.vary_compaction_head&&(L*=((T=x.compaction_head_values)==null?void 0:T.length)||3),x.vary_compaction_tail&&(L*=(($=x.compaction_tail_values)==null?void 0:$.length)||3),L},H=x.vary_compaction||x.vary_tools||x.vary_compaction_head||x.vary_compaction_tail,F=async()=>{l("starting");let L=f,A=[e.id];if(!c)try{L=(await _.mutateAsync(o)).map(ye=>ye.id)}catch(fe){console.error("Failed to import suite:",fe),alert(`Failed to import task suite: ${o}`),l("candidates");return}if(L.length===0){alert("No tasks selected. Please select tasks or choose a task suite."),l("candidates");return}try{A=(await C.mutateAsync({...x,base_name:e.name})).map(ye=>ye.id)}catch(fe){console.error("Failed to generate candidates:",fe),alert("Failed to generate candidates"),l("candidates");return}const T=P(),$={name:`${e.name} optimization (${T} candidates × ${L.length} tasks)`,candidate_ids:A,task_ids:L,parallel:j,use_llm_eval:v};N.mutate($)},U=L=>{h(A=>A.includes(L)?A.filter(T=>T!==L):[...A,L])},te=()=>{l("suite"),g(null),n()},ke=P(),Pt=c?f.length:((O=M.find(L=>L.value===o))==null?void 0:O.tasks)||3,He=ke*Pt;return i.jsx(ia,{isOpen:t,onClose:te,title:`Optimize: ${e.name}`,children:a==="success"&&m?i.jsxs("div",{className:"flex flex-col items-center py-8",children:[i.jsx("div",{className:"w-12 h-12 rounded-full bg-green-500/20 flex items-center justify-center mb-4",children:i.jsx(Ii,{size:24,className:"text-green-500"})}),i.jsx("h3",{className:"text-lg font-medium mb-2",children:"Job Started!"}),i.jsx("p",{className:"text-[var(--text-secondary)] text-center mb-2",children:"Optimization job is now running"}),i.jsxs("code",{className:"text-xs bg-[var(--bg-primary)] px-3 py-1.5 rounded font-mono mb-6",children:["ID: ",m.slice(0,8),"..."]}),i.jsxs("div",{className:"flex gap-3",children:[i.jsx(K,{variant:"secondary",onClick:te,children:"Close"}),i.jsx(K,{variant:"primary",icon:Ks,onClick:()=>{te(),r(`/jobs/${m}`)},children:"View Job"})]})]}):a==="starting"?i.jsxs("div",{className:"flex flex-col items-center py-8",children:[i.jsx(Fi,{size:32,className:"animate-spin text-[var(--accent)] mb-4"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:C.isPending?"Generating candidates...":_.isPending?"Importing tasks...":"Creating optimization job..."})]}):a==="candidates"?i.jsxs("div",{className:"space-y-6",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx("span",{className:"text-[var(--text-primary)]",children:"1. Tasks"}),i.jsx(Ss,{size:14}),i.jsx("span",{className:"text-[var(--accent)] font-medium",children:"2. Candidates"})]}),i.jsxs("div",{className:"space-y-4 p-4 border border-[var(--border)] bg-[var(--bg-secondary)]",children:[i.jsxs("div",{className:"flex items-center justify-between",children:[i.jsx("h4",{className:"font-medium text-sm",children:"Select dimensions to explore:"}),i.jsxs(q,{variant:"info",children:[ke," candidate",ke!==1?"s":""]})]}),!H&&i.jsx("p",{className:"text-xs text-[var(--text-secondary)]",children:"Select at least one dimension to generate candidate variations. Without variations, only the baseline agent will be tested."}),i.jsxs("div",{className:"space-y-2",children:[i.jsx(Rn,{label:"Compaction (on/off)",checked:x.vary_compaction,onChange:L=>w({...x,vary_compaction:L.target.checked})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6 -mt-1",children:"Test with compaction enabled vs disabled"}),i.jsx(Rn,{label:"Tool Presets",checked:x.vary_tools,onChange:L=>w({...x,vary_tools:L.target.checked})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6 -mt-1",children:"Test different tool configurations (standard, minimal, full)"}),x.vary_tools&&i.jsx("div",{className:"ml-6 flex flex-wrap gap-2",children:bp.map(L=>{var A;return i.jsxs("label",{className:"flex items-center gap-1 text-xs",children:[i.jsx("input",{type:"checkbox",checked:(A=x.tool_presets)==null?void 0:A.includes(L),onChange:T=>{const $=x.tool_presets||[];w({...x,tool_presets:T.target.checked?[...$,L]:$.filter(fe=>fe!==L)})},className:"accent-[var(--accent)]"}),i.jsx("span",{children:L})]},L)})})]}),i.jsxs("div",{className:"border-t border-[var(--border)] pt-3 mt-3 space-y-2",children:[i.jsx(Rn,{label:"Compaction Head Size",checked:x.vary_compaction_head,onChange:L=>w({...x,vary_compaction_head:L.target.checked})}),x.vary_compaction_head&&i.jsx(pt,{label:"Head sizes (comma-separated)",value:((D=x.compaction_head_values)==null?void 0:D.join(", "))||"5, 10, 20",onChange:L=>w({...x,compaction_head_values:L.target.value.split(",").map(A=>parseInt(A.trim())).filter(A=>!isNaN(A))})}),i.jsx(Rn,{label:"Compaction Tail Size",checked:x.vary_compaction_tail,onChange:L=>w({...x,vary_compaction_tail:L.target.checked})}),x.vary_compaction_tail&&i.jsx(pt,{label:"Tail sizes (comma-separated)",value:((B=x.compaction_tail_values)==null?void 0:B.join(", "))||"20, 40, 60",onChange:L=>w({...x,compaction_tail_values:L.target.value.split(",").map(A=>parseInt(A.trim())).filter(A=>!isNaN(A))})})]}),i.jsxs("div",{className:"bg-[var(--bg-tertiary)] p-3 rounded text-sm",children:[i.jsxs("div",{className:"flex justify-between",children:[i.jsx("span",{children:"Candidates:"}),i.jsx("span",{className:"font-mono",children:ke})]}),i.jsxs("div",{className:"flex justify-between",children:[i.jsx("span",{children:"Tasks:"}),i.jsx("span",{className:"font-mono",children:Pt})]}),i.jsxs("div",{className:"flex justify-between font-medium border-t border-[var(--border)] pt-2 mt-2",children:[i.jsx("span",{children:"Total experiments:"}),i.jsx("span",{className:"font-mono text-[var(--accent)]",children:He})]})]})]}),i.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[i.jsx(pt,{label:"Parallel Workers",type:"number",value:j,onChange:L=>S(parseInt(L.target.value)||1),min:1,max:16}),i.jsxs("div",{className:"space-y-1",children:[i.jsx(Rn,{label:"Use LLM evaluation",checked:v,onChange:L=>d(L.target.checked)}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6",children:v?"GPT-4o scores task completion (0-1)":"Simple pass/fail based on task success"})]})]}),i.jsxs("div",{className:"flex justify-between gap-2 pt-4 border-t border-[var(--border)]",children:[i.jsx(K,{variant:"secondary",icon:ig,onClick:()=>l("suite"),children:"Back"}),i.jsxs(K,{variant:"primary",icon:Ks,onClick:F,children:["Start Optimization (",He," runs)"]})]})]}):i.jsxs("div",{className:"space-y-6",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx("span",{className:"text-[var(--accent)] font-medium",children:"1. Tasks"}),i.jsx(Ss,{size:14}),i.jsx("span",{children:"2. Candidates"})]}),i.jsxs("div",{children:[i.jsx("label",{className:"text-sm font-medium mb-3 block",children:"Select Task Suite"}),i.jsx("div",{className:"space-y-2",children:M.map(L=>{const A=b.filter(T=>T.suite===L.value).length;return i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer transition-colors ${o===L.value&&!c?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",name:"suite",value:L.value,checked:o===L.value&&!c,onChange:()=>{u(L.value),p(!1)},className:"accent-[var(--accent)]"}),i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"font-medium",children:L.label}),i.jsxs(q,{children:[L.tasks," tasks"]}),A>0&&i.jsxs(q,{variant:"success",children:[A," imported"]})]}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:L.description})]})]},L.value)})})]}),b.length>0&&i.jsxs("div",{children:[i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer transition-colors ${c?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",checked:c,onChange:()=>p(!0),className:"accent-[var(--accent)]"}),i.jsxs("div",{className:"flex-1",children:[i.jsx("span",{className:"font-medium",children:"Custom Selection"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:"Choose specific tasks from your library"})]})]}),c&&i.jsx("div",{className:"mt-3 max-h-48 overflow-y-auto border border-[var(--border)] p-2 space-y-1",children:b.map(L=>i.jsxs("label",{className:"flex items-center gap-2 p-2 hover:bg-[var(--bg-tertiary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:f.includes(L.id),onChange:()=>U(L.id),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:L.name}),L.suite&&i.jsx(q,{children:L.suite})]},L.id))})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4 border-t border-[var(--border)]",children:[i.jsx(K,{variant:"secondary",onClick:n,children:"Cancel"}),i.jsx(K,{variant:"primary",icon:Ss,onClick:()=>l("candidates"),disabled:c&&f.length===0,children:"Next: Candidates"})]})]})})}function y0(e){const t=[],n=r=>r.type==="trace_span"&&typeof r.data=="object"&&r.data!==null?r.data:"span_id"in r?r:null;if(Array.isArray(e.spans)){for(const r of e.spans)if(typeof r=="object"&&r!==null){const s=n(r);s&&t.push(s)}}else if(e.span_id)t.push(e);else for(const r in e){const s=e[r];if(typeof s=="object"&&s!==null){const a=n(s);if(a)t.push(a);else if(Array.isArray(s)){for(const l of s)if(typeof l=="object"&&l!==null){const o=n(l);o&&t.push(o)}}}}return t}function Pp(e){const t=new Map,n=[];for(const s of e)t.set(s.span_id,{span:s,children:[]});for(const s of e){const a=t.get(s.span_id);s.parent_span_id&&t.has(s.parent_span_id)?t.get(s.parent_span_id).children.push(a):n.push(a)}const r=s=>{s.sort((a,l)=>(a.span.start_time||0)-(l.span.start_time||0)),s.forEach(a=>r(a.children))};return r(n),n}function g0(e){return e.includes("Agent")||e.includes("agent")?"bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200":e.includes("chat")||e.includes("Chat")||e.includes("llm")?"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200":e.includes("tool")||e.includes("execute")||e.includes("bash")?"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200":"bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"}function j0(e){return e>=1e3?`${(e/1e3).toFixed(2)}s`:`${e.toFixed(0)}ms`}function Du({node:e,depth:t=0}){var f,h;const[n,r]=k.useState(t<2),[s,a]=k.useState(!1),{span:l}=e,o=e.children.length>0,u=(f=l.attributes)==null?void 0:f["gen_ai.usage.input_tokens"],c=(h=l.attributes)==null?void 0:h["gen_ai.usage.output_tokens"],p=u!==void 0||c!==void 0;return i.jsxs("div",{className:"relative",children:[t>0&&i.jsx("div",{className:"absolute left-0 top-0 bottom-0 border-l-2 border-[var(--border)]",style:{marginLeft:`${(t-1)*16+8}px`}}),i.jsxs("div",{className:"flex items-center gap-2 py-1.5 px-1 hover:bg-[var(--bg-primary)] rounded transition-colors cursor-pointer",style:{paddingLeft:`${t*16}px`},onClick:()=>o?r(!n):a(!s),children:[i.jsx("div",{className:"w-4 h-4 flex items-center justify-center text-[var(--text-secondary)]",children:o?n?"▼":"▶":s?"▼":"▶"}),i.jsx("span",{className:`text-xs px-1.5 py-0.5 rounded font-medium ${g0(l.operation_name)}`,children:l.operation_name.replace("ChatAgent.","").replace("invoke_agent ","")}),l.duration_ms!==void 0&&i.jsx("span",{className:"text-xs text-[var(--text-secondary)] font-mono",children:j0(l.duration_ms)}),p&&i.jsxs("span",{className:"text-xs text-[var(--text-secondary)] font-mono",children:[u!==void 0&&i.jsxs("span",{className:"text-blue-400",children:["↑",String(u)]}),u!==void 0&&c!==void 0&&i.jsx("span",{className:"mx-0.5",children:"/"}),c!==void 0&&i.jsxs("span",{className:"text-green-400",children:["↓",String(c)]})]})]}),s&&!o&&i.jsx("div",{className:"ml-4 mt-1 mb-2 p-2 bg-[var(--bg-primary)] rounded border border-[var(--border)] text-xs",style:{marginLeft:`${t*16+20}px`},children:i.jsxs("div",{className:"space-y-1",children:[l.span_id&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Span ID:"}),i.jsx("span",{className:"font-mono text-xs break-all",children:l.span_id})]}),l.trace_id&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Trace ID:"}),i.jsx("span",{className:"font-mono text-xs break-all",children:l.trace_id})]}),l.status&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Status:"}),i.jsx("span",{className:`px-1.5 py-0.5 rounded text-xs ${l.status==="OK"||l.status==="StatusCode.UNSET"?"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200":"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"}`,children:l.status})]}),Object.keys(l.attributes||{}).length>0&&i.jsxs("div",{className:"mt-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] block mb-1",children:"Attributes:"}),i.jsx("pre",{className:"text-xs bg-[var(--bg-secondary)] border border-[var(--border)] rounded p-2 overflow-auto max-h-32 whitespace-pre-wrap break-all",children:JSON.stringify(l.attributes,null,2)})]})]})}),o&&n&&i.jsx("div",{children:e.children.map((x,w)=>i.jsx(Du,{node:x,depth:t+1},x.span.span_id||w))})]})}function Tp({trace:e}){const[t,n]=k.useState("tree"),r=k.useMemo(()=>y0(e),[e]),s=k.useMemo(()=>Pp(r),[r]);return Object.keys(e).length===0?null:i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-3",children:[i.jsx("h3",{className:"font-medium",children:"Trace Data"}),i.jsxs("div",{className:"flex gap-1",children:[i.jsx("button",{onClick:()=>n("tree"),className:`px-2 py-1 text-xs rounded ${t==="tree"?"bg-[var(--accent)] text-white":"bg-[var(--bg-primary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:"Tree"}),i.jsx("button",{onClick:()=>n("raw"),className:`px-2 py-1 text-xs rounded ${t==="raw"?"bg-[var(--accent)] text-white":"bg-[var(--bg-primary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:"Raw"})]})]}),t==="tree"?r.length>0?i.jsx("div",{className:"border border-[var(--border)] rounded overflow-hidden",children:i.jsxs("div",{className:"p-2",children:[i.jsxs("div",{className:"flex items-center gap-2 mb-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs(q,{variant:"default",children:[r.length," spans"]}),i.jsx("span",{children:"•"}),i.jsx("span",{children:"Click to expand details"})]}),s.map((a,l)=>i.jsx(Du,{node:a,depth:0},a.span.span_id||l))]})}):i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:"No structured spans found. View raw data below."}):i.jsx("pre",{className:"text-xs bg-[var(--bg-primary)] p-3 overflow-x-auto border border-[var(--border)] max-h-96 whitespace-pre-wrap",children:JSON.stringify(e,null,2)})]})}function w0({spans:e,isLive:t=!1}){const n=k.useRef(null),r=k.useMemo(()=>Pp(e),[e]);return k.useEffect(()=>{n.current&&t&&n.current.scrollTo({top:n.current.scrollHeight,behavior:"smooth"})},[e.length,t]),i.jsxs("div",{className:"border border-[var(--border)] rounded overflow-hidden h-full flex flex-col",children:[i.jsxs("div",{className:"flex items-center gap-2 p-2 border-b border-[var(--border)] bg-[var(--bg-secondary)]",children:[i.jsxs(q,{variant:"default",children:[e.length," spans"]}),t&&i.jsx("span",{className:"animate-pulse",children:i.jsx(q,{variant:"info",children:"Live"})})]}),i.jsx("div",{ref:n,className:"flex-1 overflow-auto p-2",children:r.length>0?r.map((s,a)=>i.jsx(Du,{node:s,depth:0},s.span.span_id||a)):i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:t?"Waiting for spans...":"No spans recorded"})})]})}function k0({agent:e}){const[t,n]=k.useState(""),[r,s]=k.useState(null),[a,l]=k.useState("idle"),[o,u]=k.useState(null),[c,p]=k.useState(""),[f,h]=k.useState([]),[x,w]=k.useState(null),[j,S]=k.useState(null),[v,d]=k.useState([]),m=k.useRef(null),{data:g=[]}=_e({queryKey:["tasks"],queryFn:()=>fn.list()});k.useEffect(()=>{m.current&&a==="running"&&(m.current.scrollTop=m.current.scrollHeight)},[c,a]);const b=F=>{if(s(F),F){const U=g.find(te=>te.id===F);U&&n(U.prompt)}},_=async()=>{if(t.trim()){l("running"),p(""),h([]),w(null),S(null),d([]);try{const F=await Ns.create({agent_id:e.id,prompt:t.trim(),task_id:r||void 0});u(F.id);for await(const U of Ns.start(F.id))C(U)}catch(F){S(F instanceof Error?F.message:"Test failed"),l("failed")}}},C=F=>{switch(F.event){case"started":break;case"execution":F.execution_event==="text_delta"&&F.content?p(U=>U+F.content):F.execution_event==="tool_call_start"&&F.tool_name?d(U=>[...U,{name:F.tool_name}]):F.execution_event==="tool_result"&&F.content&&d(U=>{if(U.length>0){const te=[...U];return te[te.length-1]={...te[te.length-1],content:F.content},te}return U});break;case"span":if(F.span){const U=F.span;if(U.data){const te={span_id:U.data.span_id||"",trace_id:U.data.trace_id||"",parent_span_id:U.data.parent_span_id||null,operation_name:U.data.operation_name||"",start_time:U.timestamp?new Date(U.timestamp).getTime():Date.now(),end_time:Date.now(),duration_ms:U.data.duration_ms||0,status:U.data.status||"OK",attributes:U.data.attributes||{}};h(ke=>[...ke,te])}}break;case"complete":l("completed"),F.result&&w(F.result);break;case"error":S(F.message),l("failed");break}},N=async()=>{if(o)try{await Ns.cancel(o)}catch{}l("idle")},M=()=>{l("idle"),u(null),p(""),h([]),w(null),S(null),d([])},P=a==="running",H=a==="completed"||a==="failed";return i.jsxs("div",{className:"h-full flex flex-col",children:[!P&&!H&&i.jsxs("div",{className:"mb-4 space-y-3",children:[i.jsxs("div",{className:"flex gap-3",children:[i.jsxs("div",{className:"flex-1",children:[i.jsx("label",{className:"block text-sm font-medium mb-1",children:"Select Task (optional)"}),i.jsxs("select",{value:r||"",onChange:F=>b(F.target.value||null),className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",children:[i.jsx("option",{value:"",children:"Custom prompt..."}),g.map(F=>i.jsx("option",{value:F.id,children:F.name},F.id))]})]}),i.jsx("div",{className:"flex items-end",children:i.jsx(K,{variant:"primary",icon:Ks,onClick:_,disabled:!t.trim(),children:"Run Test"})})]}),i.jsxs("div",{children:[i.jsx("label",{className:"block text-sm font-medium mb-1",children:"Prompt"}),i.jsx("textarea",{value:t,onChange:F=>n(F.target.value),placeholder:"Enter a test prompt for the agent...",className:"w-full h-32 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono resize-none"})]})]}),(P||H)&&i.jsxs("div",{className:"flex-1 flex flex-col min-h-0",children:[i.jsxs("div",{className:"flex items-center justify-between mb-3",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[P&&i.jsxs(i.Fragment,{children:[i.jsx("span",{className:"animate-pulse",children:i.jsx(q,{variant:"info",children:"Running"})}),i.jsx(K,{variant:"ghost",size:"sm",icon:gg,onClick:N,children:"Cancel"})]}),a==="completed"&&i.jsx(q,{variant:"success",children:"Completed"}),a==="failed"&&i.jsx(q,{variant:"error",children:"Failed"})]}),i.jsxs("div",{className:"flex items-center gap-3",children:[x&&i.jsxs("div",{className:"flex items-center gap-3 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{className:"flex items-center gap-1",children:[i.jsx(lg,{size:12}),x.duration_seconds.toFixed(2),"s"]}),i.jsxs("span",{className:"flex items-center gap-1",children:[i.jsx(og,{size:12}),x.tokens_total," tokens"]}),x.passed!==null&&(x.passed?i.jsx(rg,{size:14,className:"text-green-500"}):i.jsx(wg,{size:14,className:"text-red-500"}))]}),H&&i.jsx(K,{variant:"secondary",size:"sm",onClick:M,children:"New Test"})]})]}),i.jsxs("div",{className:"flex-1 grid grid-cols-2 gap-4 min-h-0",children:[i.jsxs("div",{className:"flex flex-col border border-[var(--border)] rounded overflow-hidden",children:[i.jsx("div",{className:"px-3 py-2 border-b border-[var(--border)] bg-[var(--bg-secondary)] text-sm font-medium",children:"Output"}),i.jsxs("div",{ref:m,className:"flex-1 overflow-auto p-3 font-mono text-sm whitespace-pre-wrap bg-[var(--bg-primary)]",children:[c||(P?"Waiting for response...":"No output"),v.length>0&&i.jsxs("div",{className:"mt-3 space-y-2 border-t border-[var(--border)] pt-3",children:[i.jsx("div",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:"Tool Calls"}),v.map((F,U)=>i.jsxs("div",{className:"p-2 bg-green-500/10 border border-green-500/20 rounded text-xs",children:[i.jsx(q,{variant:"success",children:F.name}),F.content&&i.jsxs("div",{className:"mt-1 text-[var(--text-secondary)] truncate max-w-full",children:[F.content.substring(0,200),F.content.length>200&&"..."]})]},U))]})]})]}),i.jsx(w0,{spans:f,isLive:P})]}),j&&i.jsx("div",{className:"mt-3 p-3 bg-red-500/10 border border-red-500/20 rounded text-sm text-red-400",children:j}),H&&o&&i.jsx("div",{className:"mt-3 flex items-center justify-end pt-3 border-t border-[var(--border)]",children:i.jsx(Hs,{to:`/tests/${o}`,children:i.jsx(K,{variant:"primary",iconRight:cg,children:"View Full Details"})})})]})]})}function S0(){const{agentId:e}=Ri(),t=Et(),n=Pn(),[r,s]=k.useState("overview"),{data:a,isLoading:l}=_e({queryKey:["configs",e],queryFn:()=>Jn.get(e),enabled:!!e}),{data:o=[]}=_e({queryKey:["tests",{agent_id:e}],queryFn:()=>Ns.list({agent_id:e}),enabled:!!e}),{data:u=[]}=_e({queryKey:["jobs"],queryFn:()=>bt.list()}),c=st({mutationFn:f=>Jn.delete(f),onSuccess:()=>{n.invalidateQueries({queryKey:["configs"]}),t("/agents")}}),p=u.filter(f=>f.candidate_ids.includes(e||""));return l?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):a?i.jsxs("div",{className:"h-full flex flex-col",children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t("/agents"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Agents"})}),i.jsxs("div",{className:"flex items-center justify-between",children:[i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:a.name}),a.is_auto_generated&&i.jsx(q,{variant:"info",children:"Auto-generated"})]}),i.jsx("div",{className:"flex gap-2",children:i.jsx(K,{variant:"secondary",icon:jp,onClick:()=>{confirm(`Delete agent "${a.name}"?`)&&c.mutate(a.id)},children:"Delete"})})]}),a.description&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:a.description})]}),i.jsxs("div",{className:"flex gap-1 mb-6 border-b border-[var(--border)]",children:[i.jsx(ml,{active:r==="overview",onClick:()=>s("overview"),icon:i.jsx(gp,{size:16}),children:"Overview"}),i.jsx(ml,{active:r==="test",onClick:()=>s("test"),icon:i.jsx(Ks,{size:16}),children:"Test"}),i.jsx(ml,{active:r==="history",onClick:()=>s("history"),icon:i.jsx(hg,{size:16}),badge:o.length,children:"History"})]}),i.jsxs("div",{className:"flex-1 min-h-0",children:[r==="overview"&&i.jsx(N0,{agent:a,recentTests:o,jobs:p}),r==="test"&&i.jsx(k0,{agent:a}),r==="history"&&i.jsx(_0,{tests:o})]})]}):i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Agent not found"})}function ml({active:e,onClick:t,icon:n,badge:r,children:s}){return i.jsxs("button",{onClick:t,className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${e?"border-[var(--accent)] text-[var(--text-primary)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[n,s,r!==void 0&&r>0&&i.jsx("span",{className:"ml-1 px-1.5 py-0.5 text-xs bg-[var(--bg-tertiary)] rounded",children:r})]})}function N0({agent:e,recentTests:t,jobs:n}){var l,o,u,c,p,f;const r=Et(),s=e.config,a=()=>{const h=s.tools;return typeof h=="string"?h:Array.isArray(h)?h.join(", "):typeof h=="object"?Object.keys(h).join(", "):"standard"};return i.jsxs("div",{className:"space-y-6",children:[i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"Configuration"}),i.jsxs("div",{className:"grid grid-cols-2 gap-4 text-sm",children:[i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Model:"}),i.jsx("div",{className:"font-mono",children:s.model||"default"})]}),i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Compaction:"}),i.jsx("div",{className:"font-mono",children:((l=s.compaction)==null?void 0:l.strategy)==="none"?"disabled":`${(o=s.compaction)==null?void 0:o.strategy} (${((c=(u=s.compaction)==null?void 0:u.params)==null?void 0:c.head_size)||0}/${((f=(p=s.compaction)==null?void 0:p.params)==null?void 0:f.tail_size)||0})`})]}),i.jsxs("div",{className:"col-span-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Tools:"}),i.jsx("div",{className:"font-mono",children:a()})]}),s.instructions&&i.jsxs("div",{className:"col-span-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Instructions:"}),i.jsx("pre",{className:"mt-1 p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-xs whitespace-pre-wrap max-h-32 overflow-auto",children:s.instructions})]})]})]}),i.jsxs(ee,{children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Recent Tests"}),t.length>0&&i.jsxs("span",{className:"text-xs text-[var(--text-secondary)]",children:[t.length," total"]})]}),t.length===0?i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:'No tests yet. Click the "Test" tab to run one.'}):i.jsx("div",{className:"space-y-2",children:t.slice(0,5).map(h=>i.jsxs(Hs,{to:`/tests/${h.id}`,className:"flex items-center justify-between p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx(Op,{status:h.status}),i.jsxs("span",{className:"text-sm truncate max-w-[200px]",children:[h.prompt.slice(0,50),"..."]})]}),i.jsxs("div",{className:"flex items-center gap-3 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[h.duration_seconds.toFixed(1),"s"]}),i.jsxs("span",{children:[h.tokens_total," tok"]})]})]},h.id))})]}),i.jsxs(ee,{children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Optimization Jobs"}),i.jsx(K,{variant:"secondary",size:"sm",icon:Ii,onClick:()=>r("/agents",{state:{optimizeAgent:e}}),children:"New Job"})]}),n.length===0?i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:"No optimization jobs yet."}):i.jsx("div",{className:"space-y-2",children:n.slice(0,5).map(h=>i.jsxs(Hs,{to:`/jobs/${h.id}`,className:"flex items-center justify-between p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx(q,{variant:h.status==="completed"?"success":h.status==="running"?"info":"default",children:h.status}),i.jsx("span",{className:"text-sm",children:h.name})]}),i.jsx("span",{className:"text-xs text-[var(--text-secondary)]",children:new Date(h.created_at).toLocaleDateString()})]},h.id))})]})]})}function _0({tests:e}){return e.length===0?i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"No test history yet. Run a test to see results here."}):i.jsx("div",{className:"space-y-2",children:e.map(t=>i.jsxs(Hs,{to:`/tests/${t.id}`,className:"flex items-center justify-between p-3 bg-[var(--bg-secondary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex-1 min-w-0",children:[i.jsxs("div",{className:"flex items-center gap-2 mb-1",children:[i.jsx(Op,{status:t.status}),t.score!==null&&i.jsxs("span",{className:`text-sm font-medium ${t.passed?"text-green-400":"text-red-400"}`,children:[(t.score*100).toFixed(0),"%"]})]}),i.jsx("p",{className:"text-sm truncate",children:t.prompt})]}),i.jsxs("div",{className:"flex flex-col items-end gap-1 ml-4",children:[i.jsx("span",{className:"text-xs text-[var(--text-secondary)]",children:new Date(t.created_at).toLocaleString()}),i.jsxs("div",{className:"flex items-center gap-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[t.duration_seconds.toFixed(1),"s"]}),i.jsx("span",{children:"•"}),i.jsxs("span",{children:[t.tokens_total," tokens"]})]})]})]},t.id))})}function Op({status:e}){const t={completed:"success",failed:"error",running:"info",cancelled:"warning",pending:"default"};return i.jsx(q,{variant:t[e]||"default",children:e})}const Lp=Lu(e=>({tasks:[],selectedTaskIds:[],setTasks:t=>e({tasks:t}),toggleTaskSelection:t=>e(n=>({selectedTaskIds:n.selectedTaskIds.includes(t)?n.selectedTaskIds.filter(r=>r!==t):[...n.selectedTaskIds,t]})),jobs:[],setJobs:t=>e({jobs:t})}));function C0(){const e=Pn(),[t,n]=k.useState(!1),[r,s]=k.useState(!1),[a,l]=k.useState(new Set(["custom"])),{selectedTaskIds:o,toggleTaskSelection:u,setTasks:c}=Lp(),p=d=>{l(m=>{const g=new Set(m);return g.has(d)?g.delete(d):g.add(d),g})},{data:f=[],isLoading:h}=_e({queryKey:["tasks"],queryFn:()=>fn.list()});k.useEffect(()=>{f.length>0&&c(f)},[f,c]);const x=st({mutationFn:fn.create,onSuccess:()=>{e.invalidateQueries({queryKey:["tasks"]}),n(!1)}}),w=st({mutationFn:fn.importSuite,onSuccess:()=>{e.invalidateQueries({queryKey:["tasks"]}),s(!1)}}),j=st({mutationFn:fn.delete,onSuccess:()=>e.invalidateQueries({queryKey:["tasks"]})}),S=f.reduce((d,m)=>{const g=m.suite||"custom";return d[g]||(d[g]=[]),d[g].push(m),d},{}),v=Object.keys(S).sort((d,m)=>d==="custom"?-1:m==="custom"?1:d.localeCompare(m));return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Tasks"}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:["Define tasks to evaluate agent configurations.",o.length>0&&i.jsxs("span",{className:"ml-2 text-[var(--accent)]",children:[o.length," selected"]})]})]}),i.jsxs("div",{className:"flex gap-2",children:[i.jsx(K,{variant:"secondary",onClick:()=>s(!0),children:"Import Suite"}),i.jsx(K,{variant:"primary",onClick:()=>n(!0),children:"+ New Task"})]})]}),h?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):f.length===0?i.jsx("div",{className:"text-center py-12 text-[var(--text-secondary)]",children:"No tasks yet. Create one or import a built-in suite."}):i.jsx("div",{className:"space-y-4",children:v.map(d=>{const m=S[d],g=a.has(d),b=m.filter(_=>o.includes(_.id)).length;return i.jsxs("div",{children:[i.jsxs("button",{onClick:()=>p(d),className:"flex items-center gap-2 py-2 hover:text-[var(--accent)] transition-colors",children:[g?i.jsx(ag,{size:16,className:"text-[var(--text-secondary)]"}):i.jsx(Ss,{size:16,className:"text-[var(--text-secondary)]"}),i.jsx("h3",{className:"text-sm font-medium uppercase tracking-wide",children:d==="custom"?"Custom Tasks":`${d} Suite`}),i.jsx(q,{variant:d==="custom"?"default":"info",children:m.length}),b>0&&i.jsxs(q,{variant:"success",children:[b," selected"]})]}),g&&i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 mt-2",children:m.map(_=>i.jsx(ee,{selectable:!0,selected:o.includes(_.id),onClick:()=>u(_.id),children:i.jsxs("div",{className:"flex flex-col h-full",children:[i.jsxs("div",{className:"flex items-start justify-between gap-2",children:[i.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[i.jsx("span",{className:"font-medium",children:_.name}),o.includes(_.id)&&i.jsx(q,{variant:"success",children:"Selected"}),_.category&&_.category!=="default"&&i.jsx(q,{variant:"default",children:_.category})]}),i.jsx(K,{variant:"ghost",size:"sm",onClick:C=>{C.stopPropagation(),confirm("Delete this task?")&&j.mutate(_.id)},children:"Delete"})]}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-2 line-clamp-3 flex-1",children:_.prompt}),_.criteria.length>0&&i.jsx("div",{className:"flex gap-1 mt-2 flex-wrap",children:_.criteria.map(C=>i.jsx(q,{variant:"default",children:C.name},C.name))})]})},_.id))})]},d)})}),i.jsx(b0,{isOpen:t,onClose:()=>n(!1),onSubmit:d=>x.mutate(d),isLoading:x.isPending}),i.jsx(E0,{isOpen:r,onClose:()=>s(!1),onSubmit:d=>w.mutate(d),isLoading:w.isPending})]})}function b0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){const[s,a]=k.useState({name:"",prompt:"",criteria:[],category:"default"}),l=()=>{a({...s,criteria:[...s.criteria,{name:"",instruction:"",weight:1}]})},o=(p,f)=>{const h=[...s.criteria];h[p]={...h[p],...f},a({...s,criteria:h})},u=p=>{a({...s,criteria:s.criteria.filter((f,h)=>h!==p)})},c=p=>{p.preventDefault(),!(!s.name.trim()||!s.prompt.trim())&&n({...s,criteria:s.criteria.filter(f=>f.name.trim()&&f.instruction.trim())})};return i.jsx(ia,{isOpen:e,onClose:t,title:"Create Task",children:i.jsxs("form",{onSubmit:c,className:"space-y-4",children:[i.jsx(pt,{label:"Name",value:s.name,onChange:p=>a({...s,name:p.target.value}),placeholder:"e.g., fizzbuzz",required:!0}),i.jsx(f0,{label:"Prompt",value:s.prompt,onChange:p=>a({...s,prompt:p.target.value}),placeholder:"The task description for the agent...",required:!0}),i.jsx(pt,{label:"Category",value:s.category,onChange:p=>a({...s,category:p.target.value}),placeholder:"e.g., coding, research"}),i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsx("label",{className:"text-sm text-[var(--text-secondary)]",children:"Evaluation Criteria"}),i.jsx(K,{type:"button",variant:"ghost",size:"sm",onClick:l,children:"+ Add"})]}),i.jsx("div",{className:"space-y-2",children:s.criteria.map((p,f)=>i.jsxs("div",{className:"flex gap-2 items-start",children:[i.jsx(pt,{value:p.name,onChange:h=>o(f,{name:h.target.value}),placeholder:"Name",className:"w-32"}),i.jsx(pt,{value:p.instruction,onChange:h=>o(f,{instruction:h.target.value}),placeholder:"Instruction",className:"flex-1"}),i.jsx(K,{type:"button",variant:"ghost",size:"sm",onClick:()=>u(f),children:"×"})]},f))})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(K,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(K,{type:"submit",variant:"primary",disabled:r||!s.name.trim()||!s.prompt.trim(),children:r?"Creating...":"Create"})]})]})})}function E0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){const[s,a]=k.useState(""),{data:l=[],isLoading:o}=_e({queryKey:["suites"],queryFn:()=>fn.listSuites(),enabled:e});return k.useEffect(()=>{l.length>0&&!s&&a(l[0].name)},[l,s]),i.jsx(ia,{isOpen:e,onClose:t,title:"Import Task Suite",children:i.jsxs("div",{className:"space-y-4",children:[i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:"Import a built-in task suite for evaluation."}),o?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading suites..."}):l.length===0?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"No suites available."}):i.jsx("div",{className:"space-y-2 max-h-80 overflow-y-auto",children:l.map(u=>i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer ${s===u.name?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",name:"suite",value:u.name,checked:s===u.name,onChange:()=>a(u.name),className:"accent-[var(--accent)]"}),i.jsxs("span",{className:"capitalize",children:[u.name.replace(/_/g," ")," (",u.task_count," tasks) - ",u.description]})]},u.name))}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(K,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(K,{variant:"primary",onClick:()=>n(s),disabled:r||!s,children:r?"Importing...":"Import"})]})]})})}function P0(){const e=Et(),t=Pn(),[n,r]=k.useState(!1),{setJobs:s}=Lp(),a=Fu(),{data:l=[],isLoading:o}=_e({queryKey:["jobs",n],queryFn:()=>bt.list({include_public:n}),refetchInterval:5e3});k.useEffect(()=>{l.length>0&&s(l)},[l,s]);const u=st({mutationFn:bt.delete,onSuccess:()=>t.invalidateQueries({queryKey:["jobs"]})});return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Optimization Jobs"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:"View and manage optimization experiments. Start new jobs from the Agents page."})]}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:n,onChange:c=>r(c.target.checked),className:"rounded border-[var(--border)]"}),i.jsx(zi,{className:"w-4 h-4"}),"Show public"]}),i.jsx(K,{variant:"secondary",onClick:()=>e("/agents"),children:"Go to Agents"})]})]}),o?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):l.length===0?i.jsx("div",{className:"text-center py-12 text-[var(--text-secondary)]",children:"No jobs yet. Go to Agents page to start an optimization."}):i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4",children:l.map(c=>{const p=!c.user_id||c.user_id==="anonymous"||a&&c.created_by_name===a;return i.jsx(Ep,{job:c,onDelete:p?f=>u.mutate(f):void 0},c.id)})})]})}function T0(e,t=!0){return Math.abs(e)<10?"text-[var(--text-secondary)]":(t?e<0:e>0)?"text-green-400":"text-red-400"}function O0(e){return`${e>0?"+":""}${e.toFixed(1)}%`}function Rp(e,t){return t===0?0:(e-t)/t*100}function os({label:e,values:t,baselineIndex:n,formatter:r,isLowerBetter:s=!0}){const a=t[n];return i.jsxs("tr",{className:"border-b border-[var(--border)] last:border-0",children:[i.jsx("td",{className:"py-2 pr-4 text-[var(--text-secondary)] text-sm",children:e}),t.map((l,o)=>{const u=Rp(l,a),c=o===n;return i.jsxs("td",{className:"py-2 px-4 text-right",children:[i.jsx("div",{className:"font-mono",children:r(l)}),!c&&i.jsx("div",{className:`text-xs ${T0(u,s)}`,children:O0(u)}),c&&i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"(baseline)"})]},o)})]})}function L0({runs:e,baselineRunId:t}){const n=k.useMemo(()=>{if(t){const a=e.findIndex(l=>l.id===t);if(a>=0)return a}return 0},[e,t]);if(e.length<2)return null;const r=Math.min(...e.map(a=>a.tokens_total)),s=Math.max(...e.map(a=>a.score));return i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-4",children:"Candidate Comparison"}),i.jsx("div",{className:"overflow-x-auto",children:i.jsxs("table",{className:"w-full text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"border-b border-[var(--border)]",children:[i.jsx("th",{className:"pb-2 pr-4 text-left text-[var(--text-secondary)] font-medium",children:"Metric"}),e.map((a,l)=>i.jsx("th",{className:"pb-2 px-4 text-right",children:i.jsxs("div",{className:"flex items-center justify-end gap-2",children:[i.jsx("span",{className:"font-medium",children:a.candidate_name}),a.is_pareto&&i.jsx(q,{variant:"success",children:"Pareto"}),l===n&&i.jsx(q,{variant:"info",children:"Base"})]})},a.id))]})}),i.jsxs("tbody",{children:[i.jsx(os,{label:"Total Tokens",values:e.map(a=>a.tokens_total),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Input Tokens",values:e.map(a=>a.tokens_input),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Output Tokens",values:e.map(a=>a.tokens_output),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Duration",values:e.map(a=>a.duration_seconds),baselineIndex:n,formatter:a=>`${a.toFixed(1)}s`,isLowerBetter:!0}),i.jsx(os,{label:"Score",values:e.map(a=>a.score*100),baselineIndex:n,formatter:a=>`${a.toFixed(1)}%`,isLowerBetter:!1})]})]})}),i.jsxs("div",{className:"mt-4 pt-4 border-t border-[var(--border)]",children:[i.jsx("h4",{className:"text-sm font-medium mb-2 text-[var(--text-secondary)]",children:"Key Insights"}),i.jsxs("ul",{className:"text-sm space-y-1 text-[var(--text-secondary)]",children:[e.map(a=>{const l=Rp(a.tokens_total,e[n].tokens_total);return a.tokens_total===r&&l<-5?i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-green-400",children:"✓"}),i.jsxs("span",{children:[i.jsx("strong",{children:a.candidate_name})," uses ",Math.abs(l).toFixed(0),"% fewer tokens"]})]},`token-${a.id}`):null}),e.map(a=>a.score===s&&a.passed?i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-green-400",children:"✓"}),i.jsxs("span",{children:[i.jsx("strong",{children:a.candidate_name})," achieved highest score (",(a.score*100).toFixed(0),"%)"]})]},`score-${a.id}`):null),e.filter(a=>a.is_pareto).length>0&&i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-purple-400",children:"★"}),i.jsxs("span",{children:["Pareto-optimal candidates:"," ",e.filter(a=>a.is_pareto).map(a=>a.candidate_name).join(", ")]})]})]})]}),i.jsxs("div",{className:"mt-4 pt-4 border-t border-[var(--border)]",children:[i.jsx("h4",{className:"text-sm font-medium mb-3 text-[var(--text-secondary)]",children:"Token Efficiency"}),i.jsx("div",{className:"space-y-2",children:e.map(a=>{const l=a.tokens_total/e[n].tokens_total*100,o=a.tokens_total<=r;return i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("div",{className:"w-24 text-sm truncate",title:a.candidate_name,children:a.candidate_name}),i.jsx("div",{className:"flex-1 h-6 bg-[var(--bg-primary)] rounded overflow-hidden",children:i.jsx("div",{className:`h-full transition-all duration-300 ${o?"bg-green-500":"bg-blue-500"}`,style:{width:`${Math.min(l,100)}%`}})}),i.jsx("div",{className:"w-20 text-right font-mono text-sm",children:a.tokens_total.toLocaleString()})]},a.id)})})]})]})}function R0({summaries:e,height:t=350}){const n=k.useRef(null),[r,s]=k.useState(600),[a,l]=k.useState("tokens"),[o,u]=k.useState(null),[c,p]=k.useState({x:0,y:0});k.useEffect(()=>{const _=()=>{n.current&&s(n.current.clientWidth)};return _(),window.addEventListener("resize",_),()=>window.removeEventListener("resize",_)},[]);const f={top:30,right:30,bottom:50,left:60},h=r-f.left-f.right,x=t-f.top-f.bottom,w=_=>a==="tokens"?_.avg_tokens:_.avg_duration,{xScale:j,yScale:S,xTicks:v,yTicks:d,paretoLine:m}=k.useMemo(()=>{if(e.length===0||h<=0)return{xScale:()=>0,yScale:()=>0,xTicks:[],yTicks:[],paretoLine:[]};const _=e.map(w),C=e.map(O=>O.avg_score),N=Math.min(..._)*.9,M=Math.max(..._)*1.1,P=Math.min(...C,.5),H=Math.min(Math.max(...C)*1.05,1),F=O=>(O-N)/(M-N)*h,U=O=>x-(O-P)/(H-P)*x,te=Array.from({length:5},(O,D)=>N+D/4*(M-N)),ke=Array.from({length:5},(O,D)=>P+D/4*(H-P)),He=e.filter(O=>O.is_pareto).sort((O,D)=>w(O)-w(D)).map(O=>({x:F(w(O)),y:U(O.avg_score)}));return{xScale:F,yScale:U,xTicks:te,yTicks:ke,paretoLine:He}},[e,h,x,a]);if(e.length===0)return i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"No data to display"});const g=_=>a==="tokens"?_>=1e6?`${(_/1e6).toFixed(1)}M`:_>=1e3?`${(_/1e3).toFixed(0)}K`:_.toFixed(0):`${_.toFixed(1)}s`,b=(_,C)=>{var M;const N=(M=n.current)==null?void 0:M.getBoundingClientRect();N&&p({x:C.clientX-N.left,y:C.clientY-N.top}),u(_)};return i.jsxs("div",{ref:n,className:"w-full relative",children:[i.jsx("div",{className:"flex justify-end mb-2",children:i.jsxs("div",{className:"inline-flex rounded border border-[var(--border)] text-xs",children:[i.jsx("button",{className:`px-3 py-1 ${a==="tokens"?"bg-[var(--accent)] text-black":"text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,onClick:()=>l("tokens"),children:"Tokens"}),i.jsx("button",{className:`px-3 py-1 ${a==="duration"?"bg-[var(--accent)] text-black":"text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,onClick:()=>l("duration"),children:"Latency"})]})}),i.jsx("svg",{width:r,height:t,className:"font-mono text-xs",children:i.jsxs("g",{transform:`translate(${f.left}, ${f.top})`,children:[v.map((_,C)=>i.jsx("line",{x1:j(_),y1:0,x2:j(_),y2:x,stroke:"var(--border)",strokeDasharray:"2,2"},`x-grid-${C}`)),d.map((_,C)=>i.jsx("line",{x1:0,y1:S(_),x2:h,y2:S(_),stroke:"var(--border)",strokeDasharray:"2,2"},`y-grid-${C}`)),m.length>1&&i.jsx("polyline",{points:m.map(_=>`${_.x},${_.y}`).join(" "),fill:"none",stroke:"var(--accent)",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),e.slice().sort((_,C)=>(_.is_pareto?1:0)-(C.is_pareto?1:0)).map(_=>{const C=j(w(_)),N=S(_.avg_score),M=_.is_pareto,P=(o==null?void 0:o.candidate_name)===_.candidate_name;return i.jsxs("g",{onMouseEnter:H=>b(_,H),onMouseLeave:()=>u(null),children:[i.jsx("circle",{cx:C,cy:N,r:P?10:M?8:6,fill:M?"var(--accent)":"transparent",stroke:P?"var(--text-primary)":M?"var(--accent)":"var(--text-secondary)",strokeWidth:P?3:2,className:"cursor-pointer transition-all"}),M&&!P&&i.jsx("text",{x:C,y:N-12,textAnchor:"middle",fill:"var(--text-primary)",fontSize:10,className:"pointer-events-none",children:_.candidate_name.replace(/^baseline_/,"").slice(0,15)})]},_.candidate_name)}),i.jsx("line",{x1:0,y1:x,x2:h,y2:x,stroke:"var(--text-secondary)"}),v.map((_,C)=>i.jsxs("g",{transform:`translate(${j(_)}, ${x})`,children:[i.jsx("line",{y2:5,stroke:"var(--text-secondary)"}),i.jsx("text",{y:20,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:10,children:g(_)})]},`x-tick-${C}`)),i.jsx("text",{x:h/2,y:x+40,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:11,children:a==="tokens"?"Tokens (cost)":"Duration (latency)"}),i.jsx("line",{x1:0,y1:0,x2:0,y2:x,stroke:"var(--text-secondary)"}),d.map((_,C)=>i.jsxs("g",{transform:`translate(0, ${S(_)})`,children:[i.jsx("line",{x2:-5,stroke:"var(--text-secondary)"}),i.jsxs("text",{x:-10,textAnchor:"end",dominantBaseline:"middle",fill:"var(--text-secondary)",fontSize:10,children:[(_*100).toFixed(0),"%"]})]},`y-tick-${C}`)),i.jsx("text",{transform:`translate(-45, ${x/2}) rotate(-90)`,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:11,children:"Score (quality)"})]})}),o&&i.jsxs("div",{className:"absolute z-10 bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-lg p-3 text-sm pointer-events-none",style:{left:Math.min(c.x+15,r-200),top:c.y-10,maxWidth:220},children:[i.jsx("div",{className:"font-medium text-[var(--text-primary)] truncate mb-2",children:o.candidate_name.replace(/^baseline_/,"")}),i.jsxs("div",{className:"grid grid-cols-2 gap-x-4 gap-y-1 text-xs",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Score:"}),i.jsxs("span",{className:"text-right font-medium",children:[(o.avg_score*100).toFixed(1),"%"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Tokens:"}),i.jsxs("span",{className:"text-right",children:[(o.avg_tokens/1e3).toFixed(1),"K"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Duration:"}),i.jsxs("span",{className:"text-right",children:[o.avg_duration.toFixed(1),"s"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Pass rate:"}),i.jsxs("span",{className:"text-right",children:[o.passed_runs,"/",o.total_runs]})]}),o.is_pareto&&i.jsx("div",{className:"mt-2 pt-2 border-t border-[var(--border)] text-xs text-[var(--accent)]",children:"Pareto optimal"})]})]})}function M0(e=2e3){const[t,n]=k.useState(!1),[r,s]=k.useState(null),a=k.useCallback(async o=>{try{return await navigator.clipboard.writeText(o),n(!0),s(null),setTimeout(()=>n(!1),e),!0}catch{return s("Failed to copy to clipboard"),n(!1),!1}},[e]),l=k.useCallback(()=>{n(!1),s(null)},[]);return{copy:a,copied:t,error:r,reset:l}}function z0({isOpen:e,onClose:t,title:n,itemId:r,itemType:s,isPublic:a,createdByName:l,onTogglePublic:o}){const[u,c]=k.useState(!1),{copy:p,copied:f}=M0(),h=`${window.location.origin}/${s}s/${r}`,x=async()=>{c(!0);try{await o(!a)}finally{c(!1)}},w=()=>{p(h)};return i.jsx(ia,{isOpen:e,onClose:t,title:n,children:i.jsxs("div",{className:"space-y-4",children:[i.jsxs("div",{className:"flex items-center justify-between p-3 bg-[var(--bg-tertiary)] rounded",children:[i.jsxs("div",{className:"flex items-center gap-3",children:[a?i.jsx(zi,{className:"w-5 h-5 text-[var(--accent)]"}):i.jsx(xp,{className:"w-5 h-5 text-[var(--text-secondary)]"}),i.jsxs("div",{children:[i.jsx("div",{className:"font-medium",children:a?"Public":"Private"}),i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:a?"Anyone with the link can view":"Only you can access"})]})]}),i.jsx("button",{onClick:x,disabled:u,className:`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${a?"bg-[var(--accent)]":"bg-[var(--border)]"} ${u?"opacity-50 cursor-not-allowed":""}`,children:i.jsx("span",{className:`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${a?"translate-x-6":"translate-x-1"}`})})]}),a&&i.jsxs("div",{className:"space-y-2",children:[i.jsx("label",{className:"text-sm text-[var(--text-secondary)]",children:"Share link"}),i.jsxs("div",{className:"flex gap-2",children:[i.jsx("input",{type:"text",readOnly:!0,value:h,className:"flex-1 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono"}),i.jsx(K,{variant:"secondary",onClick:w,children:f?i.jsxs(i.Fragment,{children:[i.jsx(sg,{className:"w-4 h-4 mr-1"}),"Copied"]}):i.jsxs(i.Fragment,{children:[i.jsx(ug,{className:"w-4 h-4 mr-1"}),"Copy"]})})]})]}),l&&i.jsxs("div",{className:"text-sm text-[var(--text-secondary)]",children:["Created by ",i.jsx("span",{className:"text-[var(--text-primary)]",children:l})]}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)] pt-2 border-t border-[var(--border)]",children:a?i.jsxs(i.Fragment,{children:[i.jsxs("p",{children:["Public ",s,"s can be viewed by anyone with the link."]}),i.jsxs("p",{className:"mt-1",children:["Only you can edit or delete this ",s,"."]})]}):i.jsxs("p",{children:["Make this ",s," public to share it with others."]})})]})})}function F0({job:e,onUpdate:t}){const[n,r]=k.useState(!1),s=async a=>{await bt.update(e.id,{is_public:a}),t()};return i.jsxs(i.Fragment,{children:[i.jsxs(K,{variant:"secondary",size:"sm",onClick:()=>r(!0),title:e.is_public?"Sharing settings":"Share this job",children:[i.jsx(yg,{className:"w-4 h-4 mr-1"}),e.is_public?"Shared":"Share"]}),i.jsx(z0,{isOpen:n,onClose:()=>r(!1),title:"Share Job",itemId:e.id,itemType:"job",isPublic:e.is_public,createdByName:e.created_by_name,onTogglePublic:s})]})}function I0(){const{jobId:e}=Ri(),t=Et(),n=Pn(),[r,s]=k.useState(null),[a,l]=k.useState(!1),[o,u]=k.useState(null),[c,p]=k.useState([]),[f,h]=k.useState(null),[x,w]=k.useState(null),[j,S]=k.useState("results"),[v,d]=k.useState("score"),[m,g]=k.useState("desc"),[b,_]=k.useState(!1),{data:C,isLoading:N}=_e({queryKey:["jobs",e],queryFn:()=>bt.get(e),enabled:!!e,refetchInterval:a?2e3:!1}),{data:M=[]}=_e({queryKey:["runs",e],queryFn:()=>Po.list({job_id:e}),enabled:!!e,refetchInterval:a?2e3:!1}),{data:P}=_e({queryKey:["job-summary",e],queryFn:()=>Po.getJobSummary(e),enabled:!!e&&(C==null?void 0:C.status)==="completed"}),H=st({mutationFn:async()=>{l(!0),p([]),h(null),w(null);for await(const T of bt.start(e))s(T),T.current_candidate&&T.current_task&&h($=>($&&($.candidate!==T.current_candidate||$.task!==T.current_task)&&p(fe=>[...fe,{candidate_name:$.candidate,task_name:$.task,completed_at:Date.now()}]),{candidate:T.current_candidate,task:T.current_task})),T.event==="error"&&(w(T.message),l(!1),n.invalidateQueries({queryKey:["jobs",e]})),T.event==="complete"&&(h($=>($&&p(fe=>[...fe,{candidate_name:$.candidate,task_name:$.task,completed_at:Date.now()}]),null)),l(!1),n.invalidateQueries({queryKey:["jobs",e]}),n.invalidateQueries({queryKey:["runs",e]}),n.invalidateQueries({queryKey:["job-summary",e]}))}}),F=st({mutationFn:()=>bt.cancel(e),onSuccess:()=>{l(!1),n.invalidateQueries({queryKey:["jobs",e]})}});k.useEffect(()=>{(C==null?void 0:C.status)==="running"&&l(!0)},[C==null?void 0:C.status]);const U=k.useMemo(()=>{const T=new Map;for(const $ of M)T.has($.task_name)||T.set($.task_name,[]),T.get($.task_name).push($);return T},[M]),te=k.useMemo(()=>Array.from(U.keys()),[U]),ke=k.useMemo(()=>{if(!(P!=null&&P.candidate_summaries))return[];let T=[...P.candidate_summaries];return b&&(T=T.filter($=>$.is_pareto)),T.sort(($,fe)=>{let ye,lt;switch(v){case"score":ye=$.avg_score,lt=fe.avg_score;break;case"tokens":ye=$.avg_tokens,lt=fe.avg_tokens;break;case"duration":ye=$.avg_duration,lt=fe.avg_duration;break;case"pass_rate":ye=$.passed_runs/$.total_runs,lt=fe.passed_runs/fe.total_runs;break}return m==="desc"?lt-ye:ye-lt}),T},[P,v,m,b]),Pt=T=>{v===T?g(m==="desc"?"asc":"desc"):(d(T),g(T==="tokens"||T==="duration"?"asc":"desc"))},He=({label:T,sortKeyVal:$})=>i.jsx("th",{className:"pb-2 cursor-pointer hover:text-[var(--text-primary)] select-none",onClick:()=>Pt($),children:i.jsxs("div",{className:"flex items-center gap-1",children:[T,v===$&&i.jsx(eg,{size:12,className:m==="asc"?"rotate-180":""})]})});if(N)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."});if(!C)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Job not found"});const O=Fu(),D=!C.user_id||C.user_id==="anonymous"||O&&C.created_by_name===O,B=C.is_public&&!D,L=()=>{n.invalidateQueries({queryKey:["jobs",e]})},A=T=>{const $={pending:"default",running:"info",completed:"success",failed:"error",cancelled:"warning"};return i.jsx(q,{variant:$[T]||"default",children:T})};return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("button",{onClick:()=>t("/jobs"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Jobs"}),i.jsx("h2",{className:"text-xl font-bold",children:C.name||`Job ${C.id.slice(0,8)}`}),A(C.status),C.is_public&&i.jsxs(q,{variant:"info",children:[i.jsx(zi,{className:"w-3 h-3 mr-1 inline"}),"Public"]})]}),i.jsxs("div",{className:"flex items-center gap-3 mt-1",children:[i.jsxs("code",{className:"text-xs bg-[var(--bg-primary)] px-2 py-0.5 rounded font-mono text-[var(--text-secondary)]",children:[C.id.slice(0,8),"..."]}),i.jsxs("span",{className:"text-sm text-[var(--text-secondary)]",children:[C.candidate_ids.length," candidates × ",C.task_ids.length," tasks = ",C.total_experiments," experiments"]}),C.is_public&&C.created_by_name&&i.jsxs("span",{className:"text-sm text-[var(--text-secondary)]",children:["Created by ",i.jsx("span",{className:"text-[var(--text-primary)]",children:C.created_by_name})]})]})]}),i.jsxs("div",{className:"flex gap-2",children:[D&&i.jsx(F0,{job:C,onUpdate:L}),B&&i.jsx(q,{variant:"default",children:"View Only"}),D&&C.status==="pending"&&i.jsx(K,{variant:"primary",onClick:()=>H.mutate(),disabled:H.isPending,children:H.isPending?"Starting...":"Start"}),D&&C.status==="running"&&i.jsx(K,{variant:"danger",onClick:()=>F.mutate(),disabled:F.isPending,children:"Cancel"})]})]}),(x||C.error)&&i.jsx(ee,{className:"mb-6 border-red-500/50 bg-red-500/10",children:i.jsxs("div",{className:"flex items-start gap-3",children:[i.jsx("div",{className:"w-5 h-5 rounded-full bg-red-500 flex items-center justify-center text-white text-xs font-bold flex-shrink-0 mt-0.5",children:"!"}),i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium text-red-400",children:"Error"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:x||C.error})]})]})}),(a||r)&&i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsx("span",{className:"font-medium",children:"Progress"}),i.jsxs("span",{className:"text-[var(--accent)]",children:[(r==null?void 0:r.completed)||C.completed_experiments,"/",(r==null?void 0:r.total)||C.total_experiments]})]}),i.jsx("div",{className:"w-full bg-[var(--bg-primary)] h-2 mb-2",children:i.jsx("div",{className:"h-full bg-[var(--accent)] transition-all",style:{width:`${((r==null?void 0:r.completed)||C.completed_experiments)/((r==null?void 0:r.total)||C.total_experiments)*100}%`}})}),(r==null?void 0:r.message)&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:r.message}),a&&i.jsxs("div",{className:"mt-4 border-t border-[var(--border)] pt-4",children:[(r==null?void 0:r.current_candidate)&&(r==null?void 0:r.current_task)&&i.jsxs("div",{className:"mb-3",children:[i.jsx("span",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:"Currently Running"}),i.jsxs("div",{className:"flex items-center gap-2 mt-1 px-3 py-2 bg-blue-500/10 border border-blue-500/30 rounded",children:[i.jsx("div",{className:"w-2 h-2 bg-blue-400 rounded-full animate-pulse"}),i.jsx("span",{className:"font-medium",children:r.current_candidate}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{children:r.current_task})]})]}),c.length>0&&i.jsxs("div",{children:[i.jsxs("span",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:["Completed (",c.length,")"]}),i.jsx("div",{className:"mt-1 max-h-40 overflow-y-auto space-y-1",children:c.map((T,$)=>i.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 bg-green-500/10 border border-green-500/30 rounded text-sm",children:[i.jsx("div",{className:"w-2 h-2 bg-green-400 rounded-full"}),i.jsx("span",{className:"font-medium",children:T.candidate_name}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{children:T.task_name})]},`${T.candidate_name}-${T.task_name}-${$}`))})]})]})]}),(C.status==="completed"||M.length>0)&&i.jsxs("div",{className:"flex gap-1 mb-6 border-b border-[var(--border)]",children:[i.jsxs("button",{onClick:()=>S("results"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="results"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(tg,{size:16}),"Results"]}),i.jsxs("button",{onClick:()=>S("compare"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="compare"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(dg,{size:16}),"Compare"]}),i.jsxs("button",{onClick:()=>S("runs"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="runs"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(mg,{size:16}),"Runs (",M.length,")"]})]}),j==="results"&&i.jsxs(i.Fragment,{children:[P&&P.candidate_summaries.length>1&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("div",{className:"flex items-start justify-between mb-4",children:i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium",children:"Pareto Frontier"}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:["Candidates on the frontier (connected line) are ",i.jsx("strong",{children:"Pareto optimal"})," - no other candidate beats them on both score AND cost."]})]})}),i.jsxs("div",{className:"mb-4 p-3 bg-[var(--bg-primary)] rounded border border-[var(--border)] text-xs text-[var(--text-secondary)]",children:[i.jsx("strong",{className:"text-[var(--text-primary)]",children:"How Pareto optimal is calculated:"})," A candidate is Pareto optimal if there's no other candidate that has both a higher score AND lower cost. For example, if Candidate A has 95% score at 50K tokens and Candidate B has 90% score at 40K tokens, both are Pareto optimal - A is better on score, B is better on cost. But if Candidate C has 85% score at 60K tokens, it's ",i.jsx("em",{children:"not"})," Pareto optimal because B beats it on both metrics."]}),i.jsx(R0,{summaries:P.candidate_summaries,height:350})]}),P&&i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Results Summary"}),i.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:b,onChange:T=>_(T.target.checked),className:"rounded border-[var(--border)]"}),"Pareto only"]})]}),i.jsx("div",{className:"overflow-x-auto",children:i.jsxs("table",{className:"w-full text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"text-left text-[var(--text-secondary)] border-b border-[var(--border)]",children:[i.jsx("th",{className:"pb-2",children:"Candidate"}),i.jsx(He,{label:"Score",sortKeyVal:"score"}),i.jsx(He,{label:"Tokens",sortKeyVal:"tokens"}),i.jsx(He,{label:"Duration",sortKeyVal:"duration"}),i.jsx(He,{label:"Pass Rate",sortKeyVal:"pass_rate"}),i.jsx("th",{className:"pb-2",children:"Pareto"})]})}),i.jsx("tbody",{children:ke.map((T,$)=>i.jsxs("tr",{className:`border-b border-[var(--border)] ${$===0?"bg-[var(--accent)]/10":""}`,children:[i.jsxs("td",{className:"py-2 font-medium",children:[$===0&&i.jsx("span",{className:"text-[var(--accent)] mr-1",children:"#1"}),T.candidate_name.replace(/^baseline_/,"")]}),i.jsxs("td",{className:"py-2",children:[(T.avg_score*100).toFixed(1),"%"]}),i.jsxs("td",{className:"py-2",children:[(T.avg_tokens/1e3).toFixed(1),"K"]}),i.jsxs("td",{className:"py-2",children:[T.avg_duration.toFixed(1),"s"]}),i.jsxs("td",{className:"py-2",children:[T.passed_runs,"/",T.total_runs]}),i.jsx("td",{className:"py-2",children:T.is_pareto&&i.jsx(q,{variant:"success",children:"Pareto"})})]},T.candidate_name))})]})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-3",children:"Click column headers to sort. The #1 ranked candidate is highlighted based on your sort criteria."})]}),!P&&i.jsx(ee,{children:i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Results will appear here after the job completes.":"No results yet. Start the job to see results."})})]}),j==="compare"&&i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"Compare Candidates by Task"}),te.length>0?i.jsxs(i.Fragment,{children:[i.jsx("div",{className:"flex flex-wrap gap-2 mb-4",children:te.map(T=>i.jsx("button",{onClick:()=>u(o===T?null:T),className:`px-3 py-1 text-sm rounded border transition-colors ${o===T?"bg-[var(--accent)] text-white border-[var(--accent)]":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:T},T))}),o&&U.get(o)?i.jsx(L0,{runs:U.get(o).map(T=>({id:T.id,candidate_name:T.candidate_name,tokens_input:0,tokens_output:0,tokens_total:T.tokens_total,duration_seconds:T.duration_seconds,score:T.score,passed:T.passed,is_pareto:T.is_pareto}))}):i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"Select a task above to compare how different candidates performed on it."})]}):i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Comparison data will appear here after runs complete.":"No runs yet. Start the job to compare candidates."})]}),j==="runs"&&i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"All Experiment Runs"}),M.length===0?i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Runs will appear here as they complete. See progress above for live status.":"No runs yet. Start the job to see results."}):i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3",children:M.map(T=>i.jsxs("div",{className:"p-3 bg-[var(--bg-primary)] rounded border border-[var(--border)] cursor-pointer hover:border-[var(--accent-dim)] transition-colors",onClick:()=>t(`/runs/${T.id}`),children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsxs("span",{className:`text-lg font-bold ${T.passed?"text-green-400":"text-red-400"}`,children:[(T.score*100).toFixed(0),"%"]}),T.is_pareto&&i.jsx(q,{variant:"success",children:"Pareto"})]}),i.jsx("div",{className:"text-sm font-medium truncate",title:T.candidate_name,children:T.candidate_name.replace(/^baseline_/,"")}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)] truncate",children:T.task_name}),i.jsxs("div",{className:"flex items-center gap-3 mt-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[(T.tokens_total/1e3).toFixed(1),"K tokens"]}),i.jsxs("span",{children:[T.duration_seconds.toFixed(1),"s"]})]})]},T.id))})]})]})}const hn={input:"bg-blue-500",output:"bg-emerald-500",inputText:"text-blue-400",outputText:"text-emerald-400"};function jd(e){return e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function wd({input:e,output:t,maxValue:n,height:r=24,showLabels:s=!0}){const a=e+t;if(a===0)return i.jsx("div",{className:"flex items-center gap-2 w-full",children:i.jsx("div",{className:"rounded bg-[var(--bg-primary)] flex-1",style:{height:`${r}px`}})});const l=n>0?a/n*100:100;return i.jsxs("div",{className:"flex items-center gap-3 w-full",children:[i.jsx("div",{className:"relative rounded overflow-hidden bg-[var(--bg-primary)] flex-1",style:{height:`${r}px`},children:i.jsxs("div",{className:"h-full flex transition-all duration-300",style:{width:`${l}%`},children:[i.jsx("div",{className:`h-full ${hn.input} transition-all`,style:{width:`${e/a*100}%`},title:`Input: ${e.toLocaleString()} tokens`}),i.jsx("div",{className:`h-full ${hn.output} transition-all`,style:{width:`${t/a*100}%`},title:`Output: ${t.toLocaleString()} tokens`})]})}),s&&i.jsxs("div",{className:"flex items-center gap-1 text-xs font-mono text-[var(--text-secondary)] min-w-[90px] justify-end",children:[i.jsxs("span",{className:hn.inputText,children:["↑",jd(e)]}),i.jsx("span",{children:"/"}),i.jsxs("span",{className:hn.outputText,children:["↓",jd(t)]})]})]})}function vl({label:e,value:t,color:n="default"}){const r={default:"text-[var(--text-primary)]",input:hn.inputText,output:hn.outputText}[n];return i.jsxs("div",{className:"flex-1 p-3 bg-[var(--bg-primary)] border border-[var(--border)] rounded",children:[i.jsx("div",{className:"text-xs text-[var(--text-secondary)] mb-1",children:e}),i.jsx("div",{className:`font-mono text-lg font-bold ${r}`,children:t})]})}function Mp({tokensInput:e,tokensOutput:t,tokensTotal:n,turns:r}){const s=n>0?Math.round(e/n*100):0,a=n>0?Math.round(t/n*100):0,l=k.useMemo(()=>{if(!r||r.length===0)return null;let u=0,c=0;return r.map(p=>(u+=p.input,c+=p.output,{input:u,output:c,total:u+c}))},[r]),o=l?Math.max(...l.map(u=>u.total)):n;return i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-4",children:"Token Usage"}),i.jsx("div",{className:"mb-4",children:i.jsx(wd,{input:e,output:t,maxValue:n,height:32})}),i.jsxs("div",{className:"flex items-center gap-6 text-xs mb-4",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("div",{className:`w-3 h-3 rounded ${hn.input}`}),i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["Input (",s,"%)"]})]}),i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("div",{className:`w-3 h-3 rounded ${hn.output}`}),i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["Output (",a,"%)"]})]})]}),i.jsxs("div",{className:"flex gap-3 mb-4",children:[i.jsx(vl,{label:"Input Tokens",value:e.toLocaleString(),color:"input"}),i.jsx(vl,{label:"Output Tokens",value:t.toLocaleString(),color:"output"}),i.jsx(vl,{label:"Total Tokens",value:n.toLocaleString()})]}),l&&l.length>1&&i.jsxs("div",{className:"border-t border-[var(--border)] pt-4",children:[i.jsxs("h4",{className:"text-sm font-medium mb-3 text-[var(--text-secondary)]",children:["Token Accumulation (",r.length," turns)"]}),i.jsx("div",{className:"space-y-2",children:r.map((u,c)=>i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("div",{className:"w-6 h-6 rounded-full bg-[var(--bg-primary)] border border-[var(--border)] flex items-center justify-center text-xs font-medium",children:c+1}),i.jsx("div",{className:"flex-1",children:i.jsx(wd,{input:l[c].input,output:l[c].output,maxValue:o,height:16})})]},c))})]}),i.jsx("div",{className:"mt-4 text-xs text-[var(--text-secondary)] border-t border-[var(--border)] pt-3",children:"Token usage affects API cost. Input tokens are typically cheaper than output tokens."})]})}function D0(){const{runId:e}=Ri(),t=Et(),{data:n,isLoading:r}=_e({queryKey:["runs",e],queryFn:()=>Po.get(e),enabled:!!e});return r?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):n?i.jsxs("div",{children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t(`/jobs/${n.job_id}`),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Job"})}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:n.candidate_name}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{className:"text-lg",children:n.task_name}),n.is_pareto&&i.jsx(q,{variant:"success",children:"Pareto Optimal"})]})]}),i.jsxs("div",{className:"grid grid-cols-4 gap-4 mb-6",children:[i.jsx(Ca,{label:"Score",value:`${(n.score*100).toFixed(1)}%`,status:n.passed?"success":"error"}),i.jsx(Ca,{label:"Total Tokens",value:n.tokens_total.toLocaleString()}),i.jsx(Ca,{label:"Duration",value:`${n.duration_seconds.toFixed(1)}s`}),i.jsx(Ca,{label:"Status",value:n.passed?"Passed":"Failed",status:n.passed?"success":"error"})]}),i.jsx(Mp,{tokensInput:n.tokens_input,tokensOutput:n.tokens_output,tokensTotal:n.tokens_total}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Evaluation"}),n.reasoning&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mb-4",children:n.reasoning}),n.criteria_results.length>0&&i.jsx("div",{className:"space-y-2",children:n.criteria_results.map(s=>i.jsx("div",{className:"flex items-start justify-between p-3 bg-[var(--bg-primary)] border border-[var(--border)]",children:i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"font-medium",children:s.name}),i.jsxs(q,{variant:s.passed?"success":"error",children:[(s.score*100).toFixed(0),"%"]})]}),s.reasoning&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:s.reasoning})]})},s.name))})]}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Agent Output"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)]",children:n.output||"(no output)"})]}),n.files_created.length>0&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Files Created"}),i.jsx("div",{className:"space-y-1",children:n.files_created.map(s=>i.jsx("div",{className:"text-sm font-mono text-[var(--text-secondary)]",children:s},s))})]}),Object.keys(n.trace).length>0&&i.jsx(Tp,{trace:n.trace})]}):i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Run not found"})}function Ca({label:e,value:t,status:n}){const r={success:"text-green-400",error:"text-red-400"};return i.jsxs(ee,{children:[i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:e}),i.jsx("div",{className:`text-xl font-bold ${n?r[n]:""}`,children:t})]})}function A0(){const{testId:e}=Ri(),t=Et(),{data:n,isLoading:r}=_e({queryKey:["tests",e],queryFn:()=>Ns.get(e),enabled:!!e}),{data:s}=_e({queryKey:["configs",n==null?void 0:n.agent_id],queryFn:()=>Jn.get(n.agent_id),enabled:!!(n!=null&&n.agent_id)});if(r)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."});if(!n)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Test not found"});const a={completed:"success",failed:"error",running:"info",pending:"default",cancelled:"warning"};return i.jsxs("div",{children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t("/agents"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Agents"})}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:"Test Run"}),s&&i.jsxs(i.Fragment,{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"•"}),i.jsx("span",{className:"text-lg",children:s.name})]}),i.jsx(q,{variant:a[n.status]||"default",children:n.status})]}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1 font-mono",children:["ID: ",n.id.slice(0,8),"..."]})]}),i.jsxs("div",{className:"grid grid-cols-4 gap-4 mb-6",children:[i.jsx(ba,{label:"Duration",value:`${n.duration_seconds.toFixed(2)}s`}),i.jsx(ba,{label:"Total Tokens",value:n.tokens_total.toLocaleString()}),n.score!==null&&i.jsx(ba,{label:"Score",value:`${(n.score*100).toFixed(1)}%`,status:n.passed?"success":"error"}),i.jsx(ba,{label:"Status",value:n.status.charAt(0).toUpperCase()+n.status.slice(1),status:n.status==="completed"?"success":n.status==="failed"?"error":void 0})]}),i.jsx(Mp,{tokensInput:n.tokens_input,tokensOutput:n.tokens_output,tokensTotal:n.tokens_total}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Prompt"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)] font-mono",children:n.prompt})]}),n.error&&i.jsxs(ee,{className:"mb-6 border-red-500/30 bg-red-500/5",children:[i.jsx("h3",{className:"font-medium mb-3 text-red-400",children:"Error"}),i.jsx("pre",{className:"text-sm text-red-300 overflow-x-auto whitespace-pre-wrap",children:n.error})]}),n.reasoning&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Evaluation"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:n.reasoning})]}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Agent Output"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)]",children:n.output||"(no output)"})]}),n.files_created&&n.files_created.length>0&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Files Created"}),i.jsx("div",{className:"space-y-1",children:n.files_created.map(l=>i.jsx("div",{className:"text-sm font-mono text-[var(--text-secondary)]",children:l},l))})]}),n.trace&&Object.keys(n.trace).length>0&&i.jsx(Tp,{trace:n.trace}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Timestamps"}),i.jsxs("div",{className:"grid grid-cols-3 gap-4 text-sm",children:[i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Created:"}),i.jsx("div",{className:"font-mono",children:new Date(n.created_at).toLocaleString()})]}),n.started_at&&i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Started:"}),i.jsx("div",{className:"font-mono",children:new Date(n.started_at).toLocaleString()})]}),n.completed_at&&i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Completed:"}),i.jsx("div",{className:"font-mono",children:new Date(n.completed_at).toLocaleString()})]})]})]})]})}function ba({label:e,value:t,status:n}){const r={success:"text-green-400",error:"text-red-400"};return i.jsxs(ee,{children:[i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:e}),i.jsx("div",{className:`text-xl font-bold ${n?r[n]:""}`,children:t})]})}function $0(){const{authConfig:e,isLoading:t,error:n,login:r,loginWithGitHub:s,clearError:a}=Iu(),[l,o]=k.useState(""),[u,c]=k.useState("");k.useEffect(()=>{n&&a()},[l,u]);const p=async h=>{h.preventDefault(),!(!l||!u)&&await r(l,u)},f=()=>{s()};return i.jsx("div",{className:"min-h-screen bg-[var(--bg-primary)] flex items-center justify-center p-4",children:i.jsxs("div",{className:"w-full max-w-md",children:[i.jsxs("div",{className:"text-center mb-8",children:[i.jsx("h1",{className:"text-2xl font-bold text-[var(--text-primary)] mb-2",children:"Flow"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:"Sign in to access the optimization dashboard"})]}),i.jsxs("div",{className:"bg-[var(--bg-secondary)] border border-[var(--border)] p-6 space-y-6",children:[n&&i.jsxs("div",{className:"flex items-start gap-3 p-3 bg-[var(--error)]/10 border border-[var(--error)]/20 text-[var(--error)]",children:[i.jsx(Zy,{size:18,className:"mt-0.5 flex-shrink-0"}),i.jsx("p",{className:"text-sm",children:n})]}),(e==null?void 0:e.mode)==="basic"&&i.jsxs("form",{onSubmit:p,className:"space-y-4",children:[i.jsxs("div",{className:"space-y-1",children:[i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:"Username"}),i.jsxs("div",{className:"relative",children:[i.jsx(wp,{size:16,className:"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)]"}),i.jsx("input",{type:"text",value:l,onChange:h=>o(h.target.value),className:"w-full bg-[var(--bg-primary)] border border-[var(--border)] pl-10 pr-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]",placeholder:"Enter username",autoComplete:"username",autoFocus:!0})]})]}),i.jsxs("div",{className:"space-y-1",children:[i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:"Password"}),i.jsxs("div",{className:"relative",children:[i.jsx(xp,{size:16,className:"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)]"}),i.jsx("input",{type:"password",value:u,onChange:h=>c(h.target.value),className:"w-full bg-[var(--bg-primary)] border border-[var(--border)] pl-10 pr-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]",placeholder:"Enter password",autoComplete:"current-password"})]})]}),i.jsx(K,{type:"submit",variant:"primary",className:"w-full justify-center",loading:t,disabled:!l||!u,children:"Sign In"})]}),(e==null?void 0:e.mode)==="github"&&i.jsxs("div",{className:"space-y-4",children:[i.jsx("p",{className:"text-sm text-[var(--text-secondary)] text-center",children:"Sign in with your GitHub account to continue"}),i.jsx(K,{onClick:f,variant:"secondary",className:"w-full justify-center",icon:fg,children:"Continue with GitHub"})]})]})]})})}function U0({children:e}){const{authConfig:t,isLoadingConfig:n,isAuthenticated:r,loadAuthConfig:s,handleOAuthCallback:a}=Iu();return k.useEffect(()=>{s()},[s]),k.useEffect(()=>{n||a()},[n,a]),n?i.jsx("div",{className:"min-h-screen bg-[var(--bg-primary)] flex items-center justify-center",children:i.jsxs("div",{className:"text-center",children:[i.jsx(Fi,{className:"w-8 h-8 animate-spin text-[var(--accent)] mx-auto mb-4"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:"Loading..."})]})}):t!=null&&t.enabled&&!r?i.jsx($0,{}):i.jsx(i.Fragment,{children:e})}function B0(){return i.jsx(Hy,{children:i.jsx(U0,{children:i.jsx(Iy,{children:i.jsxs(gt,{path:"/",element:i.jsx(a0,{}),children:[i.jsx(gt,{index:!0,element:i.jsx(gd,{})}),i.jsx(gt,{path:"agents",element:i.jsx(gd,{})}),i.jsx(gt,{path:"agents/:agentId",element:i.jsx(S0,{})}),i.jsx(gt,{path:"tasks",element:i.jsx(C0,{})}),i.jsx(gt,{path:"jobs",element:i.jsx(P0,{})}),i.jsx(gt,{path:"jobs/:jobId",element:i.jsx(I0,{})}),i.jsx(gt,{path:"runs/:runId",element:i.jsx(D0,{})}),i.jsx(gt,{path:"tests/:testId",element:i.jsx(A0,{})})]})})})})}const kd=localStorage.getItem("flow-theme");if(kd)try{const{state:e}=JSON.parse(kd);e!=null&&e.theme&&document.documentElement.setAttribute("data-theme",e.theme)}catch{}const Q0=new Lx({defaultOptions:{queries:{staleTime:5e3,refetchOnWindowFocus:!1}}});xl.createRoot(document.getElementById("root")).render(i.jsx(zo.StrictMode,{children:i.jsx(Rx,{client:Q0,children:i.jsx(B0,{})})})); diff --git a/src/flow/ui/ui/assets/index-DOP6DLVX.css b/src/flow/ui/ui/assets/index-DOP6DLVX.css new file mode 100644 index 0000000000000000000000000000000000000000..08d16e1e163a9d392ca92245cee1f98923daa583 --- /dev/null +++ b/src/flow/ui/ui/assets/index-DOP6DLVX.css @@ -0,0 +1 @@ +*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-3{left:.75rem}.top-0{top:0}.top-1\/2{top:50%}.z-10{z-index:10}.z-50{z-index:50}.col-span-2{grid-column:span 2 / span 2}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-32{height:8rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-80{max-height:20rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-0{min-height:0px}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-11{width:2.75rem}.w-12{width:3rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-\[200px\]{max-width:200px}.max-w-full{max-width:100%}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-1{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-6{--tw-translate-x: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize-y{resize:vertical}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/20{border-color:#22c55e33}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/20{border-color:#ef444433}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.border-transparent{border-color:transparent}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--border\)\]{background-color:var(--border)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-500\/5{background-color:#ef44440d}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-50{opacity:.5}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}} diff --git a/src/flow/ui/ui/assets/index-grEL5OMo.js b/src/flow/ui/ui/assets/index-grEL5OMo.js new file mode 100644 index 0000000000000000000000000000000000000000..c1f5083bdd2b534489b9bc5b8873e1f2f935deb2 --- /dev/null +++ b/src/flow/ui/ui/assets/index-grEL5OMo.js @@ -0,0 +1,260 @@ +var Au=e=>{throw TypeError(e)};var $i=(e,t,n)=>t.has(e)||Au("Cannot "+n);var y=(e,t,n)=>($i(e,t,"read from private field"),n?n.call(e):t.get(e)),I=(e,t,n)=>t.has(e)?Au("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),L=(e,t,n,r)=>($i(e,t,"write to private field"),r?r.call(e,n):t.set(e,n),n),Q=(e,t,n)=>($i(e,t,"access private method"),n);var la=(e,t,n,r)=>({set _(s){L(e,t,s,n)},get _(){return y(e,t,r)}});function zp(e,t){for(var n=0;nr[s]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const a of s)if(a.type==="childList")for(const l of a.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&r(l)}).observe(document,{childList:!0,subtree:!0});function n(s){const a={};return s.integrity&&(a.integrity=s.integrity),s.referrerPolicy&&(a.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?a.credentials="include":s.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(s){if(s.ep)return;s.ep=!0;const a=n(s);fetch(s.href,a)}})();function Md(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var zd={exports:{}},mi={},Fd={exports:{}},K={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var ea=Symbol.for("react.element"),Fp=Symbol.for("react.portal"),Ip=Symbol.for("react.fragment"),Dp=Symbol.for("react.strict_mode"),Ap=Symbol.for("react.profiler"),$p=Symbol.for("react.provider"),Up=Symbol.for("react.context"),Bp=Symbol.for("react.forward_ref"),Qp=Symbol.for("react.suspense"),Vp=Symbol.for("react.memo"),Hp=Symbol.for("react.lazy"),$u=Symbol.iterator;function Kp(e){return e===null||typeof e!="object"?null:(e=$u&&e[$u]||e["@@iterator"],typeof e=="function"?e:null)}var Id={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Dd=Object.assign,Ad={};function qr(e,t,n){this.props=e,this.context=t,this.refs=Ad,this.updater=n||Id}qr.prototype.isReactComponent={};qr.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};qr.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function $d(){}$d.prototype=qr.prototype;function Oo(e,t,n){this.props=e,this.context=t,this.refs=Ad,this.updater=n||Id}var Ro=Oo.prototype=new $d;Ro.constructor=Oo;Dd(Ro,qr.prototype);Ro.isPureReactComponent=!0;var Uu=Array.isArray,Ud=Object.prototype.hasOwnProperty,Lo={current:null},Bd={key:!0,ref:!0,__self:!0,__source:!0};function Qd(e,t,n){var r,s={},a=null,l=null;if(t!=null)for(r in t.ref!==void 0&&(l=t.ref),t.key!==void 0&&(a=""+t.key),t)Ud.call(t,r)&&!Bd.hasOwnProperty(r)&&(s[r]=t[r]);var o=arguments.length-2;if(o===1)s.children=n;else if(1>>1,A=O[R];if(0>>1;Rs(fe,B))yes(lt,fe)?(O[R]=lt,O[ye]=B,R=ye):(O[R]=fe,O[$]=B,R=$);else if(yes(lt,B))O[R]=lt,O[ye]=B,R=ye;else break e}}return D}function s(O,D){var B=O.sortIndex-D.sortIndex;return B!==0?B:O.id-D.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var l=Date,o=l.now();e.unstable_now=function(){return l.now()-o}}var u=[],c=[],p=1,f=null,h=3,x=!1,w=!1,j=!1,S=typeof setTimeout=="function"?setTimeout:null,v=typeof clearTimeout=="function"?clearTimeout:null,d=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function m(O){for(var D=n(c);D!==null;){if(D.callback===null)r(c);else if(D.startTime<=O)r(c),D.sortIndex=D.expirationTime,t(u,D);else break;D=n(c)}}function g(O){if(j=!1,m(O),!w)if(n(u)!==null)w=!0,Pt(_);else{var D=n(c);D!==null&&He(g,D.startTime-O)}}function _(O,D){w=!1,j&&(j=!1,v(b),b=-1),x=!0;var B=h;try{for(m(D),f=n(u);f!==null&&(!(f.expirationTime>D)||O&&!V());){var R=f.callback;if(typeof R=="function"){f.callback=null,h=f.priorityLevel;var A=R(f.expirationTime<=D);D=e.unstable_now(),typeof A=="function"?f.callback=A:f===n(u)&&r(u),m(D)}else r(u);f=n(u)}if(f!==null)var P=!0;else{var $=n(c);$!==null&&He(g,$.startTime-D),P=!1}return P}finally{f=null,h=B,x=!1}}var N=!1,C=null,b=-1,z=5,T=-1;function V(){return!(e.unstable_now()-TO||125R?(O.sortIndex=B,t(c,O),n(u)===null&&O===n(c)&&(j?(v(b),b=-1):j=!0,He(g,B-R))):(O.sortIndex=A,t(u,O),w||x||(w=!0,Pt(_))),O},e.unstable_shouldYield=V,e.unstable_wrapCallback=function(O){var D=h;return function(){var B=h;h=D;try{return O.apply(this,arguments)}finally{h=B}}}})(qd);Wd.exports=qd;var sm=Wd.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var am=k,Ge=sm;function E(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),yl=Object.prototype.hasOwnProperty,im=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Qu={},Vu={};function lm(e){return yl.call(Vu,e)?!0:yl.call(Qu,e)?!1:im.test(e)?Vu[e]=!0:(Qu[e]=!0,!1)}function om(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function um(e,t,n,r){if(t===null||typeof t>"u"||om(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Ie(e,t,n,r,s,a,l){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=s,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=l}var _e={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){_e[e]=new Ie(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];_e[t]=new Ie(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){_e[e]=new Ie(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){_e[e]=new Ie(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){_e[e]=new Ie(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){_e[e]=new Ie(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){_e[e]=new Ie(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){_e[e]=new Ie(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){_e[e]=new Ie(e,5,!1,e.toLowerCase(),null,!1,!1)});var Fo=/[\-:]([a-z])/g;function Io(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Fo,Io);_e[t]=new Ie(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Fo,Io);_e[t]=new Ie(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Fo,Io);_e[t]=new Ie(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){_e[e]=new Ie(e,1,!1,e.toLowerCase(),null,!1,!1)});_e.xlinkHref=new Ie("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){_e[e]=new Ie(e,1,!1,e.toLowerCase(),null,!0,!0)});function Do(e,t,n,r){var s=_e.hasOwnProperty(t)?_e[t]:null;(s!==null?s.type!==0:r||!(2o||s[l]!==a[o]){var u=` +`+s[l].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=l&&0<=o);break}}}finally{Qi=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?us(e):""}function cm(e){switch(e.tag){case 5:return us(e.type);case 16:return us("Lazy");case 13:return us("Suspense");case 19:return us("SuspenseList");case 0:case 2:case 15:return e=Vi(e.type,!1),e;case 11:return e=Vi(e.type.render,!1),e;case 1:return e=Vi(e.type,!0),e;default:return""}}function kl(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case cr:return"Fragment";case ur:return"Portal";case gl:return"Profiler";case Ao:return"StrictMode";case jl:return"Suspense";case wl:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Xd:return(e.displayName||"Context")+".Consumer";case Jd:return(e._context.displayName||"Context")+".Provider";case $o:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Uo:return t=e.displayName||null,t!==null?t:kl(e.type)||"Memo";case Gt:t=e._payload,e=e._init;try{return kl(e(t))}catch{}}return null}function dm(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return kl(t);case 8:return t===Ao?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Nn(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Zd(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function fm(e){var t=Zd(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var s=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(l){r=""+l,a.call(this,l)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(l){r=""+l},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function ca(e){e._valueTracker||(e._valueTracker=fm(e))}function ef(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Zd(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Ua(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Sl(e,t){var n=t.checked;return oe({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Ku(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Nn(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function tf(e,t){t=t.checked,t!=null&&Do(e,"checked",t,!1)}function Nl(e,t){tf(e,t);var n=Nn(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?_l(e,t.type,n):t.hasOwnProperty("defaultValue")&&_l(e,t.type,Nn(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Wu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function _l(e,t,n){(t!=="number"||Ua(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var cs=Array.isArray;function wr(e,t,n,r){if(e=e.options,t){t={};for(var s=0;s"+t.valueOf().toString()+"",t=da.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Cs(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var ps={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},hm=["Webkit","ms","Moz","O"];Object.keys(ps).forEach(function(e){hm.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),ps[t]=ps[e]})});function af(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||ps.hasOwnProperty(e)&&ps[e]?(""+t).trim():t+"px"}function lf(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,s=af(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,s):e[n]=s}}var pm=oe({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function El(e,t){if(t){if(pm[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(E(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(E(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(E(61))}if(t.style!=null&&typeof t.style!="object")throw Error(E(62))}}function Pl(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Tl=null;function Bo(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Ol=null,kr=null,Sr=null;function Ju(e){if(e=ra(e)){if(typeof Ol!="function")throw Error(E(280));var t=e.stateNode;t&&(t=ji(t),Ol(e.stateNode,e.type,t))}}function of(e){kr?Sr?Sr.push(e):Sr=[e]:kr=e}function uf(){if(kr){var e=kr,t=Sr;if(Sr=kr=null,Ju(e),t)for(e=0;e>>=0,e===0?32:31-(_m(e)/Cm|0)|0}var fa=64,ha=4194304;function ds(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Ha(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,s=e.suspendedLanes,a=e.pingedLanes,l=n&268435455;if(l!==0){var o=l&~s;o!==0?r=ds(o):(a&=l,a!==0&&(r=ds(a)))}else l=n&~s,l!==0?r=ds(l):a!==0&&(r=ds(a));if(r===0)return 0;if(t!==0&&t!==r&&!(t&s)&&(s=r&-r,a=t&-t,s>=a||s===16&&(a&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function ta(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-mt(t),e[t]=n}function Tm(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=vs),ac=" ",ic=!1;function Pf(e,t){switch(e){case"keyup":return sv.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Tf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var dr=!1;function iv(e,t){switch(e){case"compositionend":return Tf(t);case"keypress":return t.which!==32?null:(ic=!0,ac);case"textInput":return e=t.data,e===ac&&ic?null:e;default:return null}}function lv(e,t){if(dr)return e==="compositionend"||!Jo&&Pf(e,t)?(e=bf(),Oa=Wo=un=null,dr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=cc(n)}}function Mf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Mf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function zf(){for(var e=window,t=Ua();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ua(e.document)}return t}function Xo(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function vv(e){var t=zf(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Mf(n.ownerDocument.documentElement,n)){if(r!==null&&Xo(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var s=n.textContent.length,a=Math.min(r.start,s);r=r.end===void 0?a:Math.min(r.end,s),!e.extend&&a>r&&(s=r,r=a,a=s),s=dc(n,a);var l=dc(n,r);s&&l&&(e.rangeCount!==1||e.anchorNode!==s.node||e.anchorOffset!==s.offset||e.focusNode!==l.node||e.focusOffset!==l.offset)&&(t=t.createRange(),t.setStart(s.node,s.offset),e.removeAllRanges(),a>r?(e.addRange(t),e.extend(l.node,l.offset)):(t.setEnd(l.node,l.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,fr=null,Il=null,ys=null,Dl=!1;function fc(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Dl||fr==null||fr!==Ua(r)||(r=fr,"selectionStart"in r&&Xo(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),ys&&Rs(ys,r)||(ys=r,r=qa(Il,"onSelect"),0mr||(e.current=Vl[mr],Vl[mr]=null,mr--)}function ne(e,t){mr++,Vl[mr]=e.current,e.current=t}var _n={},Oe=bn(_n),Be=bn(!1),Xn=_n;function $r(e,t){var n=e.type.contextTypes;if(!n)return _n;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var s={},a;for(a in n)s[a]=t[a];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=s),s}function Qe(e){return e=e.childContextTypes,e!=null}function Ja(){se(Be),se(Oe)}function gc(e,t,n){if(Oe.current!==_n)throw Error(E(168));ne(Oe,t),ne(Be,n)}function Vf(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var s in r)if(!(s in t))throw Error(E(108,dm(e)||"Unknown",s));return oe({},n,r)}function Xa(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||_n,Xn=Oe.current,ne(Oe,e),ne(Be,Be.current),!0}function jc(e,t,n){var r=e.stateNode;if(!r)throw Error(E(169));n?(e=Vf(e,t,Xn),r.__reactInternalMemoizedMergedChildContext=e,se(Be),se(Oe),ne(Oe,e)):se(Be),ne(Be,n)}var Rt=null,wi=!1,sl=!1;function Hf(e){Rt===null?Rt=[e]:Rt.push(e)}function Ev(e){wi=!0,Hf(e)}function En(){if(!sl&&Rt!==null){sl=!0;var e=0,t=Z;try{var n=Rt;for(Z=1;e>=l,s-=l,It=1<<32-mt(t)+s|n<b?(z=C,C=null):z=C.sibling;var T=h(v,C,m[b],g);if(T===null){C===null&&(C=z);break}e&&C&&T.alternate===null&&t(v,C),d=a(T,d,b),N===null?_=T:N.sibling=T,N=T,C=z}if(b===m.length)return n(v,C),ae&&On(v,b),_;if(C===null){for(;bb?(z=C,C=null):z=C.sibling;var V=h(v,C,T.value,g);if(V===null){C===null&&(C=z);break}e&&C&&V.alternate===null&&t(v,C),d=a(V,d,b),N===null?_=V:N.sibling=V,N=V,C=z}if(T.done)return n(v,C),ae&&On(v,b),_;if(C===null){for(;!T.done;b++,T=m.next())T=f(v,T.value,g),T!==null&&(d=a(T,d,b),N===null?_=T:N.sibling=T,N=T);return ae&&On(v,b),_}for(C=r(v,C);!T.done;b++,T=m.next())T=x(C,v,b,T.value,g),T!==null&&(e&&T.alternate!==null&&C.delete(T.key===null?b:T.key),d=a(T,d,b),N===null?_=T:N.sibling=T,N=T);return e&&C.forEach(function(F){return t(v,F)}),ae&&On(v,b),_}function S(v,d,m,g){if(typeof m=="object"&&m!==null&&m.type===cr&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case ua:e:{for(var _=m.key,N=d;N!==null;){if(N.key===_){if(_=m.type,_===cr){if(N.tag===7){n(v,N.sibling),d=s(N,m.props.children),d.return=v,v=d;break e}}else if(N.elementType===_||typeof _=="object"&&_!==null&&_.$$typeof===Gt&&Sc(_)===N.type){n(v,N.sibling),d=s(N,m.props),d.ref=as(v,N,m),d.return=v,v=d;break e}n(v,N);break}else t(v,N);N=N.sibling}m.type===cr?(d=qn(m.props.children,v.mode,g,m.key),d.return=v,v=d):(g=Aa(m.type,m.key,m.props,null,v.mode,g),g.ref=as(v,d,m),g.return=v,v=g)}return l(v);case ur:e:{for(N=m.key;d!==null;){if(d.key===N)if(d.tag===4&&d.stateNode.containerInfo===m.containerInfo&&d.stateNode.implementation===m.implementation){n(v,d.sibling),d=s(d,m.children||[]),d.return=v,v=d;break e}else{n(v,d);break}else t(v,d);d=d.sibling}d=fl(m,v.mode,g),d.return=v,v=d}return l(v);case Gt:return N=m._init,S(v,d,N(m._payload),g)}if(cs(m))return w(v,d,m,g);if(es(m))return j(v,d,m,g);ja(v,m)}return typeof m=="string"&&m!==""||typeof m=="number"?(m=""+m,d!==null&&d.tag===6?(n(v,d.sibling),d=s(d,m),d.return=v,v=d):(n(v,d),d=dl(m,v.mode,g),d.return=v,v=d),l(v)):n(v,d)}return S}var Br=Gf(!0),Jf=Gf(!1),ei=bn(null),ti=null,yr=null,tu=null;function nu(){tu=yr=ti=null}function ru(e){var t=ei.current;se(ei),e._currentValue=t}function Wl(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function _r(e,t){ti=e,tu=yr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Ue=!0),e.firstContext=null)}function at(e){var t=e._currentValue;if(tu!==e)if(e={context:e,memoizedValue:t,next:null},yr===null){if(ti===null)throw Error(E(308));yr=e,ti.dependencies={lanes:0,firstContext:e}}else yr=yr.next=e;return t}var zn=null;function su(e){zn===null?zn=[e]:zn.push(e)}function Xf(e,t,n,r){var s=t.interleaved;return s===null?(n.next=n,su(t)):(n.next=s.next,s.next=n),t.interleaved=n,Qt(e,r)}function Qt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Jt=!1;function au(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Yf(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function At(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function yn(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var s=r.pending;return s===null?t.next=t:(t.next=s.next,s.next=t),r.pending=t,Qt(e,n)}return s=r.interleaved,s===null?(t.next=t,su(r)):(t.next=s.next,s.next=t),r.interleaved=t,Qt(e,n)}function La(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Vo(e,n)}}function Nc(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var s=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var l={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};a===null?s=a=l:a=a.next=l,n=n.next}while(n!==null);a===null?s=a=t:a=a.next=t}else s=a=t;n={baseState:r.baseState,firstBaseUpdate:s,lastBaseUpdate:a,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ni(e,t,n,r){var s=e.updateQueue;Jt=!1;var a=s.firstBaseUpdate,l=s.lastBaseUpdate,o=s.shared.pending;if(o!==null){s.shared.pending=null;var u=o,c=u.next;u.next=null,l===null?a=c:l.next=c,l=u;var p=e.alternate;p!==null&&(p=p.updateQueue,o=p.lastBaseUpdate,o!==l&&(o===null?p.firstBaseUpdate=c:o.next=c,p.lastBaseUpdate=u))}if(a!==null){var f=s.baseState;l=0,p=c=u=null,o=a;do{var h=o.lane,x=o.eventTime;if((r&h)===h){p!==null&&(p=p.next={eventTime:x,lane:0,tag:o.tag,payload:o.payload,callback:o.callback,next:null});e:{var w=e,j=o;switch(h=t,x=n,j.tag){case 1:if(w=j.payload,typeof w=="function"){f=w.call(x,f,h);break e}f=w;break e;case 3:w.flags=w.flags&-65537|128;case 0:if(w=j.payload,h=typeof w=="function"?w.call(x,f,h):w,h==null)break e;f=oe({},f,h);break e;case 2:Jt=!0}}o.callback!==null&&o.lane!==0&&(e.flags|=64,h=s.effects,h===null?s.effects=[o]:h.push(o))}else x={eventTime:x,lane:h,tag:o.tag,payload:o.payload,callback:o.callback,next:null},p===null?(c=p=x,u=f):p=p.next=x,l|=h;if(o=o.next,o===null){if(o=s.shared.pending,o===null)break;h=o,o=h.next,h.next=null,s.lastBaseUpdate=h,s.shared.pending=null}}while(!0);if(p===null&&(u=f),s.baseState=u,s.firstBaseUpdate=c,s.lastBaseUpdate=p,t=s.shared.interleaved,t!==null){s=t;do l|=s.lane,s=s.next;while(s!==t)}else a===null&&(s.shared.lanes=0);er|=l,e.lanes=l,e.memoizedState=f}}function _c(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=il.transition;il.transition={};try{e(!1),t()}finally{Z=n,il.transition=r}}function mh(){return it().memoizedState}function Rv(e,t,n){var r=jn(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},vh(e))xh(t,n);else if(n=Xf(e,t,n,r),n!==null){var s=ze();vt(n,e,r,s),yh(n,t,r)}}function Lv(e,t,n){var r=jn(e),s={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(vh(e))xh(t,s);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var l=t.lastRenderedState,o=a(l,n);if(s.hasEagerState=!0,s.eagerState=o,xt(o,l)){var u=t.interleaved;u===null?(s.next=s,su(t)):(s.next=u.next,u.next=s),t.interleaved=s;return}}catch{}finally{}n=Xf(e,t,s,r),n!==null&&(s=ze(),vt(n,e,r,s),yh(n,t,r))}}function vh(e){var t=e.alternate;return e===le||t!==null&&t===le}function xh(e,t){gs=si=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function yh(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Vo(e,n)}}var ai={readContext:at,useCallback:Ce,useContext:Ce,useEffect:Ce,useImperativeHandle:Ce,useInsertionEffect:Ce,useLayoutEffect:Ce,useMemo:Ce,useReducer:Ce,useRef:Ce,useState:Ce,useDebugValue:Ce,useDeferredValue:Ce,useTransition:Ce,useMutableSource:Ce,useSyncExternalStore:Ce,useId:Ce,unstable_isNewReconciler:!1},Mv={readContext:at,useCallback:function(e,t){return jt().memoizedState=[e,t===void 0?null:t],e},useContext:at,useEffect:bc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,za(4194308,4,ch.bind(null,t,e),n)},useLayoutEffect:function(e,t){return za(4194308,4,e,t)},useInsertionEffect:function(e,t){return za(4,2,e,t)},useMemo:function(e,t){var n=jt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=jt();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Rv.bind(null,le,e),[r.memoizedState,e]},useRef:function(e){var t=jt();return e={current:e},t.memoizedState=e},useState:Cc,useDebugValue:hu,useDeferredValue:function(e){return jt().memoizedState=e},useTransition:function(){var e=Cc(!1),t=e[0];return e=Ov.bind(null,e[1]),jt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=le,s=jt();if(ae){if(n===void 0)throw Error(E(407));n=n()}else{if(n=t(),we===null)throw Error(E(349));Zn&30||nh(r,t,n)}s.memoizedState=n;var a={value:n,getSnapshot:t};return s.queue=a,bc(sh.bind(null,r,a,e),[e]),r.flags|=2048,$s(9,rh.bind(null,r,a,n,t),void 0,null),n},useId:function(){var e=jt(),t=we.identifierPrefix;if(ae){var n=Dt,r=It;n=(r&~(1<<32-mt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Ds++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=l.createElement(n,{is:r.is}):(e=l.createElement(n),n==="select"&&(l=e,r.multiple?l.multiple=!0:r.size&&(l.size=r.size))):e=l.createElementNS(e,n),e[Nt]=t,e[zs]=r,Eh(e,t,!1,!1),t.stateNode=e;e:{switch(l=Pl(n,r),n){case"dialog":re("cancel",e),re("close",e),s=r;break;case"iframe":case"object":case"embed":re("load",e),s=r;break;case"video":case"audio":for(s=0;sHr&&(t.flags|=128,r=!0,is(a,!1),t.lanes=4194304)}else{if(!r)if(e=ri(l),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),is(a,!0),a.tail===null&&a.tailMode==="hidden"&&!l.alternate&&!ae)return be(t),null}else 2*he()-a.renderingStartTime>Hr&&n!==1073741824&&(t.flags|=128,r=!0,is(a,!1),t.lanes=4194304);a.isBackwards?(l.sibling=t.child,t.child=l):(n=a.last,n!==null?n.sibling=l:t.child=l,a.last=l)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=he(),t.sibling=null,n=ie.current,ne(ie,r?n&1|2:n&1),t):(be(t),null);case 22:case 23:return gu(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Ke&1073741824&&(be(t),t.subtreeFlags&6&&(t.flags|=8192)):be(t),null;case 24:return null;case 25:return null}throw Error(E(156,t.tag))}function Bv(e,t){switch(Zo(t),t.tag){case 1:return Qe(t.type)&&Ja(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Qr(),se(Be),se(Oe),ou(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return lu(t),null;case 13:if(se(ie),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(E(340));Ur()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return se(ie),null;case 4:return Qr(),null;case 10:return ru(t.type._context),null;case 22:case 23:return gu(),null;case 24:return null;default:return null}}var ka=!1,Pe=!1,Qv=typeof WeakSet=="function"?WeakSet:Set,M=null;function gr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){ce(e,t,r)}else n.current=null}function no(e,t,n){try{n()}catch(r){ce(e,t,r)}}var Dc=!1;function Vv(e,t){if(Al=Ka,e=zf(),Xo(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var s=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break e}var l=0,o=-1,u=-1,c=0,p=0,f=e,h=null;t:for(;;){for(var x;f!==n||s!==0&&f.nodeType!==3||(o=l+s),f!==a||r!==0&&f.nodeType!==3||(u=l+r),f.nodeType===3&&(l+=f.nodeValue.length),(x=f.firstChild)!==null;)h=f,f=x;for(;;){if(f===e)break t;if(h===n&&++c===s&&(o=l),h===a&&++p===r&&(u=l),(x=f.nextSibling)!==null)break;f=h,h=f.parentNode}f=x}n=o===-1||u===-1?null:{start:o,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for($l={focusedElem:e,selectionRange:n},Ka=!1,M=t;M!==null;)if(t=M,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,M=e;else for(;M!==null;){t=M;try{var w=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(w!==null){var j=w.memoizedProps,S=w.memoizedState,v=t.stateNode,d=v.getSnapshotBeforeUpdate(t.elementType===t.type?j:ut(t.type,j),S);v.__reactInternalSnapshotBeforeUpdate=d}break;case 3:var m=t.stateNode.containerInfo;m.nodeType===1?m.textContent="":m.nodeType===9&&m.documentElement&&m.removeChild(m.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(E(163))}}catch(g){ce(t,t.return,g)}if(e=t.sibling,e!==null){e.return=t.return,M=e;break}M=t.return}return w=Dc,Dc=!1,w}function js(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var s=r=r.next;do{if((s.tag&e)===e){var a=s.destroy;s.destroy=void 0,a!==void 0&&no(t,n,a)}s=s.next}while(s!==r)}}function Ni(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ro(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Oh(e){var t=e.alternate;t!==null&&(e.alternate=null,Oh(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Nt],delete t[zs],delete t[Ql],delete t[Cv],delete t[bv])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Rh(e){return e.tag===5||e.tag===3||e.tag===4}function Ac(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Rh(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function so(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Ga));else if(r!==4&&(e=e.child,e!==null))for(so(e,t,n),e=e.sibling;e!==null;)so(e,t,n),e=e.sibling}function ao(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ao(e,t,n),e=e.sibling;e!==null;)ao(e,t,n),e=e.sibling}var Se=null,ft=!1;function Wt(e,t,n){for(n=n.child;n!==null;)Lh(e,t,n),n=n.sibling}function Lh(e,t,n){if(_t&&typeof _t.onCommitFiberUnmount=="function")try{_t.onCommitFiberUnmount(vi,n)}catch{}switch(n.tag){case 5:Pe||gr(n,t);case 6:var r=Se,s=ft;Se=null,Wt(e,t,n),Se=r,ft=s,Se!==null&&(ft?(e=Se,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Se.removeChild(n.stateNode));break;case 18:Se!==null&&(ft?(e=Se,n=n.stateNode,e.nodeType===8?rl(e.parentNode,n):e.nodeType===1&&rl(e,n),Ts(e)):rl(Se,n.stateNode));break;case 4:r=Se,s=ft,Se=n.stateNode.containerInfo,ft=!0,Wt(e,t,n),Se=r,ft=s;break;case 0:case 11:case 14:case 15:if(!Pe&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){s=r=r.next;do{var a=s,l=a.destroy;a=a.tag,l!==void 0&&(a&2||a&4)&&no(n,t,l),s=s.next}while(s!==r)}Wt(e,t,n);break;case 1:if(!Pe&&(gr(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(o){ce(n,t,o)}Wt(e,t,n);break;case 21:Wt(e,t,n);break;case 22:n.mode&1?(Pe=(r=Pe)||n.memoizedState!==null,Wt(e,t,n),Pe=r):Wt(e,t,n);break;default:Wt(e,t,n)}}function $c(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Qv),t.forEach(function(r){var s=Zv.bind(null,e,r);n.has(r)||(n.add(r),r.then(s,s))})}}function ot(e,t){var n=t.deletions;if(n!==null)for(var r=0;rs&&(s=l),r&=~a}if(r=s,r=he()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Kv(r/1960))-r,10e?16:e,cn===null)var r=!1;else{if(e=cn,cn=null,oi=0,G&6)throw Error(E(331));var s=G;for(G|=4,M=e.current;M!==null;){var a=M,l=a.child;if(M.flags&16){var o=a.deletions;if(o!==null){for(var u=0;uhe()-xu?Wn(e,0):vu|=n),Ve(e,t)}function Uh(e,t){t===0&&(e.mode&1?(t=ha,ha<<=1,!(ha&130023424)&&(ha=4194304)):t=1);var n=ze();e=Qt(e,t),e!==null&&(ta(e,t,n),Ve(e,n))}function Yv(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Uh(e,n)}function Zv(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,s=e.memoizedState;s!==null&&(n=s.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(E(314))}r!==null&&r.delete(t),Uh(e,n)}var Bh;Bh=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Be.current)Ue=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return Ue=!1,$v(e,t,n);Ue=!!(e.flags&131072)}else Ue=!1,ae&&t.flags&1048576&&Kf(t,Za,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Fa(e,t),e=t.pendingProps;var s=$r(t,Oe.current);_r(t,n),s=cu(null,t,r,e,s,n);var a=du();return t.flags|=1,typeof s=="object"&&s!==null&&typeof s.render=="function"&&s.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Qe(r)?(a=!0,Xa(t)):a=!1,t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,au(t),s.updater=Si,t.stateNode=s,s._reactInternals=t,Gl(t,r,e,n),t=Yl(null,t,r,!0,a,n)):(t.tag=0,ae&&a&&Yo(t),Le(null,t,s,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Fa(e,t),e=t.pendingProps,s=r._init,r=s(r._payload),t.type=r,s=t.tag=tx(r),e=ut(r,e),s){case 0:t=Xl(null,t,r,e,n);break e;case 1:t=zc(null,t,r,e,n);break e;case 11:t=Lc(null,t,r,e,n);break e;case 14:t=Mc(null,t,r,ut(r.type,e),n);break e}throw Error(E(306,r,""))}return t;case 0:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Xl(e,t,r,s,n);case 1:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),zc(e,t,r,s,n);case 3:e:{if(_h(t),e===null)throw Error(E(387));r=t.pendingProps,a=t.memoizedState,s=a.element,Yf(e,t),ni(t,r,null,n);var l=t.memoizedState;if(r=l.element,a.isDehydrated)if(a={element:r,isDehydrated:!1,cache:l.cache,pendingSuspenseBoundaries:l.pendingSuspenseBoundaries,transitions:l.transitions},t.updateQueue.baseState=a,t.memoizedState=a,t.flags&256){s=Vr(Error(E(423)),t),t=Fc(e,t,r,n,s);break e}else if(r!==s){s=Vr(Error(E(424)),t),t=Fc(e,t,r,n,s);break e}else for(We=xn(t.stateNode.containerInfo.firstChild),qe=t,ae=!0,ht=null,n=Jf(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Ur(),r===s){t=Vt(e,t,n);break e}Le(e,t,r,n)}t=t.child}return t;case 5:return Zf(t),e===null&&Kl(t),r=t.type,s=t.pendingProps,a=e!==null?e.memoizedProps:null,l=s.children,Ul(r,s)?l=null:a!==null&&Ul(r,a)&&(t.flags|=32),Nh(e,t),Le(e,t,l,n),t.child;case 6:return e===null&&Kl(t),null;case 13:return Ch(e,t,n);case 4:return iu(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Br(t,null,r,n):Le(e,t,r,n),t.child;case 11:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Lc(e,t,r,s,n);case 7:return Le(e,t,t.pendingProps,n),t.child;case 8:return Le(e,t,t.pendingProps.children,n),t.child;case 12:return Le(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,s=t.pendingProps,a=t.memoizedProps,l=s.value,ne(ei,r._currentValue),r._currentValue=l,a!==null)if(xt(a.value,l)){if(a.children===s.children&&!Be.current){t=Vt(e,t,n);break e}}else for(a=t.child,a!==null&&(a.return=t);a!==null;){var o=a.dependencies;if(o!==null){l=a.child;for(var u=o.firstContext;u!==null;){if(u.context===r){if(a.tag===1){u=At(-1,n&-n),u.tag=2;var c=a.updateQueue;if(c!==null){c=c.shared;var p=c.pending;p===null?u.next=u:(u.next=p.next,p.next=u),c.pending=u}}a.lanes|=n,u=a.alternate,u!==null&&(u.lanes|=n),Wl(a.return,n,t),o.lanes|=n;break}u=u.next}}else if(a.tag===10)l=a.type===t.type?null:a.child;else if(a.tag===18){if(l=a.return,l===null)throw Error(E(341));l.lanes|=n,o=l.alternate,o!==null&&(o.lanes|=n),Wl(l,n,t),l=a.sibling}else l=a.child;if(l!==null)l.return=a;else for(l=a;l!==null;){if(l===t){l=null;break}if(a=l.sibling,a!==null){a.return=l.return,l=a;break}l=l.return}a=l}Le(e,t,s.children,n),t=t.child}return t;case 9:return s=t.type,r=t.pendingProps.children,_r(t,n),s=at(s),r=r(s),t.flags|=1,Le(e,t,r,n),t.child;case 14:return r=t.type,s=ut(r,t.pendingProps),s=ut(r.type,s),Mc(e,t,r,s,n);case 15:return kh(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:ut(r,s),Fa(e,t),t.tag=1,Qe(r)?(e=!0,Xa(t)):e=!1,_r(t,n),gh(t,r,s),Gl(t,r,s,n),Yl(null,t,r,!0,e,n);case 19:return bh(e,t,n);case 22:return Sh(e,t,n)}throw Error(E(156,t.tag))};function Qh(e,t){return vf(e,t)}function ex(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function nt(e,t,n,r){return new ex(e,t,n,r)}function wu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function tx(e){if(typeof e=="function")return wu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===$o)return 11;if(e===Uo)return 14}return 2}function wn(e,t){var n=e.alternate;return n===null?(n=nt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Aa(e,t,n,r,s,a){var l=2;if(r=e,typeof e=="function")wu(e)&&(l=1);else if(typeof e=="string")l=5;else e:switch(e){case cr:return qn(n.children,s,a,t);case Ao:l=8,s|=8;break;case gl:return e=nt(12,n,t,s|2),e.elementType=gl,e.lanes=a,e;case jl:return e=nt(13,n,t,s),e.elementType=jl,e.lanes=a,e;case wl:return e=nt(19,n,t,s),e.elementType=wl,e.lanes=a,e;case Yd:return Ci(n,s,a,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Jd:l=10;break e;case Xd:l=9;break e;case $o:l=11;break e;case Uo:l=14;break e;case Gt:l=16,r=null;break e}throw Error(E(130,e==null?e:typeof e,""))}return t=nt(l,n,t,s),t.elementType=e,t.type=r,t.lanes=a,t}function qn(e,t,n,r){return e=nt(7,e,r,t),e.lanes=n,e}function Ci(e,t,n,r){return e=nt(22,e,r,t),e.elementType=Yd,e.lanes=n,e.stateNode={isHidden:!1},e}function dl(e,t,n){return e=nt(6,e,null,t),e.lanes=n,e}function fl(e,t,n){return t=nt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function nx(e,t,n,r,s){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Ki(0),this.expirationTimes=Ki(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Ki(0),this.identifierPrefix=r,this.onRecoverableError=s,this.mutableSourceEagerHydrationData=null}function ku(e,t,n,r,s,a,l,o,u){return e=new nx(e,t,n,o,u),t===1?(t=1,a===!0&&(t|=8)):t=0,a=nt(3,null,null,t),e.current=a,a.stateNode=e,a.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},au(a),e}function rx(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Wh)}catch(e){console.error(e)}}Wh(),Kd.exports=Je;var ox=Kd.exports,qc=ox;xl.createRoot=qc.createRoot,xl.hydrateRoot=qc.hydrateRoot;var Xr=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},ux={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},Zt,To,Sd,cx=(Sd=class{constructor(){I(this,Zt,ux);I(this,To,!1)}setTimeoutProvider(e){L(this,Zt,e)}setTimeout(e,t){return y(this,Zt).setTimeout(e,t)}clearTimeout(e){y(this,Zt).clearTimeout(e)}setInterval(e,t){return y(this,Zt).setInterval(e,t)}clearInterval(e){y(this,Zt).clearInterval(e)}},Zt=new WeakMap,To=new WeakMap,Sd),In=new cx;function dx(e){setTimeout(e,0)}var nr=typeof window>"u"||"Deno"in globalThis;function Me(){}function fx(e,t){return typeof e=="function"?e(t):e}function co(e){return typeof e=="number"&&e>=0&&e!==1/0}function qh(e,t){return Math.max(e+(t||0)-Date.now(),0)}function kn(e,t){return typeof e=="function"?e(t):e}function Ze(e,t){return typeof e=="function"?e(t):e}function Gc(e,t){const{type:n="all",exact:r,fetchStatus:s,predicate:a,queryKey:l,stale:o}=e;if(l){if(r){if(t.queryHash!==Cu(l,t.options))return!1}else if(!Bs(t.queryKey,l))return!1}if(n!=="all"){const u=t.isActive();if(n==="active"&&!u||n==="inactive"&&u)return!1}return!(typeof o=="boolean"&&t.isStale()!==o||s&&s!==t.state.fetchStatus||a&&!a(t))}function Jc(e,t){const{exact:n,status:r,predicate:s,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(rr(t.options.mutationKey)!==rr(a))return!1}else if(!Bs(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||s&&!s(t))}function Cu(e,t){return((t==null?void 0:t.queryKeyHashFn)||rr)(e)}function rr(e){return JSON.stringify(e,(t,n)=>fo(n)?Object.keys(n).sort().reduce((r,s)=>(r[s]=n[s],r),{}):n)}function Bs(e,t){return e===t?!0:typeof e!=typeof t?!1:e&&t&&typeof e=="object"&&typeof t=="object"?Object.keys(t).every(n=>Bs(e[n],t[n])):!1}var hx=Object.prototype.hasOwnProperty;function Gh(e,t,n=0){if(e===t)return e;if(n>500)return t;const r=Xc(e)&&Xc(t);if(!r&&!(fo(e)&&fo(t)))return t;const a=(r?e:Object.keys(e)).length,l=r?t:Object.keys(t),o=l.length,u=r?new Array(o):{};let c=0;for(let p=0;p{In.setTimeout(t,e)})}function ho(e,t,n){return typeof n.structuralSharing=="function"?n.structuralSharing(e,t):n.structuralSharing!==!1?Gh(e,t):t}function mx(e,t,n=0){const r=[...e,t];return n&&r.length>n?r.slice(1):r}function vx(e,t,n=0){const r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var bu=Symbol();function Jh(e,t){return!e.queryFn&&(t!=null&&t.initialPromise)?()=>t.initialPromise:!e.queryFn||e.queryFn===bu?()=>Promise.reject(new Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function Eu(e,t){return typeof e=="function"?e(...t):!!e}function xx(e,t,n){let r=!1,s;return Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(s??(s=t()),r||(r=!0,s.aborted?n():s.addEventListener("abort",n,{once:!0})),s)}),e}var Dn,en,br,Nd,yx=(Nd=class extends Xr{constructor(){super();I(this,Dn);I(this,en);I(this,br);L(this,br,t=>{if(!nr&&window.addEventListener){const n=()=>t();return window.addEventListener("visibilitychange",n,!1),()=>{window.removeEventListener("visibilitychange",n)}}})}onSubscribe(){y(this,en)||this.setEventListener(y(this,br))}onUnsubscribe(){var t;this.hasListeners()||((t=y(this,en))==null||t.call(this),L(this,en,void 0))}setEventListener(t){var n;L(this,br,t),(n=y(this,en))==null||n.call(this),L(this,en,t(r=>{typeof r=="boolean"?this.setFocused(r):this.onFocus()}))}setFocused(t){y(this,Dn)!==t&&(L(this,Dn,t),this.onFocus())}onFocus(){const t=this.isFocused();this.listeners.forEach(n=>{n(t)})}isFocused(){var t;return typeof y(this,Dn)=="boolean"?y(this,Dn):((t=globalThis.document)==null?void 0:t.visibilityState)!=="hidden"}},Dn=new WeakMap,en=new WeakMap,br=new WeakMap,Nd),Pu=new yx;function po(){let e,t;const n=new Promise((s,a)=>{e=s,t=a});n.status="pending",n.catch(()=>{});function r(s){Object.assign(n,s),delete n.resolve,delete n.reject}return n.resolve=s=>{r({status:"fulfilled",value:s}),e(s)},n.reject=s=>{r({status:"rejected",reason:s}),t(s)},n}var gx=dx;function jx(){let e=[],t=0,n=o=>{o()},r=o=>{o()},s=gx;const a=o=>{t?e.push(o):s(()=>{n(o)})},l=()=>{const o=e;e=[],o.length&&s(()=>{r(()=>{o.forEach(u=>{n(u)})})})};return{batch:o=>{let u;t++;try{u=o()}finally{t--,t||l()}return u},batchCalls:o=>(...u)=>{a(()=>{o(...u)})},schedule:a,setNotifyFunction:o=>{n=o},setBatchNotifyFunction:o=>{r=o},setScheduler:o=>{s=o}}}var ve=jx(),Er,tn,Pr,_d,wx=(_d=class extends Xr{constructor(){super();I(this,Er,!0);I(this,tn);I(this,Pr);L(this,Pr,t=>{if(!nr&&window.addEventListener){const n=()=>t(!0),r=()=>t(!1);return window.addEventListener("online",n,!1),window.addEventListener("offline",r,!1),()=>{window.removeEventListener("online",n),window.removeEventListener("offline",r)}}})}onSubscribe(){y(this,tn)||this.setEventListener(y(this,Pr))}onUnsubscribe(){var t;this.hasListeners()||((t=y(this,tn))==null||t.call(this),L(this,tn,void 0))}setEventListener(t){var n;L(this,Pr,t),(n=y(this,tn))==null||n.call(this),L(this,tn,t(this.setOnline.bind(this)))}setOnline(t){y(this,Er)!==t&&(L(this,Er,t),this.listeners.forEach(r=>{r(t)}))}isOnline(){return y(this,Er)}},Er=new WeakMap,tn=new WeakMap,Pr=new WeakMap,_d),fi=new wx;function kx(e){return Math.min(1e3*2**e,3e4)}function Xh(e){return(e??"online")==="online"?fi.isOnline():!0}var mo=class extends Error{constructor(e){super("CancelledError"),this.revert=e==null?void 0:e.revert,this.silent=e==null?void 0:e.silent}};function Yh(e){let t=!1,n=0,r;const s=po(),a=()=>s.status!=="pending",l=j=>{var S;if(!a()){const v=new mo(j);h(v),(S=e.onCancel)==null||S.call(e,v)}},o=()=>{t=!0},u=()=>{t=!1},c=()=>Pu.isFocused()&&(e.networkMode==="always"||fi.isOnline())&&e.canRun(),p=()=>Xh(e.networkMode)&&e.canRun(),f=j=>{a()||(r==null||r(),s.resolve(j))},h=j=>{a()||(r==null||r(),s.reject(j))},x=()=>new Promise(j=>{var S;r=v=>{(a()||c())&&j(v)},(S=e.onPause)==null||S.call(e)}).then(()=>{var j;r=void 0,a()||(j=e.onContinue)==null||j.call(e)}),w=()=>{if(a())return;let j;const S=n===0?e.initialPromise:void 0;try{j=S??e.fn()}catch(v){j=Promise.reject(v)}Promise.resolve(j).then(f).catch(v=>{var N;if(a())return;const d=e.retry??(nr?0:3),m=e.retryDelay??kx,g=typeof m=="function"?m(n,v):m,_=d===!0||typeof d=="number"&&nc()?void 0:x()).then(()=>{t?h(v):w()})})};return{promise:s,status:()=>s.status,cancel:l,continue:()=>(r==null||r(),s),cancelRetry:o,continueRetry:u,canStart:p,start:()=>(p()?w():x().then(w),s)}}var An,Cd,Zh=(Cd=class{constructor(){I(this,An)}destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),co(this.gcTime)&&L(this,An,In.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(nr?1/0:5*60*1e3))}clearGcTimeout(){y(this,An)&&(In.clearTimeout(y(this,An)),L(this,An,void 0))}},An=new WeakMap,Cd),$n,Tr,Ye,Un,ge,Gs,Bn,ct,Tt,bd,Sx=(bd=class extends Zh{constructor(t){super();I(this,ct);I(this,$n);I(this,Tr);I(this,Ye);I(this,Un);I(this,ge);I(this,Gs);I(this,Bn);L(this,Bn,!1),L(this,Gs,t.defaultOptions),this.setOptions(t.options),this.observers=[],L(this,Un,t.client),L(this,Ye,y(this,Un).getQueryCache()),this.queryKey=t.queryKey,this.queryHash=t.queryHash,L(this,$n,ed(this.options)),this.state=t.state??y(this,$n),this.scheduleGc()}get meta(){return this.options.meta}get promise(){var t;return(t=y(this,ge))==null?void 0:t.promise}setOptions(t){if(this.options={...y(this,Gs),...t},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){const n=ed(this.options);n.data!==void 0&&(this.setState(Zc(n.data,n.dataUpdatedAt)),L(this,$n,n))}}optionalRemove(){!this.observers.length&&this.state.fetchStatus==="idle"&&y(this,Ye).remove(this)}setData(t,n){const r=ho(this.state.data,t,this.options);return Q(this,ct,Tt).call(this,{data:r,type:"success",dataUpdatedAt:n==null?void 0:n.updatedAt,manual:n==null?void 0:n.manual}),r}setState(t,n){Q(this,ct,Tt).call(this,{type:"setState",state:t,setStateOptions:n})}cancel(t){var r,s;const n=(r=y(this,ge))==null?void 0:r.promise;return(s=y(this,ge))==null||s.cancel(t),n?n.then(Me).catch(Me):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}reset(){this.destroy(),this.setState(y(this,$n))}isActive(){return this.observers.some(t=>Ze(t.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===bu||this.state.dataUpdateCount+this.state.errorUpdateCount===0}isStatic(){return this.getObserversCount()>0?this.observers.some(t=>kn(t.options.staleTime,this)==="static"):!1}isStale(){return this.getObserversCount()>0?this.observers.some(t=>t.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(t=0){return this.state.data===void 0?!0:t==="static"?!1:this.state.isInvalidated?!0:!qh(this.state.dataUpdatedAt,t)}onFocus(){var n;const t=this.observers.find(r=>r.shouldFetchOnWindowFocus());t==null||t.refetch({cancelRefetch:!1}),(n=y(this,ge))==null||n.continue()}onOnline(){var n;const t=this.observers.find(r=>r.shouldFetchOnReconnect());t==null||t.refetch({cancelRefetch:!1}),(n=y(this,ge))==null||n.continue()}addObserver(t){this.observers.includes(t)||(this.observers.push(t),this.clearGcTimeout(),y(this,Ye).notify({type:"observerAdded",query:this,observer:t}))}removeObserver(t){this.observers.includes(t)&&(this.observers=this.observers.filter(n=>n!==t),this.observers.length||(y(this,ge)&&(y(this,Bn)?y(this,ge).cancel({revert:!0}):y(this,ge).cancelRetry()),this.scheduleGc()),y(this,Ye).notify({type:"observerRemoved",query:this,observer:t}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||Q(this,ct,Tt).call(this,{type:"invalidate"})}async fetch(t,n){var u,c,p,f,h,x,w,j,S,v,d,m;if(this.state.fetchStatus!=="idle"&&((u=y(this,ge))==null?void 0:u.status())!=="rejected"){if(this.state.data!==void 0&&(n!=null&&n.cancelRefetch))this.cancel({silent:!0});else if(y(this,ge))return y(this,ge).continueRetry(),y(this,ge).promise}if(t&&this.setOptions(t),!this.options.queryFn){const g=this.observers.find(_=>_.options.queryFn);g&&this.setOptions(g.options)}const r=new AbortController,s=g=>{Object.defineProperty(g,"signal",{enumerable:!0,get:()=>(L(this,Bn,!0),r.signal)})},a=()=>{const g=Jh(this.options,n),N=(()=>{const C={client:y(this,Un),queryKey:this.queryKey,meta:this.meta};return s(C),C})();return L(this,Bn,!1),this.options.persister?this.options.persister(g,N,this):g(N)},o=(()=>{const g={fetchOptions:n,options:this.options,queryKey:this.queryKey,client:y(this,Un),state:this.state,fetchFn:a};return s(g),g})();(c=this.options.behavior)==null||c.onFetch(o,this),L(this,Tr,this.state),(this.state.fetchStatus==="idle"||this.state.fetchMeta!==((p=o.fetchOptions)==null?void 0:p.meta))&&Q(this,ct,Tt).call(this,{type:"fetch",meta:(f=o.fetchOptions)==null?void 0:f.meta}),L(this,ge,Yh({initialPromise:n==null?void 0:n.initialPromise,fn:o.fetchFn,onCancel:g=>{g instanceof mo&&g.revert&&this.setState({...y(this,Tr),fetchStatus:"idle"}),r.abort()},onFail:(g,_)=>{Q(this,ct,Tt).call(this,{type:"failed",failureCount:g,error:_})},onPause:()=>{Q(this,ct,Tt).call(this,{type:"pause"})},onContinue:()=>{Q(this,ct,Tt).call(this,{type:"continue"})},retry:o.options.retry,retryDelay:o.options.retryDelay,networkMode:o.options.networkMode,canRun:()=>!0}));try{const g=await y(this,ge).start();if(g===void 0)throw new Error(`${this.queryHash} data is undefined`);return this.setData(g),(x=(h=y(this,Ye).config).onSuccess)==null||x.call(h,g,this),(j=(w=y(this,Ye).config).onSettled)==null||j.call(w,g,this.state.error,this),g}catch(g){if(g instanceof mo){if(g.silent)return y(this,ge).promise;if(g.revert){if(this.state.data===void 0)throw g;return this.state.data}}throw Q(this,ct,Tt).call(this,{type:"error",error:g}),(v=(S=y(this,Ye).config).onError)==null||v.call(S,g,this),(m=(d=y(this,Ye).config).onSettled)==null||m.call(d,this.state.data,g,this),g}finally{this.scheduleGc()}}},$n=new WeakMap,Tr=new WeakMap,Ye=new WeakMap,Un=new WeakMap,ge=new WeakMap,Gs=new WeakMap,Bn=new WeakMap,ct=new WeakSet,Tt=function(t){const n=r=>{switch(t.type){case"failed":return{...r,fetchFailureCount:t.failureCount,fetchFailureReason:t.error};case"pause":return{...r,fetchStatus:"paused"};case"continue":return{...r,fetchStatus:"fetching"};case"fetch":return{...r,...ep(r.data,this.options),fetchMeta:t.meta??null};case"success":const s={...r,...Zc(t.data,t.dataUpdatedAt),dataUpdateCount:r.dataUpdateCount+1,...!t.manual&&{fetchStatus:"idle",fetchFailureCount:0,fetchFailureReason:null}};return L(this,Tr,t.manual?s:void 0),s;case"error":const a=t.error;return{...r,error:a,errorUpdateCount:r.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:r.fetchFailureCount+1,fetchFailureReason:a,fetchStatus:"idle",status:"error",isInvalidated:!0};case"invalidate":return{...r,isInvalidated:!0};case"setState":return{...r,...t.state}}};this.state=n(this.state),ve.batch(()=>{this.observers.forEach(r=>{r.onQueryUpdate()}),y(this,Ye).notify({query:this,type:"updated",action:t})})},bd);function ep(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:Xh(t.networkMode)?"fetching":"paused",...e===void 0&&{error:null,status:"pending"}}}function Zc(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:"success"}}function ed(e){const t=typeof e.initialData=="function"?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt=="function"?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?"success":"pending",fetchStatus:"idle"}}var De,q,Js,Re,Qn,Or,Lt,nn,Xs,Rr,Lr,Vn,Hn,rn,Mr,Y,hs,vo,xo,yo,go,jo,wo,ko,tp,Ed,Nx=(Ed=class extends Xr{constructor(t,n){super();I(this,Y);I(this,De);I(this,q);I(this,Js);I(this,Re);I(this,Qn);I(this,Or);I(this,Lt);I(this,nn);I(this,Xs);I(this,Rr);I(this,Lr);I(this,Vn);I(this,Hn);I(this,rn);I(this,Mr,new Set);this.options=n,L(this,De,t),L(this,nn,null),L(this,Lt,po()),this.bindMethods(),this.setOptions(n)}bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(y(this,q).addObserver(this),td(y(this,q),this.options)?Q(this,Y,hs).call(this):this.updateResult(),Q(this,Y,go).call(this))}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return So(y(this,q),this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return So(y(this,q),this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,Q(this,Y,jo).call(this),Q(this,Y,wo).call(this),y(this,q).removeObserver(this)}setOptions(t){const n=this.options,r=y(this,q);if(this.options=y(this,De).defaultQueryOptions(t),this.options.enabled!==void 0&&typeof this.options.enabled!="boolean"&&typeof this.options.enabled!="function"&&typeof Ze(this.options.enabled,y(this,q))!="boolean")throw new Error("Expected enabled to be a boolean or a callback that returns a boolean");Q(this,Y,ko).call(this),y(this,q).setOptions(this.options),n._defaulted&&!di(this.options,n)&&y(this,De).getQueryCache().notify({type:"observerOptionsUpdated",query:y(this,q),observer:this});const s=this.hasListeners();s&&nd(y(this,q),r,this.options,n)&&Q(this,Y,hs).call(this),this.updateResult(),s&&(y(this,q)!==r||Ze(this.options.enabled,y(this,q))!==Ze(n.enabled,y(this,q))||kn(this.options.staleTime,y(this,q))!==kn(n.staleTime,y(this,q)))&&Q(this,Y,vo).call(this);const a=Q(this,Y,xo).call(this);s&&(y(this,q)!==r||Ze(this.options.enabled,y(this,q))!==Ze(n.enabled,y(this,q))||a!==y(this,rn))&&Q(this,Y,yo).call(this,a)}getOptimisticResult(t){const n=y(this,De).getQueryCache().build(y(this,De),t),r=this.createResult(n,t);return Cx(this,r)&&(L(this,Re,r),L(this,Or,this.options),L(this,Qn,y(this,q).state)),r}getCurrentResult(){return y(this,Re)}trackResult(t,n){return new Proxy(t,{get:(r,s)=>(this.trackProp(s),n==null||n(s),s==="promise"&&(this.trackProp("data"),!this.options.experimental_prefetchInRender&&y(this,Lt).status==="pending"&&y(this,Lt).reject(new Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(r,s))})}trackProp(t){y(this,Mr).add(t)}getCurrentQuery(){return y(this,q)}refetch({...t}={}){return this.fetch({...t})}fetchOptimistic(t){const n=y(this,De).defaultQueryOptions(t),r=y(this,De).getQueryCache().build(y(this,De),n);return r.fetch().then(()=>this.createResult(r,n))}fetch(t){return Q(this,Y,hs).call(this,{...t,cancelRefetch:t.cancelRefetch??!0}).then(()=>(this.updateResult(),y(this,Re)))}createResult(t,n){var z;const r=y(this,q),s=this.options,a=y(this,Re),l=y(this,Qn),o=y(this,Or),c=t!==r?t.state:y(this,Js),{state:p}=t;let f={...p},h=!1,x;if(n._optimisticResults){const T=this.hasListeners(),V=!T&&td(t,n),F=T&&nd(t,r,n,s);(V||F)&&(f={...f,...ep(p.data,t.options)}),n._optimisticResults==="isRestoring"&&(f.fetchStatus="idle")}let{error:w,errorUpdatedAt:j,status:S}=f;x=f.data;let v=!1;if(n.placeholderData!==void 0&&x===void 0&&S==="pending"){let T;a!=null&&a.isPlaceholderData&&n.placeholderData===(o==null?void 0:o.placeholderData)?(T=a.data,v=!0):T=typeof n.placeholderData=="function"?n.placeholderData((z=y(this,Lr))==null?void 0:z.state.data,y(this,Lr)):n.placeholderData,T!==void 0&&(S="success",x=ho(a==null?void 0:a.data,T,n),h=!0)}if(n.select&&x!==void 0&&!v)if(a&&x===(l==null?void 0:l.data)&&n.select===y(this,Xs))x=y(this,Rr);else try{L(this,Xs,n.select),x=n.select(x),x=ho(a==null?void 0:a.data,x,n),L(this,Rr,x),L(this,nn,null)}catch(T){L(this,nn,T)}y(this,nn)&&(w=y(this,nn),x=y(this,Rr),j=Date.now(),S="error");const d=f.fetchStatus==="fetching",m=S==="pending",g=S==="error",_=m&&d,N=x!==void 0,b={status:S,fetchStatus:f.fetchStatus,isPending:m,isSuccess:S==="success",isError:g,isInitialLoading:_,isLoading:_,data:x,dataUpdatedAt:f.dataUpdatedAt,error:w,errorUpdatedAt:j,failureCount:f.fetchFailureCount,failureReason:f.fetchFailureReason,errorUpdateCount:f.errorUpdateCount,isFetched:f.dataUpdateCount>0||f.errorUpdateCount>0,isFetchedAfterMount:f.dataUpdateCount>c.dataUpdateCount||f.errorUpdateCount>c.errorUpdateCount,isFetching:d,isRefetching:d&&!m,isLoadingError:g&&!N,isPaused:f.fetchStatus==="paused",isPlaceholderData:h,isRefetchError:g&&N,isStale:Tu(t,n),refetch:this.refetch,promise:y(this,Lt),isEnabled:Ze(n.enabled,t)!==!1};if(this.options.experimental_prefetchInRender){const T=b.data!==void 0,V=b.status==="error"&&!T,F=ke=>{V?ke.reject(b.error):T&&ke.resolve(b.data)},U=()=>{const ke=L(this,Lt,b.promise=po());F(ke)},te=y(this,Lt);switch(te.status){case"pending":t.queryHash===r.queryHash&&F(te);break;case"fulfilled":(V||b.data!==te.value)&&U();break;case"rejected":(!V||b.error!==te.reason)&&U();break}}return b}updateResult(){const t=y(this,Re),n=this.createResult(y(this,q),this.options);if(L(this,Qn,y(this,q).state),L(this,Or,this.options),y(this,Qn).data!==void 0&&L(this,Lr,y(this,q)),di(n,t))return;L(this,Re,n);const r=()=>{if(!t)return!0;const{notifyOnChangeProps:s}=this.options,a=typeof s=="function"?s():s;if(a==="all"||!a&&!y(this,Mr).size)return!0;const l=new Set(a??y(this,Mr));return this.options.throwOnError&&l.add("error"),Object.keys(y(this,Re)).some(o=>{const u=o;return y(this,Re)[u]!==t[u]&&l.has(u)})};Q(this,Y,tp).call(this,{listeners:r()})}onQueryUpdate(){this.updateResult(),this.hasListeners()&&Q(this,Y,go).call(this)}},De=new WeakMap,q=new WeakMap,Js=new WeakMap,Re=new WeakMap,Qn=new WeakMap,Or=new WeakMap,Lt=new WeakMap,nn=new WeakMap,Xs=new WeakMap,Rr=new WeakMap,Lr=new WeakMap,Vn=new WeakMap,Hn=new WeakMap,rn=new WeakMap,Mr=new WeakMap,Y=new WeakSet,hs=function(t){Q(this,Y,ko).call(this);let n=y(this,q).fetch(this.options,t);return t!=null&&t.throwOnError||(n=n.catch(Me)),n},vo=function(){Q(this,Y,jo).call(this);const t=kn(this.options.staleTime,y(this,q));if(nr||y(this,Re).isStale||!co(t))return;const r=qh(y(this,Re).dataUpdatedAt,t)+1;L(this,Vn,In.setTimeout(()=>{y(this,Re).isStale||this.updateResult()},r))},xo=function(){return(typeof this.options.refetchInterval=="function"?this.options.refetchInterval(y(this,q)):this.options.refetchInterval)??!1},yo=function(t){Q(this,Y,wo).call(this),L(this,rn,t),!(nr||Ze(this.options.enabled,y(this,q))===!1||!co(y(this,rn))||y(this,rn)===0)&&L(this,Hn,In.setInterval(()=>{(this.options.refetchIntervalInBackground||Pu.isFocused())&&Q(this,Y,hs).call(this)},y(this,rn)))},go=function(){Q(this,Y,vo).call(this),Q(this,Y,yo).call(this,Q(this,Y,xo).call(this))},jo=function(){y(this,Vn)&&(In.clearTimeout(y(this,Vn)),L(this,Vn,void 0))},wo=function(){y(this,Hn)&&(In.clearInterval(y(this,Hn)),L(this,Hn,void 0))},ko=function(){const t=y(this,De).getQueryCache().build(y(this,De),this.options);if(t===y(this,q))return;const n=y(this,q);L(this,q,t),L(this,Js,t.state),this.hasListeners()&&(n==null||n.removeObserver(this),t.addObserver(this))},tp=function(t){ve.batch(()=>{t.listeners&&this.listeners.forEach(n=>{n(y(this,Re))}),y(this,De).getQueryCache().notify({query:y(this,q),type:"observerResultsUpdated"})})},Ed);function _x(e,t){return Ze(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status==="error"&&t.retryOnMount===!1)}function td(e,t){return _x(e,t)||e.state.data!==void 0&&So(e,t,t.refetchOnMount)}function So(e,t,n){if(Ze(t.enabled,e)!==!1&&kn(t.staleTime,e)!=="static"){const r=typeof n=="function"?n(e):n;return r==="always"||r!==!1&&Tu(e,t)}return!1}function nd(e,t,n,r){return(e!==t||Ze(r.enabled,e)===!1)&&(!n.suspense||e.state.status!=="error")&&Tu(e,n)}function Tu(e,t){return Ze(t.enabled,e)!==!1&&e.isStaleByTime(kn(t.staleTime,e))}function Cx(e,t){return!di(e.getCurrentResult(),t)}function rd(e){return{onFetch:(t,n)=>{var p,f,h,x,w;const r=t.options,s=(h=(f=(p=t.fetchOptions)==null?void 0:p.meta)==null?void 0:f.fetchMore)==null?void 0:h.direction,a=((x=t.state.data)==null?void 0:x.pages)||[],l=((w=t.state.data)==null?void 0:w.pageParams)||[];let o={pages:[],pageParams:[]},u=0;const c=async()=>{let j=!1;const S=m=>{xx(m,()=>t.signal,()=>j=!0)},v=Jh(t.options,t.fetchOptions),d=async(m,g,_)=>{if(j)return Promise.reject();if(g==null&&m.pages.length)return Promise.resolve(m);const C=(()=>{const V={client:t.client,queryKey:t.queryKey,pageParam:g,direction:_?"backward":"forward",meta:t.options.meta};return S(V),V})(),b=await v(C),{maxPages:z}=t.options,T=_?vx:mx;return{pages:T(m.pages,b,z),pageParams:T(m.pageParams,g,z)}};if(s&&a.length){const m=s==="backward",g=m?bx:sd,_={pages:a,pageParams:l},N=g(r,_);o=await d(_,N,m)}else{const m=e??a.length;do{const g=u===0?l[0]??r.initialPageParam:sd(r,o);if(u>0&&g==null)break;o=await d(o,g),u++}while(u{var j,S;return(S=(j=t.options).persister)==null?void 0:S.call(j,c,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n)}:t.fetchFn=c}}}function sd(e,{pages:t,pageParams:n}){const r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function bx(e,{pages:t,pageParams:n}){var r;return t.length>0?(r=e.getPreviousPageParam)==null?void 0:r.call(e,t[0],t,n[0],n):void 0}var Ys,wt,Ee,Kn,kt,qt,Pd,Ex=(Pd=class extends Zh{constructor(t){super();I(this,kt);I(this,Ys);I(this,wt);I(this,Ee);I(this,Kn);L(this,Ys,t.client),this.mutationId=t.mutationId,L(this,Ee,t.mutationCache),L(this,wt,[]),this.state=t.state||np(),this.setOptions(t.options),this.scheduleGc()}setOptions(t){this.options=t,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(t){y(this,wt).includes(t)||(y(this,wt).push(t),this.clearGcTimeout(),y(this,Ee).notify({type:"observerAdded",mutation:this,observer:t}))}removeObserver(t){L(this,wt,y(this,wt).filter(n=>n!==t)),this.scheduleGc(),y(this,Ee).notify({type:"observerRemoved",mutation:this,observer:t})}optionalRemove(){y(this,wt).length||(this.state.status==="pending"?this.scheduleGc():y(this,Ee).remove(this))}continue(){var t;return((t=y(this,Kn))==null?void 0:t.continue())??this.execute(this.state.variables)}async execute(t){var l,o,u,c,p,f,h,x,w,j,S,v,d,m,g,_,N,C;const n=()=>{Q(this,kt,qt).call(this,{type:"continue"})},r={client:y(this,Ys),meta:this.options.meta,mutationKey:this.options.mutationKey};L(this,Kn,Yh({fn:()=>this.options.mutationFn?this.options.mutationFn(t,r):Promise.reject(new Error("No mutationFn found")),onFail:(b,z)=>{Q(this,kt,qt).call(this,{type:"failed",failureCount:b,error:z})},onPause:()=>{Q(this,kt,qt).call(this,{type:"pause"})},onContinue:n,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>y(this,Ee).canRun(this)}));const s=this.state.status==="pending",a=!y(this,Kn).canStart();try{if(s)n();else{Q(this,kt,qt).call(this,{type:"pending",variables:t,isPaused:a}),y(this,Ee).config.onMutate&&await y(this,Ee).config.onMutate(t,this,r);const z=await((o=(l=this.options).onMutate)==null?void 0:o.call(l,t,r));z!==this.state.context&&Q(this,kt,qt).call(this,{type:"pending",context:z,variables:t,isPaused:a})}const b=await y(this,Kn).start();return await((c=(u=y(this,Ee).config).onSuccess)==null?void 0:c.call(u,b,t,this.state.context,this,r)),await((f=(p=this.options).onSuccess)==null?void 0:f.call(p,b,t,this.state.context,r)),await((x=(h=y(this,Ee).config).onSettled)==null?void 0:x.call(h,b,null,this.state.variables,this.state.context,this,r)),await((j=(w=this.options).onSettled)==null?void 0:j.call(w,b,null,t,this.state.context,r)),Q(this,kt,qt).call(this,{type:"success",data:b}),b}catch(b){try{await((v=(S=y(this,Ee).config).onError)==null?void 0:v.call(S,b,t,this.state.context,this,r))}catch(z){Promise.reject(z)}try{await((m=(d=this.options).onError)==null?void 0:m.call(d,b,t,this.state.context,r))}catch(z){Promise.reject(z)}try{await((_=(g=y(this,Ee).config).onSettled)==null?void 0:_.call(g,void 0,b,this.state.variables,this.state.context,this,r))}catch(z){Promise.reject(z)}try{await((C=(N=this.options).onSettled)==null?void 0:C.call(N,void 0,b,t,this.state.context,r))}catch(z){Promise.reject(z)}throw Q(this,kt,qt).call(this,{type:"error",error:b}),b}finally{y(this,Ee).runNext(this)}}},Ys=new WeakMap,wt=new WeakMap,Ee=new WeakMap,Kn=new WeakMap,kt=new WeakSet,qt=function(t){const n=r=>{switch(t.type){case"failed":return{...r,failureCount:t.failureCount,failureReason:t.error};case"pause":return{...r,isPaused:!0};case"continue":return{...r,isPaused:!1};case"pending":return{...r,context:t.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:t.isPaused,status:"pending",variables:t.variables,submittedAt:Date.now()};case"success":return{...r,data:t.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...r,data:void 0,error:t.error,failureCount:r.failureCount+1,failureReason:t.error,isPaused:!1,status:"error"}}};this.state=n(this.state),ve.batch(()=>{y(this,wt).forEach(r=>{r.onMutationUpdate(t)}),y(this,Ee).notify({mutation:this,type:"updated",action:t})})},Pd);function np(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}var Mt,dt,Zs,Td,Px=(Td=class extends Xr{constructor(t={}){super();I(this,Mt);I(this,dt);I(this,Zs);this.config=t,L(this,Mt,new Set),L(this,dt,new Map),L(this,Zs,0)}build(t,n,r){const s=new Ex({client:t,mutationCache:this,mutationId:++la(this,Zs)._,options:t.defaultMutationOptions(n),state:r});return this.add(s),s}add(t){y(this,Mt).add(t);const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n);r?r.push(t):y(this,dt).set(n,[t])}this.notify({type:"added",mutation:t})}remove(t){if(y(this,Mt).delete(t)){const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n);if(r)if(r.length>1){const s=r.indexOf(t);s!==-1&&r.splice(s,1)}else r[0]===t&&y(this,dt).delete(n)}}this.notify({type:"removed",mutation:t})}canRun(t){const n=_a(t);if(typeof n=="string"){const r=y(this,dt).get(n),s=r==null?void 0:r.find(a=>a.state.status==="pending");return!s||s===t}else return!0}runNext(t){var r;const n=_a(t);if(typeof n=="string"){const s=(r=y(this,dt).get(n))==null?void 0:r.find(a=>a!==t&&a.state.isPaused);return(s==null?void 0:s.continue())??Promise.resolve()}else return Promise.resolve()}clear(){ve.batch(()=>{y(this,Mt).forEach(t=>{this.notify({type:"removed",mutation:t})}),y(this,Mt).clear(),y(this,dt).clear()})}getAll(){return Array.from(y(this,Mt))}find(t){const n={exact:!0,...t};return this.getAll().find(r=>Jc(n,r))}findAll(t={}){return this.getAll().filter(n=>Jc(t,n))}notify(t){ve.batch(()=>{this.listeners.forEach(n=>{n(t)})})}resumePausedMutations(){const t=this.getAll().filter(n=>n.state.isPaused);return ve.batch(()=>Promise.all(t.map(n=>n.continue().catch(Me))))}},Mt=new WeakMap,dt=new WeakMap,Zs=new WeakMap,Td);function _a(e){var t;return(t=e.options.scope)==null?void 0:t.id}var zt,sn,Ae,Ft,$t,$a,No,Od,Tx=(Od=class extends Xr{constructor(n,r){super();I(this,$t);I(this,zt);I(this,sn);I(this,Ae);I(this,Ft);L(this,zt,n),this.setOptions(r),this.bindMethods(),Q(this,$t,$a).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(n){var s;const r=this.options;this.options=y(this,zt).defaultMutationOptions(n),di(this.options,r)||y(this,zt).getMutationCache().notify({type:"observerOptionsUpdated",mutation:y(this,Ae),observer:this}),r!=null&&r.mutationKey&&this.options.mutationKey&&rr(r.mutationKey)!==rr(this.options.mutationKey)?this.reset():((s=y(this,Ae))==null?void 0:s.state.status)==="pending"&&y(this,Ae).setOptions(this.options)}onUnsubscribe(){var n;this.hasListeners()||(n=y(this,Ae))==null||n.removeObserver(this)}onMutationUpdate(n){Q(this,$t,$a).call(this),Q(this,$t,No).call(this,n)}getCurrentResult(){return y(this,sn)}reset(){var n;(n=y(this,Ae))==null||n.removeObserver(this),L(this,Ae,void 0),Q(this,$t,$a).call(this),Q(this,$t,No).call(this)}mutate(n,r){var s;return L(this,Ft,r),(s=y(this,Ae))==null||s.removeObserver(this),L(this,Ae,y(this,zt).getMutationCache().build(y(this,zt),this.options)),y(this,Ae).addObserver(this),y(this,Ae).execute(n)}},zt=new WeakMap,sn=new WeakMap,Ae=new WeakMap,Ft=new WeakMap,$t=new WeakSet,$a=function(){var r;const n=((r=y(this,Ae))==null?void 0:r.state)??np();L(this,sn,{...n,isPending:n.status==="pending",isSuccess:n.status==="success",isError:n.status==="error",isIdle:n.status==="idle",mutate:this.mutate,reset:this.reset})},No=function(n){ve.batch(()=>{var r,s,a,l,o,u,c,p;if(y(this,Ft)&&this.hasListeners()){const f=y(this,sn).variables,h=y(this,sn).context,x={client:y(this,zt),meta:this.options.meta,mutationKey:this.options.mutationKey};if((n==null?void 0:n.type)==="success"){try{(s=(r=y(this,Ft)).onSuccess)==null||s.call(r,n.data,f,h,x)}catch(w){Promise.reject(w)}try{(l=(a=y(this,Ft)).onSettled)==null||l.call(a,n.data,null,f,h,x)}catch(w){Promise.reject(w)}}else if((n==null?void 0:n.type)==="error"){try{(u=(o=y(this,Ft)).onError)==null||u.call(o,n.error,f,h,x)}catch(w){Promise.reject(w)}try{(p=(c=y(this,Ft)).onSettled)==null||p.call(c,void 0,n.error,f,h,x)}catch(w){Promise.reject(w)}}}this.listeners.forEach(f=>{f(y(this,sn))})})},Od),St,Rd,Ox=(Rd=class extends Xr{constructor(t={}){super();I(this,St);this.config=t,L(this,St,new Map)}build(t,n,r){const s=n.queryKey,a=n.queryHash??Cu(s,n);let l=this.get(a);return l||(l=new Sx({client:t,queryKey:s,queryHash:a,options:t.defaultQueryOptions(n),state:r,defaultOptions:t.getQueryDefaults(s)}),this.add(l)),l}add(t){y(this,St).has(t.queryHash)||(y(this,St).set(t.queryHash,t),this.notify({type:"added",query:t}))}remove(t){const n=y(this,St).get(t.queryHash);n&&(t.destroy(),n===t&&y(this,St).delete(t.queryHash),this.notify({type:"removed",query:t}))}clear(){ve.batch(()=>{this.getAll().forEach(t=>{this.remove(t)})})}get(t){return y(this,St).get(t)}getAll(){return[...y(this,St).values()]}find(t){const n={exact:!0,...t};return this.getAll().find(r=>Gc(n,r))}findAll(t={}){const n=this.getAll();return Object.keys(t).length>0?n.filter(r=>Gc(t,r)):n}notify(t){ve.batch(()=>{this.listeners.forEach(n=>{n(t)})})}onFocus(){ve.batch(()=>{this.getAll().forEach(t=>{t.onFocus()})})}onOnline(){ve.batch(()=>{this.getAll().forEach(t=>{t.onOnline()})})}},St=new WeakMap,Rd),ue,an,ln,zr,Fr,on,Ir,Dr,Ld,Rx=(Ld=class{constructor(e={}){I(this,ue);I(this,an);I(this,ln);I(this,zr);I(this,Fr);I(this,on);I(this,Ir);I(this,Dr);L(this,ue,e.queryCache||new Ox),L(this,an,e.mutationCache||new Px),L(this,ln,e.defaultOptions||{}),L(this,zr,new Map),L(this,Fr,new Map),L(this,on,0)}mount(){la(this,on)._++,y(this,on)===1&&(L(this,Ir,Pu.subscribe(async e=>{e&&(await this.resumePausedMutations(),y(this,ue).onFocus())})),L(this,Dr,fi.subscribe(async e=>{e&&(await this.resumePausedMutations(),y(this,ue).onOnline())})))}unmount(){var e,t;la(this,on)._--,y(this,on)===0&&((e=y(this,Ir))==null||e.call(this),L(this,Ir,void 0),(t=y(this,Dr))==null||t.call(this),L(this,Dr,void 0))}isFetching(e){return y(this,ue).findAll({...e,fetchStatus:"fetching"}).length}isMutating(e){return y(this,an).findAll({...e,status:"pending"}).length}getQueryData(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=y(this,ue).get(t.queryHash))==null?void 0:n.state.data}ensureQueryData(e){const t=this.defaultQueryOptions(e),n=y(this,ue).build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(kn(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return y(this,ue).findAll(e).map(({queryKey:t,state:n})=>{const r=n.data;return[t,r]})}setQueryData(e,t,n){const r=this.defaultQueryOptions({queryKey:e}),s=y(this,ue).get(r.queryHash),a=s==null?void 0:s.state.data,l=fx(t,a);if(l!==void 0)return y(this,ue).build(this,r).setData(l,{...n,manual:!0})}setQueriesData(e,t,n){return ve.batch(()=>y(this,ue).findAll(e).map(({queryKey:r})=>[r,this.setQueryData(r,t,n)]))}getQueryState(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=y(this,ue).get(t.queryHash))==null?void 0:n.state}removeQueries(e){const t=y(this,ue);ve.batch(()=>{t.findAll(e).forEach(n=>{t.remove(n)})})}resetQueries(e,t){const n=y(this,ue);return ve.batch(()=>(n.findAll(e).forEach(r=>{r.reset()}),this.refetchQueries({type:"active",...e},t)))}cancelQueries(e,t={}){const n={revert:!0,...t},r=ve.batch(()=>y(this,ue).findAll(e).map(s=>s.cancel(n)));return Promise.all(r).then(Me).catch(Me)}invalidateQueries(e,t={}){return ve.batch(()=>(y(this,ue).findAll(e).forEach(n=>{n.invalidate()}),(e==null?void 0:e.refetchType)==="none"?Promise.resolve():this.refetchQueries({...e,type:(e==null?void 0:e.refetchType)??(e==null?void 0:e.type)??"active"},t)))}refetchQueries(e,t={}){const n={...t,cancelRefetch:t.cancelRefetch??!0},r=ve.batch(()=>y(this,ue).findAll(e).filter(s=>!s.isDisabled()&&!s.isStatic()).map(s=>{let a=s.fetch(void 0,n);return n.throwOnError||(a=a.catch(Me)),s.state.fetchStatus==="paused"?Promise.resolve():a}));return Promise.all(r).then(Me)}fetchQuery(e){const t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);const n=y(this,ue).build(this,t);return n.isStaleByTime(kn(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(Me).catch(Me)}fetchInfiniteQuery(e){return e.behavior=rd(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(Me).catch(Me)}ensureInfiniteQueryData(e){return e.behavior=rd(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return fi.isOnline()?y(this,an).resumePausedMutations():Promise.resolve()}getQueryCache(){return y(this,ue)}getMutationCache(){return y(this,an)}getDefaultOptions(){return y(this,ln)}setDefaultOptions(e){L(this,ln,e)}setQueryDefaults(e,t){y(this,zr).set(rr(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){const t=[...y(this,zr).values()],n={};return t.forEach(r=>{Bs(e,r.queryKey)&&Object.assign(n,r.defaultOptions)}),n}setMutationDefaults(e,t){y(this,Fr).set(rr(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){const t=[...y(this,Fr).values()],n={};return t.forEach(r=>{Bs(e,r.mutationKey)&&Object.assign(n,r.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;const t={...y(this,ln).queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=Cu(t.queryKey,t)),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!=="always"),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode="offlineFirst"),t.queryFn===bu&&(t.enabled=!1),t}defaultMutationOptions(e){return e!=null&&e._defaulted?e:{...y(this,ln).mutations,...(e==null?void 0:e.mutationKey)&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){y(this,ue).clear(),y(this,an).clear()}},ue=new WeakMap,an=new WeakMap,ln=new WeakMap,zr=new WeakMap,Fr=new WeakMap,on=new WeakMap,Ir=new WeakMap,Dr=new WeakMap,Ld),rp=k.createContext(void 0),Pn=e=>{const t=k.useContext(rp);if(!t)throw new Error("No QueryClient set, use QueryClientProvider to set one");return t},Lx=({client:e,children:t})=>(k.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),i.jsx(rp.Provider,{value:e,children:t})),sp=k.createContext(!1),Mx=()=>k.useContext(sp);sp.Provider;function zx(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var Fx=k.createContext(zx()),Ix=()=>k.useContext(Fx),Dx=(e,t,n)=>{const r=n!=null&&n.state.error&&typeof e.throwOnError=="function"?Eu(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},Ax=e=>{k.useEffect(()=>{e.clearReset()},[e])},$x=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:s})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(s&&e.data===void 0||Eu(n,[e.error,r])),Ux=e=>{if(e.suspense){const n=s=>s==="static"?s:Math.max(s??1e3,1e3),r=e.staleTime;e.staleTime=typeof r=="function"?(...s)=>n(r(...s)):n(r),typeof e.gcTime=="number"&&(e.gcTime=Math.max(e.gcTime,1e3))}},Bx=(e,t)=>e.isLoading&&e.isFetching&&!t,Qx=(e,t)=>(e==null?void 0:e.suspense)&&t.isPending,ad=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function Vx(e,t,n){var h,x,w,j;const r=Mx(),s=Ix(),a=Pn(),l=a.defaultQueryOptions(e);(x=(h=a.getDefaultOptions().queries)==null?void 0:h._experimental_beforeQuery)==null||x.call(h,l);const o=a.getQueryCache().get(l.queryHash);l._optimisticResults=r?"isRestoring":"optimistic",Ux(l),Dx(l,s,o),Ax(s);const u=!a.getQueryCache().get(l.queryHash),[c]=k.useState(()=>new t(a,l)),p=c.getOptimisticResult(l),f=!r&&e.subscribed!==!1;if(k.useSyncExternalStore(k.useCallback(S=>{const v=f?c.subscribe(ve.batchCalls(S)):Me;return c.updateResult(),v},[c,f]),()=>c.getCurrentResult(),()=>c.getCurrentResult()),k.useEffect(()=>{c.setOptions(l)},[l,c]),Qx(l,p))throw ad(l,c,s);if($x({result:p,errorResetBoundary:s,throwOnError:l.throwOnError,query:o,suspense:l.suspense}))throw p.error;if((j=(w=a.getDefaultOptions().queries)==null?void 0:w._experimental_afterQuery)==null||j.call(w,l,p),l.experimental_prefetchInRender&&!nr&&Bx(p,r)){const S=u?ad(l,c,s):o==null?void 0:o.promise;S==null||S.catch(Me).finally(()=>{c.updateResult()})}return l.notifyOnChangeProps?p:c.trackResult(p)}function Te(e,t){return Vx(e,Nx)}function st(e,t){const n=Pn(),[r]=k.useState(()=>new Tx(n,e));k.useEffect(()=>{r.setOptions(e)},[r,e]);const s=k.useSyncExternalStore(k.useCallback(l=>r.subscribe(ve.batchCalls(l)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),a=k.useCallback((l,o)=>{r.mutate(l,o).catch(Me)},[r]);if(s.error&&Eu(r.options.throwOnError,[s.error]))throw s.error;return{...s,mutate:a,mutateAsync:s.mutate}}/** + * @remix-run/router v1.23.2 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function Qs(){return Qs=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function Ou(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function Kx(){return Math.random().toString(36).substr(2,8)}function ld(e,t){return{usr:e.state,key:e.key,idx:t}}function _o(e,t,n,r){return n===void 0&&(n=null),Qs({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?Yr(t):t,{state:n,key:t&&t.key||r||Kx()})}function hi(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function Yr(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Wx(e,t,n,r){r===void 0&&(r={});let{window:s=document.defaultView,v5Compat:a=!1}=r,l=s.history,o=dn.Pop,u=null,c=p();c==null&&(c=0,l.replaceState(Qs({},l.state,{idx:c}),""));function p(){return(l.state||{idx:null}).idx}function f(){o=dn.Pop;let S=p(),v=S==null?null:S-c;c=S,u&&u({action:o,location:j.location,delta:v})}function h(S,v){o=dn.Push;let d=_o(j.location,S,v);c=p()+1;let m=ld(d,c),g=j.createHref(d);try{l.pushState(m,"",g)}catch(_){if(_ instanceof DOMException&&_.name==="DataCloneError")throw _;s.location.assign(g)}a&&u&&u({action:o,location:j.location,delta:1})}function x(S,v){o=dn.Replace;let d=_o(j.location,S,v);c=p();let m=ld(d,c),g=j.createHref(d);l.replaceState(m,"",g),a&&u&&u({action:o,location:j.location,delta:0})}function w(S){let v=s.location.origin!=="null"?s.location.origin:s.location.href,d=typeof S=="string"?S:hi(S);return d=d.replace(/ $/,"%20"),de(v,"No window.location.(origin|href) available to create URL for href: "+d),new URL(d,v)}let j={get action(){return o},get location(){return e(s,l)},listen(S){if(u)throw new Error("A history only accepts one active listener");return s.addEventListener(id,f),u=S,()=>{s.removeEventListener(id,f),u=null}},createHref(S){return t(s,S)},createURL:w,encodeLocation(S){let v=w(S);return{pathname:v.pathname,search:v.search,hash:v.hash}},push:h,replace:x,go(S){return l.go(S)}};return j}var od;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(od||(od={}));function qx(e,t,n){return n===void 0&&(n="/"),Gx(e,t,n)}function Gx(e,t,n,r){let s=typeof t=="string"?Yr(t):t,a=Kr(s.pathname||"/",n);if(a==null)return null;let l=ap(e);Jx(l);let o=null;for(let u=0;o==null&&u{let u={relativePath:o===void 0?a.path||"":o,caseSensitive:a.caseSensitive===!0,childrenIndex:l,route:a};u.relativePath.startsWith("/")&&(de(u.relativePath.startsWith(r),'Absolute route path "'+u.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),u.relativePath=u.relativePath.slice(r.length));let c=Sn([r,u.relativePath]),p=n.concat(u);a.children&&a.children.length>0&&(de(a.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+c+'".')),ap(a.children,t,p,c)),!(a.path==null&&!a.index)&&t.push({path:c,score:ry(c,a.index),routesMeta:p})};return e.forEach((a,l)=>{var o;if(a.path===""||!((o=a.path)!=null&&o.includes("?")))s(a,l);else for(let u of ip(a.path))s(a,l,u)}),t}function ip(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,s=n.endsWith("?"),a=n.replace(/\?$/,"");if(r.length===0)return s?[a,""]:[a];let l=ip(r.join("/")),o=[];return o.push(...l.map(u=>u===""?a:[a,u].join("/"))),s&&o.push(...l),o.map(u=>e.startsWith("/")&&u===""?"/":u)}function Jx(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:sy(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Xx=/^:[\w-]+$/,Yx=3,Zx=2,ey=1,ty=10,ny=-2,ud=e=>e==="*";function ry(e,t){let n=e.split("/"),r=n.length;return n.some(ud)&&(r+=ny),t&&(r+=Zx),n.filter(s=>!ud(s)).reduce((s,a)=>s+(Xx.test(a)?Yx:a===""?ey:ty),r)}function sy(e,t){return e.length===t.length&&e.slice(0,-1).every((r,s)=>r===t[s])?e[e.length-1]-t[t.length-1]:0}function ay(e,t,n){let{routesMeta:r}=e,s={},a="/",l=[];for(let o=0;o{let{paramName:h,isOptional:x}=p;if(h==="*"){let j=o[f]||"";l=a.slice(0,a.length-j.length).replace(/(.)\/+$/,"$1")}const w=o[f];return x&&!w?c[h]=void 0:c[h]=(w||"").replace(/%2F/g,"/"),c},{}),pathname:a,pathnameBase:l,pattern:e}}function iy(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),Ou(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],s="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(l,o,u)=>(r.push({paramName:o,isOptional:u!=null}),u?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),s+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?s+="\\/*$":e!==""&&e!=="/"&&(s+="(?:(?=\\/|$))"),[new RegExp(s,t?void 0:"i"),r]}function ly(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return Ou(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function Kr(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const oy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,uy=e=>oy.test(e);function cy(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:s=""}=typeof e=="string"?Yr(e):e,a;if(n)if(uy(n))a=n;else{if(n.includes("//")){let l=n;n=n.replace(/\/\/+/g,"/"),Ou(!1,"Pathnames cannot have embedded double slashes - normalizing "+(l+" -> "+n))}n.startsWith("/")?a=cd(n.substring(1),"/"):a=cd(n,t)}else a=t;return{pathname:a,search:hy(r),hash:py(s)}}function cd(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(s=>{s===".."?n.length>1&&n.pop():s!=="."&&n.push(s)}),n.length>1?n.join("/"):"/"}function hl(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function dy(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function lp(e,t){let n=dy(e);return t?n.map((r,s)=>s===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function op(e,t,n,r){r===void 0&&(r=!1);let s;typeof e=="string"?s=Yr(e):(s=Qs({},e),de(!s.pathname||!s.pathname.includes("?"),hl("?","pathname","search",s)),de(!s.pathname||!s.pathname.includes("#"),hl("#","pathname","hash",s)),de(!s.search||!s.search.includes("#"),hl("#","search","hash",s)));let a=e===""||s.pathname==="",l=a?"/":s.pathname,o;if(l==null)o=n;else{let f=t.length-1;if(!r&&l.startsWith("..")){let h=l.split("/");for(;h[0]==="..";)h.shift(),f-=1;s.pathname=h.join("/")}o=f>=0?t[f]:"/"}let u=cy(s,o),c=l&&l!=="/"&&l.endsWith("/"),p=(a||l===".")&&n.endsWith("/");return!u.pathname.endsWith("/")&&(c||p)&&(u.pathname+="/"),u}const Sn=e=>e.join("/").replace(/\/\/+/g,"/"),fy=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),hy=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,py=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function my(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const up=["post","put","patch","delete"];new Set(up);const vy=["get",...up];new Set(vy);/** + * React Router v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function Vs(){return Vs=Object.assign?Object.assign.bind():function(e){for(var t=1;t{o.current=!0}),k.useCallback(function(c,p){if(p===void 0&&(p={}),!o.current)return;if(typeof c=="number"){r.go(c);return}let f=op(c,JSON.parse(l),a,p.relative==="path");e==null&&t!=="/"&&(f.pathname=f.pathname==="/"?t:Sn([t,f.pathname])),(p.replace?r.replace:r.push)(f,p.state,p)},[t,r,l,a,e])}const gy=k.createContext(null);function jy(e){let t=k.useContext(Kt).outlet;return t&&k.createElement(gy.Provider,{value:e},t)}function Li(){let{matches:e}=k.useContext(Kt),t=e[e.length-1];return t?t.params:{}}function Mi(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=k.useContext(Tn),{matches:s}=k.useContext(Kt),{pathname:a}=Zr(),l=JSON.stringify(lp(s,r.v7_relativeSplatPath));return k.useMemo(()=>op(e,JSON.parse(l),a,n==="path"),[e,l,a,n])}function wy(e,t){return ky(e,t)}function ky(e,t,n,r){aa()||de(!1);let{navigator:s}=k.useContext(Tn),{matches:a}=k.useContext(Kt),l=a[a.length-1],o=l?l.params:{};l&&l.pathname;let u=l?l.pathnameBase:"/";l&&l.route;let c=Zr(),p;if(t){var f;let S=typeof t=="string"?Yr(t):t;u==="/"||(f=S.pathname)!=null&&f.startsWith(u)||de(!1),p=S}else p=c;let h=p.pathname||"/",x=h;if(u!=="/"){let S=u.replace(/^\//,"").split("/");x="/"+h.replace(/^\//,"").split("/").slice(S.length).join("/")}let w=qx(e,{pathname:x}),j=by(w&&w.map(S=>Object.assign({},S,{params:Object.assign({},o,S.params),pathname:Sn([u,s.encodeLocation?s.encodeLocation(S.pathname).pathname:S.pathname]),pathnameBase:S.pathnameBase==="/"?u:Sn([u,s.encodeLocation?s.encodeLocation(S.pathnameBase).pathname:S.pathnameBase])})),a,n,r);return t&&j?k.createElement(Ri.Provider,{value:{location:Vs({pathname:"/",search:"",hash:"",state:null,key:"default"},p),navigationType:dn.Pop}},j):j}function Sy(){let e=Oy(),t=my(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,s={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return k.createElement(k.Fragment,null,k.createElement("h2",null,"Unexpected Application Error!"),k.createElement("h3",{style:{fontStyle:"italic"}},t),n?k.createElement("pre",{style:s},n):null,null)}const Ny=k.createElement(Sy,null);class _y extends k.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?k.createElement(Kt.Provider,{value:this.props.routeContext},k.createElement(dp.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function Cy(e){let{routeContext:t,match:n,children:r}=e,s=k.useContext(Oi);return s&&s.static&&s.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(s.staticContext._deepestRenderedBoundaryId=n.route.id),k.createElement(Kt.Provider,{value:t},r)}function by(e,t,n,r){var s;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var a;if(!n)return null;if(n.errors)e=n.matches;else if((a=r)!=null&&a.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let l=e,o=(s=n)==null?void 0:s.errors;if(o!=null){let p=l.findIndex(f=>f.route.id&&(o==null?void 0:o[f.route.id])!==void 0);p>=0||de(!1),l=l.slice(0,Math.min(l.length,p+1))}let u=!1,c=-1;if(n&&r&&r.v7_partialHydration)for(let p=0;p=0?l=l.slice(0,c+1):l=[l[0]];break}}}return l.reduceRight((p,f,h)=>{let x,w=!1,j=null,S=null;n&&(x=o&&f.route.id?o[f.route.id]:void 0,j=f.route.errorElement||Ny,u&&(c<0&&h===0?(Ly("route-fallback"),w=!0,S=null):c===h&&(w=!0,S=f.route.hydrateFallbackElement||null)));let v=t.concat(l.slice(0,h+1)),d=()=>{let m;return x?m=j:w?m=S:f.route.Component?m=k.createElement(f.route.Component,null):f.route.element?m=f.route.element:m=p,k.createElement(Cy,{match:f,routeContext:{outlet:p,matches:v,isDataRoute:n!=null},children:m})};return n&&(f.route.ErrorBoundary||f.route.errorElement||h===0)?k.createElement(_y,{location:n.location,revalidation:n.revalidation,component:j,error:x,children:d(),routeContext:{outlet:null,matches:v,isDataRoute:!0}}):d()},null)}var hp=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(hp||{}),pp=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(pp||{});function Ey(e){let t=k.useContext(Oi);return t||de(!1),t}function Py(e){let t=k.useContext(cp);return t||de(!1),t}function Ty(e){let t=k.useContext(Kt);return t||de(!1),t}function mp(e){let t=Ty(),n=t.matches[t.matches.length-1];return n.route.id||de(!1),n.route.id}function Oy(){var e;let t=k.useContext(dp),n=Py(),r=mp();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function Ry(){let{router:e}=Ey(hp.UseNavigateStable),t=mp(pp.UseNavigateStable),n=k.useRef(!1);return fp(()=>{n.current=!0}),k.useCallback(function(s,a){a===void 0&&(a={}),n.current&&(typeof s=="number"?e.navigate(s):e.navigate(s,Vs({fromRouteId:t},a)))},[e,t])}const dd={};function Ly(e,t,n){dd[e]||(dd[e]=!0)}function My(e,t){e==null||e.v7_startTransition,e==null||e.v7_relativeSplatPath}function zy(e){return jy(e.context)}function gt(e){de(!1)}function Fy(e){let{basename:t="/",children:n=null,location:r,navigationType:s=dn.Pop,navigator:a,static:l=!1,future:o}=e;aa()&&de(!1);let u=t.replace(/^\/*/,"/"),c=k.useMemo(()=>({basename:u,navigator:a,static:l,future:Vs({v7_relativeSplatPath:!1},o)}),[u,o,a,l]);typeof r=="string"&&(r=Yr(r));let{pathname:p="/",search:f="",hash:h="",state:x=null,key:w="default"}=r,j=k.useMemo(()=>{let S=Kr(p,u);return S==null?null:{location:{pathname:S,search:f,hash:h,state:x,key:w},navigationType:s}},[u,p,f,h,x,w,s]);return j==null?null:k.createElement(Tn.Provider,{value:c},k.createElement(Ri.Provider,{children:n,value:j}))}function Iy(e){let{children:t,location:n}=e;return wy(bo(t),n)}new Promise(()=>{});function bo(e,t){t===void 0&&(t=[]);let n=[];return k.Children.forEach(e,(r,s)=>{if(!k.isValidElement(r))return;let a=[...t,s];if(r.type===k.Fragment){n.push.apply(n,bo(r.props.children,a));return}r.type!==gt&&de(!1),!r.props.index||!r.props.children||de(!1);let l={id:r.props.id||a.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,loader:r.props.loader,action:r.props.action,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(l.children=bo(r.props.children,a)),n.push(l)}),n}/** + * React Router DOM v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function pi(){return pi=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[s]=e[s]);return n}function Dy(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Ay(e,t){return e.button===0&&(!t||t==="_self")&&!Dy(e)}const $y=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],Uy=["aria-current","caseSensitive","className","end","style","to","viewTransition","children"],By="6";try{window.__reactRouterVersion=By}catch{}const Qy=k.createContext({isTransitioning:!1}),Vy="startTransition",fd=Xp[Vy];function Hy(e){let{basename:t,children:n,future:r,window:s}=e,a=k.useRef();a.current==null&&(a.current=Hx({window:s,v5Compat:!0}));let l=a.current,[o,u]=k.useState({action:l.action,location:l.location}),{v7_startTransition:c}=r||{},p=k.useCallback(f=>{c&&fd?fd(()=>u(f)):u(f)},[u,c]);return k.useLayoutEffect(()=>l.listen(p),[l,p]),k.useEffect(()=>My(r),[r]),k.createElement(Fy,{basename:t,children:n,location:o.location,navigationType:o.action,navigator:l,future:r})}const Ky=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",Wy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Hs=k.forwardRef(function(t,n){let{onClick:r,relative:s,reloadDocument:a,replace:l,state:o,target:u,to:c,preventScrollReset:p,viewTransition:f}=t,h=vp(t,$y),{basename:x}=k.useContext(Tn),w,j=!1;if(typeof c=="string"&&Wy.test(c)&&(w=c,Ky))try{let m=new URL(window.location.href),g=c.startsWith("//")?new URL(m.protocol+c):new URL(c),_=Kr(g.pathname,x);g.origin===m.origin&&_!=null?c=_+g.search+g.hash:j=!0}catch{}let S=xy(c,{relative:s}),v=Gy(c,{replace:l,state:o,target:u,preventScrollReset:p,relative:s,viewTransition:f});function d(m){r&&r(m),m.defaultPrevented||v(m)}return k.createElement("a",pi({},h,{href:w||S,onClick:j||a?r:d,ref:n,target:u}))}),hd=k.forwardRef(function(t,n){let{"aria-current":r="page",caseSensitive:s=!1,className:a="",end:l=!1,style:o,to:u,viewTransition:c,children:p}=t,f=vp(t,Uy),h=Mi(u,{relative:f.relative}),x=Zr(),w=k.useContext(cp),{navigator:j,basename:S}=k.useContext(Tn),v=w!=null&&Jy(h)&&c===!0,d=j.encodeLocation?j.encodeLocation(h).pathname:h.pathname,m=x.pathname,g=w&&w.navigation&&w.navigation.location?w.navigation.location.pathname:null;s||(m=m.toLowerCase(),g=g?g.toLowerCase():null,d=d.toLowerCase()),g&&S&&(g=Kr(g,S)||g);const _=d!=="/"&&d.endsWith("/")?d.length-1:d.length;let N=m===d||!l&&m.startsWith(d)&&m.charAt(_)==="/",C=g!=null&&(g===d||!l&&g.startsWith(d)&&g.charAt(d.length)==="/"),b={isActive:N,isPending:C,isTransitioning:v},z=N?r:void 0,T;typeof a=="function"?T=a(b):T=[a,N?"active":null,C?"pending":null,v?"transitioning":null].filter(Boolean).join(" ");let V=typeof o=="function"?o(b):o;return k.createElement(Hs,pi({},f,{"aria-current":z,className:T,ref:n,style:V,to:u,viewTransition:c}),typeof p=="function"?p(b):p)});var Eo;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(Eo||(Eo={}));var pd;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(pd||(pd={}));function qy(e){let t=k.useContext(Oi);return t||de(!1),t}function Gy(e,t){let{target:n,replace:r,state:s,preventScrollReset:a,relative:l,viewTransition:o}=t===void 0?{}:t,u=Et(),c=Zr(),p=Mi(e,{relative:l});return k.useCallback(f=>{if(Ay(f,n)){f.preventDefault();let h=r!==void 0?r:hi(c)===hi(p);u(e,{replace:h,state:s,preventScrollReset:a,relative:l,viewTransition:o})}},[c,u,p,r,s,n,e,a,l,o])}function Jy(e,t){t===void 0&&(t={});let n=k.useContext(Qy);n==null&&de(!1);let{basename:r}=qy(Eo.useViewTransitionState),s=Mi(e,{relative:t.relative});if(!n.isTransitioning)return!1;let a=Kr(n.currentLocation.pathname,r)||n.currentLocation.pathname,l=Kr(n.nextLocation.pathname,r)||n.nextLocation.pathname;return Co(s.pathname,l)!=null||Co(s.pathname,a)!=null}/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */var Xy={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Yy=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().trim(),J=(e,t)=>{const n=k.forwardRef(({color:r="currentColor",size:s=24,strokeWidth:a=2,absoluteStrokeWidth:l,className:o="",children:u,...c},p)=>k.createElement("svg",{ref:p,...Xy,width:s,height:s,stroke:r,strokeWidth:l?Number(a)*24/Number(s):a,className:["lucide",`lucide-${Yy(e)}`,o].join(" "),...c},[...t.map(([f,h])=>k.createElement(f,h)),...Array.isArray(u)?u:[u]]));return n.displayName=`${e}`,n};/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Zy=J("AlertCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const eg=J("ArrowUpDown",[["path",{d:"m21 16-4 4-4-4",key:"f6ql7i"}],["path",{d:"M17 20V4",key:"1ejh1v"}],["path",{d:"m3 8 4-4 4 4",key:"11wl7u"}],["path",{d:"M7 4v16",key:"1glfcx"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const tg=J("BarChart3",[["path",{d:"M3 3v18h18",key:"1s2lah"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ng=J("Bot",[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const rg=J("CheckCircle",[["path",{d:"M22 11.08V12a10 10 0 1 1-5.93-9.14",key:"g774vq"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const sg=J("Check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ag=J("ChevronDown",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ig=J("ChevronLeft",[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ss=J("ChevronRight",[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const lg=J("Clock",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16 14",key:"68esgv"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const og=J("Coins",[["circle",{cx:"8",cy:"8",r:"6",key:"3yglwk"}],["path",{d:"M18.09 10.37A6 6 0 1 1 10.34 18",key:"t5s6rm"}],["path",{d:"M7 6h1v4",key:"1obek4"}],["path",{d:"m16.71 13.88.7.71-2.82 2.82",key:"1rbuyh"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ug=J("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const cg=J("ExternalLink",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const dg=J("GitCompare",[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7",key:"1yeb86"}],["path",{d:"M11 18H8a2 2 0 0 1-2-2V9",key:"19pyzm"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const fg=J("Github",[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const zi=J("Globe",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const hg=J("History",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const pg=J("ListTodo",[["rect",{x:"3",y:"5",width:"6",height:"6",rx:"1",key:"1defrl"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"M13 6h8",key:"15sg57"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 18h8",key:"oe0vm4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const mg=J("List",[["line",{x1:"8",x2:"21",y1:"6",y2:"6",key:"7ey8pc"}],["line",{x1:"8",x2:"21",y1:"12",y2:"12",key:"rjfblc"}],["line",{x1:"8",x2:"21",y1:"18",y2:"18",key:"c3b1m8"}],["line",{x1:"3",x2:"3.01",y1:"6",y2:"6",key:"1g7gq3"}],["line",{x1:"3",x2:"3.01",y1:"12",y2:"12",key:"1pjlvk"}],["line",{x1:"3",x2:"3.01",y1:"18",y2:"18",key:"28t2mc"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Fi=J("Loader2",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const xp=J("Lock",[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const vg=J("LogOut",[["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}],["polyline",{points:"16 17 21 12 16 7",key:"1gabdz"}],["line",{x1:"21",x2:"9",y1:"12",y2:"12",key:"1uyos4"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const xg=J("Moon",[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ks=J("Play",[["polygon",{points:"5 3 19 12 5 21 5 3",key:"191637"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const yp=J("Plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const gp=J("Settings",[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",key:"1qme2f"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const yg=J("Share2",[["circle",{cx:"18",cy:"5",r:"3",key:"gq8acd"}],["circle",{cx:"6",cy:"12",r:"3",key:"w7nqdw"}],["circle",{cx:"18",cy:"19",r:"3",key:"1xt0gg"}],["line",{x1:"8.59",x2:"15.42",y1:"13.51",y2:"17.49",key:"47mynk"}],["line",{x1:"15.41",x2:"8.59",y1:"6.51",y2:"10.49",key:"1n3mei"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const gg=J("Square",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jg=J("Sun",[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jp=J("Trash2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const wp=J("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const wg=J("XCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]);/** + * @license lucide-react v0.330.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ii=J("Zap",[["polygon",{points:"13 2 3 14 12 14 11 22 21 10 12 10 13 2",key:"45s27k"}]]),kg={},md=e=>{let t;const n=new Set,r=(p,f)=>{const h=typeof p=="function"?p(t):p;if(!Object.is(h,t)){const x=t;t=f??(typeof h!="object"||h===null)?h:Object.assign({},t,h),n.forEach(w=>w(t,x))}},s=()=>t,u={setState:r,getState:s,getInitialState:()=>c,subscribe:p=>(n.add(p),()=>n.delete(p)),destroy:()=>{(kg?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),n.clear()}},c=t=e(r,s,u);return u},Sg=e=>e?md(e):md;var kp={exports:{}},Sp={},Np={exports:{}},_p={};/** + * @license React + * use-sync-external-store-shim.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Wr=k;function Ng(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var _g=typeof Object.is=="function"?Object.is:Ng,Cg=Wr.useState,bg=Wr.useEffect,Eg=Wr.useLayoutEffect,Pg=Wr.useDebugValue;function Tg(e,t){var n=t(),r=Cg({inst:{value:n,getSnapshot:t}}),s=r[0].inst,a=r[1];return Eg(function(){s.value=n,s.getSnapshot=t,pl(s)&&a({inst:s})},[e,n,t]),bg(function(){return pl(s)&&a({inst:s}),e(function(){pl(s)&&a({inst:s})})},[e]),Pg(n),n}function pl(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!_g(e,n)}catch{return!0}}function Og(e,t){return t()}var Rg=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?Og:Tg;_p.useSyncExternalStore=Wr.useSyncExternalStore!==void 0?Wr.useSyncExternalStore:Rg;Np.exports=_p;var Lg=Np.exports;/** + * @license React + * use-sync-external-store-shim/with-selector.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Di=k,Mg=Lg;function zg(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var Fg=typeof Object.is=="function"?Object.is:zg,Ig=Mg.useSyncExternalStore,Dg=Di.useRef,Ag=Di.useEffect,$g=Di.useMemo,Ug=Di.useDebugValue;Sp.useSyncExternalStoreWithSelector=function(e,t,n,r,s){var a=Dg(null);if(a.current===null){var l={hasValue:!1,value:null};a.current=l}else l=a.current;a=$g(function(){function u(x){if(!c){if(c=!0,p=x,x=r(x),s!==void 0&&l.hasValue){var w=l.value;if(s(w,x))return f=w}return f=x}if(w=f,Fg(p,x))return w;var j=r(x);return s!==void 0&&s(w,j)?(p=x,w):(p=x,f=j)}var c=!1,p,f,h=n===void 0?null:n;return[function(){return u(t())},h===null?void 0:function(){return u(h())}]},[t,n,r,s]);var o=Ig(e,a[0],a[1]);return Ag(function(){l.hasValue=!0,l.value=o},[o]),Ug(o),o};kp.exports=Sp;var Bg=kp.exports;const Qg=Md(Bg),Cp={},{useDebugValue:Vg}=zo,{useSyncExternalStoreWithSelector:Hg}=Qg;let vd=!1;const Kg=e=>e;function Wg(e,t=Kg,n){(Cp?"production":void 0)!=="production"&&n&&!vd&&(console.warn("[DEPRECATED] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`. They can be imported from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937"),vd=!0);const r=Hg(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return Vg(r),r}const xd=e=>{(Cp?"production":void 0)!=="production"&&typeof e!="function"&&console.warn("[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.");const t=typeof e=="function"?Sg(e):e,n=(r,s)=>Wg(t,r,s);return Object.assign(n,t),n},Ru=e=>e?xd(e):xd,Ai="/api",Lu="flow_auth_token",Mu="flow_auth_expires",zu="flow_auth_username";function Ws(){const e=localStorage.getItem(Lu),t=localStorage.getItem(Mu);return!e||!t?null:new Date(t)<=new Date?(Gn(),null):e}function yd(e,t,n){localStorage.setItem(Lu,e),localStorage.setItem(Mu,t),localStorage.setItem(zu,n)}function Gn(){localStorage.removeItem(Lu),localStorage.removeItem(Mu),localStorage.removeItem(zu)}function Fu(){return localStorage.getItem(zu)}let sr=null;function qg(e){sr=e}async function X(e,t,n=!1){const r={"Content-Type":"application/json",...t==null?void 0:t.headers};if(!n){const a=Ws();a&&(r.Authorization=`Bearer ${a}`)}const s=await fetch(`${Ai}${e}`,{...t,headers:r});if(s.status===401){Gn(),sr&&sr();const a=await s.json().catch(()=>({detail:"Not authenticated"}));throw new Error(a.detail||"Not authenticated")}if(!s.ok){const a=await s.json().catch(()=>({detail:s.statusText}));throw new Error(a.detail||"API request failed")}if(s.status!==204)return s.json()}const or={getConfig:()=>X("/auth/config",void 0,!0),login:e=>X("/auth/login",{method:"POST",body:JSON.stringify(e)},!0),getGitHubAuthUrl:()=>`${Ai}/auth/github`,getCurrentUser:()=>X("/auth/me"),logout:()=>X("/auth/logout",{method:"POST"})},Jn={list:e=>{const t=new URLSearchParams;e!=null&&e.include_auto_generated&&t.set("include_auto_generated","true"),e!=null&&e.include_public&&t.set("include_public","true");const n=t.toString();return X(`/configs${n?`?${n}`:""}`)},get:e=>X(`/configs/${e}`),create:e=>X("/configs",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>X(`/configs/${e}`,{method:"PUT",body:JSON.stringify(t)}),delete:e=>X(`/configs/${e}`,{method:"DELETE"}),generateCandidates:e=>X("/configs/generate-candidates",{method:"POST",body:JSON.stringify(e)})},fn={list:e=>{const t=new URLSearchParams;e!=null&&e.category&&t.set("category",e.category),e!=null&&e.suite&&t.set("suite",e.suite);const n=t.toString();return X(`/tasks${n?`?${n}`:""}`)},get:e=>X(`/tasks/${e}`),create:e=>X("/tasks",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>X(`/tasks/${e}`,{method:"PUT",body:JSON.stringify(t)}),delete:e=>X(`/tasks/${e}`,{method:"DELETE"}),listSuites:()=>X("/tasks/suites"),importSuite:e=>X(`/tasks/import-suite?suite_name=${encodeURIComponent(e)}`,{method:"POST"})},bt={list:e=>{const t=new URLSearchParams;e!=null&&e.status&&t.set("status",e.status),e!=null&&e.include_public&&t.set("include_public","true");const n=t.toString();return X(`/jobs${n?`?${n}`:""}`)},get:e=>X(`/jobs/${e}`),create:e=>X("/jobs",{method:"POST",body:JSON.stringify(e)}),update:(e,t)=>X(`/jobs/${e}`,{method:"PUT",body:JSON.stringify(t)}),start:async function*(e){var o;const t={},n=Ws();n&&(t.Authorization=`Bearer ${n}`);const r=await fetch(`${Ai}/jobs/${e}/start`,{method:"POST",headers:t});if(r.status===401)throw Gn(),sr&&sr(),new Error("Not authenticated");if(!r.ok)throw new Error("Failed to start job");const s=(o=r.body)==null?void 0:o.getReader();if(!s)throw new Error("No response body");const a=new TextDecoder;let l="";for(;;){const{done:u,value:c}=await s.read();if(u)break;l+=a.decode(c,{stream:!0});const p=l.split(` +`);l=p.pop()||"";for(const f of p)f.startsWith("data: ")&&(yield JSON.parse(f.slice(6)))}},cancel:e=>X(`/jobs/${e}/cancel`,{method:"POST"}),delete:e=>X(`/jobs/${e}`,{method:"DELETE"})},Po={list:e=>{const t=new URLSearchParams;e!=null&&e.job_id&&t.set("job_id",e.job_id),e!=null&&e.candidate_name&&t.set("candidate_name",e.candidate_name),e!=null&&e.task_name&&t.set("task_name",e.task_name),(e==null?void 0:e.is_pareto)!==void 0&&t.set("is_pareto",String(e.is_pareto));const n=t.toString();return X(`/runs${n?`?${n}`:""}`)},get:e=>X(`/runs/${e}`),getJobSummary:e=>X(`/runs/job/${e}/summary`)},Ns={list:e=>{const t=new URLSearchParams;e!=null&&e.agent_id&&t.set("agent_id",e.agent_id),e!=null&&e.limit&&t.set("limit",String(e.limit));const n=t.toString();return X(`/tests${n?`?${n}`:""}`)},get:e=>X(`/tests/${e}`),create:e=>X("/tests",{method:"POST",body:JSON.stringify(e)}),start:async function*(e){var o;const t={},n=Ws();n&&(t.Authorization=`Bearer ${n}`);const r=await fetch(`${Ai}/tests/${e}/start`,{method:"POST",headers:t});if(r.status===401)throw Gn(),sr&&sr(),new Error("Not authenticated");if(!r.ok){const u=await r.json().catch(()=>({detail:"Failed to start test"}));throw new Error(u.detail||"Failed to start test")}const s=(o=r.body)==null?void 0:o.getReader();if(!s)throw new Error("No response body");const a=new TextDecoder;let l="";for(;;){const{done:u,value:c}=await s.read();if(u)break;l+=a.decode(c,{stream:!0});const p=l.split(` +`);l=p.pop()||"";for(const f of p)f.startsWith("data: ")&&(yield JSON.parse(f.slice(6)))}},cancel:e=>X(`/tests/${e}/cancel`,{method:"POST"}),delete:e=>X(`/tests/${e}`,{method:"DELETE"})},Iu=Ru((e,t)=>(qg(()=>{e({isAuthenticated:!1,user:null,error:"Session expired. Please log in again."})}),{authConfig:null,isLoadingConfig:!0,isAuthenticated:!1,isLoading:!1,user:null,error:null,loadAuthConfig:async()=>{e({isLoadingConfig:!0});try{const n=await or.getConfig();if(e({authConfig:n,isLoadingConfig:!1}),n.enabled){const r=Ws(),s=Fu();if(r&&s)try{const a=await or.getCurrentUser();e({isAuthenticated:!0,user:a})}catch{Gn(),e({isAuthenticated:!1,user:null})}}else e({isAuthenticated:!0,user:{username:"anonymous",auth_mode:"none"}})}catch(n){console.error("Failed to load auth config:",n),e({isLoadingConfig:!1,error:"Failed to connect to server"})}},login:async(n,r)=>{e({isLoading:!0,error:null});try{const s=await or.login({username:n,password:r});return yd(s.access_token,s.expires_at,s.username),e({isAuthenticated:!0,isLoading:!1,user:{username:s.username,auth_mode:"basic"}}),!0}catch(s){return e({isLoading:!1,error:s instanceof Error?s.message:"Login failed"}),!1}},loginWithGitHub:()=>{window.location.href=or.getGitHubAuthUrl()},handleOAuthCallback:()=>{const n=new URLSearchParams(window.location.search),r=n.get("auth_error");if(r)return e({error:r}),window.history.replaceState({},"",window.location.pathname),!0;if(n.get("auth_callback")==="true"){const s=n.get("token"),a=n.get("expires_at"),l=n.get("username");return s&&a&&l&&(yd(s,a,l),e({isAuthenticated:!0,user:{username:l,auth_mode:"github"}})),window.history.replaceState({},"",window.location.pathname),!0}return!1},logout:async()=>{try{await or.logout()}catch{}Gn(),e({isAuthenticated:!1,user:null,error:null})},checkAuth:async()=>{const{authConfig:n}=t();if(!(n!=null&&n.enabled)){e({isAuthenticated:!0});return}if(!Ws()){e({isAuthenticated:!1,user:null});return}try{const s=await or.getCurrentUser();e({isAuthenticated:!0,user:s})}catch{Gn(),e({isAuthenticated:!1,user:null})}},clearError:()=>e({error:null})}));function H({variant:e="secondary",size:t="md",className:n="",icon:r,iconRight:s,loading:a=!1,children:l,disabled:o,...u}){const c="font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1.5",p={primary:"bg-[var(--accent)] text-black hover:bg-[#16a34a]",secondary:"bg-[var(--bg-tertiary)] text-[var(--text-primary)] border border-[var(--border)] hover:bg-[var(--border)]",danger:"bg-[var(--error)] text-white hover:bg-red-600",ghost:"text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"},f={sm:"px-2 py-1 text-xs",md:"px-3 py-1.5 text-sm"},h=t==="sm"?14:16;return i.jsxs("button",{className:`${c} ${p[e]} ${f[t]} ${n}`,disabled:o||a,...u,children:[a?i.jsx(Fi,{size:h,className:"animate-spin"}):r?i.jsx(r,{size:h}):null,l,s&&!a&&i.jsx(s,{size:h})]})}const Gg={};function Jg(e,t){let n;try{n=e()}catch{return}return{getItem:s=>{var a;const l=u=>u===null?null:JSON.parse(u,void 0),o=(a=n.getItem(s))!=null?a:null;return o instanceof Promise?o.then(l):l(o)},setItem:(s,a)=>n.setItem(s,JSON.stringify(a,void 0)),removeItem:s=>n.removeItem(s)}}const qs=e=>t=>{try{const n=e(t);return n instanceof Promise?n:{then(r){return qs(r)(n)},catch(r){return this}}}catch(n){return{then(r){return this},catch(r){return qs(r)(n)}}}},Xg=(e,t)=>(n,r,s)=>{let a={getStorage:()=>localStorage,serialize:JSON.stringify,deserialize:JSON.parse,partialize:S=>S,version:0,merge:(S,v)=>({...v,...S}),...t},l=!1;const o=new Set,u=new Set;let c;try{c=a.getStorage()}catch{}if(!c)return e((...S)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...S)},r,s);const p=qs(a.serialize),f=()=>{const S=a.partialize({...r()});let v;const d=p({state:S,version:a.version}).then(m=>c.setItem(a.name,m)).catch(m=>{v=m});if(v)throw v;return d},h=s.setState;s.setState=(S,v)=>{h(S,v),f()};const x=e((...S)=>{n(...S),f()},r,s);let w;const j=()=>{var S;if(!c)return;l=!1,o.forEach(d=>d(r()));const v=((S=a.onRehydrateStorage)==null?void 0:S.call(a,r()))||void 0;return qs(c.getItem.bind(c))(a.name).then(d=>{if(d)return a.deserialize(d)}).then(d=>{if(d)if(typeof d.version=="number"&&d.version!==a.version){if(a.migrate)return a.migrate(d.state,d.version);console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return d.state}).then(d=>{var m;return w=a.merge(d,(m=r())!=null?m:x),n(w,!0),f()}).then(()=>{v==null||v(w,void 0),l=!0,u.forEach(d=>d(w))}).catch(d=>{v==null||v(void 0,d)})};return s.persist={setOptions:S=>{a={...a,...S},S.getStorage&&(c=S.getStorage())},clearStorage:()=>{c==null||c.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>j(),hasHydrated:()=>l,onHydrate:S=>(o.add(S),()=>{o.delete(S)}),onFinishHydration:S=>(u.add(S),()=>{u.delete(S)})},j(),w||x},Yg=(e,t)=>(n,r,s)=>{let a={storage:Jg(()=>localStorage),partialize:j=>j,version:0,merge:(j,S)=>({...S,...j}),...t},l=!1;const o=new Set,u=new Set;let c=a.storage;if(!c)return e((...j)=>{console.warn(`[zustand persist middleware] Unable to update item '${a.name}', the given storage is currently unavailable.`),n(...j)},r,s);const p=()=>{const j=a.partialize({...r()});return c.setItem(a.name,{state:j,version:a.version})},f=s.setState;s.setState=(j,S)=>{f(j,S),p()};const h=e((...j)=>{n(...j),p()},r,s);s.getInitialState=()=>h;let x;const w=()=>{var j,S;if(!c)return;l=!1,o.forEach(d=>{var m;return d((m=r())!=null?m:h)});const v=((S=a.onRehydrateStorage)==null?void 0:S.call(a,(j=r())!=null?j:h))||void 0;return qs(c.getItem.bind(c))(a.name).then(d=>{if(d)if(typeof d.version=="number"&&d.version!==a.version){if(a.migrate)return[!0,a.migrate(d.state,d.version)];console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return[!1,d.state];return[!1,void 0]}).then(d=>{var m;const[g,_]=d;if(x=a.merge(_,(m=r())!=null?m:h),n(x,!0),g)return p()}).then(()=>{v==null||v(x,void 0),x=r(),l=!0,u.forEach(d=>d(x))}).catch(d=>{v==null||v(void 0,d)})};return s.persist={setOptions:j=>{a={...a,...j},j.storage&&(c=j.storage)},clearStorage:()=>{c==null||c.removeItem(a.name)},getOptions:()=>a,rehydrate:()=>w(),hasHydrated:()=>l,onHydrate:j=>(o.add(j),()=>{o.delete(j)}),onFinishHydration:j=>(u.add(j),()=>{u.delete(j)})},a.skipHydration||w(),x||h},Zg=(e,t)=>"getStorage"in t||"serialize"in t||"deserialize"in t?((Gg?"production":void 0)!=="production"&&console.warn("[DEPRECATED] `getStorage`, `serialize` and `deserialize` options are deprecated. Use `storage` option instead."),Xg(e,t)):Yg(e,t),e0=Zg,t0=Ru()(e0((e,t)=>({theme:"dark",setTheme:n=>{document.documentElement.setAttribute("data-theme",n),e({theme:n})},toggleTheme:()=>{const n=t().theme==="dark"?"light":"dark";document.documentElement.setAttribute("data-theme",n),e({theme:n})}}),{name:"flow-theme",onRehydrateStorage:()=>e=>{e!=null&&e.theme&&document.documentElement.setAttribute("data-theme",e.theme)}}));function n0(){const{theme:e,toggleTheme:t}=t0();return i.jsx("button",{onClick:t,className:"p-2 rounded-md text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--accent)]","aria-label":`Switch to ${e==="dark"?"light":"dark"} mode`,title:`Switch to ${e==="dark"?"light":"dark"} mode`,children:e==="dark"?i.jsx(jg,{size:16}):i.jsx(xg,{size:16})})}const r0=[{path:"/agents",label:"Agents",icon:ng},{path:"/tasks",label:"Tasks",icon:pg},{path:"/jobs",label:"Jobs",icon:Ks}];function s0(){const e=Zr(),{authConfig:t,user:n,logout:r}=Iu(),s=l=>l==="/agents"?e.pathname==="/"||e.pathname==="/agents":e.pathname.startsWith(l),a=async()=>{await r()};return i.jsxs("div",{className:"min-h-screen flex flex-col",children:[i.jsx("header",{className:"border-b border-[var(--border)] bg-[var(--bg-secondary)]",children:i.jsxs("div",{className:"max-w-7xl mx-auto px-4 py-3 flex items-center justify-between",children:[i.jsxs("div",{className:"flex items-center gap-8",children:[i.jsxs(hd,{to:"/",className:"text-lg font-bold text-[var(--accent)] flex items-center gap-2 hover:opacity-80",children:[i.jsx(Ii,{size:20}),"flow",i.jsx("span",{className:"text-[var(--text-secondary)]",children:"/optimize"})]}),i.jsx("nav",{className:"flex gap-1",children:r0.map(l=>i.jsxs(hd,{to:l.path,className:`px-3 py-1.5 rounded text-sm transition-colors flex items-center gap-2 ${s(l.path)?"bg-[var(--accent)] text-black font-medium":"text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"}`,children:[i.jsx(l.icon,{size:16}),l.label]},l.path))})]}),i.jsxs("div",{className:"flex items-center gap-4",children:[i.jsx(n0,{}),(t==null?void 0:t.enabled)&&n&&i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx(wp,{size:14}),i.jsx("span",{children:n.username})]}),i.jsx(H,{variant:"ghost",size:"sm",icon:vg,onClick:a,title:"Sign out",children:"Sign out"})]})]})]})}),i.jsx("main",{className:"flex-1 bg-[var(--bg-primary)]",children:i.jsx("div",{className:"max-w-7xl mx-auto p-4",children:i.jsx(zy,{})})})]})}const bp=["standard","minimal","full","readonly"],a0=["maf","miniagent","langgraph"],i0={maf:"Microsoft Agent Framework",miniagent:"MiniAgent",langgraph:"LangGraph"},l0=["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory","sub_agent"],o0={full:["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory","sub_agent"],standard:["read_file","write_file","list_directory","grep_search","bash_execute","check_processes","python_repl","think","task_done","memory"],minimal:["read_file","write_file","bash_execute","task_done"],readonly:["read_file","list_directory","grep_search","think","task_done"]};function ee({children:e,className:t="",onClick:n,selected:r=!1,selectable:s=!1}){const a="bg-[var(--bg-secondary)] border border-[var(--border)] p-4",l=s?"cursor-pointer hover:border-[var(--accent-dim)] transition-colors":"",o=r?"border-[var(--accent)]":"";return i.jsx("div",{className:`${a} ${l} ${o} ${t}`,onClick:n,children:e})}function W({children:e,variant:t="default"}){const n={default:"bg-[var(--bg-tertiary)] text-[var(--text-primary)] border border-[var(--border)]",success:"bg-green-600 text-white",warning:"bg-yellow-500 text-black",error:"bg-red-600 text-white",info:"bg-blue-600 text-white"};return i.jsx("span",{className:`inline-block px-2 py-0.5 text-xs font-medium rounded ${n[t]}`,children:e})}const u0={pending:"default",running:"info",completed:"success",failed:"error",cancelled:"warning"};function Ep({job:e,onDelete:t}){const n=Et(),r=e.total_experiments>0?e.completed_experiments/e.total_experiments*100:0;return i.jsxs(ee,{className:"cursor-pointer hover:border-[var(--accent-dim)] flex flex-col",onClick:()=>n(`/jobs/${e.id}`),children:[i.jsxs("div",{className:"flex items-start justify-between mb-3",children:[i.jsxs("div",{className:"flex-1 min-w-0",children:[i.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[i.jsx(W,{variant:u0[e.status]||"default",children:e.status}),e.is_public&&i.jsxs(W,{variant:"info",children:[i.jsx(zi,{className:"w-3 h-3 mr-1 inline"}),"Public"]}),e.pareto_frontier.length>0&&i.jsxs(W,{variant:"success",children:[e.pareto_frontier.length," Pareto"]}),e.use_llm_eval&&i.jsx(W,{children:"LLM"})]}),i.jsx("h3",{className:"font-medium mt-2 truncate",title:e.name||`Job ${e.id.slice(0,8)}`,children:e.name||`Job ${e.id.slice(0,8)}`}),i.jsxs("code",{className:"text-xs text-[var(--text-secondary)] font-mono",children:[e.id.slice(0,8),"..."]})]}),t&&i.jsx(H,{variant:"ghost",size:"sm",onClick:s=>{s.stopPropagation(),confirm("Delete this job?")&&t(e.id)},disabled:e.status==="running",children:"×"})]}),(e.status==="running"||e.status==="completed")&&i.jsxs("div",{className:"mb-3",children:[i.jsxs("div",{className:"flex justify-between text-xs text-[var(--text-secondary)] mb-1",children:[i.jsx("span",{children:"Progress"}),i.jsxs("span",{children:[e.completed_experiments,"/",e.total_experiments]})]}),i.jsx("div",{className:"w-full bg-[var(--bg-primary)] h-1.5 rounded-full overflow-hidden",children:i.jsx("div",{className:`h-full transition-all ${e.status==="completed"?"bg-green-500":"bg-[var(--accent)]"}`,style:{width:`${r}%`}})})]}),e.status==="failed"&&e.error&&i.jsx("div",{className:"mb-3 px-2 py-1.5 bg-red-500/10 border border-red-500/30 rounded text-xs text-red-400 line-clamp-2",children:e.error}),i.jsxs("div",{className:"grid grid-cols-3 gap-2 text-center py-2 border-t border-[var(--border)] mt-auto",children:[i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.candidate_ids.length}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"candidates"})]}),i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.task_ids.length}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"tasks"})]}),i.jsxs("div",{children:[i.jsx("div",{className:"text-lg font-bold",children:e.total_experiments}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"runs"})]})]}),i.jsxs("div",{className:"text-xs text-[var(--text-secondary)] pt-2 border-t border-[var(--border)] flex justify-between items-center",children:[i.jsxs("span",{children:[new Date(e.created_at).toLocaleDateString()," ",new Date(e.created_at).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})]}),e.is_public&&e.created_by_name&&i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["by ",e.created_by_name]})]})]})}function ia({isOpen:e,onClose:t,title:n,children:r}){return k.useEffect(()=>{const s=a=>{a.key==="Escape"&&t()};return e&&(document.addEventListener("keydown",s),document.body.style.overflow="hidden"),()=>{document.removeEventListener("keydown",s),document.body.style.overflow=""}},[e,t]),e?i.jsxs("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[i.jsx("div",{className:"absolute inset-0 bg-black/80",onClick:t}),i.jsxs("div",{className:"relative bg-[var(--bg-secondary)] border border-[var(--border)] max-w-lg w-full mx-4 max-h-[80vh] overflow-y-auto",children:[i.jsxs("div",{className:"sticky top-0 bg-[var(--bg-secondary)] border-b border-[var(--border)] px-4 py-3 flex items-center justify-between",children:[i.jsx("h2",{className:"font-semibold",children:n}),i.jsx("button",{onClick:t,className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"×"})]}),i.jsx("div",{className:"p-4",children:r})]})]}):null}function pt({label:e,className:t="",...n}){return i.jsxs("div",{className:"space-y-1",children:[e&&i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:e}),i.jsx("input",{className:`w-full bg-[var(--bg-primary)] border border-[var(--border)] px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)] ${t}`,...n})]})}function c0({label:e,className:t="",...n}){return i.jsxs("div",{className:"space-y-1",children:[e&&i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:e}),i.jsx("textarea",{className:`w-full bg-[var(--bg-primary)] border border-[var(--border)] px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)] resize-y min-h-[100px] ${t}`,...n})]})}function Ln({label:e,className:t="",...n}){return i.jsxs("label",{className:`flex items-center gap-2 cursor-pointer ${t}`,children:[i.jsx("input",{type:"checkbox",className:"w-4 h-4 bg-[var(--bg-primary)] border border-[var(--border)] accent-[var(--accent)]",...n}),i.jsx("span",{className:"text-sm",children:e})]})}function gd(){const e=Et(),t=Pn(),[n,r]=k.useState(!1),[s,a]=k.useState(null),{data:l=[],isLoading:o}=Te({queryKey:["configs"],queryFn:()=>Jn.list()}),{data:u=[]}=Te({queryKey:["jobs"],queryFn:()=>bt.list()}),c=st({mutationFn:Jn.create,onSuccess:()=>{t.invalidateQueries({queryKey:["configs"]}),r(!1)}}),p=st({mutationFn:Jn.delete,onSuccess:()=>t.invalidateQueries({queryKey:["configs"]})}),f=h=>{const x=u.filter(S=>S.candidate_ids.includes(h)),w=x.filter(S=>S.status==="running").length,j=x.filter(S=>S.status==="completed").length;return{running:w,completed:j,total:x.length}};return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Agents"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:"Define and optimize your agent configurations."})]}),i.jsx(H,{variant:"primary",icon:yp,onClick:()=>r(!0),children:"New Agent"})]}),o?i.jsxs("div",{className:"flex items-center gap-2 text-[var(--text-secondary)]",children:[i.jsx(Fi,{size:16,className:"animate-spin"}),"Loading agents..."]}):l.length===0?i.jsx(d0,{onCreateClick:()=>r(!0)}):i.jsx("div",{className:"grid gap-4 md:grid-cols-2 lg:grid-cols-3",children:l.map(h=>{const x=f(h.id);return i.jsx(h0,{agent:h,stats:x,onClick:()=>e(`/agents/${h.id}`),onOptimize:()=>a(h),onDelete:()=>{confirm(`Delete agent "${h.name}"?`)&&p.mutate(h.id)}},h.id)})}),u.length>0&&i.jsxs("div",{className:"mt-8",children:[i.jsx("h3",{className:"text-lg font-medium mb-4",children:"Recent Optimization Jobs"}),i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4",children:u.slice(0,6).map(h=>i.jsx(Ep,{job:h},h.id))}),u.length>6&&i.jsxs(H,{variant:"ghost",className:"mt-4",onClick:()=>e("/jobs"),children:["View all ",u.length," jobs →"]})]}),i.jsx(p0,{isOpen:n,onClose:()=>r(!1),onSubmit:h=>c.mutate(h),isLoading:c.isPending}),s&&i.jsx(m0,{agent:s,isOpen:!!s,onClose:()=>a(null)})]})}function d0({onCreateClick:e}){return i.jsxs("div",{className:"text-center py-16 border border-dashed border-[var(--border)] rounded-lg",children:[i.jsx("div",{className:"inline-flex items-center justify-center w-12 h-12 rounded-full bg-[var(--bg-tertiary)] mb-4",children:i.jsx(gp,{size:24,className:"text-[var(--text-secondary)]"})}),i.jsx("h3",{className:"text-lg font-medium mb-2",children:"No agents yet"}),i.jsx("p",{className:"text-[var(--text-secondary)] mb-4 max-w-md mx-auto",children:"Create your first agent to start optimizing. Each agent defines instructions, model, compaction strategy, and tool settings."}),i.jsx(H,{variant:"primary",icon:yp,onClick:e,children:"Create Your First Agent"})]})}function f0(e){return typeof e=="string"?`tools: ${e}`:Array.isArray(e)?`tools: [${e.length}]`:typeof e=="object"?`tools: [${Object.keys(e).length}]`:"tools: standard"}function h0({agent:e,stats:t,onClick:n,onOptimize:r,onDelete:s}){const a=e.config.compaction,l=(a==null?void 0:a.strategy)==="head_tail"?`compaction ${a.params.head_size}/${a.params.tail_size}`:(a==null?void 0:a.strategy)==="none"?null:(a==null?void 0:a.strategy)||null,o=f0(e.config.tools),u=e.config.framework||"maf";return i.jsxs(ee,{className:"flex flex-col cursor-pointer hover:border-[var(--accent-dim)] transition-colors",onClick:n,children:[i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-start justify-between mb-3",children:[i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium text-lg",children:e.name}),e.description&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:e.description})]}),i.jsx(H,{variant:"ghost",size:"sm",icon:jp,onClick:c=>{c.stopPropagation(),s()}})]}),i.jsxs("div",{className:"flex flex-wrap gap-1.5 mb-4",children:[i.jsx(W,{variant:u==="miniagent"?"success":"default",children:u}),l&&i.jsx(W,{children:l}),i.jsx(W,{children:o})]}),t.total>0&&i.jsxs("div",{className:"text-xs text-[var(--text-secondary)] mb-3",children:[t.running>0&&i.jsxs("span",{className:"text-[var(--accent)]",children:[t.running," running "]}),t.completed>0&&i.jsxs("span",{children:[t.completed," completed"]})]})]}),i.jsx("div",{className:"flex gap-2",children:i.jsx(H,{variant:"primary",icon:Ii,onClick:c=>{c.stopPropagation(),r()},className:"flex-1",children:"Optimize"})})]})}function p0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){var d,m,g;const s=["read_file","write_file","bash_execute","grep_search","task_done"],[a,l]=k.useState({name:"",description:"",instructions:null,model:null,compaction:{strategy:"none",params:{}},tools:s,framework:"maf"}),[o,u]=k.useState(!1),[c,p]=k.useState("custom"),[f,h]=k.useState([...s]),[x,w]=k.useState(!1),j=_=>{if(_.preventDefault(),!a.name.trim())return;const N={...a};c==="custom"&&(N.tools=f),n(N)},S=((d=a.compaction)==null?void 0:d.strategy)!=="none",v=_=>{h(N=>N.includes(_)?N.filter(C=>C!==_):[...N,_])};return i.jsx(ia,{isOpen:e,onClose:t,title:"Create Agent",children:i.jsxs("form",{onSubmit:j,className:"space-y-4",children:[i.jsx(pt,{label:"Name",value:a.name,onChange:_=>l({...a,name:_.target.value}),placeholder:"e.g., my-coding-agent",required:!0}),i.jsx("div",{className:"p-3 border border-[var(--border)] rounded bg-[var(--bg-secondary)] text-sm",children:i.jsx("p",{className:"text-[var(--text-secondary)]",children:"Uses LLM from environment variables (AZURE_OPENAI_ENDPOINT, OPENAI_API_KEY, etc.)"})}),i.jsxs("div",{children:[i.jsx("label",{className:"text-sm font-medium block mb-1.5",children:"Framework"}),i.jsx("select",{className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",value:a.framework||"maf",onChange:_=>l({...a,framework:_.target.value}),children:a0.map(_=>i.jsx("option",{value:_,children:i0[_]},_))}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-1",children:a.framework==="miniagent"?"Lightweight agent with token-aware context management.":a.framework==="langgraph"?"Graph-based workflows with state management.":"Default agent implementation."})]}),i.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[i.jsx(Ln,{label:"Custom instructions",checked:o,onChange:_=>{u(_.target.checked),_.target.checked||l({...a,instructions:null})}}),i.jsx(Ln,{label:"Enable compaction",checked:S,onChange:_=>l({...a,compaction:_.target.checked?{strategy:"head_tail",params:{head_size:10,tail_size:40}}:{strategy:"none",params:{}}})})]}),o&&i.jsx("textarea",{className:"w-full h-32 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono resize-y",value:a.instructions||"",onChange:_=>l({...a,instructions:_.target.value||null}),placeholder:"System prompt / instructions for the agent..."}),S&&i.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[i.jsx(pt,{label:"Head size",type:"number",value:((m=a.compaction)==null?void 0:m.params.head_size)??10,onChange:_=>l({...a,compaction:{strategy:"head_tail",params:{...a.compaction.params,head_size:parseInt(_.target.value)||10}}}),min:1}),i.jsx(pt,{label:"Tail size",type:"number",value:((g=a.compaction)==null?void 0:g.params.tail_size)??40,onChange:_=>l({...a,compaction:{strategy:"head_tail",params:{...a.compaction.params,tail_size:parseInt(_.target.value)||40}}}),min:1})]}),i.jsxs("div",{className:"space-y-2",children:[i.jsx("label",{className:"text-sm font-medium",children:"Tools"}),i.jsxs("div",{className:"flex gap-4",children:[i.jsxs("label",{className:"flex items-center gap-2",children:[i.jsx("input",{type:"radio",checked:c==="custom",onChange:()=>p("custom"),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:"Custom"})]}),i.jsxs("label",{className:"flex items-center gap-2",children:[i.jsx("input",{type:"radio",checked:c==="preset",onChange:()=>p("preset"),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:"Preset"})]})]}),c==="custom"?i.jsx("div",{className:"grid grid-cols-2 gap-1 p-2 border border-[var(--border)] rounded bg-[var(--bg-secondary)]",children:l0.map(_=>i.jsxs("label",{className:"flex items-center gap-2 p-1 text-sm cursor-pointer hover:bg-[var(--bg-tertiary)]",children:[i.jsx("input",{type:"checkbox",checked:f.includes(_),onChange:()=>v(_),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"font-mono text-xs",children:_})]},_))}):i.jsxs(i.Fragment,{children:[i.jsx("select",{className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",value:typeof a.tools=="string"?a.tools:"standard",onChange:_=>l({...a,tools:_.target.value}),children:bp.map(_=>i.jsx("option",{value:_,children:_},_))}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-1",children:o0[typeof a.tools=="string"?a.tools:"standard"].join(", ")})]})]}),i.jsxs("div",{className:"border-t border-[var(--border)] pt-3",children:[i.jsxs("button",{type:"button",className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors",onClick:()=>w(!x),children:[i.jsx(Ss,{size:14,className:`transition-transform ${x?"rotate-90":""}`}),"More options"]}),x&&i.jsx("div",{className:"mt-3",children:i.jsx(pt,{label:"Description (optional)",value:a.description,onChange:_=>l({...a,description:_.target.value}),placeholder:"Brief description of this agent"})})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(H,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(H,{type:"submit",variant:"primary",disabled:!a.name.trim(),loading:r,children:"Create Agent"})]})]})})}function m0({agent:e,isOpen:t,onClose:n}){var O,D,B;const r=Et(),s=Pn(),[a,l]=k.useState("suite"),[o,u]=k.useState("quick"),[c,p]=k.useState(!1),[f,h]=k.useState([]),[x,w]=k.useState({base_name:e.name,vary_compaction:!0,vary_tools:!0,vary_compaction_head:!1,vary_compaction_tail:!1,tool_presets:["standard","minimal"],compaction_head_values:[5,10,20],compaction_tail_values:[20,40,60]}),[j,S]=k.useState(4),[v,d]=k.useState(!1),[m,g]=k.useState(null),{data:_=[]}=Te({queryKey:["tasks"],queryFn:()=>fn.list()}),N=st({mutationFn:fn.importSuite,onSuccess:R=>{s.invalidateQueries({queryKey:["tasks"]}),h(R.map(A=>A.id))}}),C=st({mutationFn:Jn.generateCandidates,onSuccess:()=>{s.invalidateQueries({queryKey:["configs"]})}}),b=st({mutationFn:async R=>{const A=await bt.create(R);return bt.start(A.id).next(),A},onSuccess:R=>{s.invalidateQueries({queryKey:["jobs"]}),g(R.id),l("success")}}),z=[{value:"quick",label:"Quick",description:"3 fast tasks for rapid testing",tasks:3},{value:"core",label:"Core",description:"5 standard evaluation tasks",tasks:5},{value:"coding",label:"Coding",description:"10 comprehensive coding tasks",tasks:10}],T=()=>{var A,P,$;let R=1;return x.vary_compaction&&(R*=2),x.vary_tools&&(R*=((A=x.tool_presets)==null?void 0:A.length)||3),x.vary_compaction_head&&(R*=((P=x.compaction_head_values)==null?void 0:P.length)||3),x.vary_compaction_tail&&(R*=(($=x.compaction_tail_values)==null?void 0:$.length)||3),R},V=x.vary_compaction||x.vary_tools||x.vary_compaction_head||x.vary_compaction_tail,F=async()=>{l("starting");let R=f,A=[e.id];if(!c)try{R=(await N.mutateAsync(o)).map(ye=>ye.id)}catch(fe){console.error("Failed to import suite:",fe),alert(`Failed to import task suite: ${o}`),l("candidates");return}if(R.length===0){alert("No tasks selected. Please select tasks or choose a task suite."),l("candidates");return}try{A=(await C.mutateAsync({...x,base_name:e.name})).map(ye=>ye.id)}catch(fe){console.error("Failed to generate candidates:",fe),alert("Failed to generate candidates"),l("candidates");return}const P=T(),$={name:`${e.name} optimization (${P} candidates × ${R.length} tasks)`,candidate_ids:A,task_ids:R,parallel:j,use_llm_eval:v};b.mutate($)},U=R=>{h(A=>A.includes(R)?A.filter(P=>P!==R):[...A,R])},te=()=>{l("suite"),g(null),n()},ke=T(),Pt=c?f.length:((O=z.find(R=>R.value===o))==null?void 0:O.tasks)||3,He=ke*Pt;return i.jsx(ia,{isOpen:t,onClose:te,title:`Optimize: ${e.name}`,children:a==="success"&&m?i.jsxs("div",{className:"flex flex-col items-center py-8",children:[i.jsx("div",{className:"w-12 h-12 rounded-full bg-green-500/20 flex items-center justify-center mb-4",children:i.jsx(Ii,{size:24,className:"text-green-500"})}),i.jsx("h3",{className:"text-lg font-medium mb-2",children:"Job Started!"}),i.jsx("p",{className:"text-[var(--text-secondary)] text-center mb-2",children:"Optimization job is now running"}),i.jsxs("code",{className:"text-xs bg-[var(--bg-primary)] px-3 py-1.5 rounded font-mono mb-6",children:["ID: ",m.slice(0,8),"..."]}),i.jsxs("div",{className:"flex gap-3",children:[i.jsx(H,{variant:"secondary",onClick:te,children:"Close"}),i.jsx(H,{variant:"primary",icon:Ks,onClick:()=>{te(),r(`/jobs/${m}`)},children:"View Job"})]})]}):a==="starting"?i.jsxs("div",{className:"flex flex-col items-center py-8",children:[i.jsx(Fi,{size:32,className:"animate-spin text-[var(--accent)] mb-4"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:C.isPending?"Generating candidates...":N.isPending?"Importing tasks...":"Creating optimization job..."})]}):a==="candidates"?i.jsxs("div",{className:"space-y-6",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx("span",{className:"text-[var(--text-primary)]",children:"1. Tasks"}),i.jsx(Ss,{size:14}),i.jsx("span",{className:"text-[var(--accent)] font-medium",children:"2. Candidates"})]}),i.jsxs("div",{className:"space-y-4 p-4 border border-[var(--border)] bg-[var(--bg-secondary)]",children:[i.jsxs("div",{className:"flex items-center justify-between",children:[i.jsx("h4",{className:"font-medium text-sm",children:"Select dimensions to explore:"}),i.jsxs(W,{variant:"info",children:[ke," candidate",ke!==1?"s":""]})]}),!V&&i.jsx("p",{className:"text-xs text-[var(--text-secondary)]",children:"Select at least one dimension to generate candidate variations. Without variations, only the baseline agent will be tested."}),i.jsxs("div",{className:"space-y-2",children:[i.jsx(Ln,{label:"Compaction (on/off)",checked:x.vary_compaction,onChange:R=>w({...x,vary_compaction:R.target.checked})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6 -mt-1",children:"Test with compaction enabled vs disabled"}),i.jsx(Ln,{label:"Tool Presets",checked:x.vary_tools,onChange:R=>w({...x,vary_tools:R.target.checked})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6 -mt-1",children:"Test different tool configurations (standard, minimal, full)"}),x.vary_tools&&i.jsx("div",{className:"ml-6 flex flex-wrap gap-2",children:bp.map(R=>{var A;return i.jsxs("label",{className:"flex items-center gap-1 text-xs",children:[i.jsx("input",{type:"checkbox",checked:(A=x.tool_presets)==null?void 0:A.includes(R),onChange:P=>{const $=x.tool_presets||[];w({...x,tool_presets:P.target.checked?[...$,R]:$.filter(fe=>fe!==R)})},className:"accent-[var(--accent)]"}),i.jsx("span",{children:R})]},R)})})]}),i.jsxs("div",{className:"border-t border-[var(--border)] pt-3 mt-3 space-y-2",children:[i.jsx(Ln,{label:"Compaction Head Size",checked:x.vary_compaction_head,onChange:R=>w({...x,vary_compaction_head:R.target.checked})}),x.vary_compaction_head&&i.jsx(pt,{label:"Head sizes (comma-separated)",value:((D=x.compaction_head_values)==null?void 0:D.join(", "))||"5, 10, 20",onChange:R=>w({...x,compaction_head_values:R.target.value.split(",").map(A=>parseInt(A.trim())).filter(A=>!isNaN(A))})}),i.jsx(Ln,{label:"Compaction Tail Size",checked:x.vary_compaction_tail,onChange:R=>w({...x,vary_compaction_tail:R.target.checked})}),x.vary_compaction_tail&&i.jsx(pt,{label:"Tail sizes (comma-separated)",value:((B=x.compaction_tail_values)==null?void 0:B.join(", "))||"20, 40, 60",onChange:R=>w({...x,compaction_tail_values:R.target.value.split(",").map(A=>parseInt(A.trim())).filter(A=>!isNaN(A))})})]}),i.jsxs("div",{className:"bg-[var(--bg-tertiary)] p-3 rounded text-sm",children:[i.jsxs("div",{className:"flex justify-between",children:[i.jsx("span",{children:"Candidates:"}),i.jsx("span",{className:"font-mono",children:ke})]}),i.jsxs("div",{className:"flex justify-between",children:[i.jsx("span",{children:"Tasks:"}),i.jsx("span",{className:"font-mono",children:Pt})]}),i.jsxs("div",{className:"flex justify-between font-medium border-t border-[var(--border)] pt-2 mt-2",children:[i.jsx("span",{children:"Total experiments:"}),i.jsx("span",{className:"font-mono text-[var(--accent)]",children:He})]})]})]}),i.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[i.jsx(pt,{label:"Parallel Workers",type:"number",value:j,onChange:R=>S(parseInt(R.target.value)||1),min:1,max:16}),i.jsxs("div",{className:"space-y-1",children:[i.jsx(Ln,{label:"Use LLM evaluation",checked:v,onChange:R=>d(R.target.checked)}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] ml-6",children:v?"GPT-4o scores task completion (0-1)":"Simple pass/fail based on task success"})]})]}),i.jsxs("div",{className:"flex justify-between gap-2 pt-4 border-t border-[var(--border)]",children:[i.jsx(H,{variant:"secondary",icon:ig,onClick:()=>l("suite"),children:"Back"}),i.jsxs(H,{variant:"primary",icon:Ks,onClick:F,children:["Start Optimization (",He," runs)"]})]})]}):i.jsxs("div",{className:"space-y-6",children:[i.jsxs("div",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)]",children:[i.jsx("span",{className:"text-[var(--accent)] font-medium",children:"1. Tasks"}),i.jsx(Ss,{size:14}),i.jsx("span",{children:"2. Candidates"})]}),i.jsxs("div",{children:[i.jsx("label",{className:"text-sm font-medium mb-3 block",children:"Select Task Suite"}),i.jsx("div",{className:"space-y-2",children:z.map(R=>{const A=_.filter(P=>P.suite===R.value).length;return i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer transition-colors ${o===R.value&&!c?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",name:"suite",value:R.value,checked:o===R.value&&!c,onChange:()=>{u(R.value),p(!1)},className:"accent-[var(--accent)]"}),i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"font-medium",children:R.label}),i.jsxs(W,{children:[R.tasks," tasks"]}),A>0&&i.jsxs(W,{variant:"success",children:[A," imported"]})]}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:R.description})]})]},R.value)})})]}),_.length>0&&i.jsxs("div",{children:[i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer transition-colors ${c?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",checked:c,onChange:()=>p(!0),className:"accent-[var(--accent)]"}),i.jsxs("div",{className:"flex-1",children:[i.jsx("span",{className:"font-medium",children:"Custom Selection"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:"Choose specific tasks from your library"})]})]}),c&&i.jsx("div",{className:"mt-3 max-h-48 overflow-y-auto border border-[var(--border)] p-2 space-y-1",children:_.map(R=>i.jsxs("label",{className:"flex items-center gap-2 p-2 hover:bg-[var(--bg-tertiary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:f.includes(R.id),onChange:()=>U(R.id),className:"accent-[var(--accent)]"}),i.jsx("span",{className:"text-sm",children:R.name}),R.suite&&i.jsx(W,{children:R.suite})]},R.id))})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4 border-t border-[var(--border)]",children:[i.jsx(H,{variant:"secondary",onClick:n,children:"Cancel"}),i.jsx(H,{variant:"primary",icon:Ss,onClick:()=>l("candidates"),disabled:c&&f.length===0,children:"Next: Candidates"})]})]})})}function v0(e){const t=[],n=r=>r.type==="trace_span"&&typeof r.data=="object"&&r.data!==null?r.data:"span_id"in r?r:null;if(Array.isArray(e.spans)){for(const r of e.spans)if(typeof r=="object"&&r!==null){const s=n(r);s&&t.push(s)}}else if(e.span_id)t.push(e);else for(const r in e){const s=e[r];if(typeof s=="object"&&s!==null){const a=n(s);if(a)t.push(a);else if(Array.isArray(s)){for(const l of s)if(typeof l=="object"&&l!==null){const o=n(l);o&&t.push(o)}}}}return t}function Pp(e){const t=new Map,n=[];for(const s of e)t.set(s.span_id,{span:s,children:[]});for(const s of e){const a=t.get(s.span_id);s.parent_span_id&&t.has(s.parent_span_id)?t.get(s.parent_span_id).children.push(a):n.push(a)}const r=s=>{s.sort((a,l)=>(a.span.start_time||0)-(l.span.start_time||0)),s.forEach(a=>r(a.children))};return r(n),n}function x0(e){return e.includes("Agent")||e.includes("agent")?"bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200":e.includes("chat")||e.includes("Chat")||e.includes("llm")?"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200":e.includes("tool")||e.includes("execute")||e.includes("bash")?"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200":"bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"}function y0(e){return e>=1e3?`${(e/1e3).toFixed(2)}s`:`${e.toFixed(0)}ms`}function Du({node:e,depth:t=0}){var f,h;const[n,r]=k.useState(t<2),[s,a]=k.useState(!1),{span:l}=e,o=e.children.length>0,u=(f=l.attributes)==null?void 0:f["gen_ai.usage.input_tokens"],c=(h=l.attributes)==null?void 0:h["gen_ai.usage.output_tokens"],p=u!==void 0||c!==void 0;return i.jsxs("div",{className:"relative",children:[t>0&&i.jsx("div",{className:"absolute left-0 top-0 bottom-0 border-l-2 border-[var(--border)]",style:{marginLeft:`${(t-1)*16+8}px`}}),i.jsxs("div",{className:"flex items-center gap-2 py-1.5 px-1 hover:bg-[var(--bg-primary)] rounded transition-colors cursor-pointer",style:{paddingLeft:`${t*16}px`},onClick:()=>o?r(!n):a(!s),children:[i.jsx("div",{className:"w-4 h-4 flex items-center justify-center text-[var(--text-secondary)]",children:o?n?"▼":"▶":s?"▼":"▶"}),i.jsx("span",{className:`text-xs px-1.5 py-0.5 rounded font-medium ${x0(l.operation_name)}`,children:l.operation_name.replace("ChatAgent.","").replace("invoke_agent ","")}),l.duration_ms!==void 0&&i.jsx("span",{className:"text-xs text-[var(--text-secondary)] font-mono",children:y0(l.duration_ms)}),p&&i.jsxs("span",{className:"text-xs text-[var(--text-secondary)] font-mono",children:[u!==void 0&&i.jsxs("span",{className:"text-blue-400",children:["↑",String(u)]}),u!==void 0&&c!==void 0&&i.jsx("span",{className:"mx-0.5",children:"/"}),c!==void 0&&i.jsxs("span",{className:"text-green-400",children:["↓",String(c)]})]})]}),s&&!o&&i.jsx("div",{className:"ml-4 mt-1 mb-2 p-2 bg-[var(--bg-primary)] rounded border border-[var(--border)] text-xs",style:{marginLeft:`${t*16+20}px`},children:i.jsxs("div",{className:"space-y-1",children:[l.span_id&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Span ID:"}),i.jsx("span",{className:"font-mono text-xs break-all",children:l.span_id})]}),l.trace_id&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Trace ID:"}),i.jsx("span",{className:"font-mono text-xs break-all",children:l.trace_id})]}),l.status&&i.jsxs("div",{className:"flex gap-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] w-20",children:"Status:"}),i.jsx("span",{className:`px-1.5 py-0.5 rounded text-xs ${l.status==="OK"||l.status==="StatusCode.UNSET"?"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200":"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"}`,children:l.status})]}),Object.keys(l.attributes||{}).length>0&&i.jsxs("div",{className:"mt-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)] block mb-1",children:"Attributes:"}),i.jsx("pre",{className:"text-xs bg-[var(--bg-secondary)] border border-[var(--border)] rounded p-2 overflow-auto max-h-32 whitespace-pre-wrap break-all",children:JSON.stringify(l.attributes,null,2)})]})]})}),o&&n&&i.jsx("div",{children:e.children.map((x,w)=>i.jsx(Du,{node:x,depth:t+1},x.span.span_id||w))})]})}function Tp({trace:e}){const[t,n]=k.useState("tree"),r=k.useMemo(()=>v0(e),[e]),s=k.useMemo(()=>Pp(r),[r]);return Object.keys(e).length===0?null:i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-3",children:[i.jsx("h3",{className:"font-medium",children:"Trace Data"}),i.jsxs("div",{className:"flex gap-1",children:[i.jsx("button",{onClick:()=>n("tree"),className:`px-2 py-1 text-xs rounded ${t==="tree"?"bg-[var(--accent)] text-white":"bg-[var(--bg-primary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:"Tree"}),i.jsx("button",{onClick:()=>n("raw"),className:`px-2 py-1 text-xs rounded ${t==="raw"?"bg-[var(--accent)] text-white":"bg-[var(--bg-primary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:"Raw"})]})]}),t==="tree"?r.length>0?i.jsx("div",{className:"border border-[var(--border)] rounded overflow-hidden",children:i.jsxs("div",{className:"p-2",children:[i.jsxs("div",{className:"flex items-center gap-2 mb-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs(W,{variant:"default",children:[r.length," spans"]}),i.jsx("span",{children:"•"}),i.jsx("span",{children:"Click to expand details"})]}),s.map((a,l)=>i.jsx(Du,{node:a,depth:0},a.span.span_id||l))]})}):i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:"No structured spans found. View raw data below."}):i.jsx("pre",{className:"text-xs bg-[var(--bg-primary)] p-3 overflow-x-auto border border-[var(--border)] max-h-96 whitespace-pre-wrap",children:JSON.stringify(e,null,2)})]})}function g0({spans:e,isLive:t=!1}){const n=k.useRef(null),r=k.useMemo(()=>Pp(e),[e]);return k.useEffect(()=>{n.current&&t&&n.current.scrollTo({top:n.current.scrollHeight,behavior:"smooth"})},[e.length,t]),i.jsxs("div",{className:"border border-[var(--border)] rounded overflow-hidden h-full flex flex-col",children:[i.jsxs("div",{className:"flex items-center gap-2 p-2 border-b border-[var(--border)] bg-[var(--bg-secondary)]",children:[i.jsxs(W,{variant:"default",children:[e.length," spans"]}),t&&i.jsx("span",{className:"animate-pulse",children:i.jsx(W,{variant:"info",children:"Live"})})]}),i.jsx("div",{ref:n,className:"flex-1 overflow-auto p-2",children:r.length>0?r.map((s,a)=>i.jsx(Du,{node:s,depth:0},s.span.span_id||a)):i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:t?"Waiting for spans...":"No spans recorded"})})]})}function j0({agent:e}){const[t,n]=k.useState(""),[r,s]=k.useState(null),[a,l]=k.useState("idle"),[o,u]=k.useState(null),[c,p]=k.useState(""),[f,h]=k.useState([]),[x,w]=k.useState(null),[j,S]=k.useState(null),[v,d]=k.useState([]),m=k.useRef(null),{data:g=[]}=Te({queryKey:["tasks"],queryFn:()=>fn.list()});k.useEffect(()=>{m.current&&a==="running"&&(m.current.scrollTop=m.current.scrollHeight)},[c,a]);const _=F=>{if(s(F),F){const U=g.find(te=>te.id===F);U&&n(U.prompt)}},N=async()=>{if(t.trim()){l("running"),p(""),h([]),w(null),S(null),d([]);try{const F=await Ns.create({agent_id:e.id,prompt:t.trim(),task_id:r||void 0});u(F.id);for await(const U of Ns.start(F.id))C(U)}catch(F){S(F instanceof Error?F.message:"Test failed"),l("failed")}}},C=F=>{switch(F.event){case"started":break;case"execution":F.execution_event==="text_delta"&&F.content?p(U=>U+F.content):F.execution_event==="tool_call_start"&&F.tool_name?d(U=>[...U,{name:F.tool_name}]):F.execution_event==="tool_result"&&F.content&&d(U=>{if(U.length>0){const te=[...U];return te[te.length-1]={...te[te.length-1],content:F.content},te}return U});break;case"span":if(F.span){const U=F.span;if(U.data){const te={span_id:U.data.span_id||"",trace_id:U.data.trace_id||"",parent_span_id:U.data.parent_span_id||null,operation_name:U.data.operation_name||"",start_time:U.timestamp?new Date(U.timestamp).getTime():Date.now(),end_time:Date.now(),duration_ms:U.data.duration_ms||0,status:U.data.status||"OK",attributes:U.data.attributes||{}};h(ke=>[...ke,te])}}break;case"complete":l("completed"),F.result&&w(F.result);break;case"error":S(F.message),l("failed");break}},b=async()=>{if(o)try{await Ns.cancel(o)}catch{}l("idle")},z=()=>{l("idle"),u(null),p(""),h([]),w(null),S(null),d([])},T=a==="running",V=a==="completed"||a==="failed";return i.jsxs("div",{className:"h-full flex flex-col",children:[!T&&!V&&i.jsxs("div",{className:"mb-4 space-y-3",children:[i.jsxs("div",{className:"flex gap-3",children:[i.jsxs("div",{className:"flex-1",children:[i.jsx("label",{className:"block text-sm font-medium mb-1",children:"Select Task (optional)"}),i.jsxs("select",{value:r||"",onChange:F=>_(F.target.value||null),className:"w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm",children:[i.jsx("option",{value:"",children:"Custom prompt..."}),g.map(F=>i.jsx("option",{value:F.id,children:F.name},F.id))]})]}),i.jsx("div",{className:"flex items-end",children:i.jsx(H,{variant:"primary",icon:Ks,onClick:N,disabled:!t.trim(),children:"Run Test"})})]}),i.jsxs("div",{children:[i.jsx("label",{className:"block text-sm font-medium mb-1",children:"Prompt"}),i.jsx("textarea",{value:t,onChange:F=>n(F.target.value),placeholder:"Enter a test prompt for the agent...",className:"w-full h-32 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono resize-none"})]})]}),(T||V)&&i.jsxs("div",{className:"flex-1 flex flex-col min-h-0",children:[i.jsxs("div",{className:"flex items-center justify-between mb-3",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[T&&i.jsxs(i.Fragment,{children:[i.jsx("span",{className:"animate-pulse",children:i.jsx(W,{variant:"info",children:"Running"})}),i.jsx(H,{variant:"ghost",size:"sm",icon:gg,onClick:b,children:"Cancel"})]}),a==="completed"&&i.jsx(W,{variant:"success",children:"Completed"}),a==="failed"&&i.jsx(W,{variant:"error",children:"Failed"})]}),i.jsxs("div",{className:"flex items-center gap-3",children:[x&&i.jsxs("div",{className:"flex items-center gap-3 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{className:"flex items-center gap-1",children:[i.jsx(lg,{size:12}),x.duration_seconds.toFixed(2),"s"]}),i.jsxs("span",{className:"flex items-center gap-1",children:[i.jsx(og,{size:12}),x.tokens_total," tokens"]}),x.passed!==null&&(x.passed?i.jsx(rg,{size:14,className:"text-green-500"}):i.jsx(wg,{size:14,className:"text-red-500"}))]}),V&&i.jsx(H,{variant:"secondary",size:"sm",onClick:z,children:"New Test"})]})]}),i.jsxs("div",{className:"flex-1 grid grid-cols-2 gap-4 min-h-0",children:[i.jsxs("div",{className:"flex flex-col border border-[var(--border)] rounded overflow-hidden",children:[i.jsx("div",{className:"px-3 py-2 border-b border-[var(--border)] bg-[var(--bg-secondary)] text-sm font-medium",children:"Output"}),i.jsxs("div",{ref:m,className:"flex-1 overflow-auto p-3 font-mono text-sm whitespace-pre-wrap bg-[var(--bg-primary)]",children:[c||(T?"Waiting for response...":"No output"),v.length>0&&i.jsxs("div",{className:"mt-3 space-y-2 border-t border-[var(--border)] pt-3",children:[i.jsx("div",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:"Tool Calls"}),v.map((F,U)=>i.jsxs("div",{className:"p-2 bg-green-500/10 border border-green-500/20 rounded text-xs",children:[i.jsx(W,{variant:"success",children:F.name}),F.content&&i.jsxs("div",{className:"mt-1 text-[var(--text-secondary)] truncate max-w-full",children:[F.content.substring(0,200),F.content.length>200&&"..."]})]},U))]})]})]}),i.jsx(g0,{spans:f,isLive:T})]}),j&&i.jsx("div",{className:"mt-3 p-3 bg-red-500/10 border border-red-500/20 rounded text-sm text-red-400",children:j}),V&&o&&i.jsx("div",{className:"mt-3 flex items-center justify-end pt-3 border-t border-[var(--border)]",children:i.jsx(Hs,{to:`/tests/${o}`,children:i.jsx(H,{variant:"primary",iconRight:cg,children:"View Full Details"})})})]})]})}function w0(){const{agentId:e}=Li(),t=Et(),n=Pn(),[r,s]=k.useState("overview"),{data:a,isLoading:l}=Te({queryKey:["configs",e],queryFn:()=>Jn.get(e),enabled:!!e}),{data:o=[]}=Te({queryKey:["tests",{agent_id:e}],queryFn:()=>Ns.list({agent_id:e}),enabled:!!e}),{data:u=[]}=Te({queryKey:["jobs"],queryFn:()=>bt.list()}),c=st({mutationFn:f=>Jn.delete(f),onSuccess:()=>{n.invalidateQueries({queryKey:["configs"]}),t("/agents")}}),p=u.filter(f=>f.candidate_ids.includes(e||""));return l?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):a?i.jsxs("div",{className:"h-full flex flex-col",children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t("/agents"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Agents"})}),i.jsxs("div",{className:"flex items-center justify-between",children:[i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:a.name}),a.is_auto_generated&&i.jsx(W,{variant:"info",children:"Auto-generated"})]}),i.jsx("div",{className:"flex gap-2",children:i.jsx(H,{variant:"secondary",icon:jp,onClick:()=>{confirm(`Delete agent "${a.name}"?`)&&c.mutate(a.id)},children:"Delete"})})]}),a.description&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:a.description})]}),i.jsxs("div",{className:"flex gap-1 mb-6 border-b border-[var(--border)]",children:[i.jsx(ml,{active:r==="overview",onClick:()=>s("overview"),icon:i.jsx(gp,{size:16}),children:"Overview"}),i.jsx(ml,{active:r==="test",onClick:()=>s("test"),icon:i.jsx(Ks,{size:16}),children:"Test"}),i.jsx(ml,{active:r==="history",onClick:()=>s("history"),icon:i.jsx(hg,{size:16}),badge:o.length,children:"History"})]}),i.jsxs("div",{className:"flex-1 min-h-0",children:[r==="overview"&&i.jsx(k0,{agent:a,recentTests:o,jobs:p}),r==="test"&&i.jsx(j0,{agent:a}),r==="history"&&i.jsx(S0,{tests:o})]})]}):i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Agent not found"})}function ml({active:e,onClick:t,icon:n,badge:r,children:s}){return i.jsxs("button",{onClick:t,className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${e?"border-[var(--accent)] text-[var(--text-primary)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[n,s,r!==void 0&&r>0&&i.jsx("span",{className:"ml-1 px-1.5 py-0.5 text-xs bg-[var(--bg-tertiary)] rounded",children:r})]})}function k0({agent:e,recentTests:t,jobs:n}){var l,o,u,c,p,f;const r=Et(),s=e.config,a=()=>{const h=s.tools;return typeof h=="string"?h:Array.isArray(h)?h.join(", "):typeof h=="object"?Object.keys(h).join(", "):"standard"};return i.jsxs("div",{className:"space-y-6",children:[i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"Configuration"}),i.jsxs("div",{className:"grid grid-cols-2 gap-4 text-sm",children:[i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Model:"}),i.jsx("div",{className:"font-mono",children:s.model||"default"})]}),i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Compaction:"}),i.jsx("div",{className:"font-mono",children:((l=s.compaction)==null?void 0:l.strategy)==="none"?"disabled":`${(o=s.compaction)==null?void 0:o.strategy} (${((c=(u=s.compaction)==null?void 0:u.params)==null?void 0:c.head_size)||0}/${((f=(p=s.compaction)==null?void 0:p.params)==null?void 0:f.tail_size)||0})`})]}),i.jsxs("div",{className:"col-span-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Tools:"}),i.jsx("div",{className:"font-mono",children:a()})]}),s.instructions&&i.jsxs("div",{className:"col-span-2",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Instructions:"}),i.jsx("pre",{className:"mt-1 p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-xs whitespace-pre-wrap max-h-32 overflow-auto",children:s.instructions})]})]})]}),i.jsxs(ee,{children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Recent Tests"}),t.length>0&&i.jsxs("span",{className:"text-xs text-[var(--text-secondary)]",children:[t.length," total"]})]}),t.length===0?i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:'No tests yet. Click the "Test" tab to run one.'}):i.jsx("div",{className:"space-y-2",children:t.slice(0,5).map(h=>i.jsxs(Hs,{to:`/tests/${h.id}`,className:"flex items-center justify-between p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx(Op,{status:h.status}),i.jsxs("span",{className:"text-sm truncate max-w-[200px]",children:[h.prompt.slice(0,50),"..."]})]}),i.jsxs("div",{className:"flex items-center gap-3 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[h.duration_seconds.toFixed(1),"s"]}),i.jsxs("span",{children:[h.tokens_total," tok"]})]})]},h.id))})]}),i.jsxs(ee,{children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Optimization Jobs"}),i.jsx(H,{variant:"secondary",size:"sm",icon:Ii,onClick:()=>r("/agents",{state:{optimizeAgent:e}}),children:"New Job"})]}),n.length===0?i.jsx("div",{className:"text-sm text-[var(--text-secondary)] text-center py-4",children:"No optimization jobs yet."}):i.jsx("div",{className:"space-y-2",children:n.slice(0,5).map(h=>i.jsxs(Hs,{to:`/jobs/${h.id}`,className:"flex items-center justify-between p-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx(W,{variant:h.status==="completed"?"success":h.status==="running"?"info":"default",children:h.status}),i.jsx("span",{className:"text-sm",children:h.name})]}),i.jsx("span",{className:"text-xs text-[var(--text-secondary)]",children:new Date(h.created_at).toLocaleDateString()})]},h.id))})]})]})}function S0({tests:e}){return e.length===0?i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"No test history yet. Run a test to see results here."}):i.jsx("div",{className:"space-y-2",children:e.map(t=>i.jsxs(Hs,{to:`/tests/${t.id}`,className:"flex items-center justify-between p-3 bg-[var(--bg-secondary)] border border-[var(--border)] rounded hover:border-[var(--accent-dim)] transition-colors",children:[i.jsxs("div",{className:"flex-1 min-w-0",children:[i.jsxs("div",{className:"flex items-center gap-2 mb-1",children:[i.jsx(Op,{status:t.status}),t.score!==null&&i.jsxs("span",{className:`text-sm font-medium ${t.passed?"text-green-400":"text-red-400"}`,children:[(t.score*100).toFixed(0),"%"]})]}),i.jsx("p",{className:"text-sm truncate",children:t.prompt})]}),i.jsxs("div",{className:"flex flex-col items-end gap-1 ml-4",children:[i.jsx("span",{className:"text-xs text-[var(--text-secondary)]",children:new Date(t.created_at).toLocaleString()}),i.jsxs("div",{className:"flex items-center gap-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[t.duration_seconds.toFixed(1),"s"]}),i.jsx("span",{children:"•"}),i.jsxs("span",{children:[t.tokens_total," tokens"]})]})]})]},t.id))})}function Op({status:e}){const t={completed:"success",failed:"error",running:"info",cancelled:"warning",pending:"default"};return i.jsx(W,{variant:t[e]||"default",children:e})}const Rp=Ru(e=>({tasks:[],selectedTaskIds:[],setTasks:t=>e({tasks:t}),toggleTaskSelection:t=>e(n=>({selectedTaskIds:n.selectedTaskIds.includes(t)?n.selectedTaskIds.filter(r=>r!==t):[...n.selectedTaskIds,t]})),jobs:[],setJobs:t=>e({jobs:t})}));function N0(){const e=Pn(),[t,n]=k.useState(!1),[r,s]=k.useState(!1),[a,l]=k.useState(new Set(["custom"])),{selectedTaskIds:o,toggleTaskSelection:u,setTasks:c}=Rp(),p=d=>{l(m=>{const g=new Set(m);return g.has(d)?g.delete(d):g.add(d),g})},{data:f=[],isLoading:h}=Te({queryKey:["tasks"],queryFn:()=>fn.list()});k.useEffect(()=>{f.length>0&&c(f)},[f,c]);const x=st({mutationFn:fn.create,onSuccess:()=>{e.invalidateQueries({queryKey:["tasks"]}),n(!1)}}),w=st({mutationFn:fn.importSuite,onSuccess:()=>{e.invalidateQueries({queryKey:["tasks"]}),s(!1)}}),j=st({mutationFn:fn.delete,onSuccess:()=>e.invalidateQueries({queryKey:["tasks"]})}),S=f.reduce((d,m)=>{const g=m.suite||"custom";return d[g]||(d[g]=[]),d[g].push(m),d},{}),v=Object.keys(S).sort((d,m)=>d==="custom"?-1:m==="custom"?1:d.localeCompare(m));return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Tasks"}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:["Define tasks to evaluate agent configurations.",o.length>0&&i.jsxs("span",{className:"ml-2 text-[var(--accent)]",children:[o.length," selected"]})]})]}),i.jsxs("div",{className:"flex gap-2",children:[i.jsx(H,{variant:"secondary",onClick:()=>s(!0),children:"Import Suite"}),i.jsx(H,{variant:"primary",onClick:()=>n(!0),children:"+ New Task"})]})]}),h?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):f.length===0?i.jsx("div",{className:"text-center py-12 text-[var(--text-secondary)]",children:"No tasks yet. Create one or import a built-in suite."}):i.jsx("div",{className:"space-y-4",children:v.map(d=>{const m=S[d],g=a.has(d),_=m.filter(N=>o.includes(N.id)).length;return i.jsxs("div",{children:[i.jsxs("button",{onClick:()=>p(d),className:"flex items-center gap-2 py-2 hover:text-[var(--accent)] transition-colors",children:[g?i.jsx(ag,{size:16,className:"text-[var(--text-secondary)]"}):i.jsx(Ss,{size:16,className:"text-[var(--text-secondary)]"}),i.jsx("h3",{className:"text-sm font-medium uppercase tracking-wide",children:d==="custom"?"Custom Tasks":`${d} Suite`}),i.jsx(W,{variant:d==="custom"?"default":"info",children:m.length}),_>0&&i.jsxs(W,{variant:"success",children:[_," selected"]})]}),g&&i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 mt-2",children:m.map(N=>i.jsx(ee,{selectable:!0,selected:o.includes(N.id),onClick:()=>u(N.id),children:i.jsxs("div",{className:"flex flex-col h-full",children:[i.jsxs("div",{className:"flex items-start justify-between gap-2",children:[i.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[i.jsx("span",{className:"font-medium",children:N.name}),o.includes(N.id)&&i.jsx(W,{variant:"success",children:"Selected"}),N.category&&N.category!=="default"&&i.jsx(W,{variant:"default",children:N.category})]}),i.jsx(H,{variant:"ghost",size:"sm",onClick:C=>{C.stopPropagation(),confirm("Delete this task?")&&j.mutate(N.id)},children:"Delete"})]}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-2 line-clamp-3 flex-1",children:N.prompt}),N.criteria.length>0&&i.jsx("div",{className:"flex gap-1 mt-2 flex-wrap",children:N.criteria.map(C=>i.jsx(W,{variant:"default",children:C.name},C.name))})]})},N.id))})]},d)})}),i.jsx(_0,{isOpen:t,onClose:()=>n(!1),onSubmit:d=>x.mutate(d),isLoading:x.isPending}),i.jsx(C0,{isOpen:r,onClose:()=>s(!1),onSubmit:d=>w.mutate(d),isLoading:w.isPending})]})}function _0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){const[s,a]=k.useState({name:"",prompt:"",criteria:[],category:"default"}),l=()=>{a({...s,criteria:[...s.criteria,{name:"",instruction:"",weight:1}]})},o=(p,f)=>{const h=[...s.criteria];h[p]={...h[p],...f},a({...s,criteria:h})},u=p=>{a({...s,criteria:s.criteria.filter((f,h)=>h!==p)})},c=p=>{p.preventDefault(),!(!s.name.trim()||!s.prompt.trim())&&n({...s,criteria:s.criteria.filter(f=>f.name.trim()&&f.instruction.trim())})};return i.jsx(ia,{isOpen:e,onClose:t,title:"Create Task",children:i.jsxs("form",{onSubmit:c,className:"space-y-4",children:[i.jsx(pt,{label:"Name",value:s.name,onChange:p=>a({...s,name:p.target.value}),placeholder:"e.g., fizzbuzz",required:!0}),i.jsx(c0,{label:"Prompt",value:s.prompt,onChange:p=>a({...s,prompt:p.target.value}),placeholder:"The task description for the agent...",required:!0}),i.jsx(pt,{label:"Category",value:s.category,onChange:p=>a({...s,category:p.target.value}),placeholder:"e.g., coding, research"}),i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsx("label",{className:"text-sm text-[var(--text-secondary)]",children:"Evaluation Criteria"}),i.jsx(H,{type:"button",variant:"ghost",size:"sm",onClick:l,children:"+ Add"})]}),i.jsx("div",{className:"space-y-2",children:s.criteria.map((p,f)=>i.jsxs("div",{className:"flex gap-2 items-start",children:[i.jsx(pt,{value:p.name,onChange:h=>o(f,{name:h.target.value}),placeholder:"Name",className:"w-32"}),i.jsx(pt,{value:p.instruction,onChange:h=>o(f,{instruction:h.target.value}),placeholder:"Instruction",className:"flex-1"}),i.jsx(H,{type:"button",variant:"ghost",size:"sm",onClick:()=>u(f),children:"×"})]},f))})]}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(H,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(H,{type:"submit",variant:"primary",disabled:r||!s.name.trim()||!s.prompt.trim(),children:r?"Creating...":"Create"})]})]})})}function C0({isOpen:e,onClose:t,onSubmit:n,isLoading:r}){const[s,a]=k.useState(""),{data:l=[],isLoading:o}=Te({queryKey:["suites"],queryFn:()=>fn.listSuites(),enabled:e});return k.useEffect(()=>{l.length>0&&!s&&a(l[0].name)},[l,s]),i.jsx(ia,{isOpen:e,onClose:t,title:"Import Task Suite",children:i.jsxs("div",{className:"space-y-4",children:[i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:"Import a built-in task suite for evaluation."}),o?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading suites..."}):l.length===0?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"No suites available."}):i.jsx("div",{className:"space-y-2 max-h-80 overflow-y-auto",children:l.map(u=>i.jsxs("label",{className:`flex items-center gap-3 p-3 border cursor-pointer ${s===u.name?"border-[var(--accent)] bg-[var(--accent)]/10":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:[i.jsx("input",{type:"radio",name:"suite",value:u.name,checked:s===u.name,onChange:()=>a(u.name),className:"accent-[var(--accent)]"}),i.jsxs("span",{className:"capitalize",children:[u.name.replace(/_/g," ")," (",u.task_count," tasks) - ",u.description]})]},u.name))}),i.jsxs("div",{className:"flex justify-end gap-2 pt-4",children:[i.jsx(H,{type:"button",variant:"secondary",onClick:t,children:"Cancel"}),i.jsx(H,{variant:"primary",onClick:()=>n(s),disabled:r||!s,children:r?"Importing...":"Import"})]})]})})}function b0(){const e=Et(),t=Pn(),[n,r]=k.useState(!1),{setJobs:s}=Rp(),a=Fu(),{data:l=[],isLoading:o}=Te({queryKey:["jobs",n],queryFn:()=>bt.list({include_public:n}),refetchInterval:5e3});k.useEffect(()=>{l.length>0&&s(l)},[l,s]);const u=st({mutationFn:bt.delete,onSuccess:()=>t.invalidateQueries({queryKey:["jobs"]})});return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsx("h2",{className:"text-xl font-bold",children:"Optimization Jobs"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:"View and manage optimization experiments. Start new jobs from the Agents page."})]}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:n,onChange:c=>r(c.target.checked),className:"rounded border-[var(--border)]"}),i.jsx(zi,{className:"w-4 h-4"}),"Show public"]}),i.jsx(H,{variant:"secondary",onClick:()=>e("/agents"),children:"Go to Agents"})]})]}),o?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):l.length===0?i.jsx("div",{className:"text-center py-12 text-[var(--text-secondary)]",children:"No jobs yet. Go to Agents page to start an optimization."}):i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4",children:l.map(c=>{const p=!c.user_id||c.user_id==="anonymous"||a&&c.created_by_name===a;return i.jsx(Ep,{job:c,onDelete:p?f=>u.mutate(f):void 0},c.id)})})]})}function E0(e,t=!0){return Math.abs(e)<10?"text-[var(--text-secondary)]":(t?e<0:e>0)?"text-green-400":"text-red-400"}function P0(e){return`${e>0?"+":""}${e.toFixed(1)}%`}function Lp(e,t){return t===0?0:(e-t)/t*100}function os({label:e,values:t,baselineIndex:n,formatter:r,isLowerBetter:s=!0}){const a=t[n];return i.jsxs("tr",{className:"border-b border-[var(--border)] last:border-0",children:[i.jsx("td",{className:"py-2 pr-4 text-[var(--text-secondary)] text-sm",children:e}),t.map((l,o)=>{const u=Lp(l,a),c=o===n;return i.jsxs("td",{className:"py-2 px-4 text-right",children:[i.jsx("div",{className:"font-mono",children:r(l)}),!c&&i.jsx("div",{className:`text-xs ${E0(u,s)}`,children:P0(u)}),c&&i.jsx("div",{className:"text-xs text-[var(--text-secondary)]",children:"(baseline)"})]},o)})]})}function T0({runs:e,baselineRunId:t}){const n=k.useMemo(()=>{if(t){const a=e.findIndex(l=>l.id===t);if(a>=0)return a}return 0},[e,t]);if(e.length<2)return null;const r=Math.min(...e.map(a=>a.tokens_total)),s=Math.max(...e.map(a=>a.score));return i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-4",children:"Candidate Comparison"}),i.jsx("div",{className:"overflow-x-auto",children:i.jsxs("table",{className:"w-full text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"border-b border-[var(--border)]",children:[i.jsx("th",{className:"pb-2 pr-4 text-left text-[var(--text-secondary)] font-medium",children:"Metric"}),e.map((a,l)=>i.jsx("th",{className:"pb-2 px-4 text-right",children:i.jsxs("div",{className:"flex items-center justify-end gap-2",children:[i.jsx("span",{className:"font-medium",children:a.candidate_name}),a.is_pareto&&i.jsx(W,{variant:"success",children:"Pareto"}),l===n&&i.jsx(W,{variant:"info",children:"Base"})]})},a.id))]})}),i.jsxs("tbody",{children:[i.jsx(os,{label:"Total Tokens",values:e.map(a=>a.tokens_total),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Input Tokens",values:e.map(a=>a.tokens_input),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Output Tokens",values:e.map(a=>a.tokens_output),baselineIndex:n,formatter:a=>a.toLocaleString(),isLowerBetter:!0}),i.jsx(os,{label:"Duration",values:e.map(a=>a.duration_seconds),baselineIndex:n,formatter:a=>`${a.toFixed(1)}s`,isLowerBetter:!0}),i.jsx(os,{label:"Score",values:e.map(a=>a.score*100),baselineIndex:n,formatter:a=>`${a.toFixed(1)}%`,isLowerBetter:!1})]})]})}),i.jsxs("div",{className:"mt-4 pt-4 border-t border-[var(--border)]",children:[i.jsx("h4",{className:"text-sm font-medium mb-2 text-[var(--text-secondary)]",children:"Key Insights"}),i.jsxs("ul",{className:"text-sm space-y-1 text-[var(--text-secondary)]",children:[e.map(a=>{const l=Lp(a.tokens_total,e[n].tokens_total);return a.tokens_total===r&&l<-5?i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-green-400",children:"✓"}),i.jsxs("span",{children:[i.jsx("strong",{children:a.candidate_name})," uses ",Math.abs(l).toFixed(0),"% fewer tokens"]})]},`token-${a.id}`):null}),e.map(a=>a.score===s&&a.passed?i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-green-400",children:"✓"}),i.jsxs("span",{children:[i.jsx("strong",{children:a.candidate_name})," achieved highest score (",(a.score*100).toFixed(0),"%)"]})]},`score-${a.id}`):null),e.filter(a=>a.is_pareto).length>0&&i.jsxs("li",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"text-purple-400",children:"★"}),i.jsxs("span",{children:["Pareto-optimal candidates:"," ",e.filter(a=>a.is_pareto).map(a=>a.candidate_name).join(", ")]})]})]})]}),i.jsxs("div",{className:"mt-4 pt-4 border-t border-[var(--border)]",children:[i.jsx("h4",{className:"text-sm font-medium mb-3 text-[var(--text-secondary)]",children:"Token Efficiency"}),i.jsx("div",{className:"space-y-2",children:e.map(a=>{const l=a.tokens_total/e[n].tokens_total*100,o=a.tokens_total<=r;return i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("div",{className:"w-24 text-sm truncate",title:a.candidate_name,children:a.candidate_name}),i.jsx("div",{className:"flex-1 h-6 bg-[var(--bg-primary)] rounded overflow-hidden",children:i.jsx("div",{className:`h-full transition-all duration-300 ${o?"bg-green-500":"bg-blue-500"}`,style:{width:`${Math.min(l,100)}%`}})}),i.jsx("div",{className:"w-20 text-right font-mono text-sm",children:a.tokens_total.toLocaleString()})]},a.id)})})]})]})}function O0({summaries:e,height:t=350}){const n=k.useRef(null),[r,s]=k.useState(600),[a,l]=k.useState("tokens"),[o,u]=k.useState(null),[c,p]=k.useState({x:0,y:0});k.useEffect(()=>{const N=()=>{n.current&&s(n.current.clientWidth)};return N(),window.addEventListener("resize",N),()=>window.removeEventListener("resize",N)},[]);const f={top:30,right:30,bottom:50,left:60},h=r-f.left-f.right,x=t-f.top-f.bottom,w=N=>a==="tokens"?N.avg_tokens:N.avg_duration,{xScale:j,yScale:S,xTicks:v,yTicks:d,paretoLine:m}=k.useMemo(()=>{if(e.length===0||h<=0)return{xScale:()=>0,yScale:()=>0,xTicks:[],yTicks:[],paretoLine:[]};const N=e.map(w),C=e.map(O=>O.avg_score),b=Math.min(...N)*.9,z=Math.max(...N)*1.1,T=Math.min(...C,.5),V=Math.min(Math.max(...C)*1.05,1),F=O=>(O-b)/(z-b)*h,U=O=>x-(O-T)/(V-T)*x,te=Array.from({length:5},(O,D)=>b+D/4*(z-b)),ke=Array.from({length:5},(O,D)=>T+D/4*(V-T)),He=e.filter(O=>O.is_pareto).sort((O,D)=>w(O)-w(D)).map(O=>({x:F(w(O)),y:U(O.avg_score)}));return{xScale:F,yScale:U,xTicks:te,yTicks:ke,paretoLine:He}},[e,h,x,a]);if(e.length===0)return i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"No data to display"});const g=N=>a==="tokens"?N>=1e6?`${(N/1e6).toFixed(1)}M`:N>=1e3?`${(N/1e3).toFixed(0)}K`:N.toFixed(0):`${N.toFixed(1)}s`,_=(N,C)=>{var z;const b=(z=n.current)==null?void 0:z.getBoundingClientRect();b&&p({x:C.clientX-b.left,y:C.clientY-b.top}),u(N)};return i.jsxs("div",{ref:n,className:"w-full relative",children:[i.jsx("div",{className:"flex justify-end mb-2",children:i.jsxs("div",{className:"inline-flex rounded border border-[var(--border)] text-xs",children:[i.jsx("button",{className:`px-3 py-1 ${a==="tokens"?"bg-[var(--accent)] text-black":"text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,onClick:()=>l("tokens"),children:"Tokens"}),i.jsx("button",{className:`px-3 py-1 ${a==="duration"?"bg-[var(--accent)] text-black":"text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,onClick:()=>l("duration"),children:"Latency"})]})}),i.jsx("svg",{width:r,height:t,className:"font-mono text-xs",children:i.jsxs("g",{transform:`translate(${f.left}, ${f.top})`,children:[v.map((N,C)=>i.jsx("line",{x1:j(N),y1:0,x2:j(N),y2:x,stroke:"var(--border)",strokeDasharray:"2,2"},`x-grid-${C}`)),d.map((N,C)=>i.jsx("line",{x1:0,y1:S(N),x2:h,y2:S(N),stroke:"var(--border)",strokeDasharray:"2,2"},`y-grid-${C}`)),m.length>1&&i.jsx("polyline",{points:m.map(N=>`${N.x},${N.y}`).join(" "),fill:"none",stroke:"var(--accent)",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),e.slice().sort((N,C)=>(N.is_pareto?1:0)-(C.is_pareto?1:0)).map(N=>{const C=j(w(N)),b=S(N.avg_score),z=N.is_pareto,T=(o==null?void 0:o.candidate_name)===N.candidate_name;return i.jsxs("g",{onMouseEnter:V=>_(N,V),onMouseLeave:()=>u(null),children:[i.jsx("circle",{cx:C,cy:b,r:T?10:z?8:6,fill:z?"var(--accent)":"transparent",stroke:T?"var(--text-primary)":z?"var(--accent)":"var(--text-secondary)",strokeWidth:T?3:2,className:"cursor-pointer transition-all"}),z&&!T&&i.jsx("text",{x:C,y:b-12,textAnchor:"middle",fill:"var(--text-primary)",fontSize:10,className:"pointer-events-none",children:N.candidate_name.replace(/^baseline_/,"").slice(0,15)})]},N.candidate_name)}),i.jsx("line",{x1:0,y1:x,x2:h,y2:x,stroke:"var(--text-secondary)"}),v.map((N,C)=>i.jsxs("g",{transform:`translate(${j(N)}, ${x})`,children:[i.jsx("line",{y2:5,stroke:"var(--text-secondary)"}),i.jsx("text",{y:20,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:10,children:g(N)})]},`x-tick-${C}`)),i.jsx("text",{x:h/2,y:x+40,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:11,children:a==="tokens"?"Tokens (cost)":"Duration (latency)"}),i.jsx("line",{x1:0,y1:0,x2:0,y2:x,stroke:"var(--text-secondary)"}),d.map((N,C)=>i.jsxs("g",{transform:`translate(0, ${S(N)})`,children:[i.jsx("line",{x2:-5,stroke:"var(--text-secondary)"}),i.jsxs("text",{x:-10,textAnchor:"end",dominantBaseline:"middle",fill:"var(--text-secondary)",fontSize:10,children:[(N*100).toFixed(0),"%"]})]},`y-tick-${C}`)),i.jsx("text",{transform:`translate(-45, ${x/2}) rotate(-90)`,textAnchor:"middle",fill:"var(--text-secondary)",fontSize:11,children:"Score (quality)"})]})}),o&&i.jsxs("div",{className:"absolute z-10 bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-lg p-3 text-sm pointer-events-none",style:{left:Math.min(c.x+15,r-200),top:c.y-10,maxWidth:220},children:[i.jsx("div",{className:"font-medium text-[var(--text-primary)] truncate mb-2",children:o.candidate_name.replace(/^baseline_/,"")}),i.jsxs("div",{className:"grid grid-cols-2 gap-x-4 gap-y-1 text-xs",children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Score:"}),i.jsxs("span",{className:"text-right font-medium",children:[(o.avg_score*100).toFixed(1),"%"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Tokens:"}),i.jsxs("span",{className:"text-right",children:[(o.avg_tokens/1e3).toFixed(1),"K"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Duration:"}),i.jsxs("span",{className:"text-right",children:[o.avg_duration.toFixed(1),"s"]}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Pass rate:"}),i.jsxs("span",{className:"text-right",children:[o.passed_runs,"/",o.total_runs]})]}),o.is_pareto&&i.jsx("div",{className:"mt-2 pt-2 border-t border-[var(--border)] text-xs text-[var(--accent)]",children:"Pareto optimal"})]})]})}function R0(e=2e3){const[t,n]=k.useState(!1),[r,s]=k.useState(null),a=k.useCallback(async o=>{try{return await navigator.clipboard.writeText(o),n(!0),s(null),setTimeout(()=>n(!1),e),!0}catch{return s("Failed to copy to clipboard"),n(!1),!1}},[e]),l=k.useCallback(()=>{n(!1),s(null)},[]);return{copy:a,copied:t,error:r,reset:l}}function L0({isOpen:e,onClose:t,title:n,itemId:r,itemType:s,isPublic:a,createdByName:l,onTogglePublic:o}){const[u,c]=k.useState(!1),{copy:p,copied:f}=R0(),h=`${window.location.origin}/${s}s/${r}`,x=async()=>{c(!0);try{await o(!a)}finally{c(!1)}},w=()=>{p(h)};return i.jsx(ia,{isOpen:e,onClose:t,title:n,children:i.jsxs("div",{className:"space-y-4",children:[i.jsxs("div",{className:"flex items-center justify-between p-3 bg-[var(--bg-tertiary)] rounded",children:[i.jsxs("div",{className:"flex items-center gap-3",children:[a?i.jsx(zi,{className:"w-5 h-5 text-[var(--accent)]"}):i.jsx(xp,{className:"w-5 h-5 text-[var(--text-secondary)]"}),i.jsxs("div",{children:[i.jsx("div",{className:"font-medium",children:a?"Public":"Private"}),i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:a?"Anyone with the link can view":"Only you can access"})]})]}),i.jsx("button",{onClick:x,disabled:u,className:`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${a?"bg-[var(--accent)]":"bg-[var(--border)]"} ${u?"opacity-50 cursor-not-allowed":""}`,children:i.jsx("span",{className:`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${a?"translate-x-6":"translate-x-1"}`})})]}),a&&i.jsxs("div",{className:"space-y-2",children:[i.jsx("label",{className:"text-sm text-[var(--text-secondary)]",children:"Share link"}),i.jsxs("div",{className:"flex gap-2",children:[i.jsx("input",{type:"text",readOnly:!0,value:h,className:"flex-1 px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border)] rounded text-sm font-mono"}),i.jsx(H,{variant:"secondary",onClick:w,children:f?i.jsxs(i.Fragment,{children:[i.jsx(sg,{className:"w-4 h-4 mr-1"}),"Copied"]}):i.jsxs(i.Fragment,{children:[i.jsx(ug,{className:"w-4 h-4 mr-1"}),"Copy"]})})]})]}),l&&i.jsxs("div",{className:"text-sm text-[var(--text-secondary)]",children:["Created by ",i.jsx("span",{className:"text-[var(--text-primary)]",children:l})]}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)] pt-2 border-t border-[var(--border)]",children:a?i.jsxs(i.Fragment,{children:[i.jsxs("p",{children:["Public ",s,"s can be viewed by anyone with the link."]}),i.jsxs("p",{className:"mt-1",children:["Only you can edit or delete this ",s,"."]})]}):i.jsxs("p",{children:["Make this ",s," public to share it with others."]})})]})})}function M0({job:e,onUpdate:t}){const[n,r]=k.useState(!1),s=async a=>{await bt.update(e.id,{is_public:a}),t()};return i.jsxs(i.Fragment,{children:[i.jsxs(H,{variant:"secondary",size:"sm",onClick:()=>r(!0),title:e.is_public?"Sharing settings":"Share this job",children:[i.jsx(yg,{className:"w-4 h-4 mr-1"}),e.is_public?"Shared":"Share"]}),i.jsx(L0,{isOpen:n,onClose:()=>r(!1),title:"Share Job",itemId:e.id,itemType:"job",isPublic:e.is_public,createdByName:e.created_by_name,onTogglePublic:s})]})}function z0(){const{jobId:e}=Li(),t=Et(),n=Pn(),[r,s]=k.useState(null),[a,l]=k.useState(!1),[o,u]=k.useState(null),[c,p]=k.useState([]),[f,h]=k.useState(null),[x,w]=k.useState(null),[j,S]=k.useState("results"),[v,d]=k.useState("score"),[m,g]=k.useState("desc"),[_,N]=k.useState(!1),{data:C,isLoading:b}=Te({queryKey:["jobs",e],queryFn:()=>bt.get(e),enabled:!!e,refetchInterval:a?2e3:!1}),{data:z=[]}=Te({queryKey:["runs",e],queryFn:()=>Po.list({job_id:e}),enabled:!!e,refetchInterval:a?2e3:!1}),{data:T}=Te({queryKey:["job-summary",e],queryFn:()=>Po.getJobSummary(e),enabled:!!e&&(C==null?void 0:C.status)==="completed"}),V=st({mutationFn:async()=>{l(!0),p([]),h(null),w(null);for await(const P of bt.start(e))s(P),P.current_candidate&&P.current_task&&h($=>($&&($.candidate!==P.current_candidate||$.task!==P.current_task)&&p(fe=>[...fe,{candidate_name:$.candidate,task_name:$.task,completed_at:Date.now()}]),{candidate:P.current_candidate,task:P.current_task})),P.event==="error"&&(w(P.message),l(!1),n.invalidateQueries({queryKey:["jobs",e]})),P.event==="complete"&&(h($=>($&&p(fe=>[...fe,{candidate_name:$.candidate,task_name:$.task,completed_at:Date.now()}]),null)),l(!1),n.invalidateQueries({queryKey:["jobs",e]}),n.invalidateQueries({queryKey:["runs",e]}),n.invalidateQueries({queryKey:["job-summary",e]}))}}),F=st({mutationFn:()=>bt.cancel(e),onSuccess:()=>{l(!1),n.invalidateQueries({queryKey:["jobs",e]})}});k.useEffect(()=>{(C==null?void 0:C.status)==="running"&&l(!0)},[C==null?void 0:C.status]);const U=k.useMemo(()=>{const P=new Map;for(const $ of z)P.has($.task_name)||P.set($.task_name,[]),P.get($.task_name).push($);return P},[z]),te=k.useMemo(()=>Array.from(U.keys()),[U]),ke=k.useMemo(()=>{if(!(T!=null&&T.candidate_summaries))return[];let P=[...T.candidate_summaries];return _&&(P=P.filter($=>$.is_pareto)),P.sort(($,fe)=>{let ye,lt;switch(v){case"score":ye=$.avg_score,lt=fe.avg_score;break;case"tokens":ye=$.avg_tokens,lt=fe.avg_tokens;break;case"duration":ye=$.avg_duration,lt=fe.avg_duration;break;case"pass_rate":ye=$.passed_runs/$.total_runs,lt=fe.passed_runs/fe.total_runs;break}return m==="desc"?lt-ye:ye-lt}),P},[T,v,m,_]),Pt=P=>{v===P?g(m==="desc"?"asc":"desc"):(d(P),g(P==="tokens"||P==="duration"?"asc":"desc"))},He=({label:P,sortKeyVal:$})=>i.jsx("th",{className:"pb-2 cursor-pointer hover:text-[var(--text-primary)] select-none",onClick:()=>Pt($),children:i.jsxs("div",{className:"flex items-center gap-1",children:[P,v===$&&i.jsx(eg,{size:12,className:m==="asc"?"rotate-180":""})]})});if(b)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."});if(!C)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Job not found"});const O=Fu(),D=!C.user_id||C.user_id==="anonymous"||O&&C.created_by_name===O,B=C.is_public&&!D,R=()=>{n.invalidateQueries({queryKey:["jobs",e]})},A=P=>{const $={pending:"default",running:"info",completed:"success",failed:"error",cancelled:"warning"};return i.jsx(W,{variant:$[P]||"default",children:P})};return i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center justify-between mb-6",children:[i.jsxs("div",{children:[i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("button",{onClick:()=>t("/jobs"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Jobs"}),i.jsx("h2",{className:"text-xl font-bold",children:C.name||`Job ${C.id.slice(0,8)}`}),A(C.status),C.is_public&&i.jsxs(W,{variant:"info",children:[i.jsx(zi,{className:"w-3 h-3 mr-1 inline"}),"Public"]})]}),i.jsxs("div",{className:"flex items-center gap-3 mt-1",children:[i.jsxs("code",{className:"text-xs bg-[var(--bg-primary)] px-2 py-0.5 rounded font-mono text-[var(--text-secondary)]",children:[C.id.slice(0,8),"..."]}),i.jsxs("span",{className:"text-sm text-[var(--text-secondary)]",children:[C.candidate_ids.length," candidates × ",C.task_ids.length," tasks = ",C.total_experiments," experiments"]}),C.is_public&&C.created_by_name&&i.jsxs("span",{className:"text-sm text-[var(--text-secondary)]",children:["Created by ",i.jsx("span",{className:"text-[var(--text-primary)]",children:C.created_by_name})]})]})]}),i.jsxs("div",{className:"flex gap-2",children:[D&&i.jsx(M0,{job:C,onUpdate:R}),B&&i.jsx(W,{variant:"default",children:"View Only"}),D&&C.status==="pending"&&i.jsx(H,{variant:"primary",onClick:()=>V.mutate(),disabled:V.isPending,children:V.isPending?"Starting...":"Start"}),D&&C.status==="running"&&i.jsx(H,{variant:"danger",onClick:()=>F.mutate(),disabled:F.isPending,children:"Cancel"})]})]}),(x||C.error)&&i.jsx(ee,{className:"mb-6 border-red-500/50 bg-red-500/10",children:i.jsxs("div",{className:"flex items-start gap-3",children:[i.jsx("div",{className:"w-5 h-5 rounded-full bg-red-500 flex items-center justify-center text-white text-xs font-bold flex-shrink-0 mt-0.5",children:"!"}),i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium text-red-400",children:"Error"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:x||C.error})]})]})}),(a||r)&&i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsx("span",{className:"font-medium",children:"Progress"}),i.jsxs("span",{className:"text-[var(--accent)]",children:[(r==null?void 0:r.completed)||C.completed_experiments,"/",(r==null?void 0:r.total)||C.total_experiments]})]}),i.jsx("div",{className:"w-full bg-[var(--bg-primary)] h-2 mb-2",children:i.jsx("div",{className:"h-full bg-[var(--accent)] transition-all",style:{width:`${((r==null?void 0:r.completed)||C.completed_experiments)/((r==null?void 0:r.total)||C.total_experiments)*100}%`}})}),(r==null?void 0:r.message)&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:r.message}),a&&i.jsxs("div",{className:"mt-4 border-t border-[var(--border)] pt-4",children:[(r==null?void 0:r.current_candidate)&&(r==null?void 0:r.current_task)&&i.jsxs("div",{className:"mb-3",children:[i.jsx("span",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:"Currently Running"}),i.jsxs("div",{className:"flex items-center gap-2 mt-1 px-3 py-2 bg-blue-500/10 border border-blue-500/30 rounded",children:[i.jsx("div",{className:"w-2 h-2 bg-blue-400 rounded-full animate-pulse"}),i.jsx("span",{className:"font-medium",children:r.current_candidate}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{children:r.current_task})]})]}),c.length>0&&i.jsxs("div",{children:[i.jsxs("span",{className:"text-xs text-[var(--text-secondary)] uppercase tracking-wider",children:["Completed (",c.length,")"]}),i.jsx("div",{className:"mt-1 max-h-40 overflow-y-auto space-y-1",children:c.map((P,$)=>i.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 bg-green-500/10 border border-green-500/30 rounded text-sm",children:[i.jsx("div",{className:"w-2 h-2 bg-green-400 rounded-full"}),i.jsx("span",{className:"font-medium",children:P.candidate_name}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{children:P.task_name})]},`${P.candidate_name}-${P.task_name}-${$}`))})]})]})]}),(C.status==="completed"||z.length>0)&&i.jsxs("div",{className:"flex gap-1 mb-6 border-b border-[var(--border)]",children:[i.jsxs("button",{onClick:()=>S("results"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="results"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(tg,{size:16}),"Results"]}),i.jsxs("button",{onClick:()=>S("compare"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="compare"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(dg,{size:16}),"Compare"]}),i.jsxs("button",{onClick:()=>S("runs"),className:`flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 transition-colors ${j==="runs"?"border-[var(--accent)] text-[var(--accent)]":"border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,children:[i.jsx(mg,{size:16}),"Runs (",z.length,")"]})]}),j==="results"&&i.jsxs(i.Fragment,{children:[T&&T.candidate_summaries.length>1&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("div",{className:"flex items-start justify-between mb-4",children:i.jsxs("div",{children:[i.jsx("h3",{className:"font-medium",children:"Pareto Frontier"}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:["Candidates on the frontier (connected line) are ",i.jsx("strong",{children:"Pareto optimal"})," - no other candidate beats them on both score AND cost."]})]})}),i.jsxs("div",{className:"mb-4 p-3 bg-[var(--bg-primary)] rounded border border-[var(--border)] text-xs text-[var(--text-secondary)]",children:[i.jsx("strong",{className:"text-[var(--text-primary)]",children:"How Pareto optimal is calculated:"})," A candidate is Pareto optimal if there's no other candidate that has both a higher score AND lower cost. For example, if Candidate A has 95% score at 50K tokens and Candidate B has 90% score at 40K tokens, both are Pareto optimal - A is better on score, B is better on cost. But if Candidate C has 85% score at 60K tokens, it's ",i.jsx("em",{children:"not"})," Pareto optimal because B beats it on both metrics."]}),i.jsx(O0,{summaries:T.candidate_summaries,height:350})]}),T&&i.jsxs(ee,{className:"mb-6",children:[i.jsxs("div",{className:"flex items-center justify-between mb-4",children:[i.jsx("h3",{className:"font-medium",children:"Results Summary"}),i.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--text-secondary)] cursor-pointer",children:[i.jsx("input",{type:"checkbox",checked:_,onChange:P=>N(P.target.checked),className:"rounded border-[var(--border)]"}),"Pareto only"]})]}),i.jsx("div",{className:"overflow-x-auto",children:i.jsxs("table",{className:"w-full text-sm",children:[i.jsx("thead",{children:i.jsxs("tr",{className:"text-left text-[var(--text-secondary)] border-b border-[var(--border)]",children:[i.jsx("th",{className:"pb-2",children:"Candidate"}),i.jsx(He,{label:"Score",sortKeyVal:"score"}),i.jsx(He,{label:"Tokens",sortKeyVal:"tokens"}),i.jsx(He,{label:"Duration",sortKeyVal:"duration"}),i.jsx(He,{label:"Pass Rate",sortKeyVal:"pass_rate"}),i.jsx("th",{className:"pb-2",children:"Pareto"})]})}),i.jsx("tbody",{children:ke.map((P,$)=>i.jsxs("tr",{className:`border-b border-[var(--border)] ${$===0?"bg-[var(--accent)]/10":""}`,children:[i.jsxs("td",{className:"py-2 font-medium",children:[$===0&&i.jsx("span",{className:"text-[var(--accent)] mr-1",children:"#1"}),P.candidate_name.replace(/^baseline_/,"")]}),i.jsxs("td",{className:"py-2",children:[(P.avg_score*100).toFixed(1),"%"]}),i.jsxs("td",{className:"py-2",children:[(P.avg_tokens/1e3).toFixed(1),"K"]}),i.jsxs("td",{className:"py-2",children:[P.avg_duration.toFixed(1),"s"]}),i.jsxs("td",{className:"py-2",children:[P.passed_runs,"/",P.total_runs]}),i.jsx("td",{className:"py-2",children:P.is_pareto&&i.jsx(W,{variant:"success",children:"Pareto"})})]},P.candidate_name))})]})}),i.jsx("p",{className:"text-xs text-[var(--text-secondary)] mt-3",children:"Click column headers to sort. The #1 ranked candidate is highlighted based on your sort criteria."})]}),!T&&i.jsx(ee,{children:i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Results will appear here after the job completes.":"No results yet. Start the job to see results."})})]}),j==="compare"&&i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"Compare Candidates by Task"}),te.length>0?i.jsxs(i.Fragment,{children:[i.jsx("div",{className:"flex flex-wrap gap-2 mb-4",children:te.map(P=>i.jsx("button",{onClick:()=>u(o===P?null:P),className:`px-3 py-1 text-sm rounded border transition-colors ${o===P?"bg-[var(--accent)] text-white border-[var(--accent)]":"border-[var(--border)] hover:border-[var(--accent-dim)]"}`,children:P},P))}),o&&U.get(o)?i.jsx(T0,{runs:U.get(o).map(P=>({id:P.id,candidate_name:P.candidate_name,tokens_input:0,tokens_output:0,tokens_total:P.tokens_total,duration_seconds:P.duration_seconds,score:P.score,passed:P.passed,is_pareto:P.is_pareto}))}):i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:"Select a task above to compare how different candidates performed on it."})]}):i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Comparison data will appear here after runs complete.":"No runs yet. Start the job to compare candidates."})]}),j==="runs"&&i.jsxs(ee,{children:[i.jsx("h3",{className:"font-medium mb-4",children:"All Experiment Runs"}),z.length===0?i.jsx("div",{className:"text-center py-8 text-[var(--text-secondary)]",children:a?"Runs will appear here as they complete. See progress above for live status.":"No runs yet. Start the job to see results."}):i.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3",children:z.map(P=>i.jsxs("div",{className:"p-3 bg-[var(--bg-primary)] rounded border border-[var(--border)] cursor-pointer hover:border-[var(--accent-dim)] transition-colors",onClick:()=>t(`/runs/${P.id}`),children:[i.jsxs("div",{className:"flex items-center justify-between mb-2",children:[i.jsxs("span",{className:`text-lg font-bold ${P.passed?"text-green-400":"text-red-400"}`,children:[(P.score*100).toFixed(0),"%"]}),P.is_pareto&&i.jsx(W,{variant:"success",children:"Pareto"})]}),i.jsx("div",{className:"text-sm font-medium truncate",title:P.candidate_name,children:P.candidate_name.replace(/^baseline_/,"")}),i.jsx("div",{className:"text-xs text-[var(--text-secondary)] truncate",children:P.task_name}),i.jsxs("div",{className:"flex items-center gap-3 mt-2 text-xs text-[var(--text-secondary)]",children:[i.jsxs("span",{children:[(P.tokens_total/1e3).toFixed(1),"K tokens"]}),i.jsxs("span",{children:[P.duration_seconds.toFixed(1),"s"]})]})]},P.id))})]})]})}const hn={input:"bg-blue-500",output:"bg-emerald-500",inputText:"text-blue-400",outputText:"text-emerald-400"};function jd(e){return e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function wd({input:e,output:t,maxValue:n,height:r=24,showLabels:s=!0}){const a=e+t;if(a===0)return i.jsx("div",{className:"flex items-center gap-2 w-full",children:i.jsx("div",{className:"rounded bg-[var(--bg-primary)] flex-1",style:{height:`${r}px`}})});const l=n>0?a/n*100:100;return i.jsxs("div",{className:"flex items-center gap-3 w-full",children:[i.jsx("div",{className:"relative rounded overflow-hidden bg-[var(--bg-primary)] flex-1",style:{height:`${r}px`},children:i.jsxs("div",{className:"h-full flex transition-all duration-300",style:{width:`${l}%`},children:[i.jsx("div",{className:`h-full ${hn.input} transition-all`,style:{width:`${e/a*100}%`},title:`Input: ${e.toLocaleString()} tokens`}),i.jsx("div",{className:`h-full ${hn.output} transition-all`,style:{width:`${t/a*100}%`},title:`Output: ${t.toLocaleString()} tokens`})]})}),s&&i.jsxs("div",{className:"flex items-center gap-1 text-xs font-mono text-[var(--text-secondary)] min-w-[90px] justify-end",children:[i.jsxs("span",{className:hn.inputText,children:["↑",jd(e)]}),i.jsx("span",{children:"/"}),i.jsxs("span",{className:hn.outputText,children:["↓",jd(t)]})]})]})}function vl({label:e,value:t,color:n="default"}){const r={default:"text-[var(--text-primary)]",input:hn.inputText,output:hn.outputText}[n];return i.jsxs("div",{className:"flex-1 p-3 bg-[var(--bg-primary)] border border-[var(--border)] rounded",children:[i.jsx("div",{className:"text-xs text-[var(--text-secondary)] mb-1",children:e}),i.jsx("div",{className:`font-mono text-lg font-bold ${r}`,children:t})]})}function Mp({tokensInput:e,tokensOutput:t,tokensTotal:n,turns:r}){const s=n>0?Math.round(e/n*100):0,a=n>0?Math.round(t/n*100):0,l=k.useMemo(()=>{if(!r||r.length===0)return null;let u=0,c=0;return r.map(p=>(u+=p.input,c+=p.output,{input:u,output:c,total:u+c}))},[r]),o=l?Math.max(...l.map(u=>u.total)):n;return i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-4",children:"Token Usage"}),i.jsx("div",{className:"mb-4",children:i.jsx(wd,{input:e,output:t,maxValue:n,height:32})}),i.jsxs("div",{className:"flex items-center gap-6 text-xs mb-4",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("div",{className:`w-3 h-3 rounded ${hn.input}`}),i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["Input (",s,"%)"]})]}),i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("div",{className:`w-3 h-3 rounded ${hn.output}`}),i.jsxs("span",{className:"text-[var(--text-secondary)]",children:["Output (",a,"%)"]})]})]}),i.jsxs("div",{className:"flex gap-3 mb-4",children:[i.jsx(vl,{label:"Input Tokens",value:e.toLocaleString(),color:"input"}),i.jsx(vl,{label:"Output Tokens",value:t.toLocaleString(),color:"output"}),i.jsx(vl,{label:"Total Tokens",value:n.toLocaleString()})]}),l&&l.length>1&&i.jsxs("div",{className:"border-t border-[var(--border)] pt-4",children:[i.jsxs("h4",{className:"text-sm font-medium mb-3 text-[var(--text-secondary)]",children:["Token Accumulation (",r.length," turns)"]}),i.jsx("div",{className:"space-y-2",children:r.map((u,c)=>i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("div",{className:"w-6 h-6 rounded-full bg-[var(--bg-primary)] border border-[var(--border)] flex items-center justify-center text-xs font-medium",children:c+1}),i.jsx("div",{className:"flex-1",children:i.jsx(wd,{input:l[c].input,output:l[c].output,maxValue:o,height:16})})]},c))})]}),i.jsx("div",{className:"mt-4 text-xs text-[var(--text-secondary)] border-t border-[var(--border)] pt-3",children:"Token usage affects API cost. Input tokens are typically cheaper than output tokens."})]})}function F0(){const{runId:e}=Li(),t=Et(),{data:n,isLoading:r}=Te({queryKey:["runs",e],queryFn:()=>Po.get(e),enabled:!!e});return r?i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."}):n?i.jsxs("div",{children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t(`/jobs/${n.job_id}`),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Job"})}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:n.candidate_name}),i.jsx("span",{className:"text-[var(--text-secondary)]",children:"→"}),i.jsx("span",{className:"text-lg",children:n.task_name}),n.is_pareto&&i.jsx(W,{variant:"success",children:"Pareto Optimal"})]})]}),i.jsxs("div",{className:"grid grid-cols-4 gap-4 mb-6",children:[i.jsx(Ca,{label:"Score",value:`${(n.score*100).toFixed(1)}%`,status:n.passed?"success":"error"}),i.jsx(Ca,{label:"Total Tokens",value:n.tokens_total.toLocaleString()}),i.jsx(Ca,{label:"Duration",value:`${n.duration_seconds.toFixed(1)}s`}),i.jsx(Ca,{label:"Status",value:n.passed?"Passed":"Failed",status:n.passed?"success":"error"})]}),i.jsx(Mp,{tokensInput:n.tokens_input,tokensOutput:n.tokens_output,tokensTotal:n.tokens_total}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Evaluation"}),n.reasoning&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mb-4",children:n.reasoning}),n.criteria_results.length>0&&i.jsx("div",{className:"space-y-2",children:n.criteria_results.map(s=>i.jsx("div",{className:"flex items-start justify-between p-3 bg-[var(--bg-primary)] border border-[var(--border)]",children:i.jsxs("div",{className:"flex-1",children:[i.jsxs("div",{className:"flex items-center gap-2",children:[i.jsx("span",{className:"font-medium",children:s.name}),i.jsxs(W,{variant:s.passed?"success":"error",children:[(s.score*100).toFixed(0),"%"]})]}),s.reasoning&&i.jsx("p",{className:"text-sm text-[var(--text-secondary)] mt-1",children:s.reasoning})]})},s.name))})]}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Agent Output"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)]",children:n.output||"(no output)"})]}),n.files_created.length>0&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Files Created"}),i.jsx("div",{className:"space-y-1",children:n.files_created.map(s=>i.jsx("div",{className:"text-sm font-mono text-[var(--text-secondary)]",children:s},s))})]}),Object.keys(n.trace).length>0&&i.jsx(Tp,{trace:n.trace})]}):i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Run not found"})}function Ca({label:e,value:t,status:n}){const r={success:"text-green-400",error:"text-red-400"};return i.jsxs(ee,{children:[i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:e}),i.jsx("div",{className:`text-xl font-bold ${n?r[n]:""}`,children:t})]})}function I0(){const{testId:e}=Li(),t=Et(),{data:n,isLoading:r}=Te({queryKey:["tests",e],queryFn:()=>Ns.get(e),enabled:!!e}),{data:s}=Te({queryKey:["configs",n==null?void 0:n.agent_id],queryFn:()=>Jn.get(n.agent_id),enabled:!!(n!=null&&n.agent_id)});if(r)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Loading..."});if(!n)return i.jsx("div",{className:"text-[var(--text-secondary)]",children:"Test not found"});const a={completed:"success",failed:"error",running:"info",pending:"default",cancelled:"warning"};return i.jsxs("div",{children:[i.jsxs("div",{className:"mb-6",children:[i.jsx("div",{className:"flex items-center gap-3 mb-2",children:i.jsx("button",{onClick:()=>t("/agents"),className:"text-[var(--text-secondary)] hover:text-[var(--text-primary)]",children:"← Back to Agents"})}),i.jsxs("div",{className:"flex items-center gap-3",children:[i.jsx("h2",{className:"text-xl font-bold",children:"Test Run"}),s&&i.jsxs(i.Fragment,{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"•"}),i.jsx("span",{className:"text-lg",children:s.name})]}),i.jsx(W,{variant:a[n.status]||"default",children:n.status})]}),i.jsxs("p",{className:"text-sm text-[var(--text-secondary)] mt-1 font-mono",children:["ID: ",n.id.slice(0,8),"..."]})]}),i.jsxs("div",{className:"grid grid-cols-4 gap-4 mb-6",children:[i.jsx(ba,{label:"Duration",value:`${n.duration_seconds.toFixed(2)}s`}),i.jsx(ba,{label:"Total Tokens",value:n.tokens_total.toLocaleString()}),n.score!==null&&i.jsx(ba,{label:"Score",value:`${(n.score*100).toFixed(1)}%`,status:n.passed?"success":"error"}),i.jsx(ba,{label:"Status",value:n.status.charAt(0).toUpperCase()+n.status.slice(1),status:n.status==="completed"?"success":n.status==="failed"?"error":void 0})]}),i.jsx(Mp,{tokensInput:n.tokens_input,tokensOutput:n.tokens_output,tokensTotal:n.tokens_total}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Prompt"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)] font-mono",children:n.prompt})]}),n.error&&i.jsxs(ee,{className:"mb-6 border-red-500/30 bg-red-500/5",children:[i.jsx("h3",{className:"font-medium mb-3 text-red-400",children:"Error"}),i.jsx("pre",{className:"text-sm text-red-300 overflow-x-auto whitespace-pre-wrap",children:n.error})]}),n.reasoning&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Evaluation"}),i.jsx("p",{className:"text-sm text-[var(--text-secondary)]",children:n.reasoning})]}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Agent Output"}),i.jsx("pre",{className:"text-sm bg-[var(--bg-primary)] p-3 overflow-x-auto whitespace-pre-wrap border border-[var(--border)]",children:n.output||"(no output)"})]}),n.files_created&&n.files_created.length>0&&i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Files Created"}),i.jsx("div",{className:"space-y-1",children:n.files_created.map(l=>i.jsx("div",{className:"text-sm font-mono text-[var(--text-secondary)]",children:l},l))})]}),n.trace&&Object.keys(n.trace).length>0&&i.jsx(Tp,{trace:n.trace}),i.jsxs(ee,{className:"mb-6",children:[i.jsx("h3",{className:"font-medium mb-3",children:"Timestamps"}),i.jsxs("div",{className:"grid grid-cols-3 gap-4 text-sm",children:[i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Created:"}),i.jsx("div",{className:"font-mono",children:new Date(n.created_at).toLocaleString()})]}),n.started_at&&i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Started:"}),i.jsx("div",{className:"font-mono",children:new Date(n.started_at).toLocaleString()})]}),n.completed_at&&i.jsxs("div",{children:[i.jsx("span",{className:"text-[var(--text-secondary)]",children:"Completed:"}),i.jsx("div",{className:"font-mono",children:new Date(n.completed_at).toLocaleString()})]})]})]})]})}function ba({label:e,value:t,status:n}){const r={success:"text-green-400",error:"text-red-400"};return i.jsxs(ee,{children:[i.jsx("div",{className:"text-sm text-[var(--text-secondary)]",children:e}),i.jsx("div",{className:`text-xl font-bold ${n?r[n]:""}`,children:t})]})}function D0(){const{authConfig:e,isLoading:t,error:n,login:r,loginWithGitHub:s,clearError:a}=Iu(),[l,o]=k.useState(""),[u,c]=k.useState("");k.useEffect(()=>{n&&a()},[l,u]);const p=async h=>{h.preventDefault(),!(!l||!u)&&await r(l,u)},f=()=>{s()};return i.jsx("div",{className:"min-h-screen bg-[var(--bg-primary)] flex items-center justify-center p-4",children:i.jsxs("div",{className:"w-full max-w-md",children:[i.jsxs("div",{className:"text-center mb-8",children:[i.jsx("h1",{className:"text-2xl font-bold text-[var(--text-primary)] mb-2",children:"Flow"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:"Sign in to access the optimization dashboard"})]}),i.jsxs("div",{className:"bg-[var(--bg-secondary)] border border-[var(--border)] p-6 space-y-6",children:[n&&i.jsxs("div",{className:"flex items-start gap-3 p-3 bg-[var(--error)]/10 border border-[var(--error)]/20 text-[var(--error)]",children:[i.jsx(Zy,{size:18,className:"mt-0.5 flex-shrink-0"}),i.jsx("p",{className:"text-sm",children:n})]}),(e==null?void 0:e.mode)==="basic"&&i.jsxs("form",{onSubmit:p,className:"space-y-4",children:[i.jsxs("div",{className:"space-y-1",children:[i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:"Username"}),i.jsxs("div",{className:"relative",children:[i.jsx(wp,{size:16,className:"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)]"}),i.jsx("input",{type:"text",value:l,onChange:h=>o(h.target.value),className:"w-full bg-[var(--bg-primary)] border border-[var(--border)] pl-10 pr-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]",placeholder:"Enter username",autoComplete:"username",autoFocus:!0})]})]}),i.jsxs("div",{className:"space-y-1",children:[i.jsx("label",{className:"block text-sm text-[var(--text-secondary)]",children:"Password"}),i.jsxs("div",{className:"relative",children:[i.jsx(xp,{size:16,className:"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)]"}),i.jsx("input",{type:"password",value:u,onChange:h=>c(h.target.value),className:"w-full bg-[var(--bg-primary)] border border-[var(--border)] pl-10 pr-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]",placeholder:"Enter password",autoComplete:"current-password"})]})]}),i.jsx(H,{type:"submit",variant:"primary",className:"w-full justify-center",loading:t,disabled:!l||!u,children:"Sign In"})]}),(e==null?void 0:e.mode)==="github"&&i.jsxs("div",{className:"space-y-4",children:[i.jsx("p",{className:"text-sm text-[var(--text-secondary)] text-center",children:"Sign in with your GitHub account to continue"}),i.jsx(H,{onClick:f,variant:"secondary",className:"w-full justify-center",icon:fg,children:"Continue with GitHub"})]})]})]})})}function A0({children:e}){const{authConfig:t,isLoadingConfig:n,isAuthenticated:r,loadAuthConfig:s,handleOAuthCallback:a}=Iu();return k.useEffect(()=>{s()},[s]),k.useEffect(()=>{n||a()},[n,a]),n?i.jsx("div",{className:"min-h-screen bg-[var(--bg-primary)] flex items-center justify-center",children:i.jsxs("div",{className:"text-center",children:[i.jsx(Fi,{className:"w-8 h-8 animate-spin text-[var(--accent)] mx-auto mb-4"}),i.jsx("p",{className:"text-[var(--text-secondary)]",children:"Loading..."})]})}):t!=null&&t.enabled&&!r?i.jsx(D0,{}):i.jsx(i.Fragment,{children:e})}function $0(){return i.jsx(Hy,{children:i.jsx(A0,{children:i.jsx(Iy,{children:i.jsxs(gt,{path:"/",element:i.jsx(s0,{}),children:[i.jsx(gt,{index:!0,element:i.jsx(gd,{})}),i.jsx(gt,{path:"agents",element:i.jsx(gd,{})}),i.jsx(gt,{path:"agents/:agentId",element:i.jsx(w0,{})}),i.jsx(gt,{path:"tasks",element:i.jsx(N0,{})}),i.jsx(gt,{path:"jobs",element:i.jsx(b0,{})}),i.jsx(gt,{path:"jobs/:jobId",element:i.jsx(z0,{})}),i.jsx(gt,{path:"runs/:runId",element:i.jsx(F0,{})}),i.jsx(gt,{path:"tests/:testId",element:i.jsx(I0,{})})]})})})})}const kd=localStorage.getItem("flow-theme");if(kd)try{const{state:e}=JSON.parse(kd);e!=null&&e.theme&&document.documentElement.setAttribute("data-theme",e.theme)}catch{}const U0=new Rx({defaultOptions:{queries:{staleTime:5e3,refetchOnWindowFocus:!1}}});xl.createRoot(document.getElementById("root")).render(i.jsx(zo.StrictMode,{children:i.jsx(Lx,{client:U0,children:i.jsx($0,{})})})); diff --git a/src/flow/ui/ui/index.html b/src/flow/ui/ui/index.html index 5dd095ca721428a7c3dc08b23ab7f388c7d50aaa..1a8c3f143877ccef1f012c0183eba8ec85e8b580 100644 --- a/src/flow/ui/ui/index.html +++ b/src/flow/ui/ui/index.html @@ -8,8 +8,8 @@ - - + +
                              diff --git a/uv.lock b/uv.lock index 215106684c8057aced8677ab6d87b2a2b81b2a96..0a8611fbe1731d5aeb8a160f0ccf7370222c1f84 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,10 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" [[package]] name = "agent-framework-core" -version = "1.0.0b260116" +version = "1.0.0b260130" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-identity" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pydantic-settings" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/45/ba6cc2b61603bf64dff333131bdea2cbce31309f7da35c608e6182ec1695/agent_framework_core-1.0.0b260116.tar.gz", hash = "sha256:f8db80765a2460721ad1e752a78c63390d3ada174dc55b06b55771e46e619366", size = 300322, upload-time = "2026-01-16T21:31:50.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/39/e508e778219bd6d20e023a6f48235861a639e3cf888776f9e873bbad3c6b/agent_framework_core-1.0.0b260130.tar.gz", hash = "sha256:030a5b2ced796eec6839c2dabad90b4bd1ea33d1026f3ed1813050a56ccfa4ec", size = 301823, upload-time = "2026-01-30T19:01:09.629Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/35/b757c8b7446167ee20760b00c9b0f98ce9565d1e9fb1b86cba2ad3ecc7f8/agent_framework_core-1.0.0b260116-py3-none-any.whl", hash = "sha256:b8aecf5146e42e56fd01517f339db507290bd34ca907627042fdbbcbcc2ce435", size = 346138, upload-time = "2026-01-16T21:32:10.498Z" }, + { url = "https://files.pythonhosted.org/packages/36/68/afe66c72951a279e0fe048fd5af1e775528cde40dbdab8ec03b42c545df4/agent_framework_core-1.0.0b260130-py3-none-any.whl", hash = "sha256:75b4dd0ca2ae52574d406cf5c9ed7adf63e187379f72fce891743254d83dfd56", size = 348724, upload-time = "2026-01-30T18:56:47.15Z" }, ] [[package]] @@ -336,101 +336,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" }, - { url = "https://files.pythonhosted.org/packages/7e/45/7e6bdc94d89cd7c8017ce735cf50478ddfe765d4fbf0c24d71d30ea33d7a/coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", size = 219147, upload-time = "2025-12-28T15:40:12.069Z" }, - { url = "https://files.pythonhosted.org/packages/f7/38/0d6a258625fd7f10773fe94097dc16937a5f0e3e0cdf3adef67d3ac6baef/coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", size = 245894, upload-time = "2025-12-28T15:40:13.556Z" }, - { url = "https://files.pythonhosted.org/packages/27/58/409d15ea487986994cbd4d06376e9860e9b157cfbfd402b1236770ab8dd2/coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", size = 247721, upload-time = "2025-12-28T15:40:15.37Z" }, - { url = "https://files.pythonhosted.org/packages/da/bf/6e8056a83fd7a96c93341f1ffe10df636dd89f26d5e7b9ca511ce3bcf0df/coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", size = 249585, upload-time = "2025-12-28T15:40:17.226Z" }, - { url = "https://files.pythonhosted.org/packages/f4/15/e1daff723f9f5959acb63cbe35b11203a9df77ee4b95b45fffd38b318390/coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", size = 246597, upload-time = "2025-12-28T15:40:19.028Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/1efd31c5433743a6ddbc9d37ac30c196bb07c7eab3d74fbb99b924c93174/coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", size = 247626, upload-time = "2025-12-28T15:40:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9f/1609267dd3e749f57fdd66ca6752567d1c13b58a20a809dc409b263d0b5f/coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", size = 245629, upload-time = "2025-12-28T15:40:22.397Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f6/6815a220d5ec2466383d7cc36131b9fa6ecbe95c50ec52a631ba733f306a/coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", size = 245901, upload-time = "2025-12-28T15:40:23.836Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/40576554cd12e0872faf6d2c0eb3bc85f71d78427946ddd19ad65201e2c0/coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", size = 246505, upload-time = "2025-12-28T15:40:25.421Z" }, - { url = "https://files.pythonhosted.org/packages/3b/77/9233a90253fba576b0eee81707b5781d0e21d97478e5377b226c5b096c0f/coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", size = 221257, upload-time = "2025-12-28T15:40:27.217Z" }, - { url = "https://files.pythonhosted.org/packages/e0/43/e842ff30c1a0a623ec80db89befb84a3a7aad7bfe44a6ea77d5a3e61fedd/coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", size = 222191, upload-time = "2025-12-28T15:40:28.916Z" }, - { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, - { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, - { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, - { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, - { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, - { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, - { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, - { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, - { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, - { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, - { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, - { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, - { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, - { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, - { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, - { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, - { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, - { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, - { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, - { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, - { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, - { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +version = "7.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/2d/63e37369c8e81a643afe54f76073b020f7b97ddbe698c5c944b51b0a2bc5/coverage-7.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4af3b01763909f477ea17c962e2cca8f39b350a4e46e3a30838b2c12e31b81b", size = 218842, upload-time = "2026-01-25T12:57:15.3Z" }, + { url = "https://files.pythonhosted.org/packages/57/06/86ce882a8d58cbcb3030e298788988e618da35420d16a8c66dac34f138d0/coverage-7.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36393bd2841fa0b59498f75466ee9bdec4f770d3254f031f23e8fd8e140ffdd2", size = 219360, upload-time = "2026-01-25T12:57:17.572Z" }, + { url = "https://files.pythonhosted.org/packages/cd/84/70b0eb1ee19ca4ef559c559054c59e5b2ae4ec9af61398670189e5d276e9/coverage-7.13.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cc7573518b7e2186bd229b1a0fe24a807273798832c27032c4510f47ffdb896", size = 246123, upload-time = "2026-01-25T12:57:19.087Z" }, + { url = "https://files.pythonhosted.org/packages/35/fb/05b9830c2e8275ebc031e0019387cda99113e62bb500ab328bb72578183b/coverage-7.13.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca9566769b69a5e216a4e176d54b9df88f29d750c5b78dbb899e379b4e14b30c", size = 247930, upload-time = "2026-01-25T12:57:20.929Z" }, + { url = "https://files.pythonhosted.org/packages/81/aa/3f37858ca2eed4f09b10ca3c6ddc9041be0a475626cd7fd2712f4a2d526f/coverage-7.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c9bdea644e94fd66d75a6f7e9a97bb822371e1fe7eadae2cacd50fcbc28e4dc", size = 249804, upload-time = "2026-01-25T12:57:22.904Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b3/c904f40c56e60a2d9678a5ee8df3d906d297d15fb8bec5756c3b0a67e2df/coverage-7.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5bd447332ec4f45838c1ad42268ce21ca87c40deb86eabd59888859b66be22a5", size = 246815, upload-time = "2026-01-25T12:57:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/41/91/ddc1c5394ca7fd086342486440bfdd6b9e9bda512bf774599c7c7a0081e0/coverage-7.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7c79ad5c28a16a1277e1187cf83ea8dafdcc689a784228a7d390f19776db7c31", size = 247843, upload-time = "2026-01-25T12:57:26.544Z" }, + { url = "https://files.pythonhosted.org/packages/87/d2/cdff8f4cd33697883c224ea8e003e9c77c0f1a837dc41d95a94dd26aad67/coverage-7.13.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:76e06ccacd1fb6ada5d076ed98a8c6f66e2e6acd3df02819e2ee29fd637b76ad", size = 245850, upload-time = "2026-01-25T12:57:28.507Z" }, + { url = "https://files.pythonhosted.org/packages/f5/42/e837febb7866bf2553ab53dd62ed52f9bb36d60c7e017c55376ad21fbb05/coverage-7.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:49d49e9a5e9f4dc3d3dac95278a020afa6d6bdd41f63608a76fa05a719d5b66f", size = 246116, upload-time = "2026-01-25T12:57:30.16Z" }, + { url = "https://files.pythonhosted.org/packages/09/b1/4a3f935d7df154df02ff4f71af8d61298d713a7ba305d050ae475bfbdde2/coverage-7.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed2bce0e7bfa53f7b0b01c722da289ef6ad4c18ebd52b1f93704c21f116360c8", size = 246720, upload-time = "2026-01-25T12:57:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/538a6fd44c515f1c5197a3f078094cbaf2ce9f945df5b44e29d95c864bff/coverage-7.13.2-cp310-cp310-win32.whl", hash = "sha256:1574983178b35b9af4db4a9f7328a18a14a0a0ce76ffaa1c1bacb4cc82089a7c", size = 221465, upload-time = "2026-01-25T12:57:33.511Z" }, + { url = "https://files.pythonhosted.org/packages/5e/09/4b63a024295f326ec1a40ec8def27799300ce8775b1cbf0d33b1790605c4/coverage-7.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:a360a8baeb038928ceb996f5623a4cd508728f8f13e08d4e96ce161702f3dd99", size = 222397, upload-time = "2026-01-25T12:57:34.927Z" }, + { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, + { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, + { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, + { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, + { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, + { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, + { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, + { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, + { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, + { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, + { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, + { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, + { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, + { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, + { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, + { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, + { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f0/3d3eac7568ab6096ff23791a526b0048a1ff3f49d0e236b2af6fb6558e88/coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6", size = 219168, upload-time = "2026-01-25T12:58:23.376Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a6/f8b5cfeddbab95fdef4dcd682d82e5dcff7a112ced57a959f89537ee9995/coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc", size = 219537, upload-time = "2026-01-25T12:58:24.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/8d8e6e0c516c838229d1e41cadcec91745f4b1031d4db17ce0043a0423b4/coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f", size = 250528, upload-time = "2026-01-25T12:58:26.567Z" }, + { url = "https://files.pythonhosted.org/packages/8e/78/befa6640f74092b86961f957f26504c8fba3d7da57cc2ab7407391870495/coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1", size = 253132, upload-time = "2026-01-25T12:58:28.251Z" }, + { url = "https://files.pythonhosted.org/packages/9d/10/1630db1edd8ce675124a2ee0f7becc603d2bb7b345c2387b4b95c6907094/coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9", size = 254374, upload-time = "2026-01-25T12:58:30.294Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1d/0d9381647b1e8e6d310ac4140be9c428a0277330991e0c35bdd751e338a4/coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c", size = 250762, upload-time = "2026-01-25T12:58:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5636dfc9a7c871ee8776af83ee33b4c26bc508ad6cee1e89b6419a366582/coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5", size = 252502, upload-time = "2026-01-25T12:58:33.961Z" }, + { url = "https://files.pythonhosted.org/packages/02/2a/7ff2884d79d420cbb2d12fed6fff727b6d0ef27253140d3cdbbd03187ee0/coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4", size = 250463, upload-time = "2026-01-25T12:58:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/91/c0/ba51087db645b6c7261570400fc62c89a16278763f36ba618dc8657a187b/coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c", size = 250288, upload-time = "2026-01-25T12:58:37.226Z" }, + { url = "https://files.pythonhosted.org/packages/03/07/44e6f428551c4d9faf63ebcefe49b30e5c89d1be96f6a3abd86a52da9d15/coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31", size = 252063, upload-time = "2026-01-25T12:58:38.821Z" }, + { url = "https://files.pythonhosted.org/packages/c2/67/35b730ad7e1859dd57e834d1bc06080d22d2f87457d53f692fce3f24a5a9/coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8", size = 221716, upload-time = "2026-01-25T12:58:40.484Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/e5fcf5a97c72f45fc14829237a6550bf49d0ab882ac90e04b12a69db76b4/coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb", size = 222522, upload-time = "2026-01-25T12:58:43.247Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/25d7b2f946d239dd2d6644ca2cc060d24f97551e2af13b6c24c722ae5f97/coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557", size = 221145, upload-time = "2026-01-25T12:58:45Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f7/080376c029c8f76fadfe43911d0daffa0cbdc9f9418a0eead70c56fb7f4b/coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e", size = 219861, upload-time = "2026-01-25T12:58:46.586Z" }, + { url = "https://files.pythonhosted.org/packages/42/11/0b5e315af5ab35f4c4a70e64d3314e4eec25eefc6dec13be3a7d5ffe8ac5/coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7", size = 220207, upload-time = "2026-01-25T12:58:48.277Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0c/0874d0318fb1062117acbef06a09cf8b63f3060c22265adaad24b36306b7/coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3", size = 261504, upload-time = "2026-01-25T12:58:49.904Z" }, + { url = "https://files.pythonhosted.org/packages/83/5e/1cd72c22ecb30751e43a72f40ba50fcef1b7e93e3ea823bd9feda8e51f9a/coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3", size = 263582, upload-time = "2026-01-25T12:58:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/9b/da/8acf356707c7a42df4d0657020308e23e5a07397e81492640c186268497c/coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421", size = 266008, upload-time = "2026-01-25T12:58:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/41/41/ea1730af99960309423c6ea8d6a4f1fa5564b2d97bd1d29dda4b42611f04/coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5", size = 260762, upload-time = "2026-01-25T12:58:55.372Z" }, + { url = "https://files.pythonhosted.org/packages/22/fa/02884d2080ba71db64fdc127b311db60e01fe6ba797d9c8363725e39f4d5/coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23", size = 263571, upload-time = "2026-01-25T12:58:57.52Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6b/4083aaaeba9b3112f55ac57c2ce7001dc4d8fa3fcc228a39f09cc84ede27/coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c", size = 261200, upload-time = "2026-01-25T12:58:59.255Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d2/aea92fa36d61955e8c416ede9cf9bf142aa196f3aea214bb67f85235a050/coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f", size = 260095, upload-time = "2026-01-25T12:59:01.066Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ae/04ffe96a80f107ea21b22b2367175c621da920063260a1c22f9452fd7866/coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573", size = 262284, upload-time = "2026-01-25T12:59:02.802Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7a/6f354dcd7dfc41297791d6fb4e0d618acb55810bde2c1fd14b3939e05c2b/coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343", size = 222389, upload-time = "2026-01-25T12:59:04.563Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d5/080ad292a4a3d3daf411574be0a1f56d6dee2c4fdf6b005342be9fac807f/coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47", size = 223450, upload-time = "2026-01-25T12:59:06.677Z" }, + { url = "https://files.pythonhosted.org/packages/88/96/df576fbacc522e9fb8d1c4b7a7fc62eb734be56e2cba1d88d2eabe08ea3f/coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7", size = 221707, upload-time = "2026-01-25T12:59:08.363Z" }, + { url = "https://files.pythonhosted.org/packages/55/53/1da9e51a0775634b04fcc11eb25c002fc58ee4f92ce2e8512f94ac5fc5bf/coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef", size = 219213, upload-time = "2026-01-25T12:59:11.909Z" }, + { url = "https://files.pythonhosted.org/packages/46/35/b3caac3ebbd10230fea5a33012b27d19e999a17c9285c4228b4b2e35b7da/coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f", size = 219549, upload-time = "2026-01-25T12:59:13.638Z" }, + { url = "https://files.pythonhosted.org/packages/76/9c/e1cf7def1bdc72c1907e60703983a588f9558434a2ff94615747bd73c192/coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5", size = 250586, upload-time = "2026-01-25T12:59:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ba/49/f54ec02ed12be66c8d8897270505759e057b0c68564a65c429ccdd1f139e/coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4", size = 253093, upload-time = "2026-01-25T12:59:17.491Z" }, + { url = "https://files.pythonhosted.org/packages/fb/5e/aaf86be3e181d907e23c0f61fccaeb38de8e6f6b47aed92bf57d8fc9c034/coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27", size = 254446, upload-time = "2026-01-25T12:59:19.752Z" }, + { url = "https://files.pythonhosted.org/packages/28/c8/a5fa01460e2d75b0c853b392080d6829d3ca8b5ab31e158fa0501bc7c708/coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548", size = 250615, upload-time = "2026-01-25T12:59:21.928Z" }, + { url = "https://files.pythonhosted.org/packages/86/0b/6d56315a55f7062bb66410732c24879ccb2ec527ab6630246de5fe45a1df/coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660", size = 252452, upload-time = "2026-01-25T12:59:23.592Z" }, + { url = "https://files.pythonhosted.org/packages/30/19/9bc550363ebc6b0ea121977ee44d05ecd1e8bf79018b8444f1028701c563/coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92", size = 250418, upload-time = "2026-01-25T12:59:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/580530a31ca2f0cc6f07a8f2ab5460785b02bb11bdf815d4c4d37a4c5169/coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82", size = 250231, upload-time = "2026-01-25T12:59:27.888Z" }, + { url = "https://files.pythonhosted.org/packages/e2/42/dd9093f919dc3088cb472893651884bd675e3df3d38a43f9053656dca9a2/coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892", size = 251888, upload-time = "2026-01-25T12:59:29.636Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a6/0af4053e6e819774626e133c3d6f70fae4d44884bfc4b126cb647baee8d3/coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe", size = 221968, upload-time = "2026-01-25T12:59:31.424Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cc/5aff1e1f80d55862442855517bb8ad8ad3a68639441ff6287dde6a58558b/coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859", size = 222783, upload-time = "2026-01-25T12:59:33.118Z" }, + { url = "https://files.pythonhosted.org/packages/de/20/09abafb24f84b3292cc658728803416c15b79f9ee5e68d25238a895b07d9/coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6", size = 221348, upload-time = "2026-01-25T12:59:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/b6/60/a3820c7232db63be060e4019017cd3426751c2699dab3c62819cdbcea387/coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b", size = 219950, upload-time = "2026-01-25T12:59:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/fd/37/e4ef5975fdeb86b1e56db9a82f41b032e3d93a840ebaf4064f39e770d5c5/coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417", size = 220209, upload-time = "2026-01-25T12:59:38.339Z" }, + { url = "https://files.pythonhosted.org/packages/54/df/d40e091d00c51adca1e251d3b60a8b464112efa3004949e96a74d7c19a64/coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee", size = 261576, upload-time = "2026-01-25T12:59:40.446Z" }, + { url = "https://files.pythonhosted.org/packages/c5/44/5259c4bed54e3392e5c176121af9f71919d96dde853386e7730e705f3520/coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1", size = 263704, upload-time = "2026-01-25T12:59:42.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/ae9f005827abcbe2c70157459ae86053971c9fa14617b63903abbdce26d9/coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d", size = 266109, upload-time = "2026-01-25T12:59:44.073Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c0/8e279c1c0f5b1eaa3ad9b0fb7a5637fc0379ea7d85a781c0fe0bb3cfc2ab/coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6", size = 260686, upload-time = "2026-01-25T12:59:45.804Z" }, + { url = "https://files.pythonhosted.org/packages/b2/47/3a8112627e9d863e7cddd72894171c929e94491a597811725befdcd76bce/coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a", size = 263568, upload-time = "2026-01-25T12:59:47.929Z" }, + { url = "https://files.pythonhosted.org/packages/92/bc/7ea367d84afa3120afc3ce6de294fd2dcd33b51e2e7fbe4bbfd200f2cb8c/coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04", size = 261174, upload-time = "2026-01-25T12:59:49.717Z" }, + { url = "https://files.pythonhosted.org/packages/33/b7/f1092dcecb6637e31cc2db099581ee5c61a17647849bae6b8261a2b78430/coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f", size = 260017, upload-time = "2026-01-25T12:59:51.463Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cd/f3d07d4b95fbe1a2ef0958c15da614f7e4f557720132de34d2dc3aa7e911/coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f", size = 262337, upload-time = "2026-01-25T12:59:53.407Z" }, + { url = "https://files.pythonhosted.org/packages/e0/db/b0d5b2873a07cb1e06a55d998697c0a5a540dcefbf353774c99eb3874513/coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3", size = 222749, upload-time = "2026-01-25T12:59:56.316Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/838a5394c082ac57d85f57f6aba53093b30d9089781df72412126505716f/coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba", size = 223857, upload-time = "2026-01-25T12:59:58.201Z" }, + { url = "https://files.pythonhosted.org/packages/44/d4/b608243e76ead3a4298824b50922b89ef793e50069ce30316a65c1b4d7ef/coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c", size = 221881, upload-time = "2026-01-25T13:00:00.449Z" }, + { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, ] [package.optional-dependencies] @@ -440,67 +440,62 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.3" +version = "46.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, - { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" }, - { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, - { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, - { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, - { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" }, + { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" }, + { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" }, + { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" }, + { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" }, + { url = "https://files.pythonhosted.org/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" }, + { url = "https://files.pythonhosted.org/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" }, + { url = "https://files.pythonhosted.org/packages/b9/27/542b029f293a5cce59349d799d4d8484b3b1654a7b9a0585c266e974a488/cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908", size = 7116417, upload-time = "2026-01-28T00:23:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f5/ed3fcddd0a5e39321e595e144615399e47e7c153a1fb8c4862aec3151ff9/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085", size = 4926748, upload-time = "2026-01-28T00:23:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" }, + { url = "https://files.pythonhosted.org/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" }, + { url = "https://files.pythonhosted.org/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/17/b7/0f6b8c1dd0779df2b526e78978ff00462355e31c0a6f6cff8a3e99889c90/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e", size = 4891908, upload-time = "2026-01-28T00:23:46.48Z" }, + { url = "https://files.pythonhosted.org/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" }, + { url = "https://files.pythonhosted.org/packages/00/03/0de4ed43c71c31e4fe954edd50b9d28d658fef56555eba7641696370a8e2/cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061", size = 3001986, upload-time = "2026-01-28T00:23:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/5c/70/81830b59df7682917d7a10f833c4dab2a5574cd664e86d18139f2b421329/cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7", size = 3468288, upload-time = "2026-01-28T00:23:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" }, + { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" }, + { url = "https://files.pythonhosted.org/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" }, + { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" }, + { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" }, + { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" }, + { url = "https://files.pythonhosted.org/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" }, + { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" }, + { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" }, + { url = "https://files.pythonhosted.org/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" }, + { url = "https://files.pythonhosted.org/packages/59/e0/f9c6c53e1f2a1c2507f00f2faba00f01d2f334b35b0fbfe5286715da2184/cryptography-46.0.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:766330cce7416c92b5e90c3bb71b1b79521760cdcfc3a6a1a182d4c9fab23d2b", size = 3476316, upload-time = "2026-01-28T00:24:24.144Z" }, + { url = "https://files.pythonhosted.org/packages/27/7a/f8d2d13227a9a1a9fe9c7442b057efecffa41f1e3c51d8622f26b9edbe8f/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da", size = 4216693, upload-time = "2026-01-28T00:24:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/c5/de/3787054e8f7972658370198753835d9d680f6cd4a39df9f877b57f0dd69c/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80", size = 4382765, upload-time = "2026-01-28T00:24:27.577Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/60e0afb019973ba6a0b322e86b3d61edf487a4f5597618a430a2a15f2d22/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822", size = 4216066, upload-time = "2026-01-28T00:24:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/81/8e/bf4a0de294f147fee66f879d9bae6f8e8d61515558e3d12785dd90eca0be/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947", size = 4382025, upload-time = "2026-01-28T00:24:30.681Z" }, + { url = "https://files.pythonhosted.org/packages/79/f4/9ceb90cfd6a3847069b0b0b353fd3075dc69b49defc70182d8af0c4ca390/cryptography-46.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be8c01a7d5a55f9a47d1888162b76c8f49d62b234d88f0ff91a9fbebe32ffbc3", size = 3406043, upload-time = "2026-01-28T00:24:32.236Z" }, ] [[package]] @@ -576,6 +571,7 @@ dependencies = [ { name = "pyyaml" }, { name = "rich" }, { name = "sqlmodel" }, + { name = "tiktoken" }, { name = "typer" }, { name = "uvicorn" }, ] @@ -583,7 +579,11 @@ dependencies = [ [package.optional-dependencies] all = [ { name = "beautifulsoup4" }, + { name = "gepa" }, { name = "html2text" }, + { name = "langchain-core" }, + { name = "langchain-openai" }, + { name = "langgraph" }, ] dev = [ { name = "mypy" }, @@ -595,6 +595,14 @@ dev = [ { name = "pytest-cov" }, { name = "ruff" }, ] +langgraph = [ + { name = "langchain-core" }, + { name = "langchain-openai" }, + { name = "langgraph" }, +] +optimizer = [ + { name = "gepa" }, +] research = [ { name = "beautifulsoup4" }, { name = "html2text" }, @@ -602,14 +610,18 @@ research = [ [package.metadata] requires-dist = [ - { name = "agent-framework-core", specifier = ">=1.0.0b0" }, + { name = "agent-framework-core", specifier = ">=1.0.0b5" }, { name = "aiosqlite", specifier = ">=0.19.0" }, { name = "azure-identity", specifier = ">=1.15.0" }, { name = "beautifulsoup4", marker = "extra == 'research'", specifier = ">=4.12.0" }, { name = "fastapi", specifier = ">=0.109.0" }, - { name = "flow-agent", extras = ["research"], marker = "extra == 'all'" }, + { name = "flow-agent", extras = ["research", "langgraph", "optimizer"], marker = "extra == 'all'" }, + { name = "gepa", marker = "extra == 'optimizer'", specifier = ">=0.0.20" }, { name = "html2text", marker = "extra == 'research'", specifier = ">=2024.2.26" }, { name = "httpx", specifier = ">=0.25.0" }, + { name = "langchain-core", marker = "extra == 'langgraph'", specifier = ">=0.3.0" }, + { name = "langchain-openai", marker = "extra == 'langgraph'", specifier = ">=0.2.0" }, + { name = "langgraph", marker = "extra == 'langgraph'", specifier = ">=0.2.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.8.0" }, { name = "opentelemetry-api", specifier = ">=1.20.0" }, { name = "opentelemetry-sdk", specifier = ">=1.20.0" }, @@ -627,10 +639,20 @@ requires-dist = [ { name = "rich", specifier = ">=13.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.2.0" }, { name = "sqlmodel", specifier = ">=0.0.14" }, + { name = "tiktoken", specifier = ">=0.12.0" }, { name = "typer", specifier = ">=0.9.0" }, { name = "uvicorn", specifier = ">=0.27.0" }, ] -provides-extras = ["research", "all", "dev"] +provides-extras = ["research", "langgraph", "optimizer", "all", "dev"] + +[[package]] +name = "gepa" +version = "0.0.27" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/99/6840f84498f2dcbfd27e8a15eeb4e637e84e9dbbd6331977b71f6ffad9c7/gepa-0.0.27.tar.gz", hash = "sha256:02ecb19e4aa6a1f5bb2994cd54b2057f25cd5e8cd662fee514303b2a8ba0a738", size = 155106, upload-time = "2026-01-28T00:33:51.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/bd/f9e83519099a9fd5d090520ab0c51a6278e473b914a9b32d1e812662dd3b/gepa-0.0.27-py3-none-any.whl", hash = "sha256:592beb084fd638d525edae84ca75ad8e9e9c3758e5498b933c17153addf5dd2d", size = 146454, upload-time = "2026-01-28T00:33:50.262Z" }, +] [[package]] name = "greenlet" @@ -641,7 +663,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/65/5b235b40581ad75ab97dcd8b4218022ae8e3ab77c13c919f1a1dfe9171fd/greenlet-3.3.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:04bee4775f40ecefcdaa9d115ab44736cd4b9c5fba733575bfe9379419582e13", size = 273723, upload-time = "2026-01-23T15:30:37.521Z" }, { url = "https://files.pythonhosted.org/packages/ce/ad/eb4729b85cba2d29499e0a04ca6fbdd8f540afd7be142fd571eea43d712f/greenlet-3.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e1457f4fed12a50e427988a07f0f9df53cf0ee8da23fab16e6732c2ec909d4", size = 574874, upload-time = "2026-01-23T16:00:54.551Z" }, { url = "https://files.pythonhosted.org/packages/87/32/57cad7fe4c8b82fdaa098c89498ef85ad92dfbb09d5eb713adedfc2ae1f5/greenlet-3.3.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:070472cd156f0656f86f92e954591644e158fd65aa415ffbe2d44ca77656a8f5", size = 586309, upload-time = "2026-01-23T16:05:25.18Z" }, - { url = "https://files.pythonhosted.org/packages/66/66/f041005cb87055e62b0d68680e88ec1a57f4688523d5e2fb305841bc8307/greenlet-3.3.1-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1108b61b06b5224656121c3c8ee8876161c491cbe74e5c519e0634c837cf93d5", size = 597461, upload-time = "2026-01-23T16:15:51.943Z" }, { url = "https://files.pythonhosted.org/packages/87/eb/8a1ec2da4d55824f160594a75a9d8354a5fe0a300fb1c48e7944265217e1/greenlet-3.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a300354f27dd86bae5fbf7002e6dd2b3255cd372e9242c933faf5e859b703fe", size = 586985, upload-time = "2026-01-23T15:32:47.968Z" }, { url = "https://files.pythonhosted.org/packages/15/1c/0621dd4321dd8c351372ee8f9308136acb628600658a49be1b7504208738/greenlet-3.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e84b51cbebf9ae573b5fbd15df88887815e3253fc000a7d0ff95170e8f7e9729", size = 1547271, upload-time = "2026-01-23T16:04:18.977Z" }, { url = "https://files.pythonhosted.org/packages/9d/53/24047f8924c83bea7a59c8678d9571209c6bfe5f4c17c94a78c06024e9f2/greenlet-3.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0093bd1a06d899892427217f0ff2a3c8f306182b8c754336d32e2d587c131b4", size = 1613427, upload-time = "2026-01-23T15:33:44.428Z" }, @@ -649,7 +670,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/e8/2e1462c8fdbe0f210feb5ac7ad2d9029af8be3bf45bd9fa39765f821642f/greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c", size = 274974, upload-time = "2026-01-23T15:31:02.891Z" }, { url = "https://files.pythonhosted.org/packages/7e/a8/530a401419a6b302af59f67aaf0b9ba1015855ea7e56c036b5928793c5bd/greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd", size = 577175, upload-time = "2026-01-23T16:00:56.213Z" }, { url = "https://files.pythonhosted.org/packages/8e/89/7e812bb9c05e1aaef9b597ac1d0962b9021d2c6269354966451e885c4e6b/greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5", size = 590401, upload-time = "2026-01-23T16:05:26.365Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/e2d5f0e59b94a2269b68a629173263fa40b63da32f5c231307c349315871/greenlet-3.3.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67ea3fc73c8cd92f42467a72b75e8f05ed51a0e9b1d15398c913416f2dafd49f", size = 601161, upload-time = "2026-01-23T16:15:53.456Z" }, { url = "https://files.pythonhosted.org/packages/5c/ae/8d472e1f5ac5efe55c563f3eabb38c98a44b832602e12910750a7c025802/greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2", size = 590272, upload-time = "2026-01-23T15:32:49.411Z" }, { url = "https://files.pythonhosted.org/packages/a8/51/0fde34bebfcadc833550717eade64e35ec8738e6b097d5d248274a01258b/greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9", size = 1550729, upload-time = "2026-01-23T16:04:20.867Z" }, { url = "https://files.pythonhosted.org/packages/16/c9/2fb47bee83b25b119d5a35d580807bb8b92480a54b68fef009a02945629f/greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f", size = 1615552, upload-time = "2026-01-23T15:33:45.743Z" }, @@ -658,7 +678,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, - { url = "https://files.pythonhosted.org/packages/3b/cd/7a7ca57588dac3389e97f7c9521cb6641fd8b6602faf1eaa4188384757df/greenlet-3.3.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca", size = 622363, upload-time = "2026-01-23T16:15:54.754Z" }, { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, @@ -667,7 +686,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, - { url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" }, { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, @@ -676,7 +694,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, - { url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" }, { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, @@ -685,7 +702,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, - { url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" }, { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, @@ -788,99 +804,120 @@ wheels = [ [[package]] name = "jiter" -version = "0.12.0" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" }, + { url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" }, + { url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" }, + { url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, + { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, + { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, + { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, + { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, + { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, + { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, + { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/91/13cb9505f7be74a933f37da3af22e029f6ba64f5669416cb8b2774bc9682/jiter-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7acbaba9703d5de82a2c98ae6a0f59ab9770ab5af5fa35e43a303aee962cf65", size = 316652, upload-time = "2025-11-09T20:46:41.021Z" }, - { url = "https://files.pythonhosted.org/packages/4e/76/4e9185e5d9bb4e482cf6dec6410d5f78dfeb374cfcecbbe9888d07c52daa/jiter-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:364f1a7294c91281260364222f535bc427f56d4de1d8ffd718162d21fbbd602e", size = 319829, upload-time = "2025-11-09T20:46:43.281Z" }, - { url = "https://files.pythonhosted.org/packages/86/af/727de50995d3a153138139f259baae2379d8cb0522c0c00419957bc478a6/jiter-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ee4d25805d4fb23f0a5167a962ef8e002dbfb29c0989378488e32cf2744b62", size = 350568, upload-time = "2025-11-09T20:46:45.075Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c1/d6e9f4b7a3d5ac63bcbdfddeb50b2dcfbdc512c86cffc008584fdc350233/jiter-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:796f466b7942107eb889c08433b6e31b9a7ed31daceaecf8af1be26fb26c0ca8", size = 369052, upload-time = "2025-11-09T20:46:46.818Z" }, - { url = "https://files.pythonhosted.org/packages/eb/be/00824cd530f30ed73fa8a4f9f3890a705519e31ccb9e929f1e22062e7c76/jiter-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35506cb71f47dba416694e67af996bbdefb8e3608f1f78799c2e1f9058b01ceb", size = 481585, upload-time = "2025-11-09T20:46:48.319Z" }, - { url = "https://files.pythonhosted.org/packages/74/b6/2ad7990dff9504d4b5052eef64aa9574bd03d722dc7edced97aad0d47be7/jiter-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:726c764a90c9218ec9e4f99a33d6bf5ec169163f2ca0fc21b654e88c2abc0abc", size = 380541, upload-time = "2025-11-09T20:46:49.643Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c7/f3c26ecbc1adbf1db0d6bba99192143d8fe8504729d9594542ecc4445784/jiter-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa47810c5565274810b726b0dc86d18dce5fd17b190ebdc3890851d7b2a0e74", size = 364423, upload-time = "2025-11-09T20:46:51.731Z" }, - { url = "https://files.pythonhosted.org/packages/18/51/eac547bf3a2d7f7e556927278e14c56a0604b8cddae75815d5739f65f81d/jiter-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ec0259d3f26c62aed4d73b198c53e316ae11f0f69c8fbe6682c6dcfa0fcce2", size = 389958, upload-time = "2025-11-09T20:46:53.432Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1f/9ca592e67175f2db156cff035e0d817d6004e293ee0c1d73692d38fcb596/jiter-0.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:79307d74ea83465b0152fa23e5e297149506435535282f979f18b9033c0bb025", size = 522084, upload-time = "2025-11-09T20:46:54.848Z" }, - { url = "https://files.pythonhosted.org/packages/83/ff/597d9cdc3028f28224f53e1a9d063628e28b7a5601433e3196edda578cdd/jiter-0.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf6e6dd18927121fec86739f1a8906944703941d000f0639f3eb6281cc601dca", size = 513054, upload-time = "2025-11-09T20:46:56.487Z" }, - { url = "https://files.pythonhosted.org/packages/24/6d/1970bce1351bd02e3afcc5f49e4f7ef3dabd7fb688f42be7e8091a5b809a/jiter-0.12.0-cp310-cp310-win32.whl", hash = "sha256:b6ae2aec8217327d872cbfb2c1694489057b9433afce447955763e6ab015b4c4", size = 206368, upload-time = "2025-11-09T20:46:58.638Z" }, - { url = "https://files.pythonhosted.org/packages/e3/6b/eb1eb505b2d86709b59ec06681a2b14a94d0941db091f044b9f0e16badc0/jiter-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c7f49ce90a71e44f7e1aa9e7ec415b9686bbc6a5961e57eab511015e6759bc11", size = 204847, upload-time = "2025-11-09T20:47:00.295Z" }, - { url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435, upload-time = "2025-11-09T20:47:02.087Z" }, - { url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548, upload-time = "2025-11-09T20:47:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915, upload-time = "2025-11-09T20:47:05.171Z" }, - { url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966, upload-time = "2025-11-09T20:47:06.508Z" }, - { url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047, upload-time = "2025-11-09T20:47:08.382Z" }, - { url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835, upload-time = "2025-11-09T20:47:09.81Z" }, - { url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587, upload-time = "2025-11-09T20:47:11.529Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492, upload-time = "2025-11-09T20:47:12.993Z" }, - { url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046, upload-time = "2025-11-09T20:47:14.6Z" }, - { url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392, upload-time = "2025-11-09T20:47:16.011Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096, upload-time = "2025-11-09T20:47:17.344Z" }, - { url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899, upload-time = "2025-11-09T20:47:19.365Z" }, - { url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070, upload-time = "2025-11-09T20:47:20.75Z" }, - { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" }, - { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" }, - { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" }, - { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" }, - { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" }, - { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" }, - { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" }, - { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658, upload-time = "2025-11-09T20:47:44.424Z" }, - { url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605, upload-time = "2025-11-09T20:47:45.973Z" }, - { url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803, upload-time = "2025-11-09T20:47:47.535Z" }, - { url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120, upload-time = "2025-11-09T20:47:49.284Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918, upload-time = "2025-11-09T20:47:50.807Z" }, - { url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008, upload-time = "2025-11-09T20:47:52.211Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785, upload-time = "2025-11-09T20:47:53.512Z" }, - { url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108, upload-time = "2025-11-09T20:47:54.893Z" }, - { url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937, upload-time = "2025-11-09T20:47:56.253Z" }, - { url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853, upload-time = "2025-11-09T20:47:58.32Z" }, - { url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699, upload-time = "2025-11-09T20:47:59.686Z" }, - { url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258, upload-time = "2025-11-09T20:48:01.01Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503, upload-time = "2025-11-09T20:48:02.35Z" }, - { url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965, upload-time = "2025-11-09T20:48:03.783Z" }, - { url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831, upload-time = "2025-11-09T20:48:05.55Z" }, - { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" }, - { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" }, - { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" }, - { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" }, - { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" }, - { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" }, - { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" }, - { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" }, - { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" }, - { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" }, - { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" }, - { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" }, - { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" }, - { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" }, - { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" }, - { url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212, upload-time = "2025-11-09T20:49:15.643Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" }, - { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" }, - { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" }, +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] [[package]] @@ -910,6 +947,115 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] +[[package]] +name = "langchain-core" +version = "1.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/cc/55bf57b83cbc164cbf84cbf0c5e4fb640d673546af131db70797b97b125b/langchain_core-1.2.8.tar.gz", hash = "sha256:76d933c3f4cfd8484d8131c39bf25f562e2df4d0d5fe3218e05ff773210713b6", size = 814506, upload-time = "2026-02-02T15:35:33.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/d4/37fef9639b701c1fb1eea9e68447b72d86852ca3dc3253cdfd9c0afe228d/langchain_core-1.2.8-py3-none-any.whl", hash = "sha256:c732301272d63cfbcd75d114540257678627878f11b87046241272a25ba12ea7", size = 495753, upload-time = "2026-02-02T15:35:31.284Z" }, +] + +[[package]] +name = "langchain-openai" +version = "1.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "openai" }, + { name = "tiktoken" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/b7/30bfc4d1b658a9ee524bcce3b0b2ec9c45a11c853a13c4f0c9da9882784b/langchain_openai-1.1.7.tar.gz", hash = "sha256:f5ec31961ed24777548b63a5fe313548bc6e0eb9730d6552b8c6418765254c81", size = 1039134, upload-time = "2026-01-07T19:44:59.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/a1/50e7596aca775d8c3883eceeaf47489fac26c57c1abe243c00174f715a8a/langchain_openai-1.1.7-py3-none-any.whl", hash = "sha256:34e9cd686aac1a120d6472804422792bf8080a2103b5d21ee450c9e42d053815", size = 84753, upload-time = "2026-01-07T19:44:58.629Z" }, +] + +[[package]] +name = "langgraph" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/5b/f72655717c04e33d3b62f21b166dc063d192b53980e9e3be0e2a117f1c9f/langgraph-1.0.7.tar.gz", hash = "sha256:0cfdfee51e6e8cfe503ecc7367c73933437c505b03fa10a85c710975c8182d9a", size = 497098, upload-time = "2026-01-22T16:57:47.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/0e/fe80144e3e4048e5d19ccdb91ac547c1a7dc3da8dbd1443e210048194c14/langgraph-1.0.7-py3-none-any.whl", hash = "sha256:9d68e8f8dd8f3de2fec45f9a06de05766d9b075b78fb03171779893b7a52c4d2", size = 157353, upload-time = "2026-01-22T16:57:45.997Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/76/55a18c59dedf39688d72c4b06af73a5e3ea0d1a01bc867b88fbf0659f203/langgraph_checkpoint-4.0.0.tar.gz", hash = "sha256:814d1bd050fac029476558d8e68d87bce9009a0262d04a2c14b918255954a624", size = 137320, upload-time = "2026-01-12T20:30:26.38Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/de/ddd53b7032e623f3c7bcdab2b44e8bf635e468f62e10e5ff1946f62c9356/langgraph_checkpoint-4.0.0-py3-none-any.whl", hash = "sha256:3fa9b2635a7c5ac28b338f631abf6a030c3b508b7b9ce17c22611513b589c784", size = 46329, upload-time = "2026-01-12T20:30:25.2Z" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/59/711aecd1a50999456850dc328f3cad72b4372d8218838d8d5326f80cb76f/langgraph_prebuilt-1.0.7.tar.gz", hash = "sha256:38e097e06de810de4d0e028ffc0e432bb56d1fb417620fb1dfdc76c5e03e4bf9", size = 163692, upload-time = "2026-01-22T16:45:22.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/49/5e37abb3f38a17a3487634abc2a5da87c208cc1d14577eb8d7184b25c886/langgraph_prebuilt-1.0.7-py3-none-any.whl", hash = "sha256:e14923516504405bb5edc3977085bc9622c35476b50c1808544490e13871fe7c", size = 35324, upload-time = "2026-01-22T16:45:21.784Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/0f/ed0634c222eed48a31ba48eab6881f94ad690d65e44fe7ca838240a260c1/langgraph_sdk-0.3.3.tar.gz", hash = "sha256:c34c3dce3b6848755eb61f0c94369d1ba04aceeb1b76015db1ea7362c544fb26", size = 130589, upload-time = "2026-01-13T00:30:43.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/be/4ad511bacfdd854afb12974f407cb30010dceb982dc20c55491867b34526/langgraph_sdk-0.3.3-py3-none-any.whl", hash = "sha256:a52ebaf09d91143e55378bb2d0b033ed98f57f48c9ad35c8f81493b88705fc7b", size = 67021, upload-time = "2026-01-13T00:30:42.264Z" }, +] + +[[package]] +name = "langsmith" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "uuid-utils" }, + { name = "xxhash" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/bb/b8a196c9b9a7ca8b8845eaec7dbae35bcfcb0da3068794e76b29211eae2b/langsmith-0.6.7.tar.gz", hash = "sha256:d89c604a18fc606b7835d8e7924f7cdbe130ca2207bdff8f989590e50d65b802", size = 963940, upload-time = "2026-01-31T02:10:34.069Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/9a/5bc17ea3c746363e73c11df5e89068fea1ed175ca9c00fdb6886efc35b21/langsmith-0.6.7-py3-none-any.whl", hash = "sha256:4bd4372b8bf724b86314f64644562b5598407614e04e74b536c09490d153bd61", size = 309369, upload-time = "2026-01-31T02:10:32.6Z" }, +] + [[package]] name = "librt" version = "0.7.8" @@ -997,7 +1143,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.25.0" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1015,9 +1161,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/2d/649d80a0ecf6a1f82632ca44bec21c0461a9d9fc8934d38cb5b319f2db5e/mcp-1.25.0.tar.gz", hash = "sha256:56310361ebf0364e2d438e5b45f7668cbb124e158bb358333cd06e49e83a6802", size = 605387, upload-time = "2025-12-19T10:19:56.985Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/fc/6dc7659c2ae5ddf280477011f4213a74f806862856b796ef08f028e664bf/mcp-1.25.0-py3-none-any.whl", hash = "sha256:b37c38144a666add0862614cc79ec276e97d72aa8ca26d622818d4e278b9721a", size = 233076, upload-time = "2025-12-19T10:19:55.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, ] [package.optional-dependencies] @@ -1126,7 +1272,7 @@ wheels = [ [[package]] name = "openai" -version = "2.15.0" +version = "2.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1138,9 +1284,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/f4/4690ecb5d70023ce6bfcfeabfe717020f654bde59a775058ec6ac4692463/openai-2.15.0.tar.gz", hash = "sha256:42eb8cbb407d84770633f31bf727d4ffb4138711c670565a41663d9439174fba", size = 627383, upload-time = "2026-01-09T22:10:08.603Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/6c/e4c964fcf1d527fdf4739e7cc940c60075a4114d50d03871d5d5b1e13a88/openai-2.16.0.tar.gz", hash = "sha256:42eaa22ca0d8ded4367a77374104d7a2feafee5bd60a107c3c11b5243a11cd12", size = 629649, upload-time = "2026-01-27T23:28:02.579Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/df/c306f7375d42bafb379934c2df4c2fa3964656c8c782bac75ee10c102818/openai-2.15.0-py3-none-any.whl", hash = "sha256:6ae23b932cd7230f7244e52954daa6602716d6b9bf235401a107af731baea6c3", size = 1067879, upload-time = "2026-01-09T22:10:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/16/83/0315bf2cfd75a2ce8a7e54188e9456c60cec6c0cf66728ed07bd9859ff26/openai-2.16.0-py3-none-any.whl", hash = "sha256:5f46643a8f42899a84e80c38838135d7038e7718333ce61396994f887b09a59b", size = 1068612, upload-time = "2026-01-27T23:28:00.356Z" }, ] [[package]] @@ -1192,13 +1338,150 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/b5/cf25da2218910f0d6cdf7f876a06bed118c4969eacaf60a887cbaef44f44/opentelemetry_semantic_conventions_ai-0.4.13-py3-none-any.whl", hash = "sha256:883a30a6bb5deaec0d646912b5f9f6dcbb9f6f72557b73d0f2560bf25d13e2d5", size = 6080, upload-time = "2025-08-22T10:14:16.477Z" }, ] +[[package]] +name = "orjson" +version = "3.11.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1a/a373746fa6d0e116dd9e54371a7b54622c44d12296d5d0f3ad5e3ff33490/orjson-3.11.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a02c833f38f36546ba65a452127633afce4cf0dd7296b753d3bb54e55e5c0174", size = 229140, upload-time = "2026-02-02T15:37:06.082Z" }, + { url = "https://files.pythonhosted.org/packages/52/a2/fa129e749d500f9b183e8a3446a193818a25f60261e9ce143ad61e975208/orjson-3.11.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63c6e6738d7c3470ad01601e23376aa511e50e1f3931395b9f9c722406d1a67", size = 128670, upload-time = "2026-02-02T15:37:08.002Z" }, + { url = "https://files.pythonhosted.org/packages/08/93/1e82011cd1e0bd051ef9d35bed1aa7fb4ea1f0a055dc2c841b46b43a9ebd/orjson-3.11.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043d3006b7d32c7e233b8cfb1f01c651013ea079e08dcef7189a29abd8befe11", size = 123832, upload-time = "2026-02-02T15:37:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d8/a26b431ef962c7d55736674dddade876822f3e33223c1f47a36879350d04/orjson-3.11.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57036b27ac8a25d81112eb0cc9835cd4833c5b16e1467816adc0015f59e870dc", size = 129171, upload-time = "2026-02-02T15:37:11.112Z" }, + { url = "https://files.pythonhosted.org/packages/a7/19/f47819b84a580f490da260c3ee9ade214cf4cf78ac9ce8c1c758f80fdfc9/orjson-3.11.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:733ae23ada68b804b222c44affed76b39e30806d38660bf1eb200520d259cc16", size = 141967, upload-time = "2026-02-02T15:37:12.282Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cd/37ece39a0777ba077fdcdbe4cccae3be8ed00290c14bf8afdc548befc260/orjson-3.11.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fdfad2093bdd08245f2e204d977facd5f871c88c4a71230d5bcbd0e43bf6222", size = 130991, upload-time = "2026-02-02T15:37:13.465Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ed/f2b5d66aa9b6b5c02ff5f120efc7b38c7c4962b21e6be0f00fd99a5c348e/orjson-3.11.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cededd6738e1c153530793998e31c05086582b08315db48ab66649768f326baa", size = 133674, upload-time = "2026-02-02T15:37:14.694Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6e/baa83e68d1aa09fa8c3e5b2c087d01d0a0bd45256de719ed7bc22c07052d/orjson-3.11.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:14f440c7268c8f8633d1b3d443a434bd70cb15686117ea6beff8fdc8f5917a1e", size = 138722, upload-time = "2026-02-02T15:37:16.501Z" }, + { url = "https://files.pythonhosted.org/packages/0c/47/7f8ef4963b772cd56999b535e553f7eb5cd27e9dd6c049baee6f18bfa05d/orjson-3.11.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3a2479753bbb95b0ebcf7969f562cdb9668e6d12416a35b0dda79febf89cdea2", size = 409056, upload-time = "2026-02-02T15:37:17.895Z" }, + { url = "https://files.pythonhosted.org/packages/38/eb/2df104dd2244b3618f25325a656f85cc3277f74bbd91224752410a78f3c7/orjson-3.11.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:71924496986275a737f38e3f22b4e0878882b3f7a310d2ff4dc96e812789120c", size = 144196, upload-time = "2026-02-02T15:37:19.349Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2a/ee41de0aa3a6686598661eae2b4ebdff1340c65bfb17fcff8b87138aab21/orjson-3.11.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4a9eefdc70bf8bf9857f0290f973dec534ac84c35cd6a7f4083be43e7170a8f", size = 134979, upload-time = "2026-02-02T15:37:20.906Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/92fc5d3d402b87a8b28277a9ed35386218a6a5287c7fe5ee9b9f02c53fb2/orjson-3.11.7-cp310-cp310-win32.whl", hash = "sha256:ae9e0b37a834cef7ce8f99de6498f8fad4a2c0bf6bfc3d02abd8ed56aa15b2de", size = 127968, upload-time = "2026-02-02T15:37:23.178Z" }, + { url = "https://files.pythonhosted.org/packages/07/29/a576bf36d73d60df06904d3844a9df08e25d59eba64363aaf8ec2f9bff41/orjson-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:d772afdb22555f0c58cfc741bdae44180122b3616faa1ecadb595cd526e4c993", size = 125128, upload-time = "2026-02-02T15:37:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/37/02/da6cb01fc6087048d7f61522c327edf4250f1683a58a839fdcc435746dd5/orjson-3.11.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9487abc2c2086e7c8eb9a211d2ce8855bae0e92586279d0d27b341d5ad76c85c", size = 228664, upload-time = "2026-02-02T15:37:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c2/5885e7a5881dba9a9af51bc564e8967225a642b3e03d089289a35054e749/orjson-3.11.7-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:79cacb0b52f6004caf92405a7e1f11e6e2de8bdf9019e4f76b44ba045125cd6b", size = 125344, upload-time = "2026-02-02T15:37:26.92Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1d/4e7688de0a92d1caf600dfd5fb70b4c5bfff51dfa61ac555072ef2d0d32a/orjson-3.11.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e85fe4698b6a56d5e2ebf7ae87544d668eb6bde1ad1226c13f44663f20ec9e", size = 128404, upload-time = "2026-02-02T15:37:28.108Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b2/ec04b74ae03a125db7bd69cffd014b227b7f341e3261bf75b5eb88a1aa92/orjson-3.11.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8d14b71c0b12963fe8a62aac87119f1afdf4cb88a400f61ca5ae581449efcb5", size = 123677, upload-time = "2026-02-02T15:37:30.287Z" }, + { url = "https://files.pythonhosted.org/packages/4c/69/f95bdf960605f08f827f6e3291fe243d8aa9c5c9ff017a8d7232209184c3/orjson-3.11.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91c81ef070c8f3220054115e1ef468b1c9ce8497b4e526cb9f68ab4dc0a7ac62", size = 128950, upload-time = "2026-02-02T15:37:31.595Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1b/de59c57bae1d148ef298852abd31909ac3089cff370dfd4cd84cc99cbc42/orjson-3.11.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:411ebaf34d735e25e358a6d9e7978954a9c9d58cfb47bc6683cdc3964cd2f910", size = 141756, upload-time = "2026-02-02T15:37:32.985Z" }, + { url = "https://files.pythonhosted.org/packages/ee/9e/9decc59f4499f695f65c650f6cfa6cd4c37a3fbe8fa235a0a3614cb54386/orjson-3.11.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a16bcd08ab0bcdfc7e8801d9c4a9cc17e58418e4d48ddc6ded4e9e4b1a94062b", size = 130812, upload-time = "2026-02-02T15:37:34.204Z" }, + { url = "https://files.pythonhosted.org/packages/28/e6/59f932bcabd1eac44e334fe8e3281a92eacfcb450586e1f4bde0423728d8/orjson-3.11.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0b51672e466fd7e56230ffbae7f1639e18d0ce023351fb75da21b71bc2c960", size = 133444, upload-time = "2026-02-02T15:37:35.446Z" }, + { url = "https://files.pythonhosted.org/packages/f1/36/b0f05c0eaa7ca30bc965e37e6a2956b0d67adb87a9872942d3568da846ae/orjson-3.11.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:136dcd6a2e796dfd9ffca9fc027d778567b0b7c9968d092842d3c323cef88aa8", size = 138609, upload-time = "2026-02-02T15:37:36.657Z" }, + { url = "https://files.pythonhosted.org/packages/b8/03/58ec7d302b8d86944c60c7b4b82975d5161fcce4c9bc8c6cb1d6741b6115/orjson-3.11.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7ba61079379b0ae29e117db13bda5f28d939766e410d321ec1624afc6a0b0504", size = 408918, upload-time = "2026-02-02T15:37:38.076Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/868d65ef9a8b99be723bd510de491349618abd9f62c826cf206d962db295/orjson-3.11.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0527a4510c300e3b406591b0ba69b5dc50031895b0a93743526a3fc45f59d26e", size = 143998, upload-time = "2026-02-02T15:37:39.706Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c7/1e18e1c83afe3349f4f6dc9e14910f0ae5f82eac756d1412ea4018938535/orjson-3.11.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a709e881723c9b18acddcfb8ba357322491ad553e277cf467e1e7e20e2d90561", size = 134802, upload-time = "2026-02-02T15:37:41.002Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0b/ccb7ee1a65b37e8eeb8b267dc953561d72370e85185e459616d4345bab34/orjson-3.11.7-cp311-cp311-win32.whl", hash = "sha256:c43b8b5bab288b6b90dac410cca7e986a4fa747a2e8f94615aea407da706980d", size = 127828, upload-time = "2026-02-02T15:37:42.241Z" }, + { url = "https://files.pythonhosted.org/packages/af/9e/55c776dffda3f381e0f07d010a4f5f3902bf48eaba1bb7684d301acd4924/orjson-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:6543001328aa857187f905308a028935864aefe9968af3848401b6fe80dbb471", size = 124941, upload-time = "2026-02-02T15:37:43.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/424a620fa7d263b880162505fb107ef5e0afaa765b5b06a88312ac291560/orjson-3.11.7-cp311-cp311-win_arm64.whl", hash = "sha256:1ee5cc7160a821dfe14f130bc8e63e7611051f964b463d9e2a3a573204446a4d", size = 126245, upload-time = "2026-02-02T15:37:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, + { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, + { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, + { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, + { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391, upload-time = "2026-02-02T15:38:27.757Z" }, + { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188, upload-time = "2026-02-02T15:38:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097, upload-time = "2026-02-02T15:38:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364, upload-time = "2026-02-02T15:38:32.363Z" }, + { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076, upload-time = "2026-02-02T15:38:33.68Z" }, + { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705, upload-time = "2026-02-02T15:38:34.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855, upload-time = "2026-02-02T15:38:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386, upload-time = "2026-02-02T15:38:37.704Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295, upload-time = "2026-02-02T15:38:39.096Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720, upload-time = "2026-02-02T15:38:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152, upload-time = "2026-02-02T15:38:42.262Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814, upload-time = "2026-02-02T15:38:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, + { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/fa/a91f70829ebccf6387c4946e0a1a109f6ba0d6a28d65f628bedfad94b890/ormsgpack-1.12.2-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c1429217f8f4d7fcb053523bbbac6bed5e981af0b85ba616e6df7cce53c19657", size = 378262, upload-time = "2026-01-18T20:55:22.284Z" }, + { url = "https://files.pythonhosted.org/packages/5f/62/3698a9a0c487252b5c6a91926e5654e79e665708ea61f67a8bdeceb022bf/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f13034dc6c84a6280c6c33db7ac420253852ea233fc3ee27c8875f8dd651163", size = 203034, upload-time = "2026-01-18T20:55:53.324Z" }, + { url = "https://files.pythonhosted.org/packages/66/3a/f716f64edc4aec2744e817660b317e2f9bb8de372338a95a96198efa1ac1/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59f5da97000c12bc2d50e988bdc8576b21f6ab4e608489879d35b2c07a8ab51a", size = 210538, upload-time = "2026-01-18T20:55:20.097Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/a436be9ce27d693d4e19fa94900028067133779f09fc45776db3f689c822/ormsgpack-1.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e4459c3f27066beadb2b81ea48a076a417aafffff7df1d3c11c519190ed44f2", size = 212401, upload-time = "2026-01-18T20:55:46.447Z" }, + { url = "https://files.pythonhosted.org/packages/10/c5/cde98300fd33fee84ca71de4751b19aeeca675f0cf3c0ec4b043f40f3b76/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a1c460655d7288407ffa09065e322a7231997c0d62ce914bf3a96ad2dc6dedd", size = 387080, upload-time = "2026-01-18T20:56:00.884Z" }, + { url = "https://files.pythonhosted.org/packages/6a/31/30bf445ef827546747c10889dd254b3d84f92b591300efe4979d792f4c41/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:458e4568be13d311ef7d8877275e7ccbe06c0e01b39baaac874caaa0f46d826c", size = 482346, upload-time = "2026-01-18T20:55:39.831Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f5/e1745ddf4fa246c921b5ca253636c4c700ff768d78032f79171289159f6e/ormsgpack-1.12.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cde5eaa6c6cbc8622db71e4a23de56828e3d876aeb6460ffbcb5b8aff91093b", size = 425178, upload-time = "2026-01-18T20:55:27.106Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a2/e6532ed7716aed03dede8df2d0d0d4150710c2122647d94b474147ccd891/ormsgpack-1.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc7a33be14c347893edbb1ceda89afbf14c467d593a5ee92c11de4f1666b4d4f", size = 117183, upload-time = "2026-01-18T20:55:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/08/8b68f24b18e69d92238aa8f258218e6dfeacf4381d9d07ab8df303f524a9/ormsgpack-1.12.2-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bd5f4bf04c37888e864f08e740c5a573c4017f6fd6e99fa944c5c935fabf2dd9", size = 378266, upload-time = "2026-01-18T20:55:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/29fc13044ecb7c153523ae0a1972269fcd613650d1fa1a9cec1044c6b666/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d5b28b3570e9fed9a5a76528fc7230c3c76333bc214798958e58e9b79cc18a", size = 203035, upload-time = "2026-01-18T20:55:30.59Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c2/00169fb25dd8f9213f5e8a549dfb73e4d592009ebc85fbbcd3e1dcac575b/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3708693412c28f3538fb5a65da93787b6bbab3484f6bc6e935bfb77a62400ae5", size = 210539, upload-time = "2026-01-18T20:55:48.569Z" }, + { url = "https://files.pythonhosted.org/packages/1b/33/543627f323ff3c73091f51d6a20db28a1a33531af30873ea90c5ac95a9b5/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43013a3f3e2e902e1d05e72c0f1aeb5bedbb8e09240b51e26792a3c89267e181", size = 212401, upload-time = "2026-01-18T20:56:10.101Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5d/f70e2c3da414f46186659d24745483757bcc9adccb481a6eb93e2b729301/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c8b1667a72cbba74f0ae7ecf3105a5e01304620ed14528b2cb4320679d2869b", size = 387082, upload-time = "2026-01-18T20:56:12.047Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d6/06e8dc920c7903e051f30934d874d4afccc9bb1c09dcaf0bc03a7de4b343/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6961442140193e517303d0b5d7bc2e20e69a879c2d774316125350c4a76b92", size = 482346, upload-time = "2026-01-18T20:56:05.152Z" }, + { url = "https://files.pythonhosted.org/packages/66/c4/f337ac0905eed9c393ef990c54565cd33644918e0a8031fe48c098c71dbf/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6a4c34ddef109647c769d69be65fa1de7a6022b02ad45546a69b3216573eb4a", size = 425181, upload-time = "2026-01-18T20:55:37.83Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/6d5758fabef3babdf4bbbc453738cc7de9cd3334e4c38dd5737e27b85653/ormsgpack-1.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:73670ed0375ecc303858e3613f407628dd1fca18fe6ac57b7b7ce66cc7bb006c", size = 117182, upload-time = "2026-01-18T20:55:31.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/17a15549233c37e7fd054c48fe9207492e06b026dbd872b826a0b5f833b6/ormsgpack-1.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2be829954434e33601ae5da328cccce3266b098927ca7a30246a0baec2ce7bd", size = 111464, upload-time = "2026-01-18T20:55:38.811Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, + { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, + { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, + { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, +] + [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -1212,11 +1495,11 @@ wheels = [ [[package]] name = "pathspec" -version = "1.0.3" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] @@ -1269,11 +1552,11 @@ wheels = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +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/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { 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]] @@ -1434,11 +1717,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, ] [package.optional-dependencies] @@ -1516,11 +1799,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.21" +version = "0.0.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, ] [[package]] @@ -1623,6 +1906,127 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] +[[package]] +name = "regex" +version = "2026.1.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/86/07d5056945f9ec4590b518171c4254a5925832eb727b56d3c38a7476f316/regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5", size = 414811, upload-time = "2026-01-14T23:18:02.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/d2/e6ee96b7dff201a83f650241c52db8e5bd080967cb93211f57aa448dc9d6/regex-2026.1.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e3dd93c8f9abe8aa4b6c652016da9a3afa190df5ad822907efe6b206c09896e", size = 488166, upload-time = "2026-01-14T23:13:46.408Z" }, + { url = "https://files.pythonhosted.org/packages/23/8a/819e9ce14c9f87af026d0690901b3931f3101160833e5d4c8061fa3a1b67/regex-2026.1.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97499ff7862e868b1977107873dd1a06e151467129159a6ffd07b66706ba3a9f", size = 290632, upload-time = "2026-01-14T23:13:48.688Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c3/23dfe15af25d1d45b07dfd4caa6003ad710dcdcb4c4b279909bdfe7a2de8/regex-2026.1.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bda75ebcac38d884240914c6c43d8ab5fb82e74cde6da94b43b17c411aa4c2b", size = 288500, upload-time = "2026-01-14T23:13:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/c6/31/1adc33e2f717df30d2f4d973f8776d2ba6ecf939301efab29fca57505c95/regex-2026.1.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dcc02368585334f5bc81fc73a2a6a0bbade60e7d83da21cead622faf408f32c", size = 781670, upload-time = "2026-01-14T23:13:52.453Z" }, + { url = "https://files.pythonhosted.org/packages/23/ce/21a8a22d13bc4adcb927c27b840c948f15fc973e21ed2346c1bd0eae22dc/regex-2026.1.15-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:693b465171707bbe882a7a05de5e866f33c76aa449750bee94a8d90463533cc9", size = 850820, upload-time = "2026-01-14T23:13:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/3eeacdf587a4705a44484cd0b30e9230a0e602811fb3e2cc32268c70d509/regex-2026.1.15-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0d190e6f013ea938623a58706d1469a62103fb2a241ce2873a9906e0386582c", size = 898777, upload-time = "2026-01-14T23:13:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/79/a9/1898a077e2965c35fc22796488141a22676eed2d73701e37c73ad7c0b459/regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ff818702440a5878a81886f127b80127f5d50563753a28211482867f8318106", size = 791750, upload-time = "2026-01-14T23:13:58.527Z" }, + { url = "https://files.pythonhosted.org/packages/4c/84/e31f9d149a178889b3817212827f5e0e8c827a049ff31b4b381e76b26e2d/regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f052d1be37ef35a54e394de66136e30fa1191fab64f71fc06ac7bc98c9a84618", size = 782674, upload-time = "2026-01-14T23:13:59.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ff/adf60063db24532add6a1676943754a5654dcac8237af024ede38244fd12/regex-2026.1.15-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6bfc31a37fd1592f0c4fc4bfc674b5c42e52efe45b4b7a6a14f334cca4bcebe4", size = 767906, upload-time = "2026-01-14T23:14:01.298Z" }, + { url = "https://files.pythonhosted.org/packages/af/3e/e6a216cee1e2780fec11afe7fc47b6f3925d7264e8149c607ac389fd9b1a/regex-2026.1.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d6ce5ae80066b319ae3bc62fd55a557c9491baa5efd0d355f0de08c4ba54e79", size = 774798, upload-time = "2026-01-14T23:14:02.715Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/23a4a8378a9208514ed3efc7e7850c27fa01e00ed8557c958df0335edc4a/regex-2026.1.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1704d204bd42b6bb80167df0e4554f35c255b579ba99616def38f69e14a5ccb9", size = 845861, upload-time = "2026-01-14T23:14:04.824Z" }, + { url = "https://files.pythonhosted.org/packages/f8/57/d7605a9d53bd07421a8785d349cd29677fe660e13674fa4c6cbd624ae354/regex-2026.1.15-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e3174a5ed4171570dc8318afada56373aa9289eb6dc0d96cceb48e7358b0e220", size = 755648, upload-time = "2026-01-14T23:14:06.371Z" }, + { url = "https://files.pythonhosted.org/packages/6f/76/6f2e24aa192da1e299cc1101674a60579d3912391867ce0b946ba83e2194/regex-2026.1.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87adf5bd6d72e3e17c9cb59ac4096b1faaf84b7eb3037a5ffa61c4b4370f0f13", size = 836250, upload-time = "2026-01-14T23:14:08.343Z" }, + { url = "https://files.pythonhosted.org/packages/11/3a/1f2a1d29453299a7858eab7759045fc3d9d1b429b088dec2dc85b6fa16a2/regex-2026.1.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e85dc94595f4d766bd7d872a9de5ede1ca8d3063f3bdf1e2c725f5eb411159e3", size = 779919, upload-time = "2026-01-14T23:14:09.954Z" }, + { url = "https://files.pythonhosted.org/packages/c0/67/eab9bc955c9dcc58e9b222c801e39cff7ca0b04261792a2149166ce7e792/regex-2026.1.15-cp310-cp310-win32.whl", hash = "sha256:21ca32c28c30d5d65fc9886ff576fc9b59bbca08933e844fa2363e530f4c8218", size = 265888, upload-time = "2026-01-14T23:14:11.35Z" }, + { url = "https://files.pythonhosted.org/packages/1d/62/31d16ae24e1f8803bddb0885508acecaec997fcdcde9c243787103119ae4/regex-2026.1.15-cp310-cp310-win_amd64.whl", hash = "sha256:3038a62fc7d6e5547b8915a3d927a0fbeef84cdbe0b1deb8c99bbd4a8961b52a", size = 277830, upload-time = "2026-01-14T23:14:12.908Z" }, + { url = "https://files.pythonhosted.org/packages/e5/36/5d9972bccd6417ecd5a8be319cebfd80b296875e7f116c37fb2a2deecebf/regex-2026.1.15-cp310-cp310-win_arm64.whl", hash = "sha256:505831646c945e3e63552cc1b1b9b514f0e93232972a2d5bedbcc32f15bc82e3", size = 270376, upload-time = "2026-01-14T23:14:14.782Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c9/0c80c96eab96948363d270143138d671d5731c3a692b417629bf3492a9d6/regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a", size = 488168, upload-time = "2026-01-14T23:14:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/17/f0/271c92f5389a552494c429e5cc38d76d1322eb142fb5db3c8ccc47751468/regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f", size = 290636, upload-time = "2026-01-14T23:14:17.715Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f9/5f1fd077d106ca5655a0f9ff8f25a1ab55b92128b5713a91ed7134ff688e/regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1", size = 288496, upload-time = "2026-01-14T23:14:19.326Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e1/8f43b03a4968c748858ec77f746c286d81f896c2e437ccf050ebc5d3128c/regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b", size = 793503, upload-time = "2026-01-14T23:14:20.922Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/a39a5e8edc5377a46a7c875c2f9a626ed3338cb3bb06931be461c3e1a34a/regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8", size = 860535, upload-time = "2026-01-14T23:14:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1c/9dce667a32a9477f7a2869c1c767dc00727284a9fa3ff5c09a5c6c03575e/regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413", size = 907225, upload-time = "2026-01-14T23:14:23.897Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3c/87ca0a02736d16b6262921425e84b48984e77d8e4e572c9072ce96e66c30/regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026", size = 800526, upload-time = "2026-01-14T23:14:26.039Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/647d5715aeea7c87bdcbd2f578f47b415f55c24e361e639fe8c0cc88878f/regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785", size = 773446, upload-time = "2026-01-14T23:14:28.109Z" }, + { url = "https://files.pythonhosted.org/packages/af/89/bf22cac25cb4ba0fe6bff52ebedbb65b77a179052a9d6037136ae93f42f4/regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e", size = 783051, upload-time = "2026-01-14T23:14:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f4/6ed03e71dca6348a5188363a34f5e26ffd5db1404780288ff0d79513bce4/regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763", size = 854485, upload-time = "2026-01-14T23:14:31.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/8e8560bd78caded8eb137e3e47612430a05b9a772caf60876435192d670a/regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb", size = 762195, upload-time = "2026-01-14T23:14:32.802Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61fc710f9aa8dfcd764fe27d37edfaa023b1a23305a0d84fccd5adb346ea/regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2", size = 845986, upload-time = "2026-01-14T23:14:34.898Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2e/fbee4cb93f9d686901a7ca8d94285b80405e8c34fe4107f63ffcbfb56379/regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1", size = 788992, upload-time = "2026-01-14T23:14:37.116Z" }, + { url = "https://files.pythonhosted.org/packages/ed/14/3076348f3f586de64b1ab75a3fbabdaab7684af7f308ad43be7ef1849e55/regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569", size = 265893, upload-time = "2026-01-14T23:14:38.426Z" }, + { url = "https://files.pythonhosted.org/packages/0f/19/772cf8b5fc803f5c89ba85d8b1870a1ca580dc482aa030383a9289c82e44/regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7", size = 277840, upload-time = "2026-01-14T23:14:39.785Z" }, + { url = "https://files.pythonhosted.org/packages/78/84/d05f61142709474da3c0853222d91086d3e1372bcdab516c6fd8d80f3297/regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec", size = 270374, upload-time = "2026-01-14T23:14:41.592Z" }, + { url = "https://files.pythonhosted.org/packages/92/81/10d8cf43c807d0326efe874c1b79f22bfb0fb226027b0b19ebc26d301408/regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1", size = 489398, upload-time = "2026-01-14T23:14:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/90/b0/7c2a74e74ef2a7c32de724658a69a862880e3e4155cba992ba04d1c70400/regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681", size = 291339, upload-time = "2026-01-14T23:14:45.183Z" }, + { url = "https://files.pythonhosted.org/packages/19/4d/16d0773d0c818417f4cc20aa0da90064b966d22cd62a8c46765b5bd2d643/regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f", size = 289003, upload-time = "2026-01-14T23:14:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e4/1fc4599450c9f0863d9406e944592d968b8d6dfd0d552a7d569e43bceada/regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa", size = 798656, upload-time = "2026-01-14T23:14:48.77Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e6/59650d73a73fa8a60b3a590545bfcf1172b4384a7df2e7fe7b9aab4e2da9/regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804", size = 864252, upload-time = "2026-01-14T23:14:50.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ab/1d0f4d50a1638849a97d731364c9a80fa304fec46325e48330c170ee8e80/regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c", size = 912268, upload-time = "2026-01-14T23:14:52.952Z" }, + { url = "https://files.pythonhosted.org/packages/dd/df/0d722c030c82faa1d331d1921ee268a4e8fb55ca8b9042c9341c352f17fa/regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5", size = 803589, upload-time = "2026-01-14T23:14:55.182Z" }, + { url = "https://files.pythonhosted.org/packages/66/23/33289beba7ccb8b805c6610a8913d0131f834928afc555b241caabd422a9/regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3", size = 775700, upload-time = "2026-01-14T23:14:56.707Z" }, + { url = "https://files.pythonhosted.org/packages/e7/65/bf3a42fa6897a0d3afa81acb25c42f4b71c274f698ceabd75523259f6688/regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb", size = 787928, upload-time = "2026-01-14T23:14:58.312Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f5/13bf65864fc314f68cdd6d8ca94adcab064d4d39dbd0b10fef29a9da48fc/regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410", size = 858607, upload-time = "2026-01-14T23:15:00.657Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/040e589834d7a439ee43fb0e1e902bc81bd58a5ba81acffe586bb3321d35/regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4", size = 763729, upload-time = "2026-01-14T23:15:02.248Z" }, + { url = "https://files.pythonhosted.org/packages/9b/84/6921e8129687a427edf25a34a5594b588b6d88f491320b9de5b6339a4fcb/regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d", size = 850697, upload-time = "2026-01-14T23:15:03.878Z" }, + { url = "https://files.pythonhosted.org/packages/8a/87/3d06143d4b128f4229158f2de5de6c8f2485170c7221e61bf381313314b2/regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22", size = 789849, upload-time = "2026-01-14T23:15:06.102Z" }, + { url = "https://files.pythonhosted.org/packages/77/69/c50a63842b6bd48850ebc7ab22d46e7a2a32d824ad6c605b218441814639/regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913", size = 266279, upload-time = "2026-01-14T23:15:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/f2/36/39d0b29d087e2b11fd8191e15e81cce1b635fcc845297c67f11d0d19274d/regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a", size = 277166, upload-time = "2026-01-14T23:15:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/28/32/5b8e476a12262748851fa8ab1b0be540360692325975b094e594dfebbb52/regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056", size = 270415, upload-time = "2026-01-14T23:15:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2e/6870bb16e982669b674cce3ee9ff2d1d46ab80528ee6bcc20fb2292efb60/regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e", size = 489164, upload-time = "2026-01-14T23:15:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/9774542e203849b0286badf67199970a44ebdb0cc5fb739f06e47ada72f8/regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10", size = 291218, upload-time = "2026-01-14T23:15:15.647Z" }, + { url = "https://files.pythonhosted.org/packages/b2/87/b0cda79f22b8dee05f774922a214da109f9a4c0eca5da2c9d72d77ea062c/regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc", size = 288895, upload-time = "2026-01-14T23:15:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6a/0041f0a2170d32be01ab981d6346c83a8934277d82c780d60b127331f264/regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599", size = 798680, upload-time = "2026-01-14T23:15:19.342Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/30e1cfcdbe3e891324aa7568b7c968771f82190df5524fabc1138cb2d45a/regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae", size = 864210, upload-time = "2026-01-14T23:15:22.005Z" }, + { url = "https://files.pythonhosted.org/packages/64/44/4db2f5c5ca0ccd40ff052ae7b1e9731352fcdad946c2b812285a7505ca75/regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5", size = 912358, upload-time = "2026-01-14T23:15:24.569Z" }, + { url = "https://files.pythonhosted.org/packages/79/b6/e6a5665d43a7c42467138c8a2549be432bad22cbd206f5ec87162de74bd7/regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6", size = 803583, upload-time = "2026-01-14T23:15:26.526Z" }, + { url = "https://files.pythonhosted.org/packages/e7/53/7cd478222169d85d74d7437e74750005e993f52f335f7c04ff7adfda3310/regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788", size = 775782, upload-time = "2026-01-14T23:15:29.352Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b5/75f9a9ee4b03a7c009fe60500fe550b45df94f0955ca29af16333ef557c5/regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714", size = 787978, upload-time = "2026-01-14T23:15:31.295Z" }, + { url = "https://files.pythonhosted.org/packages/72/b3/79821c826245bbe9ccbb54f6eadb7879c722fd3e0248c17bfc90bf54e123/regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d", size = 858550, upload-time = "2026-01-14T23:15:33.558Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/2ab5f77a1c465745bfbfcb3ad63178a58337ae8d5274315e2cc623a822fa/regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3", size = 763747, upload-time = "2026-01-14T23:15:35.206Z" }, + { url = "https://files.pythonhosted.org/packages/6d/84/c27df502d4bfe2873a3e3a7cf1bdb2b9cc10284d1a44797cf38bed790470/regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31", size = 850615, upload-time = "2026-01-14T23:15:37.523Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b7/658a9782fb253680aa8ecb5ccbb51f69e088ed48142c46d9f0c99b46c575/regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3", size = 789951, upload-time = "2026-01-14T23:15:39.582Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/5928af114441e059f15b2f63e188bd00c6529b3051c974ade7444b85fcda/regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f", size = 266275, upload-time = "2026-01-14T23:15:42.108Z" }, + { url = "https://files.pythonhosted.org/packages/4f/16/5bfbb89e435897bff28cf0352a992ca719d9e55ebf8b629203c96b6ce4f7/regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e", size = 277145, upload-time = "2026-01-14T23:15:44.244Z" }, + { url = "https://files.pythonhosted.org/packages/56/c1/a09ff7392ef4233296e821aec5f78c51be5e91ffde0d163059e50fd75835/regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337", size = 270411, upload-time = "2026-01-14T23:15:45.858Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/0cfd5a78e5c6db00e6782fdae70458f89850ce95baa5e8694ab91d89744f/regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be", size = 492068, upload-time = "2026-01-14T23:15:47.616Z" }, + { url = "https://files.pythonhosted.org/packages/50/72/6c86acff16cb7c959c4355826bbf06aad670682d07c8f3998d9ef4fee7cd/regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8", size = 292756, upload-time = "2026-01-14T23:15:49.307Z" }, + { url = "https://files.pythonhosted.org/packages/4e/58/df7fb69eadfe76526ddfce28abdc0af09ffe65f20c2c90932e89d705153f/regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd", size = 291114, upload-time = "2026-01-14T23:15:51.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6c/a4011cd1cf96b90d2cdc7e156f91efbd26531e822a7fbb82a43c1016678e/regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a", size = 807524, upload-time = "2026-01-14T23:15:53.102Z" }, + { url = "https://files.pythonhosted.org/packages/1d/25/a53ffb73183f69c3e9f4355c4922b76d2840aee160af6af5fac229b6201d/regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93", size = 873455, upload-time = "2026-01-14T23:15:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/66/0b/8b47fc2e8f97d9b4a851736f3890a5f786443aa8901061c55f24c955f45b/regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af", size = 915007, upload-time = "2026-01-14T23:15:57.041Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/97de0d681e6d26fabe71968dbee06dd52819e9a22fdce5dac7256c31ed84/regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09", size = 812794, upload-time = "2026-01-14T23:15:58.916Z" }, + { url = "https://files.pythonhosted.org/packages/22/38/e752f94e860d429654aa2b1c51880bff8dfe8f084268258adf9151cf1f53/regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5", size = 781159, upload-time = "2026-01-14T23:16:00.817Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a7/d739ffaef33c378fc888302a018d7f81080393d96c476b058b8c64fd2b0d/regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794", size = 795558, upload-time = "2026-01-14T23:16:03.267Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c4/542876f9a0ac576100fc73e9c75b779f5c31e3527576cfc9cb3009dcc58a/regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a", size = 868427, upload-time = "2026-01-14T23:16:05.646Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0f/d5655bea5b22069e32ae85a947aa564912f23758e112cdb74212848a1a1b/regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80", size = 769939, upload-time = "2026-01-14T23:16:07.542Z" }, + { url = "https://files.pythonhosted.org/packages/20/06/7e18a4fa9d326daeda46d471a44ef94201c46eaa26dbbb780b5d92cbfdda/regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2", size = 854753, upload-time = "2026-01-14T23:16:10.395Z" }, + { url = "https://files.pythonhosted.org/packages/3b/67/dc8946ef3965e166f558ef3b47f492bc364e96a265eb4a2bb3ca765c8e46/regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60", size = 799559, upload-time = "2026-01-14T23:16:12.347Z" }, + { url = "https://files.pythonhosted.org/packages/a5/61/1bba81ff6d50c86c65d9fd84ce9699dd106438ee4cdb105bf60374ee8412/regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952", size = 268879, upload-time = "2026-01-14T23:16:14.049Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/cef7d4c5fb0ea3ac5c775fd37db5747f7378b29526cc83f572198924ff47/regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10", size = 280317, upload-time = "2026-01-14T23:16:15.718Z" }, + { url = "https://files.pythonhosted.org/packages/b4/52/4317f7a5988544e34ab57b4bde0f04944c4786128c933fb09825924d3e82/regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829", size = 271551, upload-time = "2026-01-14T23:16:17.533Z" }, + { url = "https://files.pythonhosted.org/packages/52/0a/47fa888ec7cbbc7d62c5f2a6a888878e76169170ead271a35239edd8f0e8/regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac", size = 489170, upload-time = "2026-01-14T23:16:19.835Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c4/d000e9b7296c15737c9301708e9e7fbdea009f8e93541b6b43bdb8219646/regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6", size = 291146, upload-time = "2026-01-14T23:16:21.541Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b6/921cc61982e538682bdf3bdf5b2c6ab6b34368da1f8e98a6c1ddc503c9cf/regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2", size = 288986, upload-time = "2026-01-14T23:16:23.381Z" }, + { url = "https://files.pythonhosted.org/packages/ca/33/eb7383dde0bbc93f4fb9d03453aab97e18ad4024ac7e26cef8d1f0a2cff0/regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846", size = 799098, upload-time = "2026-01-14T23:16:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/27/56/b664dccae898fc8d8b4c23accd853f723bde0f026c747b6f6262b688029c/regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b", size = 864980, upload-time = "2026-01-14T23:16:27.297Z" }, + { url = "https://files.pythonhosted.org/packages/16/40/0999e064a170eddd237bae9ccfcd8f28b3aa98a38bf727a086425542a4fc/regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e", size = 911607, upload-time = "2026-01-14T23:16:29.235Z" }, + { url = "https://files.pythonhosted.org/packages/07/78/c77f644b68ab054e5a674fb4da40ff7bffb2c88df58afa82dbf86573092d/regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde", size = 803358, upload-time = "2026-01-14T23:16:31.369Z" }, + { url = "https://files.pythonhosted.org/packages/27/31/d4292ea8566eaa551fafc07797961c5963cf5235c797cc2ae19b85dfd04d/regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5", size = 775833, upload-time = "2026-01-14T23:16:33.141Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b2/cff3bf2fea4133aa6fb0d1e370b37544d18c8350a2fa118c7e11d1db0e14/regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34", size = 788045, upload-time = "2026-01-14T23:16:35.005Z" }, + { url = "https://files.pythonhosted.org/packages/8d/99/2cb9b69045372ec877b6f5124bda4eb4253bc58b8fe5848c973f752bc52c/regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75", size = 859374, upload-time = "2026-01-14T23:16:36.919Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/710b0a5abe8e077b1729a562d2f297224ad079f3a66dce46844c193416c8/regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e", size = 763940, upload-time = "2026-01-14T23:16:38.685Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/7585c8e744e40eb3d32f119191969b91de04c073fca98ec14299041f6e7e/regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160", size = 850112, upload-time = "2026-01-14T23:16:40.646Z" }, + { url = "https://files.pythonhosted.org/packages/af/d6/43e1dd85df86c49a347aa57c1f69d12c652c7b60e37ec162e3096194a278/regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1", size = 789586, upload-time = "2026-01-14T23:16:42.799Z" }, + { url = "https://files.pythonhosted.org/packages/93/38/77142422f631e013f316aaae83234c629555729a9fbc952b8a63ac91462a/regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1", size = 271691, upload-time = "2026-01-14T23:16:44.671Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a9/ab16b4649524ca9e05213c1cdbb7faa85cc2aa90a0230d2f796cbaf22736/regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903", size = 280422, upload-time = "2026-01-14T23:16:46.607Z" }, + { url = "https://files.pythonhosted.org/packages/be/2a/20fd057bf3521cb4791f69f869635f73e0aaf2b9ad2d260f728144f9047c/regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705", size = 273467, upload-time = "2026-01-14T23:16:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/ad/77/0b1e81857060b92b9cad239104c46507dd481b3ff1fa79f8e7f865aae38a/regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8", size = 492073, upload-time = "2026-01-14T23:16:51.154Z" }, + { url = "https://files.pythonhosted.org/packages/70/f3/f8302b0c208b22c1e4f423147e1913fd475ddd6230565b299925353de644/regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf", size = 292757, upload-time = "2026-01-14T23:16:53.08Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f0/ef55de2460f3b4a6da9d9e7daacd0cb79d4ef75c64a2af316e68447f0df0/regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d", size = 291122, upload-time = "2026-01-14T23:16:55.383Z" }, + { url = "https://files.pythonhosted.org/packages/cf/55/bb8ccbacabbc3a11d863ee62a9f18b160a83084ea95cdfc5d207bfc3dd75/regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84", size = 807761, upload-time = "2026-01-14T23:16:57.251Z" }, + { url = "https://files.pythonhosted.org/packages/8f/84/f75d937f17f81e55679a0509e86176e29caa7298c38bd1db7ce9c0bf6075/regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df", size = 873538, upload-time = "2026-01-14T23:16:59.349Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/0da86327df70349aa8d86390da91171bd3ca4f0e7c1d1d453a9c10344da3/regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434", size = 915066, upload-time = "2026-01-14T23:17:01.607Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5e/f660fb23fc77baa2a61aa1f1fe3a4eea2bbb8a286ddec148030672e18834/regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a", size = 812938, upload-time = "2026-01-14T23:17:04.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/a47a29bfecebbbfd1e5cd3f26b28020a97e4820f1c5148e66e3b7d4b4992/regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10", size = 781314, upload-time = "2026-01-14T23:17:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/65/ec/7ec2bbfd4c3f4e494a24dec4c6943a668e2030426b1b8b949a6462d2c17b/regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac", size = 795652, upload-time = "2026-01-14T23:17:08.521Z" }, + { url = "https://files.pythonhosted.org/packages/46/79/a5d8651ae131fe27d7c521ad300aa7f1c7be1dbeee4d446498af5411b8a9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea", size = 868550, upload-time = "2026-01-14T23:17:10.573Z" }, + { url = "https://files.pythonhosted.org/packages/06/b7/25635d2809664b79f183070786a5552dd4e627e5aedb0065f4e3cf8ee37d/regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e", size = 769981, upload-time = "2026-01-14T23:17:12.871Z" }, + { url = "https://files.pythonhosted.org/packages/16/8b/fc3fcbb2393dcfa4a6c5ffad92dc498e842df4581ea9d14309fcd3c55fb9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521", size = 854780, upload-time = "2026-01-14T23:17:14.837Z" }, + { url = "https://files.pythonhosted.org/packages/d0/38/dde117c76c624713c8a2842530be9c93ca8b606c0f6102d86e8cd1ce8bea/regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db", size = 799778, upload-time = "2026-01-14T23:17:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/e3/0d/3a6cfa9ae99606afb612d8fb7a66b245a9d5ff0f29bb347c8a30b6ad561b/regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e", size = 274667, upload-time = "2026-01-14T23:17:19.301Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b2/297293bb0742fd06b8d8e2572db41a855cdf1cae0bf009b1cb74fe07e196/regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf", size = 284386, upload-time = "2026-01-14T23:17:21.231Z" }, + { url = "https://files.pythonhosted.org/packages/95/e4/a3b9480c78cf8ee86626cb06f8d931d74d775897d44201ccb813097ae697/regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70", size = 274837, upload-time = "2026-01-14T23:17:23.146Z" }, +] + [[package]] name = "requests" version = "2.32.5" @@ -1638,17 +2042,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + [[package]] name = "rich" -version = "14.2.0" +version = "14.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, ] [[package]] @@ -1775,28 +2191,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, - { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, - { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, - { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, - { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, ] [[package]] @@ -1819,11 +2235,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.2" +version = "2.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/93/f2/21d6ca70c3cf35d01ae9e01be534bf6b6b103c157a728082a5028350c310/soupsieve-2.8.2.tar.gz", hash = "sha256:78a66b0fdee2ab40b7199dc3e747ee6c6e231899feeaae0b9b98a353afd48fd8", size = 118601, upload-time = "2026-01-18T16:21:31.09Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/9a/b4450ccce353e2430621b3bb571899ffe1033d5cd72c9e065110f95b1a63/soupsieve-2.8.2-py3-none-any.whl", hash = "sha256:0f4c2f6b5a5fb97a641cf69c0bd163670a0e45e6d6c01a2107f93a6a6f93c51a", size = 37016, upload-time = "2026-01-18T16:21:29.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] [[package]] @@ -1884,15 +2300,15 @@ wheels = [ [[package]] name = "sqlmodel" -version = "0.0.31" +version = "0.0.32" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/b8/e7cd6def4a773f25d6e29ffce63ccbfd6cf9488b804ab6fb9b80d334b39d/sqlmodel-0.0.31.tar.gz", hash = "sha256:2d41a8a9ee05e40736e2f9db8ea28cbfe9b5d4e5a18dd139e80605025e0c516c", size = 94952, upload-time = "2025-12-28T12:35:01.436Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/89/67f8964f3b2ed073fa4e95201e708291935d00e3600f36f09c1be3e279fe/sqlmodel-0.0.32.tar.gz", hash = "sha256:48e8fe4c8c3d7d8bf8468db17fa92ca680421e86cfec8b352217ef40736767be", size = 94140, upload-time = "2026-02-01T18:19:14.752Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/72/5aa5be921800f6418a949a73c9bb7054890881143e6bc604a93d228a95a3/sqlmodel-0.0.31-py3-none-any.whl", hash = "sha256:6d946d56cac4c2db296ba1541357cee2e795d68174e2043cd138b916794b1513", size = 27093, upload-time = "2025-12-28T12:35:00.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/de/d9b40ed2c570fd612c2abd57e4d9084a9d8eb1797447e2ce897b77b1c4b2/sqlmodel-0.0.32-py3-none-any.whl", hash = "sha256:d62f0702599592046c1a136d3512feab3d5a80e2988642ef0ed2c89b9b8b297b", size = 27416, upload-time = "2026-02-01T18:19:15.992Z" }, ] [[package]] @@ -1921,6 +2337,76 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/b3/2cb7c17b6c4cf8ca983204255d3f1d95eda7213e247e6947a0ee2c747a2c/tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970", size = 1051991, upload-time = "2025-10-06T20:21:34.098Z" }, + { url = "https://files.pythonhosted.org/packages/27/0f/df139f1df5f6167194ee5ab24634582ba9a1b62c6b996472b0277ec80f66/tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16", size = 995798, upload-time = "2025-10-06T20:21:35.579Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5d/26a691f28ab220d5edc09b9b787399b130f24327ef824de15e5d85ef21aa/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030", size = 1129865, upload-time = "2025-10-06T20:21:36.675Z" }, + { url = "https://files.pythonhosted.org/packages/b2/94/443fab3d4e5ebecac895712abd3849b8da93b7b7dec61c7db5c9c7ebe40c/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134", size = 1152856, upload-time = "2025-10-06T20:21:37.873Z" }, + { url = "https://files.pythonhosted.org/packages/54/35/388f941251b2521c70dd4c5958e598ea6d2c88e28445d2fb8189eecc1dfc/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a", size = 1195308, upload-time = "2025-10-06T20:21:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/f8/00/c6681c7f833dd410576183715a530437a9873fa910265817081f65f9105f/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892", size = 1255697, upload-time = "2025-10-06T20:21:41.154Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d2/82e795a6a9bafa034bf26a58e68fe9a89eeaaa610d51dbeb22106ba04f0a/tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1", size = 879375, upload-time = "2025-10-06T20:21:43.201Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb", size = 1051565, upload-time = "2025-10-06T20:21:44.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa", size = 995284, upload-time = "2025-10-06T20:21:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc", size = 1129201, upload-time = "2025-10-06T20:21:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded", size = 1152444, upload-time = "2025-10-06T20:21:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd", size = 1195080, upload-time = "2025-10-06T20:21:49.246Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967", size = 1255240, upload-time = "2025-10-06T20:21:50.274Z" }, + { url = "https://files.pythonhosted.org/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def", size = 879422, upload-time = "2025-10-06T20:21:51.734Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, +] + [[package]] name = "tomli" version = "2.4.0" @@ -1977,14 +2463,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.1" +version = "4.67.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/89/4b0001b2dab8df0a5ee2787dcbe771de75ded01f18f1f8d53dedeea2882b/tqdm-4.67.2.tar.gz", hash = "sha256:649aac53964b2cb8dec76a14b405a4c0d13612cb8933aae547dd144eacc99653", size = 169514, upload-time = "2026-01-30T23:12:06.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/31eac96de2915cf20ccaed0225035db149dfb9165a9ed28d4b252ef3f7f7/tqdm-4.67.2-py3-none-any.whl", hash = "sha256:9a12abcbbff58b6036b2167d9d3853042b9d436fe7330f06ae047867f2f8e0a7", size = 78354, upload-time = "2026-01-30T23:12:04.368Z" }, ] [[package]] @@ -2032,6 +2518,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] +[[package]] +name = "uuid-utils" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/7c/3a926e847516e67bc6838634f2e54e24381105b4e80f9338dc35cca0086b/uuid_utils-0.14.0.tar.gz", hash = "sha256:fc5bac21e9933ea6c590433c11aa54aaca599f690c08069e364eb13a12f670b4", size = 22072, upload-time = "2026-01-20T20:37:15.729Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/42/42d003f4a99ddc901eef2fd41acb3694163835e037fb6dde79ad68a72342/uuid_utils-0.14.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f6695c0bed8b18a904321e115afe73b34444bc8451d0ce3244a1ec3b84deb0e5", size = 601786, upload-time = "2026-01-20T20:37:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/96/e6/775dfb91f74b18f7207e3201eb31ee666d286579990dc69dd50db2d92813/uuid_utils-0.14.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4f0a730bbf2d8bb2c11b93e1005e91769f2f533fa1125ed1f00fd15b6fcc732b", size = 303943, upload-time = "2026-01-20T20:37:18.767Z" }, + { url = "https://files.pythonhosted.org/packages/17/82/ea5f5e85560b08a1f30cdc65f75e76494dc7aba9773f679e7eaa27370229/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ce3fd1a4fdedae618fc3edc8faf91897012469169d600133470f49fd699ed3", size = 340467, upload-time = "2026-01-20T20:37:11.794Z" }, + { url = "https://files.pythonhosted.org/packages/ca/33/54b06415767f4569882e99b6470c6c8eeb97422686a6d432464f9967fd91/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ae4a98416a440e78f7d9543d11b11cae4bab538b7ed94ec5da5221481748f2", size = 346333, upload-time = "2026-01-20T20:37:12.818Z" }, + { url = "https://files.pythonhosted.org/packages/cb/10/a6bce636b8f95e65dc84bf4a58ce8205b8e0a2a300a38cdbc83a3f763d27/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:971e8c26b90d8ae727e7f2ac3ee23e265971d448b3672882f2eb44828b2b8c3e", size = 470859, upload-time = "2026-01-20T20:37:01.512Z" }, + { url = "https://files.pythonhosted.org/packages/8a/27/84121c51ea72f013f0e03d0886bcdfa96b31c9b83c98300a7bd5cc4fa191/uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5cde1fa82804a8f9d2907b7aec2009d440062c63f04abbdb825fce717a5e860", size = 341988, upload-time = "2026-01-20T20:37:22.881Z" }, + { url = "https://files.pythonhosted.org/packages/90/a4/01c1c7af5e6a44f20b40183e8dac37d6ed83e7dc9e8df85370a15959b804/uuid_utils-0.14.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7343862a2359e0bd48a7f3dfb5105877a1728677818bb694d9f40703264a2db", size = 365784, upload-time = "2026-01-20T20:37:10.808Z" }, + { url = "https://files.pythonhosted.org/packages/04/f0/65ee43ec617b8b6b1bf2a5aecd56a069a08cca3d9340c1de86024331bde3/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c51e4818fdb08ccec12dc7083a01f49507b4608770a0ab22368001685d59381b", size = 523750, upload-time = "2026-01-20T20:37:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/95/d3/6bf503e3f135a5dfe705a65e6f89f19bccd55ac3fb16cb5d3ec5ba5388b8/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:181bbcccb6f93d80a8504b5bd47b311a1c31395139596edbc47b154b0685b533", size = 615818, upload-time = "2026-01-20T20:37:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/df/6c/99937dd78d07f73bba831c8dc9469dfe4696539eba2fc269ae1b92752f9e/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:5c8ae96101c3524ba8dbf762b6f05e9e9d896544786c503a727c5bf5cb9af1a7", size = 580831, upload-time = "2026-01-20T20:37:19.691Z" }, + { url = "https://files.pythonhosted.org/packages/44/fa/bbc9e2c25abd09a293b9b097a0d8fc16acd6a92854f0ec080f1ea7ad8bb3/uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00ac3c6edfdaff7e1eed041f4800ae09a3361287be780d7610a90fdcde9befdc", size = 546333, upload-time = "2026-01-20T20:37:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9b/e5e99b324b1b5f0c62882230455786df0bc66f67eff3b452447e703f45d2/uuid_utils-0.14.0-cp39-abi3-win32.whl", hash = "sha256:ec2fd80adf8e0e6589d40699e6f6df94c93edcc16dd999be0438dd007c77b151", size = 177319, upload-time = "2026-01-20T20:37:04.208Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/2c7d417ea483b6ff7820c948678fdf2ac98899dc7e43bb15852faa95acaf/uuid_utils-0.14.0-cp39-abi3-win_amd64.whl", hash = "sha256:efe881eb43a5504fad922644cb93d725fd8a6a6d949bd5a4b4b7d1a1587c7fd1", size = 182566, upload-time = "2026-01-20T20:37:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/b8/86/49e4bdda28e962fbd7266684171ee29b3d92019116971d58783e51770745/uuid_utils-0.14.0-cp39-abi3-win_arm64.whl", hash = "sha256:32b372b8fd4ebd44d3a219e093fe981af4afdeda2994ee7db208ab065cfcd080", size = 182809, upload-time = "2026-01-20T20:37:05.139Z" }, + { url = "https://files.pythonhosted.org/packages/f1/03/1f1146e32e94d1f260dfabc81e1649102083303fb4ad549775c943425d9a/uuid_utils-0.14.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:762e8d67992ac4d2454e24a141a1c82142b5bde10409818c62adbe9924ebc86d", size = 587430, upload-time = "2026-01-20T20:37:24.998Z" }, + { url = "https://files.pythonhosted.org/packages/87/ba/d5a7469362594d885fd9219fe9e851efbe65101d3ef1ef25ea321d7ce841/uuid_utils-0.14.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:40be5bf0b13aa849d9062abc86c198be6a25ff35316ce0b89fc25f3bac6d525e", size = 298106, upload-time = "2026-01-20T20:37:23.896Z" }, + { url = "https://files.pythonhosted.org/packages/8a/11/3dafb2a5502586f59fd49e93f5802cd5face82921b3a0f3abb5f357cb879/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:191a90a6f3940d1b7322b6e6cceff4dd533c943659e0a15f788674407856a515", size = 333423, upload-time = "2026-01-20T20:37:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f2/c8987663f0cdcf4d717a36d85b5db2a5589df0a4e129aa10f16f4380ef48/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa4525f4ad82f9d9c842f9a3703f1539c1808affbaec07bb1b842f6b8b96aa5", size = 338659, upload-time = "2026-01-20T20:37:14.286Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c8/929d81665d83f0b2ffaecb8e66c3091a50f62c7cb5b65e678bd75a96684e/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdbd82ff20147461caefc375551595ecf77ebb384e46267f128aca45a0f2cdfc", size = 467029, upload-time = "2026-01-20T20:37:08.277Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a0/27d7daa1bfed7163f4ccaf52d7d2f4ad7bb1002a85b45077938b91ee584f/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff57e8a5d540006ce73cf0841a643d445afe78ba12e75ac53a95ca2924a56be", size = 333298, upload-time = "2026-01-20T20:37:07.271Z" }, + { url = "https://files.pythonhosted.org/packages/63/d4/acad86ce012b42ce18a12f31ee2aa3cbeeb98664f865f05f68c882945913/uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fd9112ca96978361201e669729784f26c71fecc9c13a7f8a07162c31bd4d1e2", size = 359217, upload-time = "2026-01-20T20:36:59.687Z" }, +] + [[package]] name = "uvicorn" version = "0.40.0" @@ -2129,6 +2644,124 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] +[[package]] +name = "xxhash" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/ee/f9f1d656ad168681bb0f6b092372c1e533c4416b8069b1896a175c46e484/xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71", size = 32845, upload-time = "2025-10-02T14:33:51.573Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/93508d9460b292c74a09b83d16750c52a0ead89c51eea9951cb97a60d959/xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d", size = 30807, upload-time = "2025-10-02T14:33:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/07/55/28c93a3662f2d200c70704efe74aab9640e824f8ce330d8d3943bf7c9b3c/xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8", size = 193786, upload-time = "2025-10-02T14:33:54.272Z" }, + { url = "https://files.pythonhosted.org/packages/c1/96/fec0be9bb4b8f5d9c57d76380a366f31a1781fb802f76fc7cda6c84893c7/xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058", size = 212830, upload-time = "2025-10-02T14:33:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a0/c706845ba77b9611f81fd2e93fad9859346b026e8445e76f8c6fd057cc6d/xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2", size = 211606, upload-time = "2025-10-02T14:33:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/164126a2999e5045f04a69257eea946c0dc3e86541b400d4385d646b53d7/xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc", size = 444872, upload-time = "2025-10-02T14:33:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4b/55ab404c56cd70a2cf5ecfe484838865d0fea5627365c6c8ca156bd09c8f/xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc", size = 193217, upload-time = "2025-10-02T14:33:59.724Z" }, + { url = "https://files.pythonhosted.org/packages/45/e6/52abf06bac316db33aa269091ae7311bd53cfc6f4b120ae77bac1b348091/xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07", size = 210139, upload-time = "2025-10-02T14:34:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/34/37/db94d490b8691236d356bc249c08819cbcef9273a1a30acf1254ff9ce157/xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4", size = 197669, upload-time = "2025-10-02T14:34:03.664Z" }, + { url = "https://files.pythonhosted.org/packages/b7/36/c4f219ef4a17a4f7a64ed3569bc2b5a9c8311abdb22249ac96093625b1a4/xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06", size = 210018, upload-time = "2025-10-02T14:34:05.325Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/bfac889a374fc2fc439a69223d1750eed2e18a7db8514737ab630534fa08/xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4", size = 413058, upload-time = "2025-10-02T14:34:06.925Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d1/555d8447e0dd32ad0930a249a522bb2e289f0d08b6b16204cfa42c1f5a0c/xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b", size = 190628, upload-time = "2025-10-02T14:34:08.669Z" }, + { url = "https://files.pythonhosted.org/packages/d1/15/8751330b5186cedc4ed4b597989882ea05e0408b53fa47bcb46a6125bfc6/xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b", size = 30577, upload-time = "2025-10-02T14:34:10.234Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/53f87e8b5871a6eb2ff7e89c48c66093bda2be52315a8161ddc54ea550c4/xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb", size = 31487, upload-time = "2025-10-02T14:34:11.618Z" }, + { url = "https://files.pythonhosted.org/packages/9f/00/60f9ea3bb697667a14314d7269956f58bf56bb73864f8f8d52a3c2535e9a/xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d", size = 27863, upload-time = "2025-10-02T14:34:12.619Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a", size = 32844, upload-time = "2025-10-02T14:34:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248", size = 194665, upload-time = "2025-10-02T14:34:16.541Z" }, + { url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" }, + { url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" }, + { url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77", size = 198654, upload-time = "2025-10-02T14:34:25.644Z" }, + { url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd", size = 30574, upload-time = "2025-10-02T14:34:31.028Z" }, + { url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef", size = 31481, upload-time = "2025-10-02T14:34:32.062Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7", size = 27861, upload-time = "2025-10-02T14:34:33.555Z" }, + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0", size = 30662, upload-time = "2025-10-02T14:37:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296", size = 41056, upload-time = "2025-10-02T14:37:02.879Z" }, + { url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" }, +] + [[package]] name = "zipp" version = "3.23.0" @@ -2137,3 +2770,93 @@ sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50e wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/28efd1d371f1acd037ac64ed1c5e2b41514a6cc937dd6ab6a13ab9f0702f/zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd", size = 795256, upload-time = "2025-09-14T22:15:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/ef34ef77f1ee38fc8e4f9775217a613b452916e633c4f1d98f31db52c4a5/zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7", size = 640565, upload-time = "2025-09-14T22:15:58.177Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1b/4fdb2c12eb58f31f28c4d28e8dc36611dd7205df8452e63f52fb6261d13e/zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550", size = 5345306, upload-time = "2025-09-14T22:16:00.165Z" }, + { url = "https://files.pythonhosted.org/packages/73/28/a44bdece01bca027b079f0e00be3b6bd89a4df180071da59a3dd7381665b/zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d", size = 5055561, upload-time = "2025-09-14T22:16:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/e9/74/68341185a4f32b274e0fc3410d5ad0750497e1acc20bd0f5b5f64ce17785/zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b", size = 5402214, upload-time = "2025-09-14T22:16:04.109Z" }, + { url = "https://files.pythonhosted.org/packages/8b/67/f92e64e748fd6aaffe01e2b75a083c0c4fd27abe1c8747fee4555fcee7dd/zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0", size = 5449703, upload-time = "2025-09-14T22:16:06.312Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e5/6d36f92a197c3c17729a2125e29c169f460538a7d939a27eaaa6dcfcba8e/zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0", size = 5556583, upload-time = "2025-09-14T22:16:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/41939e60d8d7ebfe2b747be022d0806953799140a702b90ffe214d557638/zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd", size = 5045332, upload-time = "2025-09-14T22:16:10.444Z" }, + { url = "https://files.pythonhosted.org/packages/b3/87/d3ee185e3d1aa0133399893697ae91f221fda79deb61adbe998a7235c43f/zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701", size = 5572283, upload-time = "2025-09-14T22:16:12.128Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/58635ae6104df96671076ac7d4ae7816838ce7debd94aecf83e30b7121b0/zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1", size = 4959754, upload-time = "2025-09-14T22:16:14.225Z" }, + { url = "https://files.pythonhosted.org/packages/75/d6/57e9cb0a9983e9a229dd8fd2e6e96593ef2aa82a3907188436f22b111ccd/zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150", size = 5266477, upload-time = "2025-09-14T22:16:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/ee891e5edf33a6ebce0a028726f0bbd8567effe20fe3d5808c42323e8542/zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab", size = 5440914, upload-time = "2025-09-14T22:16:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/58/08/a8522c28c08031a9521f27abc6f78dbdee7312a7463dd2cfc658b813323b/zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e", size = 5819847, upload-time = "2025-09-14T22:16:20.559Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/4c91411805c3f7b6f31c60e78ce347ca48f6f16d552fc659af6ec3b73202/zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74", size = 5363131, upload-time = "2025-09-14T22:16:22.206Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d6/8c4bd38a3b24c4c7676a7a3d8de85d6ee7a983602a734b9f9cdefb04a5d6/zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa", size = 436469, upload-time = "2025-09-14T22:16:25.002Z" }, + { url = "https://files.pythonhosted.org/packages/93/90/96d50ad417a8ace5f841b3228e93d1bb13e6ad356737f42e2dde30d8bd68/zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e", size = 506100, upload-time = "2025-09-14T22:16:23.569Z" }, + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, + { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, + { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +]