diff --git a/AGENTS.md b/AGENTS.md index cb06ff5..5eb57ca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,11 +47,14 @@ Event-driven, layered architecture with: - `types.py`: Event-stream union (`AgentEvent`), `AgentSessionProtocol`, tool result dataclasses, and re-exports of patching types. - `config_types.py`: `BackendConfig` and `ModelConfig` for provider/model presets. - `command_types.py`: `CommandType`, `CommandResult`, and `CommandEffect` for command parsing/execution. - - `deps.py`: `RunDeps` context and `NextAgent` alias for dependency-injected agent runs. + - `deps.py`: `RunDeps` context for dependency-injected agent runs. - `config.py`: Centralized system constants (output caps, suffixes, default skill dirs, `DEFAULT_MODEL`, `DEFAULT_PROVIDER_DIRS`). Must be UI-agnostic. - `loop.py`: `AgentSession` implementing the bidirectional async generator loop and mapping pydantic_ai events to `AgentEvent`. - `factory.py`: `create_agent` factory assembling the `pydantic_ai.Agent` using the model preset configured in `providers.toml` (default preset: `local-oss` on the `ollama` backend), plus the shared toolset and skills table. - - `commands.py`: Command parsing (`CommandParser`) and effect-based execution (`execute_command`). Commands produce pure `CommandEffect` data; UIs apply effects. + - `commands.py`: Command parsing (`CommandParser`) and effect-based execution (`execute_command`). + - `CommandParser` performs pure parsing without validation + - Commands produce pure `CommandEffect` data containing `SessionConfig` + - Session factories validate model names and apply configuration to create new sessions - `tool_parsing.py`: Robust JSON/dict argument handling for tool calls. - `tools/`: Tool package organized by category (see Available Tools section) - `skill_loader.py`: Discovers `SKILL.md` skills from project directories (`.github/skills`, `.claude/skills`), user directory (`~/.agentc/skills`), and bundled skills (installed to platform-specific user data directory). Earlier directories take precedence. @@ -66,11 +69,17 @@ Event-driven, layered architecture with: - `textual_messages.py`: Textual-specific `Message` types (e.g., `AgentText`, `AgentApprovalRequest`). - `console_messages.py`: Console event dataclasses. -- **`ui/`**: User interface implementations (Textual TUI, Console) +- **`ui/`**: User interface implementations (Textual TUI widgets and components) - `textual_app.py`: The main Textual `App` implementation. + - Receives model names list via dependency injection from composition root + - Each backend's entry point discovers models using backend-specific mechanisms + - UI layer remains completely backend-agnostic - `widgets.py`: Reusable UI components (status bar, approval forms, etc.). - - `run_textual.py`: Launcher for the Textual UI (entry point: `agent-c`). - - `run_console.py`: Launcher for the Console UI demo (auto-approval sample prompt, entry point: `run-console`). + +- **`entrypoints/`**: Application composition roots (dependency injection and bootstrapping) + - `run_textual.py`: Pydantic AI backend launcher (entry point: `agent-c`, `run-textual`) + - `run_textual_gh.py`: GitHub Copilot SDK backend launcher (entry point: `run-textual-gh`) + - `run_console.py`: Console UI demo launcher (entry point: `run-console`) - **`skills/`**: Bundled skills (e.g., fibonacci-number) packaged with the application @@ -93,6 +102,16 @@ Supported commands: `/clear`, `/reset`, `/exit`, `/quit`, `/bye`, `/model Framework-specific commands (`/exit`, unknown commands) return `None` and are handled directly by the UI layer. +### Session Factory Pattern + +Commands produce `SessionConfig` (pure data), which session factories translate into backend-specific sessions: + +- **`SessionFactoryProtocol`** (`types.py`): Interface for session creation +- **`PydanticAISessionFactory`** (`backends/pydantic_ai/`): Uses `create_agent()` + `AgentSession` +- **`GhCopilotSessionFactory`** (`backends/github_copilot/`): Uses `CopilotClient.create_session()` + +Entry points inject concrete factories into the UI layer, which uses the Protocol abstraction. + ### Skills System `SkillLoader` scans bundled skills (installed to user data directory) and project directories (`.github/skills` and `.claude/skills` by default) for `SKILL.md` descriptors. `create_agent` injects a table of discovered skills into the system prompt, with guidance to `cd` into the skill base directory before running documented commands. @@ -275,6 +294,7 @@ When making changes, include in your response: - **Build system**: `uv_build` - **Entry points**: - - `agentc.ui.run_textual:main` (agent-c command - default Textual UI) - - `agentc.ui.run_console:main` (run-console command) - - `agentc.ui.run_textual:main` (run-textual command) + - `agentc.entrypoints.run_textual:main` (agent-c command - default Textual UI) + - `agentc.entrypoints.run_console:main` (run-console command) + - `agentc.entrypoints.run_textual:main` (run-textual command) + - `agentc.entrypoints.run_textual_gh:main_sync` (run-textual-gh command) diff --git a/pyproject.toml b/pyproject.toml index 369cd14..ee9291c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,11 @@ [project] name = "agent-c" version = "0.1.0" -description = "Agent C is a simple code editing assistant powered by Pydantic AI, supporting multiple LLM providers and delegation to sub-agents." +description = "Agent C is a simple code editing assistant powered by Pydantic AI or the GitHub Copilot SDK." readme = "README.md" requires-python = ">=3.13" dependencies = [ + "github-copilot-sdk>=0.1.15", "pathspec>=0.12.1", "platformdirs>=4.5.0", "pydantic-ai>=1.6.0", @@ -28,6 +29,7 @@ source-include = [ ] [project.scripts] -agent-c = "agentc.ui.run_textual:main" -run-console = "agentc.ui.run_console:main" -run-textual = "agentc.ui.run_textual:main" +agent-c = "agentc.entrypoints.run_textual:main" +run-console = "agentc.entrypoints.run_console:main" +run-textual = "agentc.entrypoints.run_textual:main" +run-textual-gh = "agentc.entrypoints.run_textual_gh:main_sync" diff --git a/src/agentc/adapters/textual.py b/src/agentc/adapters/textual.py index 9476484..5bf9a44 100644 --- a/src/agentc/adapters/textual.py +++ b/src/agentc/adapters/textual.py @@ -41,7 +41,7 @@ def __init__( session: AgentSessionProtocol, prompt: str, cancellation_event: asyncio.Event | None = None, - debounce_threshold: int = 40, + debounce_threshold: int = 1, # TODO: make configurable ): self._app = app self._session = session diff --git a/src/agentc/core/backends/__init__.py b/src/agentc/core/backends/__init__.py new file mode 100644 index 0000000..8a576fd --- /dev/null +++ b/src/agentc/core/backends/__init__.py @@ -0,0 +1 @@ +"""Backend-specific implementations for Agent C.""" diff --git a/src/agentc/core/backends/github_copilot/__init__.py b/src/agentc/core/backends/github_copilot/__init__.py new file mode 100644 index 0000000..8d5e06c --- /dev/null +++ b/src/agentc/core/backends/github_copilot/__init__.py @@ -0,0 +1,6 @@ +"""GitHub Copilot backend implementation for Agent C.""" + +from .loop import GhAgentSession +from .session_factory import GhCopilotSessionFactory + +__all__ = ["GhAgentSession", "GhCopilotSessionFactory"] diff --git a/src/agentc/core/backends/github_copilot/loop.py b/src/agentc/core/backends/github_copilot/loop.py new file mode 100644 index 0000000..b2b4270 --- /dev/null +++ b/src/agentc/core/backends/github_copilot/loop.py @@ -0,0 +1,101 @@ +import asyncio +from asyncio import Event, AbstractEventLoop +from typing import Optional + +from copilot import CopilotSession +from copilot.generated.session_events import SessionEvent, SessionEventType + +from ...types import ( + AgentChunk, + AgentDone, + AgentSessionProtocol, + ToolCallInfo, + ToolCallResultInfo, + ToolResult, + AgentEventStream, +) + + +class GhAgentSession(AgentSessionProtocol): + """ + Encapsulates the state of a GitHub agentic session. + + This class keeps the history and agent implementation details opaque to the UI. + """ + + def __init__( + self, + session: CopilotSession, + ): + self._session = session + self._event_queue: asyncio.Queue[SessionEvent] = asyncio.Queue() + self._loop: Optional[AbstractEventLoop] + try: + self._loop = asyncio.get_running_loop() + except RuntimeError: + self._loop = None + + def _on_event(event: SessionEvent): + # Ensure we enqueue events on the asyncio event loop thread-safely. + if self._loop and self._loop.is_running(): + try: + self._loop.call_soon_threadsafe(self._event_queue.put_nowait, event) + except Exception: + # Fallback to direct put if scheduling fails. + try: + self._event_queue.put_nowait(event) + except Exception: + pass + else: + try: + self._event_queue.put_nowait(event) + except Exception: + pass + + self._session.on(_on_event) + + def _on_event(self, event: SessionEvent): + self._event_queue.put_nowait(event) + + async def run( + self, + prompt: str, + cancellation_event: Event | None = None, + ) -> AgentEventStream: + """Run the agentic session with the given prompt.""" + await self._session.send({"prompt": prompt}) + + # Yield events from the queue until SESSION_IDLE. + while True: + if cancellation_event is not None and cancellation_event.is_set(): + break + + event = await self._event_queue.get() + + match event.type: + case SessionEventType.ASSISTANT_MESSAGE_DELTA: + delta = event.data.delta_content or "" + yield AgentChunk(content=delta, is_thought=False) + case SessionEventType.ASSISTANT_REASONING_DELTA: + delta = event.data.delta_content or "" + yield AgentChunk(content=delta, is_thought=True) + case SessionEventType.TOOL_EXECUTION_START: + internal_tools = {"report_intent"} + if event.data.tool_name not in internal_tools: + yield ToolCallInfo( + tool_name=event.data.tool_name or "unknown", + args=event.data.arguments, + tool_call_id=event.data.tool_call_id or "unknown", + ) + case SessionEventType.TOOL_EXECUTION_COMPLETE: + yield ToolCallResultInfo( + tool_call_id=event.data.tool_call_id or "unknown", + result=ToolResult( + success=event.data.success if event.data.success is not None else False, + content=str(event.data.result) if event.data.result is not None else "", + error=str(event.data.error) if event.data.error else None, + ), + ) + case SessionEventType.SESSION_IDLE: + yield AgentDone(history=None) # TOOD: populate history + break diff --git a/src/agentc/core/backends/github_copilot/session_factory.py b/src/agentc/core/backends/github_copilot/session_factory.py new file mode 100644 index 0000000..291132c --- /dev/null +++ b/src/agentc/core/backends/github_copilot/session_factory.py @@ -0,0 +1,86 @@ +"""Session factory for the GitHub Copilot SDK backend.""" + +from __future__ import annotations + +from typing import Any + +from copilot import CopilotClient +from copilot.types import SessionConfig as CopilotSessionConfig + +from ...command_types import SessionConfig +from ...types import AgentSessionProtocol +from .loop import GhAgentSession + + +class GhCopilotSessionFactory: + """Factory for creating GitHub Copilot SDK sessions. + + Implements the `SessionFactoryProtocol` by wrapping CopilotClient + session creation. Manages SDK-level session lifecycle (destroying + old sessions before creating new ones). + """ + + def __init__( + self, + client: CopilotClient, + base_config: CopilotSessionConfig, + ) -> None: + """Initialize with a running CopilotClient and base configuration. + + Args: + client: Running CopilotClient instance (managed by entry point). + base_config: Base SDK configuration (model, permissions, system + message, etc). Model and skill_dirs can be overridden per + session via SessionConfig. + """ + self._client = client + self._base_config = base_config + self._current_copilot_session: Any = None + + async def create_session(self, config: SessionConfig) -> AgentSessionProtocol: + """Create a GitHub Copilot SDK session. + + Destroys any existing session before creating a new one, since the + SDK manages session state internally. + + Args: + config: Backend-agnostic session configuration. + + Returns: + GhAgentSession wrapping a CopilotSession. + """ + if self._current_copilot_session is not None: + try: + await self._current_copilot_session.destroy() + except Exception: + pass + finally: + self._current_copilot_session = None + + sdk_config = CopilotSessionConfig( + model=config.model_name or self._base_config["model"], # type: ignore + skill_directories=( + [str(d) for d in config.skill_dirs] + if config.skill_dirs + else self._base_config["skill_directories"] # type: ignore + ), + streaming=self._base_config["streaming"], # type: ignore + system_message=self._base_config["system_message"], # type: ignore + on_permission_request=self._base_config["on_permission_request"], # type: ignore + ) + + self._current_copilot_session = await self._client.create_session(sdk_config) + return GhAgentSession(self._current_copilot_session) + + async def cleanup(self) -> None: + """Clean up the current session (called on app shutdown).""" + if self._current_copilot_session is not None: + try: + await self._current_copilot_session.destroy() + except Exception: + pass + finally: + self._current_copilot_session = None + + +__all__ = ["GhCopilotSessionFactory"] diff --git a/src/agentc/core/backends/pydantic_ai/__init__.py b/src/agentc/core/backends/pydantic_ai/__init__.py new file mode 100644 index 0000000..095b1c5 --- /dev/null +++ b/src/agentc/core/backends/pydantic_ai/__init__.py @@ -0,0 +1,5 @@ +"""Pydantic_AI backend implementation for Agent C.""" + +from .session_factory import PydanticAISessionFactory + +__all__ = ["PydanticAISessionFactory"] diff --git a/src/agentc/core/factory.py b/src/agentc/core/backends/pydantic_ai/factory.py similarity index 84% rename from src/agentc/core/factory.py rename to src/agentc/core/backends/pydantic_ai/factory.py index fe274f5..e6fd103 100644 --- a/src/agentc/core/factory.py +++ b/src/agentc/core/backends/pydantic_ai/factory.py @@ -1,26 +1,19 @@ -""" -Agent factory for Agent C Next. - -This module assembles the agent using tools and types. -""" +"""Agent factory for the Pydantic_AI backend.""" from dataclasses import replace from pathlib import Path from typing import Any -from pydantic_ai import ( - Agent, - DeferredToolRequests, - Tool, -) +from pydantic_ai import Agent, DeferredToolRequests, Tool -from .config import DEFAULT_MODEL +from ...config import DEFAULT_MODEL +from ...skill_loader import SkillLoader +from ...deps import RunDeps +from .types import NextAgent +from .provider_loader import load_providers, build_model from .tools.filesystem import list_files, glob_paths, search_files from .tools.editing import read_file, create_file, edit_file, apply_hunks from .tools.execution import run_command -from .skill_loader import SkillLoader -from .deps import RunDeps, NextAgent -from .provider_loader import load_providers, build_model def create_agent( @@ -29,14 +22,8 @@ def create_agent( override_model_name: str | None = None, **model_params: Any, ) -> NextAgent: - """Factory function to create a configured agent instance. - - Args: - skill_dirs: Optional skill directories to include during skill discovery. - model_name: Name of the model preset to load from providers.toml. - override_model_name: Runtime override for the model string passed to the backend. - **model_params: Extra model keyword arguments merged with preset params. - """ + """Factory function to create a configured pydantic_ai agent instance.""" + loader = SkillLoader() if skill_dirs is None: skill_dirs = loader.get_default_skill_dirs() @@ -75,7 +62,6 @@ def create_agent( Tool(run_command, takes_ctx=True, requires_approval=True), ] - # Dynamically load skills discovered_skills = loader.discover_skills(skill_dirs) skills_summary = loader.get_skills_summary(discovered_skills) @@ -84,7 +70,7 @@ def create_agent( tools=tools, deps_type=RunDeps, output_type=str | DeferredToolRequests, # type: ignore - system_prompt=f"""\ + system_prompt=f""" You are an expert coding assistant with comprehensive file system access and command execution capabilities. You help users navigate, analyze, edit, and manage their codebase efficiently. ## Tool Usage Strategy @@ -125,6 +111,4 @@ def create_agent( ) -__all__ = [ - "create_agent", -] +__all__ = ["create_agent"] diff --git a/src/agentc/core/loop.py b/src/agentc/core/backends/pydantic_ai/loop.py similarity index 90% rename from src/agentc/core/loop.py rename to src/agentc/core/backends/pydantic_ai/loop.py index c7c96f5..545f789 100644 --- a/src/agentc/core/loop.py +++ b/src/agentc/core/backends/pydantic_ai/loop.py @@ -1,10 +1,8 @@ """ -Agent runtime utilities for Agent C Next. +Pydantic_AI agent runtime utilities for Agent C. -Provides simple file-oriented tools, an `Agent` factory, event types used -by the UI, and an async streaming run loop that yields pydantic_ai -events (text/thinking parts, tool approval requests) and handles the -tool-approval flow. +Maps pydantic_ai streaming events to core AgentEvent types and handles the +approval handshake. """ import asyncio @@ -28,27 +26,24 @@ ToolReturnPart, ) -from .deps import NextAgent, RunDeps -from .tool_parsing import parse_tool_args -from .types import ( +from ...deps import RunDeps +from ...tool_parsing import parse_tool_args +from ...types import ( AgentChunk, AgentDone, + AgentEventStream, AgentSessionProtocol, ApprovalRequest, ApprovalResponse, ToolCallInfo, ToolCallResultInfo, ToolResult, - AgentEventStream, ) +from .types import NextAgent class AgentSession(AgentSessionProtocol): - """ - Encapsulates the state of an agentic session. - - This class keeps the history and agent implementation details opaque to the UI. - """ + """Encapsulates the state of a pydantic_ai agentic session.""" def __init__( self, @@ -75,7 +70,7 @@ def update_history(self, history: list[Any]) -> None: self._history = history def _map_event_to_chunk(self, event: Any) -> AgentChunk | None: - """Map a Pydantic AI event to an agnostic AgentChunk.""" + """Map a pydantic_ai event to an agnostic AgentChunk.""" match event: case PartStartEvent(part=TextPart(content=text)) if text: return AgentChunk(content=text, is_thought=False) @@ -94,7 +89,7 @@ def _map_event_to_chunk(self, event: Any) -> AgentChunk | None: return None def _map_tool_call(self, event: Any) -> ToolCallInfo | None: - """Map a Pydantic AI PartStartEvent to an agnostic ToolCallInfo.""" + """Map a pydantic_ai PartStartEvent to an agnostic ToolCallInfo.""" match event: case PartStartEvent( part=ToolCallPart(tool_name=name, args=args, tool_call_id=call_id) @@ -107,7 +102,7 @@ def _map_tool_call(self, event: Any) -> ToolCallInfo | None: return None def _map_tool_result(self, event: Any) -> ToolCallResultInfo | None: - """Map a Pydantic AI FunctionToolResultEvent to an agnostic ToolCallResultInfo.""" + """Map a pydantic_ai FunctionToolResultEvent to an agnostic ToolCallResultInfo.""" if not isinstance(event, FunctionToolResultEvent): return None @@ -238,9 +233,6 @@ async def run( else: # Fallback if the stream ends without a result event yield AgentDone(history=self._history) - return -__all__ = [ - "AgentSession", -] +__all__ = ["AgentSession"] diff --git a/src/agentc/core/provider_loader.py b/src/agentc/core/backends/pydantic_ai/provider_loader.py similarity index 84% rename from src/agentc/core/provider_loader.py rename to src/agentc/core/backends/pydantic_ai/provider_loader.py index ee50bbe..f1bfb85 100644 --- a/src/agentc/core/provider_loader.py +++ b/src/agentc/core/backends/pydantic_ai/provider_loader.py @@ -1,13 +1,15 @@ -"""Provider and model loading for Agent C Next.""" +"""Provider and model loading for the Pydantic_AI backend.""" import os +import sys from importlib import import_module from pathlib import Path from typing import Any import tomllib -from .config_types import BackendConfig, ModelConfig +from ...config import DEFAULT_PROVIDER_DIRS +from ...config_types import BackendConfig, ModelConfig class MissingAPIKeyError(ValueError): @@ -64,12 +66,10 @@ def _load_single_file(path: Path) -> tuple[dict[str, BackendConfig], dict[str, M def get_default_provider_dirs() -> list[Path]: """Return provider directories in priority order: repo, user, bundled.""" - from .config import DEFAULT_PROVIDER_DIRS - return [ *DEFAULT_PROVIDER_DIRS, Path.home() / ".agentc", - Path(__file__).parent.parent, + Path(__file__).parent.parent.parent.parent, ] @@ -99,7 +99,7 @@ def load_providers( merged_models |= models if not merged_backends or not merged_models: - default_bundled = Path(__file__).parent.parent / "providers.toml" + default_bundled = Path(__file__).parent.parent.parent.parent / "providers.toml" if not default_bundled.exists(): raise FileNotFoundError("Bundled providers.toml not found") if not merged_backends: @@ -124,14 +124,15 @@ def build_model( model_config: ModelConfig, backend_config: BackendConfig, ) -> tuple[Any, Any]: - """Build provider and model instances from configuration. + """Build provider and model instances from configuration.""" - Model-level `api_key_env` and `base_url` override backend values when set, - and `params` are forwarded as keyword arguments to the model constructor. - """ + get_class = _get_class + shim = sys.modules.get("agentc.core.provider_loader") + if shim and hasattr(shim, "_get_class"): + get_class = getattr(shim, "_get_class") - provider_cls = _get_class(backend_config.provider_cls_path) - model_cls = _get_class(backend_config.model_cls_path) + provider_cls = get_class(backend_config.provider_cls_path) + model_cls = get_class(backend_config.model_cls_path) api_key_env = model_config.api_key_env or backend_config.api_key_env base_url = model_config.base_url or backend_config.base_url @@ -154,3 +155,12 @@ def build_model( ) return provider, model + + +__all__ = [ + "MissingAPIKeyError", + "get_default_provider_dirs", + "load_providers", + "build_model", + "_get_class", +] diff --git a/src/agentc/core/backends/pydantic_ai/session_factory.py b/src/agentc/core/backends/pydantic_ai/session_factory.py new file mode 100644 index 0000000..205834e --- /dev/null +++ b/src/agentc/core/backends/pydantic_ai/session_factory.py @@ -0,0 +1,49 @@ +"""Session factory for the Pydantic_AI backend.""" + +from __future__ import annotations + +from ...command_types import SessionConfig +from ...types import AgentSessionProtocol +from .factory import create_agent +from .loop import AgentSession +from .provider_loader import MissingAPIKeyError + + +class PydanticAISessionFactory: + """Factory for creating Pydantic_AI agent sessions. + + Implements the `SessionFactoryProtocol` by using `create_agent()` to + build a pydantic_ai agent, then wrapping it in `AgentSession`. + """ + + async def create_session(self, config: SessionConfig) -> AgentSessionProtocol: + """Create a Pydantic_AI agent session. + + Args: + config: Backend-agnostic session configuration. + + Returns: + AgentSession wrapping a configured pydantic_ai Agent. + + Raises: + MissingAPIKeyError: If model requires API key that's not set. + ValueError: If model_name is unknown. + """ + try: + agent = create_agent( + model_name=config.model_name, + skill_dirs=config.skill_dirs, + ) + except MissingAPIKeyError: + raise + + history: list | None = [] if config.clear_history else None + + return AgentSession( + agent=agent, + history=history, + deps=config.deps, + ) + + +__all__ = ["PydanticAISessionFactory"] diff --git a/src/agentc/core/backends/pydantic_ai/tools/__init__.py b/src/agentc/core/backends/pydantic_ai/tools/__init__.py new file mode 100644 index 0000000..50dbd71 --- /dev/null +++ b/src/agentc/core/backends/pydantic_ai/tools/__init__.py @@ -0,0 +1 @@ +"""Pydantic_AI-specific tools.""" diff --git a/src/agentc/core/backends/pydantic_ai/tools/_shared.py b/src/agentc/core/backends/pydantic_ai/tools/_shared.py new file mode 100644 index 0000000..37b3ec8 --- /dev/null +++ b/src/agentc/core/backends/pydantic_ai/tools/_shared.py @@ -0,0 +1,24 @@ +"""Shared utilities for Pydantic_AI tools.""" + +from pathlib import Path + +from pydantic_ai import ModelRetry, RunContext + +from ....deps import RunDeps +from ....types import ToolResult + + +def resolve_path(path_str: str, deps: RunDeps, *, must_exist: bool = True) -> Path: + """Resolve and validate a path within allowed root directories.""" + candidate = Path(path_str).expanduser().resolve(strict=False) + + for root in deps.root_dirs: + if candidate.is_relative_to(root.resolve()): + if must_exist and not candidate.exists(): + raise ModelRetry(f"Path does not exist: {path_str}") + return candidate + + raise ModelRetry(f"Path is outside allowed directories: {path_str}") + + +__all__ = ["resolve_path", "Path", "ModelRetry", "RunContext", "RunDeps", "ToolResult"] diff --git a/src/agentc/core/backends/pydantic_ai/tools/editing.py b/src/agentc/core/backends/pydantic_ai/tools/editing.py new file mode 100644 index 0000000..b35fd60 --- /dev/null +++ b/src/agentc/core/backends/pydantic_ai/tools/editing.py @@ -0,0 +1,172 @@ +"""File editing and patching tools for the Pydantic_AI backend.""" + +from dataclasses import asdict +import json +from pathlib import Path + +from pydantic_ai import ModelRetry + +from ._shared import resolve_path, RunContext, RunDeps, ToolResult +from ....config import BACKUP_SUFFIX, TEMP_SUFFIX +from ....patching.engine import apply_file_patch +from ....patching.errors import PatchTransactionError +from ....patching.transaction import ( + apply_updates_transactionally, + create_backup, + write_text_atomic, +) +from ....types import FilePatchResult, PatchApplySummary, PatchPlan + + +def _cat_n_format(text: str) -> str: + """Return text with `cat -n` style line numbers, preserving trailing newline.""" + lines = text.splitlines() + trailing_newline = text.endswith("\n") + numbered = [f"{i:6}\t{line}" for i, line in enumerate(lines, 1)] + result = "\n".join(numbered) + if trailing_newline: + result += "\n" + return result + + +def _serialize_patch_summary(summary: PatchApplySummary) -> str: + """Serialize patch summary to JSON for tool output.""" + return json.dumps(asdict(summary), ensure_ascii=True) + + +def read_file(ctx: RunContext[RunDeps], path: str) -> ToolResult: + """Read a UTF-8 text file and return cat -n style output.""" + try: + target = resolve_path(path, ctx.deps) + if target.is_dir(): + raise ModelRetry(f"Path must be a file, not a directory: {path}") + + text = target.read_text(encoding="utf-8") + content = _cat_n_format(text) + return ToolResult(success=True, content=content) + except UnicodeDecodeError: + # Signal retry so caller can decide how to proceed with binary files + raise ModelRetry("File is not UTF-8 text") + except ModelRetry: + raise + except Exception as e: + return ToolResult(success=False, content="", error=str(e)) + + +def create_file(ctx: RunContext[RunDeps], path: str, content: str) -> ToolResult: + """Create a new file with the given content.""" + try: + target = resolve_path(path, ctx.deps, must_exist=False) + if target.exists(): + raise ModelRetry(f"File already exists: {path}") + + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content, encoding="utf-8") + return ToolResult(success=True, content=f"Created file: {path}") + except ModelRetry: + raise + except Exception as e: + return ToolResult(success=False, content="", error=str(e)) + + +def edit_file(ctx: RunContext[RunDeps], path: str, find: str, replace: str) -> ToolResult: + """Replace a unique string occurrence in a file.""" + try: + target = resolve_path(path, ctx.deps) + if target.is_dir(): + raise ModelRetry(f"Path must be a file, not a directory: {path}") + + text = target.read_text(encoding="utf-8") + occurrences = text.count(find) + if occurrences == 0: + raise ModelRetry("String to replace was not found in the file") + if occurrences > 1: + raise ModelRetry("String to replace was found multiple times; please be specific") + + updated = text.replace(find, replace, 1) + backup_path = create_backup(target, backup_suffix=BACKUP_SUFFIX) + target.write_text(updated, encoding="utf-8") + return ToolResult( + success=True, + content=( + f"Edit completed successfully for {path}\n" + f"Backup created at: {backup_path.name}" + ), + ) + except ModelRetry: + raise + except Exception as e: + return ToolResult(success=False, content="", error=str(e)) + + +def apply_hunks(ctx: RunContext[RunDeps], plan: PatchPlan) -> ToolResult: + """Apply structured patch hunks to one or more files atomically.""" + backups: dict[Path, Path] = {} + try: + file_results: list[FilePatchResult] = [] + + for file_patch in plan.files: + target = resolve_path(file_patch.path, ctx.deps) + if target.is_dir(): + raise ModelRetry(f"Path must be a file, not a directory: {file_patch.path}") + + original = target.read_text(encoding="utf-8") + patched, file_result = apply_file_patch(file_patch, original) + file_results.append(file_result) + + if not file_result.applied: + for target, backup in backups.items(): + if backup.exists(): + backup.replace(target) + summary = PatchApplySummary(applied=False, files=file_results) + return ToolResult( + success=False, + content="", + error=_serialize_patch_summary(summary), + ) + + backups[target] = create_backup(target, backup_suffix=BACKUP_SUFFIX) + write_text_atomic(target, patched, temp_suffix=TEMP_SUFFIX) + + summary = PatchApplySummary( + applied=all(result.applied for result in file_results), + files=file_results, + ) + return ToolResult(success=True, content=_serialize_patch_summary(summary)) + except ModelRetry: + raise + except PatchTransactionError as e: + for target, backup in backups.items(): + if backup.exists(): + backup.replace(target) + return ToolResult(success=False, content="", error=str(e)) + except Exception as e: + for target, backup in backups.items(): + if backup.exists(): + backup.replace(target) + return ToolResult(success=False, content="", error=str(e)) + + +def apply_updates(ctx: RunContext[RunDeps], plan: PatchPlan) -> ToolResult: + """Apply multiple file updates transactionally using patch plan.""" + try: + resolved = {resolve_path(path, ctx.deps): content for path, content in plan.updates.items()} + backups = apply_updates_transactionally( + resolved, backup_suffix=BACKUP_SUFFIX, temp_suffix=TEMP_SUFFIX + ) + summary = PatchApplySummary( + applied=True, + files=[FilePatchResult(path=p.as_posix(), applied=True, hunks=[]) for p in resolved], + ) + backup_names = ", ".join(b.name for b in backups) + return ToolResult( + success=True, + content=( + _serialize_patch_summary(summary) + + f"\nBackups created: {backup_names}" + ), + ) + except ModelRetry: + raise + except Exception as e: + return ToolResult(success=False, content="", error=str(e)) diff --git a/src/agentc/core/backends/pydantic_ai/tools/execution.py b/src/agentc/core/backends/pydantic_ai/tools/execution.py new file mode 100644 index 0000000..b5d7943 --- /dev/null +++ b/src/agentc/core/backends/pydantic_ai/tools/execution.py @@ -0,0 +1,37 @@ +"""Command execution tools for the Pydantic_AI backend.""" + +import asyncio +from asyncio.subprocess import PIPE + +from pydantic_ai import ModelRetry + +from ._shared import resolve_path, RunContext, RunDeps, ToolResult + + +async def _run_shell_command(command: str, cwd: str | None = None) -> tuple[str, int]: + """Run a shell command asynchronously and capture output plus return code.""" + process = await asyncio.create_subprocess_shell( + command, + stdout=PIPE, + stderr=PIPE, + cwd=cwd, + ) + stdout, stderr = await process.communicate() + output = stdout.decode() if stdout else "" + error = stderr.decode() if stderr else "" + return output + error, process.returncode if process.returncode is not None else 1 + + +async def run_command(ctx: RunContext[RunDeps], command: str, cwd: str | None = None) -> ToolResult: + """Execute a shell command inside the allowed workspace.""" + try: + base = resolve_path(cwd or ".", ctx.deps) if cwd else None + output, returncode = await _run_shell_command(command, str(base) if base else None) + success = returncode == 0 + if success: + return ToolResult(success=True, content=output) + return ToolResult(success=False, content=output, error=output) + except ModelRetry: + raise + except Exception as e: + return ToolResult(success=False, content="", error=str(e)) diff --git a/src/agentc/core/backends/pydantic_ai/tools/filesystem.py b/src/agentc/core/backends/pydantic_ai/tools/filesystem.py new file mode 100644 index 0000000..1d58936 --- /dev/null +++ b/src/agentc/core/backends/pydantic_ai/tools/filesystem.py @@ -0,0 +1,121 @@ +"""Filesystem discovery tools for the Pydantic_AI backend.""" + +from pathlib import Path + +import pathspec + +from pydantic_ai import ModelRetry + +from ._shared import resolve_path, RunContext, RunDeps, ToolResult +from ....config import DEFAULT_IGNORE_PATTERNS, MAX_TOOL_OUTPUT_LINES + + +def _load_ignore_spec(base: Path) -> pathspec.PathSpec: + """Load combined ignore patterns from defaults and .gitignore.""" + patterns = list(DEFAULT_IGNORE_PATTERNS) + gitignore_path = base / ".gitignore" + if gitignore_path.is_file(): + patterns.extend(gitignore_path.read_text(encoding="utf-8").splitlines()) + return pathspec.PathSpec.from_lines("gitignore", patterns) + + +def list_files(ctx: RunContext[RunDeps], path: str = ".") -> ToolResult: + """List entries in a directory inside the current working tree.""" + try: + target = resolve_path(path, ctx.deps) + if not target.is_dir(): + raise ModelRetry(f"Path must be a directory: {path}") + + items = [] + for entry in sorted(target.iterdir(), key=lambda p: p.name.lower()): + suffix = "/" if entry.is_dir() else "" + items.append(f"{entry.name}{suffix}") + + content = "\n".join(items) if items else "Directory is empty." + return ToolResult(success=True, content=content) + except ModelRetry: + raise + except Exception as e: + return ToolResult(success=False, content="", error=str(e)) + + +def glob_paths(ctx: RunContext[RunDeps], pattern: str, path: str = ".") -> ToolResult: + """Find files matching glob pattern recursively within the working tree.""" + try: + base = resolve_path(path, ctx.deps) + if not base.is_dir(): + raise ModelRetry(f"Path must be a directory: {path}") + + spec = _load_ignore_spec(base) + matches = [] + all_matches = [] + for p in base.rglob(pattern): + rel = p.relative_to(base) + rel_str = rel.as_posix() + rel_for_match = rel_str + "/" if p.is_dir() else rel_str + if spec.match_file(rel_for_match): + continue + entry = rel_str + ("/" if p.is_dir() else "") + all_matches.append(entry) + + all_matches.sort() + + if len(all_matches) > MAX_TOOL_OUTPUT_LINES: + matches = all_matches[:MAX_TOOL_OUTPUT_LINES] + remaining = len(all_matches) - MAX_TOOL_OUTPUT_LINES + matches.append(f"... {remaining} more") + else: + matches = all_matches + + content = "\n".join(matches) if matches else "No matches." + return ToolResult(success=True, content=content) + except ModelRetry: + raise + except Exception as e: + return ToolResult(success=False, content="", error=str(e)) + + +def search_files(ctx: RunContext[RunDeps], query: str, path: str = ".") -> ToolResult: + """Search for text in files within the working tree.""" + try: + base = resolve_path(path, ctx.deps) + if not base.is_dir(): + raise ModelRetry(f"Path must be a directory: {path}") + + spec = _load_ignore_spec(base) + matches: list[str] = [] + all_matches: list[str] = [] + for file_path in base.rglob("*"): + rel = file_path.relative_to(base) + rel_str = rel.as_posix() + rel_for_match = rel_str + "/" if file_path.is_dir() else rel_str + if spec.match_file(rel_for_match): + continue + + if file_path.is_dir(): + continue + + try: + text = file_path.read_text(encoding="utf-8") + except UnicodeDecodeError: + continue + + for lineno, line in enumerate(text.splitlines(), 1): + if query in line: + all_matches.append(f"{rel_str}:{lineno}: {line}") + + all_matches.sort() + + if len(all_matches) > MAX_TOOL_OUTPUT_LINES: + matches = all_matches[:MAX_TOOL_OUTPUT_LINES] + remaining = len(all_matches) - MAX_TOOL_OUTPUT_LINES + matches.append(f"... {remaining} more") + else: + matches = all_matches + + content = "\n".join(matches) if matches else "No matches." + return ToolResult(success=True, content=content) + except ModelRetry: + raise + except Exception as e: + return ToolResult(success=False, content="", error=str(e)) diff --git a/src/agentc/core/backends/pydantic_ai/types.py b/src/agentc/core/backends/pydantic_ai/types.py new file mode 100644 index 0000000..61bbae3 --- /dev/null +++ b/src/agentc/core/backends/pydantic_ai/types.py @@ -0,0 +1,13 @@ +"""Pydantic_AI-specific type aliases.""" + +from __future__ import annotations + +from pydantic_ai import Agent, DeferredToolRequests + +from ...deps import RunDeps + + +# Alias for the configured pydantic_ai Agent used by this backend. +NextAgent = Agent[RunDeps, str | DeferredToolRequests] + +__all__ = ["NextAgent"] diff --git a/src/agentc/core/command_types.py b/src/agentc/core/command_types.py index 245c248..6265df0 100644 --- a/src/agentc/core/command_types.py +++ b/src/agentc/core/command_types.py @@ -8,10 +8,11 @@ from dataclasses import dataclass from enum import Enum +from pathlib import Path from typing import Any, TYPE_CHECKING if TYPE_CHECKING: # pragma: no cover - from .types import AgentSessionProtocol + from .deps import RunDeps class CommandType(Enum): @@ -33,24 +34,45 @@ class CommandResult: args: dict[str, Any] +@dataclass +class SessionConfig: + """Backend-agnostic session configuration. + + Pure data describing what kind of session to create, without + knowing how to create it. The session factory translates this + into backend-specific session construction. + + Attributes: + model_name: Model preset name (e.g., "claude-sonnet", "gpt-4o"). + clear_history: Whether to start with empty conversation history. + skill_dirs: Directories to scan for SKILL.md files. + deps: Runtime dependencies (root_dirs, skill_dirs). + """ + + model_name: str | None = None + clear_history: bool = False + skill_dirs: list[Path] | None = None + deps: "RunDeps" | None = None + + @dataclass class CommandEffect: """Effect produced by executing a command. Represents the pure data outcome of a command execution. The UI layer - is responsible for interpreting and applying these effects. Commands - like EXIT and UNKNOWN are handled directly by the UI since they require - framework-specific actions. + is responsible for interpreting and applying these effects using its + injected session factory. Commands like EXIT and UNKNOWN are handled + directly by the UI since they require framework-specific actions. Attributes: - new_session: A new agent session to replace the current one, or None. + session_config: Configuration for creating a new session, or None. notification: A message to display to the user, or None. should_reset_ui: Whether the UI should clear its state. """ - new_session: "AgentSessionProtocol" | None = None + session_config: SessionConfig | None = None notification: str | None = None should_reset_ui: bool = False -__all__ = ["CommandType", "CommandResult", "CommandEffect"] +__all__ = ["CommandType", "CommandResult", "SessionConfig", "CommandEffect"] diff --git a/src/agentc/core/commands.py b/src/agentc/core/commands.py index b42a2fa..5438480 100644 --- a/src/agentc/core/commands.py +++ b/src/agentc/core/commands.py @@ -13,11 +13,7 @@ from pathlib import Path -from .factory import create_agent -from .loop import AgentSession -from .provider_loader import MissingAPIKeyError -from .command_types import CommandEffect, CommandResult, CommandType -from .config_types import ModelConfig +from .command_types import CommandEffect, CommandResult, CommandType, SessionConfig from .deps import RunDeps COMMAND_METADATA: list[dict[str, str]] = [ @@ -59,21 +55,19 @@ def _skill_dirs_from_deps(deps: RunDeps | None) -> list[Path] | None: class CommandParser: - """Centralized command parsing and validation. + """Centralized command parsing. Responsibilities: - Parse user input into structured commands - - Validate commands (e.g., provider names) - Return structured results for callers to act on + + Note: Model name validation happens in the session factory, + not during parsing. This keeps the parser pure and backend-agnostic. """ - def __init__(self, models: dict[str, ModelConfig]): - """Initialize parser with available providers. - - Args: - models: Dict of model preset names to ModelConfig. - """ - self.models = models + def __init__(self) -> None: + """Initialize the command parser.""" + pass def parse(self, user_input: str) -> CommandResult: """Parse user input into structured command. @@ -113,19 +107,11 @@ def parse(self, user_input: str) -> CommandResult: parts = command.split(maxsplit=1) if len(parts) >= 2 and parts[0] == "/model": model_name = parts[1].strip() - if model_name in self.models: - return CommandResult( - CommandType.MODEL_SWITCH, - {"model": model_name}, - ) - else: - return CommandResult( - CommandType.UNKNOWN, - { - "input": command, - "error": f"Unknown model: {model_name}", - }, - ) + # Model validation happens in session factory + return CommandResult( + CommandType.MODEL_SWITCH, + {"model": model_name}, + ) # Unknown command (starts with / but not recognized) if command.startswith("/"): @@ -140,7 +126,6 @@ def parse(self, user_input: str) -> CommandResult: def execute_command( result: CommandResult, - model_name: str | None = None, deps: RunDeps | None = None, ) -> CommandEffect | None: """Execute a command and return its effect. @@ -151,7 +136,7 @@ def execute_command( Args: result: The parsed command result from CommandParser. - model_name: Optional model name for MODEL_SWITCH command. + deps: Runtime dependencies (root_dirs, skill_dirs). Returns: CommandEffect describing what should happen, or None if the @@ -162,29 +147,27 @@ def execute_command( match result.command_type: case CommandType.CLEAR: return CommandEffect( - new_session=AgentSession( - agent=create_agent(skill_dirs=skill_dirs), deps=deps + session_config=SessionConfig( + clear_history=True, + skill_dirs=skill_dirs, + deps=deps, ), notification="Conversation cleared", should_reset_ui=True, ) case CommandType.MODEL_SWITCH: - model = result.args.get("model", model_name) - try: - return CommandEffect( - new_session=AgentSession( - agent=create_agent(model_name=model, skill_dirs=skill_dirs), - deps=deps, - ), - notification=f"Switched to model: {model}", - should_reset_ui=True, - ) - except MissingAPIKeyError as e: - return CommandEffect( - notification=str(e), - should_reset_ui=False, - ) + model = result.args.get("model") + return CommandEffect( + session_config=SessionConfig( + model_name=model, + clear_history=True, + skill_dirs=skill_dirs, + deps=deps, + ), + notification=f"Switched to model: {model}", + should_reset_ui=True, + ) case CommandType.HELP: help_lines = ["Available Commands:"] diff --git a/src/agentc/core/deps.py b/src/agentc/core/deps.py index a298fc1..88dc238 100644 --- a/src/agentc/core/deps.py +++ b/src/agentc/core/deps.py @@ -5,9 +5,6 @@ from dataclasses import dataclass, field from pathlib import Path -from pydantic_ai import Agent, DeferredToolRequests - - @dataclass class RunDeps: """Dependencies for the agent run context.""" @@ -39,7 +36,4 @@ def _consolidate_paths(self, paths: list[Path]) -> list[Path]: return unique -type NextAgent = Agent[RunDeps, str | DeferredToolRequests] - - -__all__ = ["RunDeps", "NextAgent"] +__all__ = ["RunDeps"] diff --git a/src/agentc/core/patching/types.py b/src/agentc/core/patching/types.py index 4ecef26..a979b97 100644 --- a/src/agentc/core/patching/types.py +++ b/src/agentc/core/patching/types.py @@ -37,6 +37,7 @@ class PatchPlan: """Structured patch plan for multiple files.""" files: list[FilePatch] = field(default_factory=list) + updates: dict[str, str] = field(default_factory=dict) @dataclass diff --git a/src/agentc/core/tools/_shared.py b/src/agentc/core/tools/_shared.py deleted file mode 100644 index 2d106ec..0000000 --- a/src/agentc/core/tools/_shared.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Shared utilities for Agent C tools. - -This module provides common infrastructure used across all tool categories, -including path validation, security boundaries, and shared type imports. -""" - -from pathlib import Path - -from pydantic_ai import ModelRetry, RunContext - -from ..deps import RunDeps -from ..types import ToolResult - - -def resolve_path(path_str: str, deps: RunDeps, *, must_exist: bool = True) -> Path: - """Resolve and validate a path within allowed root directories. - - This function serves as the primary security boundary for all file-based tools, - ensuring that operations are sandboxed to configured root directories. - - Args: - path_str: The path string to resolve (supports ~/ expansion). - deps: Runtime dependencies containing allowed root_dirs. - must_exist: If True, raises ModelRetry if the path doesn't exist. - - Returns: - A fully resolved Path object guaranteed to be within one of the root_dirs. - - Raises: - ModelRetry: If the path is outside all root_dirs, or if must_exist=True - and the path doesn't exist. This signals to the LLM that it should - retry with a corrected path. - - Security guarantees: - - All paths are resolved via Path.resolve() to handle symlinks and '..' traversal - - Paths must be descendants of at least one configured root directory - - No access is permitted outside the sandboxed root_dirs boundaries - - Examples: - >>> resolve_path("src/main.py", deps) - PosixPath('/workspace/src/main.py') - - >>> resolve_path("../../etc/passwd", deps) - ModelRetry: Path is outside allowed directories: ../../etc/passwd - - >>> resolve_path("nonexistent.txt", deps, must_exist=False) - PosixPath('/workspace/nonexistent.txt') - """ - candidate = Path(path_str).expanduser().resolve(strict=False) - - for root in deps.root_dirs: - if candidate.is_relative_to(root.resolve()): - if must_exist and not candidate.exists(): - raise ModelRetry(f"Path does not exist: {path_str}") - return candidate - - raise ModelRetry(f"Path is outside allowed directories: {path_str}") - - -__all__ = ["resolve_path", "Path", "ModelRetry", "RunContext", "RunDeps", "ToolResult"] diff --git a/src/agentc/core/tools/editing.py b/src/agentc/core/tools/editing.py deleted file mode 100644 index db628c8..0000000 --- a/src/agentc/core/tools/editing.py +++ /dev/null @@ -1,236 +0,0 @@ -"""File editing and patching tools for Agent C. - -Provides tools for reading, creating, editing files, and applying -structured patches with atomic writes and transactional guarantees. -""" - -from dataclasses import asdict -import json -from pathlib import Path - -from pydantic_ai import ModelRetry - -from ._shared import resolve_path, RunContext, RunDeps, ToolResult -from ..config import BACKUP_SUFFIX, TEMP_SUFFIX -from ..patching.engine import apply_file_patch -from ..patching.errors import PatchTransactionError -from ..patching.transaction import ( - apply_updates_transactionally, - create_backup, - write_text_atomic, -) -from ..types import FilePatchResult, PatchApplySummary, PatchPlan - - -def _cat_n_format(text: str) -> str: - """Return text with `cat -n` style line numbers, preserving trailing newline.""" - lines = text.splitlines() - trailing_newline = text.endswith("\n") - numbered = [f"{i:6}\t{line}" for i, line in enumerate(lines, 1)] - result = "\n".join(numbered) - if trailing_newline: - result += "\n" - return result - - -def _serialize_patch_summary(summary: PatchApplySummary) -> str: - """Serialize patch summary to JSON for tool output.""" - return json.dumps(asdict(summary), ensure_ascii=True) - - -def read_file(ctx: RunContext[RunDeps], path: str) -> ToolResult: - """Read a UTF-8 text file and return cat -n style output. - - - Path must be inside the current working directory and must be a file; directories are rejected. - - Output is line-numbered like `cat -n` (right-aligned 6-digit number, tab, then content); trailing newline is preserved. - - Raises ModelRetry for invalid paths, directories, or non-UTF-8 content. - """ - try: - target = resolve_path(path, ctx.deps) - if target.is_dir(): - raise ModelRetry(f"Path must be a file, not a directory: {path}") - - content = target.read_text(encoding="utf-8") - return ToolResult(success=True, content=_cat_n_format(content)) - except UnicodeDecodeError: - raise ModelRetry(f"File must be UTF-8 encoded text: {path}") - except ModelRetry: - raise - except Exception as e: - return ToolResult(success=False, content="", error=str(e)) - - -def create_file(ctx: RunContext[RunDeps], path: str, content: str) -> ToolResult: - """Create a new file with the given content. - - - Path must be inside currently working directory. - - Path must NOT exist (use edit_file to modify existing files). - - Creates parent directories if needed. - - Writes atomically via a temp file. - """ - try: - target = resolve_path(path, ctx.deps, must_exist=False) - if target.exists(): - raise ModelRetry( - f"File already exists: {path}. Use edit_file to modify it." - ) - - target.parent.mkdir(parents=True, exist_ok=True) - write_text_atomic(target, content, temp_suffix=TEMP_SUFFIX) - return ToolResult(success=True, content="File created successfully") - except ModelRetry: - raise - except Exception as e: - return ToolResult(success=False, content="", error=str(e)) - - -def edit_file( - ctx: RunContext[RunDeps], path: str, old_str: str, new_str: str -) -> ToolResult: - """Replace a unique occurrence of old_str with new_str in a UTF-8 text file. - - Use this tool for making a SINGLE edit to a file. For making multiple - non-contiguous edits to the same file, use `apply_hunks` instead — it's - more efficient (single tool call) and atomic (all changes succeed or none apply). - - - Path must be inside the current working directory and must be a file; directories are rejected. - - The file must decode as UTF-8; otherwise ModelRetry is raised. - - old_str must appear exactly once; otherwise ModelRetry is raised requesting more context. - - A timestamped backup is created alongside the file before writing; writes are atomic via temp file. - - Returns a short success or error message; on error, backup name is included when available. - """ - try: - target = resolve_path(path, ctx.deps) - if target.is_dir(): - raise ModelRetry(f"Path must be a file, not a directory: {path}") - - content = target.read_text(encoding="utf-8") - except UnicodeDecodeError: - raise ModelRetry(f"File must be UTF-8 encoded text: {path}") - except ModelRetry: - raise - except Exception as e: - return ToolResult(success=False, content="", error=str(e)) - - occurrences = content.count(old_str) - if occurrences == 0: - raise ModelRetry("old_str not found in file") - if occurrences > 1: - raise ModelRetry( - "old_str appears multiple times; provide more context to match uniquely" - ) - - backup_path = create_backup(target, backup_suffix=BACKUP_SUFFIX) - - try: - new_content = content.replace(old_str, new_str, 1) - write_text_atomic(target, new_content, temp_suffix=TEMP_SUFFIX) - return ToolResult(success=True, content="Edit completed successfully") - except Exception as e: - return ToolResult( - success=False, content="", error=f"{e}. Backup saved to {backup_path.name}" - ) - - -def apply_hunks(ctx: RunContext[RunDeps], plan: PatchPlan) -> ToolResult: - """Apply structured patch hunks to one or more files atomically. - - IMPORTANT: Before using this tool, ALWAYS use `read_file` to see the exact - file contents. Lines must match EXACTLY — including heading levels (e.g., - `##` vs `###`), whitespace, punctuation, and spelling. Even small mismatches - will cause the hunk to fail with "No match found". - - The plan contains a list of FilePatch objects, each targeting a file with one - or more hunks. All hunks must match successfully or no files are modified. - - Each hunk specifies: - - anchor_before: Lines that must appear immediately before the edit location. - - remove: Lines to be deleted (must match the file content EXACTLY). - - add: Lines to insert in place of the removed lines. - - anchor_after: Lines that must appear immediately after the removed lines. - - expected_near_line: Optional 1-based line number hint to disambiguate when - the anchor+remove+anchor pattern appears multiple times in the file. - - match_options: Optional matching behavior (e.g., ignore_trailing_whitespace). - - The full match sequence is: anchor_before + remove + anchor_after. If found - exactly once (or disambiguated by expected_near_line), remove is replaced - with add. Anchors are preserved. - - To insert lines without removing any, set remove to an empty list. - To delete lines without inserting, set add to an empty list. - - Structure (note the exact field names): - - PatchPlan has field `files`: list of FilePatch - - FilePatch has fields `path` and `hunks` (NOT 'patches') - - Each PatchHunk has: anchor_before, remove, add, anchor_after, expected_near_line - - Example: - { - "files": [{ - "path": "/absolute/path/to/file.py", - "hunks": [{ - "anchor_before": ["def foo():"], - "remove": [" old_line"], - "add": [" new_line"], - "anchor_after": [" return result"] - }] - }] - } - - All paths must be absolute and inside the allowed working directories. - Files must exist and be UTF-8 encoded text. - Backups are created before each write; writes are atomic via temp files. - """ - if not plan.files: - raise ModelRetry("No patch files provided") - - pending_updates: dict[Path, str] = {} - file_results: list[FilePatchResult] = [] - - for file_patch in plan.files: - if not file_patch.hunks: - raise ModelRetry(f"No hunks provided for file: {file_patch.path}") - try: - target = resolve_path(file_patch.path, ctx.deps) - if target.is_dir(): - raise ModelRetry( - f"Path must be a file, not a directory: {file_patch.path}" - ) - content = target.read_text(encoding="utf-8") - except UnicodeDecodeError: - raise ModelRetry(f"File must be UTF-8 encoded text: {file_patch.path}") - except ModelRetry: - raise - except Exception as e: - return ToolResult(success=False, content="", error=str(e)) - - updated_content, result = apply_file_patch(file_patch, content) - file_results.append(result) - if not result.applied: - summary = PatchApplySummary(applied=False, files=file_results) - return ToolResult( - success=False, - content=_serialize_patch_summary(summary), - error=f"Patch failed for {file_patch.path}: {result.error}", - ) - - pending_updates[target] = updated_content - - try: - apply_updates_transactionally( - pending_updates, - backup_suffix=BACKUP_SUFFIX, - temp_suffix=TEMP_SUFFIX, - ) - summary = PatchApplySummary(applied=True, files=file_results) - return ToolResult(success=True, content=_serialize_patch_summary(summary)) - except PatchTransactionError as exc: - summary = PatchApplySummary(applied=False, files=file_results) - return ToolResult( - success=False, - content=_serialize_patch_summary(summary), - error=str(exc), - ) - - -__all__ = ["read_file", "create_file", "edit_file", "apply_hunks"] diff --git a/src/agentc/core/tools/execution.py b/src/agentc/core/tools/execution.py deleted file mode 100644 index c38fb77..0000000 --- a/src/agentc/core/tools/execution.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Command execution tools for Agent C. - -Provides tools for running shell commands asynchronously with -proper stdout/stderr capture and error handling. -""" - -import asyncio - -from ._shared import RunContext, RunDeps, ToolResult - - -async def run_command(ctx: RunContext[RunDeps], command: str) -> ToolResult: - """Run a shell command asynchronously and return combined stdout+stderr. - - - Uses asyncio.create_subprocess_shell with stdin piped to avoid stealing the UI terminal. - - Returns captured output (stdout then stderr) trimmed; if empty, returns a default success message. - - On failure, returns an error string. - """ - try: - process = await asyncio.create_subprocess_shell( - command, - stdin=asyncio.subprocess.PIPE, # Or DEVNULL if you prefer. - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - stdout, stderr = await process.communicate() - output = (stdout.decode() + stderr.decode()).strip() - content = output or "Command executed successfully with no output." - return ToolResult(success=process.returncode == 0, content=content) - except Exception as e: - return ToolResult(success=False, content="", error=str(e)) - - -__all__ = ["run_command"] diff --git a/src/agentc/core/tools/filesystem.py b/src/agentc/core/tools/filesystem.py deleted file mode 100644 index 3d8d4d9..0000000 --- a/src/agentc/core/tools/filesystem.py +++ /dev/null @@ -1,149 +0,0 @@ -"""Filesystem discovery tools for Agent C. - -Provides tools for listing, searching, and discovering files within -the workspace, with gitignore support and security sandboxing. -""" - -from pathlib import Path - -import pathspec - -from pydantic_ai import ModelRetry - -from ._shared import resolve_path, RunContext, RunDeps, ToolResult -from ..config import DEFAULT_IGNORE_PATTERNS, MAX_TOOL_OUTPUT_LINES - - -def _load_ignore_spec(base: Path) -> pathspec.PathSpec: - """Load combined ignore patterns from defaults and .gitignore.""" - patterns = list(DEFAULT_IGNORE_PATTERNS) - gitignore_path = base / ".gitignore" - if gitignore_path.is_file(): - patterns.extend(gitignore_path.read_text(encoding="utf-8").splitlines()) - return pathspec.PathSpec.from_lines("gitignore", patterns) - - -def list_files(ctx: RunContext[RunDeps], path: str = ".") -> ToolResult: - """List entries in a directory inside the current working tree. - - - Path must be inside the current working directory; otherwise ModelRetry is raised. - - Returns one entry per line, sorted case-insensitively; directories are suffixed with "/". - """ - try: - target = resolve_path(path, ctx.deps) - if not target.is_dir(): - raise ModelRetry(f"Path must be a directory: {path}") - - items = [] - for entry in sorted(target.iterdir(), key=lambda p: p.name.lower()): - suffix = "/" if entry.is_dir() else "" - items.append(f"{entry.name}{suffix}") - - content = "\n".join(items) if items else "Directory is empty." - return ToolResult(success=True, content=content) - except ModelRetry: - raise - except Exception as e: - return ToolResult(success=False, content="", error=str(e)) - - -def glob_paths(ctx: RunContext[RunDeps], pattern: str, path: str = ".") -> ToolResult: - """Glob for files/directories under a base path, returning relative paths. - - - path must be a directory inside the current working directory; otherwise ModelRetry is raised. - - Respects default ignore patterns and `.gitignore` if it exists in the base path. - - pattern follows pathlib.rglob rules (supports "**" for recursion). - - Returns newline-separated relative paths from the base path; directories end with a trailing slash. - - Caps results at MAX_TOOL_OUTPUT_LINES; if truncated, a summary line is appended. - """ - try: - base = resolve_path(path, ctx.deps) - if not base.is_dir(): - raise ModelRetry(f"Path must be a directory: {path}") - - spec = _load_ignore_spec(base) - - matches: list[str] = [] - for entry in base.rglob(pattern): - rel = entry.relative_to(base).as_posix() - if entry.is_dir(): - rel += "/" - - # Skip entries that match ignore patterns. - if spec.match_file(rel): - continue - - matches.append(rel) - - if not matches: - return ToolResult(success=True, content="No matches.") - - matches.sort() - limit = MAX_TOOL_OUTPUT_LINES - truncated = len(matches) > limit - shown = matches[:limit] - output = "\n".join(shown) - if truncated: - output += f"\n... {len(matches) - limit} more" - return ToolResult(success=True, content=output) - except ModelRetry: - raise - except Exception as e: - return ToolResult(success=False, content="", error=str(e)) - - -def search_files(ctx: RunContext[RunDeps], query: str, path: str = ".") -> ToolResult: - """Recursively search for a substring in text files under a base path. - - - path must be a directory inside the current working directory; otherwise ModelRetry is raised. - - Respects default ignore patterns and `.gitignore` if it exists in the base path. - - Matches are case-sensitive substring checks on UTF-8 text (binary data is skipped via errors="ignore"). - - Returns relative paths from the base directory with `path:line: text`; results are sorted and capped at MAX_TOOL_OUTPUT_LINES with a summary line if truncated. - - If no matches are found, returns "No matches.". - """ - try: - base = resolve_path(path, ctx.deps) - if not base.is_dir(): - raise ModelRetry(f"Path must be a directory: {path}") - - spec = _load_ignore_spec(base) - - matches: list[str] = [] - for file_path in base.rglob("*"): - rel = file_path.relative_to(base).as_posix() - if file_path.is_dir(): - rel += "/" - - # Skip files/dirs that match ignore patterns. - if spec.match_file(rel): - continue - - if not file_path.is_file(): - continue - - try: - with file_path.open(encoding="utf-8", errors="ignore") as fh: - for line_no, line in enumerate(fh, 1): - if query in line: - matches.append(f"{rel}:{line_no}: {line.rstrip()}") - except OSError: - # Skip unreadable files. - continue - - if not matches: - return ToolResult(success=True, content="No matches.") - - matches.sort() - limit = MAX_TOOL_OUTPUT_LINES - truncated = len(matches) > limit - output = "\n".join(matches[:limit]) - if truncated: - output += f"\n... {len(matches) - limit} more" - return ToolResult(success=True, content=output) - except ModelRetry: - raise - except Exception as e: - return ToolResult(success=False, content="", error=str(e)) - - -__all__ = ["list_files", "glob_paths", "search_files"] diff --git a/src/agentc/core/types.py b/src/agentc/core/types.py index a980e7f..3ec781e 100644 --- a/src/agentc/core/types.py +++ b/src/agentc/core/types.py @@ -9,7 +9,7 @@ from asyncio import Event from dataclasses import dataclass -from typing import Any, AsyncGenerator, Protocol +from typing import Any, AsyncGenerator, Protocol, TYPE_CHECKING from .patching.types import ( FilePatch, @@ -21,6 +21,9 @@ PatchPlan, ) +if TYPE_CHECKING: # pragma: no cover + from .command_types import SessionConfig + __all__ = [ "AgentChunk", "ToolCallInfo", @@ -32,6 +35,7 @@ "AgentEvent", "AgentEventStream", "AgentSessionProtocol", + "SessionFactoryProtocol", # Re-exported patching types "FilePatch", "FilePatchResult", @@ -116,3 +120,26 @@ def run( ) -> AgentEventStream: """Run the agentic session with the given prompt.""" ... + + +class SessionFactoryProtocol(Protocol): + """Protocol for creating agent sessions. + + Each backend provides its own implementation that knows how to + construct sessions using backend-specific machinery. The UI layer + depends on this abstraction, not concrete implementations. + """ + + async def create_session(self, config: "SessionConfig") -> AgentSessionProtocol: + """Create a new agent session with the given configuration. + + Args: + config: Backend-agnostic configuration for the session. + + Returns: + A new agent session ready to run. + + Raises: + Backend-specific exceptions (e.g., MissingAPIKeyError). + """ + ... diff --git a/src/agentc/entrypoints/__init__.py b/src/agentc/entrypoints/__init__.py new file mode 100644 index 0000000..7bd5d6e --- /dev/null +++ b/src/agentc/entrypoints/__init__.py @@ -0,0 +1,3 @@ +"""Application entry points (composition roots).""" + +__all__ = ["main"] diff --git a/src/agentc/ui/run_console.py b/src/agentc/entrypoints/run_console.py similarity index 96% rename from src/agentc/ui/run_console.py rename to src/agentc/entrypoints/run_console.py index dfb0666..7421293 100644 --- a/src/agentc/ui/run_console.py +++ b/src/agentc/entrypoints/run_console.py @@ -18,8 +18,8 @@ ConsoleToolCallEvent, ConsoleToolResultEvent, ) -from ..core.factory import create_agent -from ..core.loop import AgentSession +from ..core.backends.pydantic_ai.factory import create_agent +from ..core.backends.pydantic_ai.loop import AgentSession from ..core.skill_loader import SkillLoader from ..core.deps import RunDeps from ..core.types import ApprovalResponse diff --git a/src/agentc/entrypoints/run_textual.py b/src/agentc/entrypoints/run_textual.py new file mode 100644 index 0000000..1f8aa86 --- /dev/null +++ b/src/agentc/entrypoints/run_textual.py @@ -0,0 +1,41 @@ +""" +CLI entry point for the Textual UI. + +Instantiates the `TextualAgentApp` with a created `Agent` and runs the +Textual application. +""" + +from ..core.backends.pydantic_ai.factory import create_agent +from ..core.backends.pydantic_ai.loop import AgentSession +from ..core.backends.pydantic_ai.provider_loader import load_providers +from ..core.backends.pydantic_ai.session_factory import PydanticAISessionFactory +from ..core.skill_loader import SkillLoader +from ..core.deps import RunDeps +from ..ui.textual_app import TextualAgentApp + + +def main() -> None: + """CLI entry point for the Textual UI application.""" + from pathlib import Path + + loader = SkillLoader() + skill_dirs = loader.get_default_skill_dirs() + + # Load available models from providers.toml + _, models = load_providers() + model_names = sorted(models.keys()) + + deps = RunDeps(root_dirs=[Path.cwd()], skill_dirs=skill_dirs) + session_factory = PydanticAISessionFactory() + session = AgentSession(agent=create_agent(skill_dirs=deps.skill_dirs), deps=deps) + app = TextualAgentApp( + session=session, + session_factory=session_factory, + model_names=model_names, + deps=deps, + ) + app.run() + + +if __name__ == "__main__": + main() diff --git a/src/agentc/entrypoints/run_textual_gh.py b/src/agentc/entrypoints/run_textual_gh.py new file mode 100644 index 0000000..6858681 --- /dev/null +++ b/src/agentc/entrypoints/run_textual_gh.py @@ -0,0 +1,136 @@ +""" +CLI entry point for the Textual UI with GitHub Copilot integration. +""" + +import asyncio +import shutil +import sys +from pathlib import Path + +from copilot import CopilotClient +from copilot.types import ( + PermissionRequest, + PermissionRequestResult, + SessionConfig as CopilotSessionConfig, + SystemMessageReplaceConfig, +) + +from ..ui.textual_app import TextualAgentApp +from ..core.backends.github_copilot.session_factory import GhCopilotSessionFactory +from ..core.command_types import SessionConfig +from ..core.deps import RunDeps +from ..core.skill_loader import SkillLoader + + +async def main() -> None: + """CLI entry point for the Textual UI with GitHub Copilot integration.""" + + # Handler for permission requests. + def on_permission_request( + permission_request: PermissionRequest, args: dict[str, str] + ) -> PermissionRequestResult: + print(f"\nPermission request {permission_request} with args {args}\n") + return PermissionRequestResult(kind="approved") + + cli_path = shutil.which("copilot") + if not cli_path: + raise RuntimeError("Copilot CLI not found in PATH") + + # Determine root directories. + args = sys.argv[1:] + root_dirs = [Path(arg).resolve() for arg in args] if args else [Path.cwd()] + + loader = SkillLoader() + skill_dirs = loader.get_default_skill_dirs() + deps = RunDeps(root_dirs=root_dirs, skill_dirs=skill_dirs) + + # Create and start client. + client = CopilotClient({"cli_path": cli_path}) + await client.start() + + try: + # Discover available models from GitHub Copilot SDK + model_infos = await client.list_models() + model_names = [model["id"] for model in model_infos] # type: ignore + + # This prompt replaces the system message, but leaves tools and skills intact. + system_message_config = SystemMessageReplaceConfig( + mode="replace", + content=f"""\ +# Instructions + +You are an expert coding assistant with comprehensive file system access and command execution capabilities. You help users navigate, analyze, edit, and manage their codebase efficiently. + +## Communication Guidelines +- **Be succinct but informative**: Provide clear, actionable responses +- **Use markdown**: Format code snippets with appropriate syntax highlighting +- **Ask when uncertain**: If a request is ambiguous, ask clarifying questions before acting +- **Explain errors**: When things go wrong, explain the issue and suggest alternatives +- **Summarize changes**: After modifications, clearly state what was changed and why +- **Warn about risks**: If uncertain about a change's impact, warn the user explicitly + +## Output Encoding +By default, use ASCII encoding. Only introduce non-ASCII or Unicode characters if: +- The file already contains them +- There's a domain-specific need (e.g., internationalization, mathematical notation) +Always explain why non-ASCII characters are necessary. + +## Success Criteria +- All requested changes are correctly implemented +- Changes align with existing code style and patterns +- No syntax errors or regressions are introduced +- User's intent is fully addressed + + +You are working in the following environment. You do not need to make additional tool calls to verify this. +* Current working directory: {Path.cwd()} + +""", + ) + + # Create a session. + model = "gpt-5 mini" + # model = "Claude Haiku 4.5" + # provider_config = ProviderConfig( + # type="openai", wire_api="completions", base_url="http://localhost:11434/v1" + # ) + # model = "gpt-oss:20b" + is_streaming = True + session_config = CopilotSessionConfig( + model=model, # type: ignore + on_permission_request=on_permission_request, + # provider=provider_config, + skill_directories=[str(d) for d in skill_dirs], + streaming=is_streaming, + system_message=system_message_config, + ) + + session_factory = GhCopilotSessionFactory(client, session_config) + agent_session = await session_factory.create_session( + SessionConfig( + model_name=model, + skill_dirs=skill_dirs, + deps=deps, + ) + ) + app = TextualAgentApp( + session=agent_session, + session_factory=session_factory, + model_names=model_names, + deps=deps, + ) + await app.run_async() + + # Clean up. + await session_factory.cleanup() + finally: + await client.stop() + + +def main_sync(): + """Synchronous wrapper for the async main function.""" + asyncio.run(main()) + + +if __name__ == "__main__": + main_sync() diff --git a/src/agentc/providers.toml b/src/agentc/providers.toml index d00ece5..e31bb3c 100644 --- a/src/agentc/providers.toml +++ b/src/agentc/providers.toml @@ -37,6 +37,14 @@ model_name = "gpt-oss:120b-cloud" backend = "ollama" model_name = "gpt-oss:20b" +[models.ollama-kimi-k2-5] +backend = "ollama" +model_name = "kimi-k2.5:cloud" + +[models.ollama-glm-4-7] +backend = "ollama" +model_name = "glm-4.7:cloud" + [models.gpt-4o-mini] backend = "openai" model_name = "gpt-4o-mini" diff --git a/src/agentc/ui/run_textual.py b/src/agentc/ui/run_textual.py deleted file mode 100644 index a05e8db..0000000 --- a/src/agentc/ui/run_textual.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -CLI entry point for the Textual UI. - -Instantiates the `TextualAgentApp` with a created `Agent` and runs the -Textual application. -""" - -from ..core.factory import create_agent -from ..core.loop import AgentSession -from ..core.skill_loader import SkillLoader -from ..core.deps import RunDeps -from .textual_app import TextualAgentApp - - -def main() -> None: - """CLI entry point for the Textual UI application.""" - from pathlib import Path - - loader = SkillLoader() - skill_dirs = loader.get_default_skill_dirs() - - deps = RunDeps(root_dirs=[Path.cwd()], skill_dirs=skill_dirs) - session = AgentSession(agent=create_agent(skill_dirs=deps.skill_dirs), deps=deps) - app = TextualAgentApp(session=session, deps=deps) - app.run() - - -if __name__ == "__main__": - main() diff --git a/src/agentc/ui/textual_app.py b/src/agentc/ui/textual_app.py index 29f603b..91744d9 100644 --- a/src/agentc/ui/textual_app.py +++ b/src/agentc/ui/textual_app.py @@ -34,10 +34,9 @@ ) from ..adapters.textual import TextualAgentAdapter from ..core.commands import CommandParser, execute_command -from ..core.provider_loader import load_providers -from ..core.command_types import CommandType +from ..core.command_types import CommandEffect, CommandType from ..core.deps import RunDeps -from ..core.types import AgentSessionProtocol +from ..core.types import AgentSessionProtocol, SessionFactoryProtocol class TextualAgentApp(App): @@ -83,11 +82,22 @@ class TextualAgentApp(App): def __init__( self, session: AgentSessionProtocol, + session_factory: SessionFactoryProtocol, + model_names: list[str], deps: RunDeps | None = None, **kwargs, ): + """Initialize the Textual agent application. + + Args: + session: Initial agent session. + session_factory: Factory for creating new sessions. + model_names: List of available model names for autocomplete. + deps: Runtime dependencies (root directories, skill directories). + """ super().__init__(**kwargs) self._session = session + self._session_factory = session_factory self._deps = deps or RunDeps() self._cancellation_event: asyncio.Event | None = None self._pending_tool_widgets: dict[str, ToolCallWidget] = {} @@ -98,9 +108,8 @@ def __init__( self._thinking_output: Static | None = None self._stream_writer: Any | None = None self._thinking_text = "" - _, models = load_providers() - self._model_names = sorted(models.keys()) - self.command_parser = CommandParser(models) + self._model_names = model_names + self.command_parser = CommandParser() async def _reset_ui_state(self) -> None: """Clear the scroll area and reset tracking variables.""" @@ -173,17 +182,7 @@ async def handle_submitted(self, message: HistoryTextArea.Submitted) -> None: effect = execute_command(result, deps=self._deps) if effect is not None: input_widget.clear() - if effect.should_reset_ui: - await self._reset_ui_state() - if effect.new_session is not None: - self._session = effect.new_session - if effect.notification: - # If no new session was created but we have a notification, - # it's likely an error (e.g., missing API key) - if effect.new_session is None and not effect.should_reset_ui: - self.notify(effect.notification, severity="error") - else: - self.notify(effect.notification) + await self._apply_command_effect(effect) return # Normal input. @@ -256,6 +255,28 @@ async def handle_approval_request( await self._reset_output() + async def _apply_command_effect(self, effect: CommandEffect) -> None: + """Apply a command effect using the injected session factory. + + Args: + effect: The effect to apply (from execute_command). + """ + if effect.should_reset_ui: + await self._reset_ui_state() + + if effect.session_config is not None: + try: + self._session = await self._session_factory.create_session( + effect.session_config + ) + if effect.notification: + self.notify(effect.notification) + except Exception as exc: # pragma: no cover - UI surface error handling + error_msg = effect.notification or str(exc) + self.notify(f"Error: {error_msg}", severity="error") + elif effect.notification: + self.notify(effect.notification) + async def _reset_output(self) -> None: if self._collapsible: self._collapsible.collapsed = True diff --git a/tests/core/test_commands.py b/tests/core/test_commands.py index be0216b..ebf1f50 100644 --- a/tests/core/test_commands.py +++ b/tests/core/test_commands.py @@ -1,31 +1,12 @@ -from unittest.mock import MagicMock, patch - import pytest from agentc.core.commands import CommandParser, execute_command -from agentc.core.command_types import CommandResult, CommandType -from agentc.core.config_types import ModelConfig - - -@pytest.fixture -def mock_models(): - return { - "ollama-gpt-oss-120b": ModelConfig( - name="ollama-gpt-oss-120b", - backend="ollama", - model_name="gpt-oss", - ), - "claude": ModelConfig( - name="claude", - backend="anthropic", - model_name="claude-3", - ), - } +from agentc.core.command_types import CommandResult, CommandType, SessionConfig @pytest.fixture -def parser(mock_models): - return CommandParser(mock_models) +def parser(): + return CommandParser() def test_parse_normal_input(parser): result = parser.parse("hello agent") @@ -51,10 +32,12 @@ def test_parse_model_switch_success(parser): assert result.command_type == CommandType.MODEL_SWITCH assert result.args["model"] == "claude" -def test_parse_model_switch_unknown(parser): - result = parser.parse("/model unknown-llm") - assert result.command_type == CommandType.UNKNOWN - assert "Unknown model" in result.args["error"] + +def test_parse_model_switch_any_name(parser): + """Test /model accepts any model name (validation happens in factory).""" + result = parser.parse("/model any-model-name") + assert result.command_type == CommandType.MODEL_SWITCH + assert result.args["model"] == "any-model-name" def test_parse_unknown_command(parser): result = parser.parse("/invalid command") @@ -87,36 +70,30 @@ def test_command_metadata_structure(): class TestExecuteCommand: """Tests for the execute_command function.""" - @patch("agentc.core.commands.create_agent") - def test_execute_clear_returns_effect(self, mock_create_agent): - """CLEAR command should return effect with new session and notification.""" - mock_create_agent.return_value = MagicMock() - + def test_execute_clear_returns_effect(self): + """CLEAR command should return effect with session config and notification.""" result = CommandResult(CommandType.CLEAR, {}) effect = execute_command(result) assert effect is not None - assert effect.new_session is not None + assert effect.session_config is not None + assert isinstance(effect.session_config, SessionConfig) + assert effect.session_config.clear_history is True assert effect.notification == "Conversation cleared" assert effect.should_reset_ui is True - mock_create_agent.assert_called_once() - - @patch("agentc.core.commands.create_agent") - def test_execute_model_switch_returns_effect(self, mock_create_agent): - """MODEL_SWITCH command should return effect with new session.""" - mock_create_agent.return_value = MagicMock() + def test_execute_model_switch_returns_effect(self): + """MODEL_SWITCH command should return effect with session config.""" result = CommandResult(CommandType.MODEL_SWITCH, {"model": "claude"}) effect = execute_command(result) assert effect is not None - assert effect.new_session is not None + assert effect.session_config is not None + assert isinstance(effect.session_config, SessionConfig) + assert effect.session_config.model_name == "claude" + assert effect.session_config.clear_history is True assert effect.notification == "Switched to model: claude" assert effect.should_reset_ui is True - - # Verify create_agent was called with correct model - call_kwargs = mock_create_agent.call_args.kwargs - assert call_kwargs.get("model_name") == "claude" def test_execute_exit_returns_none(self): """EXIT command should return None (handled by UI).""" @@ -153,20 +130,23 @@ def test_execute_help_returns_effect(self): assert "/help" in effect.notification assert effect.should_reset_ui is False - @patch("agentc.core.commands.create_agent") - def test_execute_model_switch_missing_api_key(self, mock_create_agent): - """MODEL_SWITCH with missing API key should return error notification.""" - from agentc.core.provider_loader import MissingAPIKeyError - mock_create_agent.side_effect = MissingAPIKeyError( - "GOOGLE_API_KEY", "gemini-flash" - ) +def test_session_config_structure(): + """Test SessionConfig dataclass structure.""" + from pathlib import Path - result = CommandResult(CommandType.MODEL_SWITCH, {"model": "gemini-flash"}) - effect = execute_command(result) + from agentc.core.deps import RunDeps - assert effect is not None - assert effect.new_session is None - assert "GOOGLE_API_KEY" in effect.notification - assert effect.should_reset_ui is False + deps = RunDeps(root_dirs=[Path("/tmp")]) + config = SessionConfig( + model_name="test-model", + clear_history=True, + skill_dirs=[Path("/skills")], + deps=deps, + ) + + assert config.model_name == "test-model" + assert config.clear_history is True + assert config.skill_dirs == [Path("/skills")] + assert config.deps == deps diff --git a/tests/core/test_factory.py b/tests/core/test_factory.py index e91d164..f9eaa9b 100644 --- a/tests/core/test_factory.py +++ b/tests/core/test_factory.py @@ -3,7 +3,7 @@ import pytest from pydantic_ai import Agent -from agentc.core.factory import create_agent +from agentc.core.backends.pydantic_ai.factory import create_agent from agentc.core.config_types import BackendConfig, ModelConfig @@ -28,9 +28,9 @@ def test_create_agent_unknown_model() -> None: create_agent(model_name="does-not-exist") -@patch("agentc.core.factory.Agent") -@patch("agentc.core.factory.load_providers") -@patch("agentc.core.factory.build_model") +@patch("agentc.core.backends.pydantic_ai.factory.Agent") +@patch("agentc.core.backends.pydantic_ai.factory.load_providers") +@patch("agentc.core.backends.pydantic_ai.factory.build_model") def test_create_agent_merges_params( mock_build_model: MagicMock, mock_load: MagicMock, mock_agent: MagicMock ) -> None: diff --git a/tests/core/test_ignore_logic.py b/tests/core/test_ignore_logic.py index be7639a..b9b7f17 100644 --- a/tests/core/test_ignore_logic.py +++ b/tests/core/test_ignore_logic.py @@ -1,7 +1,7 @@ from unittest.mock import MagicMock import pytest from pydantic_ai import RunContext -from agentc.core.tools.filesystem import glob_paths, search_files +from agentc.core.backends.pydantic_ai.tools.filesystem import glob_paths, search_files from agentc.core.deps import RunDeps @pytest.fixture diff --git a/tests/core/test_loop.py b/tests/core/test_loop.py index 07fe51f..ccf1143 100644 --- a/tests/core/test_loop.py +++ b/tests/core/test_loop.py @@ -12,8 +12,9 @@ ToolCallPart, ) -from agentc.core.loop import AgentSession -from agentc.core.deps import NextAgent, RunDeps +from agentc.core.backends.pydantic_ai.loop import AgentSession +from agentc.core.backends.pydantic_ai.types import NextAgent +from agentc.core.deps import RunDeps from agentc.core.types import ( AgentChunk, AgentDone, diff --git a/tests/core/test_provider_loader.py b/tests/core/test_provider_loader.py index 201a1ea..ddd9b0b 100644 --- a/tests/core/test_provider_loader.py +++ b/tests/core/test_provider_loader.py @@ -3,7 +3,7 @@ import pytest -from agentc.core.provider_loader import ( +from agentc.core.backends.pydantic_ai.provider_loader import ( build_model, get_default_provider_dirs, load_providers, @@ -124,7 +124,7 @@ def get_class_side_effect(path: str) -> MagicMock: monkeypatch.setenv("MODEL_KEY", "model-secret") monkeypatch.setenv("BACKEND_KEY", "backend-secret") - with patch("agentc.core.provider_loader._get_class", side_effect=get_class_side_effect): + with patch("agentc.core.backends.pydantic_ai.provider_loader._get_class", side_effect=get_class_side_effect): provider, _ = build_model(model, backend) mock_provider_cls.assert_called_once_with( diff --git a/tests/core/test_session_factory_pydantic.py b/tests/core/test_session_factory_pydantic.py new file mode 100644 index 0000000..ea1bcf5 --- /dev/null +++ b/tests/core/test_session_factory_pydantic.py @@ -0,0 +1,51 @@ +"""Tests for PydanticAISessionFactory.""" + +from pathlib import Path + +import pytest + +from agentc.core.backends.pydantic_ai.loop import AgentSession +from agentc.core.backends.pydantic_ai.session_factory import PydanticAISessionFactory +from agentc.core.command_types import SessionConfig +from agentc.core.deps import RunDeps + + +@pytest.mark.anyio +async def test_pydantic_session_factory_creates_session() -> None: + """Test that factory creates a valid AgentSession.""" + factory = PydanticAISessionFactory() + config = SessionConfig( + model_name=None, + clear_history=True, + skill_dirs=None, + deps=RunDeps(root_dirs=[Path.cwd()]), + ) + + session = await factory.create_session(config) + + assert isinstance(session, AgentSession) + assert session.history == [] + + +@pytest.mark.anyio +async def test_pydantic_session_factory_with_model_override() -> None: + """Test that factory respects model_name override.""" + factory = PydanticAISessionFactory() + config = SessionConfig( + model_name="ollama-gpt-oss-120b", + clear_history=True, + ) + + session = await factory.create_session(config) + + assert isinstance(session, AgentSession) + + +@pytest.mark.anyio +async def test_pydantic_session_factory_unknown_model_raises() -> None: + """Test that unknown model raises ValueError.""" + factory = PydanticAISessionFactory() + config = SessionConfig(model_name="nonexistent-model") + + with pytest.raises(ValueError, match="Unknown model preset"): + await factory.create_session(config) diff --git a/tests/core/test_tool_result.py b/tests/core/test_tool_result.py index 8c86465..f80b3d5 100644 --- a/tests/core/test_tool_result.py +++ b/tests/core/test_tool_result.py @@ -7,9 +7,13 @@ from pathlib import Path from unittest.mock import MagicMock -from agentc.core.tools.filesystem import list_files, glob_paths, search_files -from agentc.core.tools.editing import read_file, edit_file -from agentc.core.tools.execution import run_command +from agentc.core.backends.pydantic_ai.tools.filesystem import ( + list_files, + glob_paths, + search_files, +) +from agentc.core.backends.pydantic_ai.tools.editing import read_file, edit_file +from agentc.core.backends.pydantic_ai.tools.execution import run_command from agentc.core.deps import RunDeps from agentc.core.types import ToolResult from pydantic_ai import ModelRetry, RunContext diff --git a/tests/core/test_tools_editing.py b/tests/core/test_tools_editing.py index 2fe08d9..62291b0 100644 --- a/tests/core/test_tools_editing.py +++ b/tests/core/test_tools_editing.py @@ -6,7 +6,12 @@ import pytest from pydantic_ai import ModelRetry, RunContext -from agentc.core.tools.editing import read_file, create_file, edit_file, apply_hunks +from agentc.core.backends.pydantic_ai.tools.editing import ( + read_file, + create_file, + edit_file, + apply_hunks, +) from agentc.core.deps import RunDeps from agentc.core.types import FilePatch, PatchHunk, PatchPlan diff --git a/tests/core/test_tools_execution.py b/tests/core/test_tools_execution.py index 94292d2..e7831c2 100644 --- a/tests/core/test_tools_execution.py +++ b/tests/core/test_tools_execution.py @@ -5,7 +5,7 @@ from pydantic_ai import RunContext -from agentc.core.tools.execution import run_command +from agentc.core.backends.pydantic_ai.tools.execution import run_command from agentc.core.deps import RunDeps from agentc.core.types import ToolResult diff --git a/tests/core/test_tools_filesystem.py b/tests/core/test_tools_filesystem.py index 0c20ad7..0471fc7 100644 --- a/tests/core/test_tools_filesystem.py +++ b/tests/core/test_tools_filesystem.py @@ -5,7 +5,11 @@ import pytest from pydantic_ai import ModelRetry, RunContext -from agentc.core.tools.filesystem import glob_paths, list_files, search_files +from agentc.core.backends.pydantic_ai.tools.filesystem import ( + glob_paths, + list_files, + search_files, +) from agentc.core.deps import RunDeps diff --git a/uv.lock b/uv.lock index a1f15de..d445309 100644 --- a/uv.lock +++ b/uv.lock @@ -19,6 +19,7 @@ name = "agent-c" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "github-copilot-sdk" }, { name = "pathspec" }, { name = "platformdirs" }, { name = "pydantic-ai" }, @@ -35,6 +36,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "github-copilot-sdk", specifier = ">=0.1.15" }, { name = "pathspec", specifier = ">=0.12.1" }, { name = "platformdirs", specifier = ">=4.5.0" }, { name = "pydantic-ai", specifier = ">=1.6.0" }, @@ -219,39 +221,39 @@ wheels = [ [[package]] name = "boto3" -version = "1.42.30" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/79/2dac8b7cb075cfa43908ee9af3f8ee06880d84b86013854c5cca8945afac/boto3-1.42.30.tar.gz", hash = "sha256:ba9cd2f7819637d15bfbeb63af4c567fcc8a7dcd7b93dd12734ec58601169538", size = 112809, upload-time = "2026-01-16T20:37:23.636Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/ef/0d6ceb88ae2b3638b956190a431e4a8a3697d5769d4bbbede8efcccacaea/boto3-1.42.37.tar.gz", hash = "sha256:d8b6c52c86f3bf04f71a5a53e7fb4d1527592afebffa5170cf3ef7d70966e610", size = 112830, upload-time = "2026-01-28T20:38:43.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/b3/2c0d828c9f668292e277ca5232e6160dd5b4b660a3f076f20dd5378baa1e/boto3-1.42.30-py3-none-any.whl", hash = "sha256:d7e548bea65e0ae2c465c77de937bc686b591aee6a352d5a19a16bc751e591c1", size = 140573, upload-time = "2026-01-16T20:37:22.089Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/cd334f74498acc6ad42a69c48e8c495f6f721d8abe13f8ef0d4b862fb1c0/boto3-1.42.37-py3-none-any.whl", hash = "sha256:e1e38fd178ffc66cfbe9cb6838b8c460000c3eb741e5f40f57eb730780ef0ed4", size = 140604, upload-time = "2026-01-28T20:38:42.135Z" }, ] [[package]] name = "botocore" -version = "1.42.30" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/38/23862628a0eb044c8b8b3d7a9ad1920b3bfd6bce6d746d5a871e8382c7e4/botocore-1.42.30.tar.gz", hash = "sha256:9bf1662b8273d5cc3828a49f71ca85abf4e021011c1f0a71f41a2ea5769a5116", size = 14891439, upload-time = "2026-01-16T20:37:13.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/4d/94292e7686e64d2ede8dae7102bbb11a1474e407c830de4192f2518e6cff/botocore-1.42.37.tar.gz", hash = "sha256:3ec58eb98b0857f67a2ae6aa3ded51597e7335f7640be654e0e86da4f173b5b2", size = 14914621, upload-time = "2026-01-28T20:38:34.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/8d/6d7b016383b1f74dd93611b1c5078bbaddaca901553ab886dcda87cae365/botocore-1.42.30-py3-none-any.whl", hash = "sha256:97070a438cac92430bb7b65f8ebd7075224f4a289719da4ee293d22d1e98db02", size = 14566340, upload-time = "2026-01-16T20:37:10.94Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/54042dd3ad8161964f8f47aa418785079bd8d2f17053c40d65bafb9f6eed/botocore-1.42.37-py3-none-any.whl", hash = "sha256:f13bb8b560a10714d96fb7b0c7f17828dfa6e6606a1ead8c01c6ebb8765acbd8", size = 14589390, upload-time = "2026-01-28T20:38:31.306Z" }, ] [[package]] name = "cachetools" -version = "6.2.4" +version = "6.2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/1d/ede8680603f6016887c062a2cf4fc8fdba905866a3ab8831aa8aa651320c/cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607", size = 31731, upload-time = "2025-12-15T18:24:53.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/fc/1d7b80d0eb7b714984ce40efc78859c022cd930e402f599d8ca9e39c78a4/cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51", size = 11551, upload-time = "2025-12-15T18:24:52.332Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" }, ] [[package]] @@ -372,7 +374,7 @@ wheels = [ [[package]] name = "cohere" -version = "5.20.1" +version = "5.20.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastavro" }, @@ -384,9 +386,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/ed/bb02083654bdc089ae4ef1cd7691fd2233f1fd9f32bcbfacc80ff57d9775/cohere-5.20.1.tar.gz", hash = "sha256:50973f63d2c6138ff52ce37d8d6f78ccc539af4e8c43865e960d68e0bf835b6f", size = 180820, upload-time = "2025-12-18T16:39:50.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/52/08564d1820970010d30421cd6e36f2e4ca552646504d3fe532eef282c88d/cohere-5.20.2.tar.gz", hash = "sha256:0aa9f3735626b70eedf15c231c61f3a58e7f8bbe5f0509fe7b2e6606c5d420f1", size = 180820, upload-time = "2026-01-23T13:42:51.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/e3/94eb11ac3ebaaa3a6afb5d2ff23db95d58bc468ae538c388edf49f2f20b5/cohere-5.20.1-py3-none-any.whl", hash = "sha256:d230fd13d95ba92ae927fce3dd497599b169883afc7954fe29b39fb8d5df5fc7", size = 318973, upload-time = "2025-12-18T16:39:49.504Z" }, + { url = "https://files.pythonhosted.org/packages/e0/10/d76f045eefe42fb3f4e271d17ab41b5e73a3b6de69c98e15ab1cb0c8e6f6/cohere-5.20.2-py3-none-any.whl", hash = "sha256:26156d83bf3e3e4475e4caa1d8c4148475c5b0a253aee6066d83c643e9045be6", size = 318986, upload-time = "2026-01-23T13:42:50.151Z" }, ] [[package]] @@ -400,63 +402,60 @@ wheels = [ [[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'" }, ] -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" }, +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" }, ] [[package]] name = "cyclopts" -version = "4.5.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -464,9 +463,9 @@ dependencies = [ { name = "rich" }, { name = "rich-rst" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/7b/663f3285c1ac0e5d0854bd9db2c87caa6fa3d1a063185e3394a6cdca9151/cyclopts-4.5.0.tar.gz", hash = "sha256:717ac4235548b58d500baf7e688aa4d024caf0ee68f61a012ffd5e29db3099f9", size = 161980, upload-time = "2026-01-16T02:07:16.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/93/6085aa89c3fff78a5180987354538d72e43b0db27e66a959302d0c07821a/cyclopts-4.5.1.tar.gz", hash = "sha256:fadc45304763fd9f5d6033727f176898d17a1778e194436964661a005078a3dd", size = 162075, upload-time = "2026-01-25T15:23:54.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/a3/2e00fececc34a99ae3a5d5702a5dd29c5371e4ed016647301a2b9bcc1976/cyclopts-4.5.0-py3-none-any.whl", hash = "sha256:305b9aa90a9cd0916f0a450b43e50ad5df9c252680731a0719edfb9b20381bf5", size = 199772, upload-time = "2026-01-16T02:07:14.707Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/996760c30f1302704af57c66ff2d723f7d656d0d0b93563b5528a51484bb/cyclopts-4.5.1-py3-none-any.whl", hash = "sha256:0642c93601e554ca6b7b9abd81093847ea4448b2616280f2a0952416574e8c7a", size = 199807, upload-time = "2026-01-25T15:23:55.219Z" }, ] [[package]] @@ -603,16 +602,18 @@ wheels = [ [[package]] name = "fastmcp" -version = "2.14.3" +version = "2.14.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, { name = "cyclopts" }, { name = "exceptiongroup" }, { name = "httpx" }, + { name = "jsonref" }, { name = "jsonschema-path" }, { name = "mcp" }, { name = "openapi-pydantic" }, + { name = "packaging" }, { name = "platformdirs" }, { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, @@ -623,9 +624,9 @@ dependencies = [ { name = "uvicorn" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/b5/7c4744dc41390ed2c17fd462ef2d42f4448a1ec53dda8fe3a01ff2872313/fastmcp-2.14.3.tar.gz", hash = "sha256:abc9113d5fcf79dfb4c060a1e1c55fccb0d4bce4a2e3eab15ca352341eec8dd6", size = 8279206, upload-time = "2026-01-12T20:00:40.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/a9/a57d5e5629ebd4ef82b495a7f8e346ce29ef80cc86b15c8c40570701b94d/fastmcp-2.14.4.tar.gz", hash = "sha256:c01f19845c2adda0a70d59525c9193be64a6383014c8d40ce63345ac664053ff", size = 8302239, upload-time = "2026-01-22T17:29:37.024Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/dc/f7dd14213bf511690dccaa5094d436947c253b418c86c86211d1c76e6e44/fastmcp-2.14.3-py3-none-any.whl", hash = "sha256:103c6b4c6e97a9acc251c81d303f110fe4f2bdba31353df515d66272bf1b9414", size = 416220, upload-time = "2026-01-12T20:00:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/3e/41/c4d407e2218fd60d84acb6cc5131d28ff876afecf325e3fd9d27b8318581/fastmcp-2.14.4-py3-none-any.whl", hash = "sha256:5858cff5e4c8ea8107f9bca2609d71d6256e0fce74495912f6e51625e466c49a", size = 417788, upload-time = "2026-01-22T17:29:35.159Z" }, ] [[package]] @@ -721,28 +722,43 @@ wheels = [ [[package]] name = "genai-prices" -version = "0.0.51" +version = "0.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/22/427934ef8e7ed29c35afc274666b87fe01a3a27ec7ff102f5839ce4723c0/genai_prices-0.0.51.tar.gz", hash = "sha256:003da98172641c94d7516b0fd8cec5ecf2dbab64a884996c26cc194c5e0b592e", size = 58071, upload-time = "2026-01-13T12:49:11.872Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/87/bdc11c1671e3a3fe701c3c4aaae4aa2bb7a84a6bb1812dfb5693c87d3872/genai_prices-0.0.52.tar.gz", hash = "sha256:0df7420b555fa3a48d09e5c7802ba35b5dfa9fd49b0c3bb2c150c59060d83f52", size = 58364, upload-time = "2026-01-28T12:07:49.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/33/6316b4907a0bffc1bcc99074c7e2d01184fdfeee401c864146a40d55ad10/genai_prices-0.0.52-py3-none-any.whl", hash = "sha256:639e7a2ae7eddf5710febb9779b9c9e31ff5acf464b4eb1f6018798ea642e6d3", size = 60937, upload-time = "2026-01-28T12:07:47.921Z" }, +] + +[[package]] +name = "github-copilot-sdk" +version = "0.1.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/e7/87955115ae99668a7dc1d1314c2ac07a7d5e8d621c52ce322090616d342e/github_copilot_sdk-0.1.19.tar.gz", hash = "sha256:2bba9db1ee0b3b6ff751568489777224e381c15427e253f98731c01a936833c2", size = 81767, upload-time = "2026-01-27T17:51:02.679Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/af/b11b80d02aaefc2fc6bfaabb3ae873439c90dc464b3a29eda51b969842b0/genai_prices-0.0.51-py3-none-any.whl", hash = "sha256:4e0f5892a7ec757d59f343c5dbf9675b0f9e8ed65f4fe26ac7df600e34788ca0", size = 60656, upload-time = "2026-01-13T12:49:12.867Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/f5b0ddab9b3c4e9d0a8b5233fd12543f07f2fb65ab593652826c1ff96359/github_copilot_sdk-0.1.19-py3-none-any.whl", hash = "sha256:98d2e6ce65b88b470756d0d2ced12714fd76d3394c245e29a42185e3a4e83d0b", size = 34243, upload-time = "2026-01-27T17:50:59.566Z" }, ] [[package]] name = "google-auth" -version = "2.47.0" +version = "2.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "cryptography" }, { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/3c/ec64b9a275ca22fa1cd3b6e77fefcf837b0732c890aa32d2bd21313d9b33/google_auth-2.47.0.tar.gz", hash = "sha256:833229070a9dfee1a353ae9877dcd2dec069a8281a4e72e72f77d4a70ff945da", size = 323719, upload-time = "2026-01-06T21:55:31.045Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/18/79e9008530b79527e0d5f79e7eef08d3b179b7f851cfd3a2f27822fbdfa9/google_auth-2.47.0-py3-none-any.whl", hash = "sha256:c516d68336bfde7cf0da26aab674a36fedcf04b37ac4edd59c597178760c3498", size = 234867, upload-time = "2026-01-06T21:55:28.6Z" }, + { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, ] [package.optional-dependencies] @@ -752,7 +768,7 @@ requests = [ [[package]] name = "google-genai" -version = "1.59.0" +version = "1.60.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -766,9 +782,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/34/c03bcbc759d67ac3d96077838cdc1eac85417de6ea3b65b313fe53043eee/google_genai-1.59.0.tar.gz", hash = "sha256:0b7a2dc24582850ae57294209d8dfc2c4f5fcfde0a3f11d81dc5aca75fb619e2", size = 487374, upload-time = "2026-01-15T20:29:46.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/3f/a753be0dcee352b7d63bc6d1ba14a72591d63b6391dac0cdff7ac168c530/google_genai-1.60.0.tar.gz", hash = "sha256:9768061775fddfaecfefb0d6d7a6cabefb3952ebd246cd5f65247151c07d33d1", size = 487721, upload-time = "2026-01-21T22:17:30.398Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/53/6d00692fe50d73409b3406ae90c71bc4499c8ae7fac377ba16e283da917c/google_genai-1.59.0-py3-none-any.whl", hash = "sha256:59fc01a225d074fe9d1e626c3433da292f33249dadce4deb34edea698305a6df", size = 719099, upload-time = "2026-01-15T20:29:44.604Z" }, + { url = "https://files.pythonhosted.org/packages/31/e5/384b1f383917b5f0ae92e28f47bc27b16e3d26cd9bacb25e9f8ecab3c8fe/google_genai-1.60.0-py3-none-any.whl", hash = "sha256:967338378ffecebec19a8ed90cf8797b26818bacbefd7846a9280beb1099f7f3", size = 719431, upload-time = "2026-01-21T22:17:28.086Z" }, ] [[package]] @@ -812,6 +828,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/88/3175759d2ef30406ea721f4d837bfa1ba4339fde3b81ba8c5640a96ed231/groq-1.0.0-py3-none-any.whl", hash = "sha256:6e22bf92ffad988f01d2d4df7729add66b8fd5dbfb2154b5bbf3af245b72c731", size = 138292, upload-time = "2025-12-17T23:34:21.957Z" }, ] +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, + { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, + { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, + { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, + { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, + { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417, upload-time = "2025-10-21T16:22:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219, upload-time = "2025-10-21T16:22:17.954Z" }, + { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826, upload-time = "2025-10-21T16:22:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550, upload-time = "2025-10-21T16:22:23.637Z" }, + { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564, upload-time = "2025-10-21T16:22:26.016Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236, upload-time = "2025-10-21T16:22:28.362Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795, upload-time = "2025-10-21T16:22:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214, upload-time = "2025-10-21T16:22:33.831Z" }, + { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961, upload-time = "2025-10-21T16:22:36.468Z" }, + { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462, upload-time = "2025-10-21T16:22:39.772Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1054,11 +1101,20 @@ wheels = [ [[package]] name = "jmespath" -version = "1.0.1" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "jsonref" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, ] [[package]] @@ -1175,7 +1231,7 @@ wheels = [ [[package]] name = "logfire" -version = "4.19.0" +version = "4.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "executing" }, @@ -1186,9 +1242,9 @@ dependencies = [ { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/a4/518b4deb1b6bffc150c5229b209121b76185ecd369a460fc3b740e8e4af0/logfire-4.19.0.tar.gz", hash = "sha256:4ec49f1e6be2512f2c60ade053fd8a7610539dbe570e4ba53df91435ef7db888", size = 634738, upload-time = "2026-01-16T10:11:57.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/6a/387d114faf39a13f8e81f09dedc1ed89fe81c2d9eb63ee625e1abc7c79d2/logfire-4.21.0.tar.gz", hash = "sha256:57051f10e7faae4ab4905893d13d3ebeca96ca822ecf35ab68a0b7da4e5d3550", size = 651979, upload-time = "2026-01-28T18:55:43.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/63/d1dfb4df34ada7a5064bbd46009ed5a14f5fdfda8381381bdba21eeeda3c/logfire-4.19.0-py3-none-any.whl", hash = "sha256:cbd52f6edb33944c2d38e801bbf1e7c76c14d5c9eb351aeeecb4d6d286b41443", size = 236432, upload-time = "2026-01-16T10:11:54.672Z" }, + { url = "https://files.pythonhosted.org/packages/5b/03/af72df300c4659ea26cb2e1f1f212e26b1b373e89b82a64912bd9e898be5/logfire-4.21.0-py3-none-any.whl", hash = "sha256:cfd0ce7048ed7b415bd569cb2f20fe487e9dfcad926666c66c3c3f124d6a6238", size = 241687, upload-time = "2026-01-28T18:55:40.753Z" }, ] [package.optional-dependencies] @@ -1198,11 +1254,11 @@ httpx = [ [[package]] name = "logfire-api" -version = "4.19.0" +version = "4.21.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/30/d8c098989ac64d74f279187626b602862a139521b970f44ca3bc1330598a/logfire_api-4.19.0.tar.gz", hash = "sha256:a38b3b42160df8c02d3cb722cf2d6a43d3a633e7b8ee832d01ac9a9a41fc0c76", size = 58668, upload-time = "2026-01-16T10:11:58.302Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/d5/c183261d5560e33335443b377c921aa6a15e9890ceac63024237e8c1279b/logfire_api-4.21.0.tar.gz", hash = "sha256:5d709a0d3adfd573db70964cb48c03b750966de395ed9c8da4de111707a75fab", size = 59331, upload-time = "2026-01-28T18:55:44.985Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/f5/b702b77968c08f5dc9407fd00b455e6c336a9f8c1e1aeee7180b5ac7186d/logfire_api-4.19.0-py3-none-any.whl", hash = "sha256:98c47e4995ab735791070529263f7cc19d07807204552be1f449bf1045f23ebc", size = 97027, upload-time = "2026-01-16T10:11:56.173Z" }, + { url = "https://files.pythonhosted.org/packages/d7/00/5045f889be4a450b321db998d0a5581d30423138a04dffe18b52730cb926/logfire_api-4.21.0-py3-none-any.whl", hash = "sha256:32f9b48e6b73c270d1aeb6478dcbecc5f82120b8eae70559e0d1b05d1b86541e", size = 98061, upload-time = "2026-01-28T18:55:42.342Z" }, ] [[package]] @@ -1265,7 +1321,7 @@ linkify = [ [[package]] name = "mcp" -version = "1.25.0" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1283,9 +1339,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]] @@ -1338,83 +1394,83 @@ wheels = [ [[package]] name = "multidict" -version = "6.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, - { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, - { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, - { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, - { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, - { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, - { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, - { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, - { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, - { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, - { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, - { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, - { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, - { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, - { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, - { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, - { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, - { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, - { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, - { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, - { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, - { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, - { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, - { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, - { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, - { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, - { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, - { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, - { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, - { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, - { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, - { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, - { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, - { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, - { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, - { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, - { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, - { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, - { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, - { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, - { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, - { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, - { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, - { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] [[package]] @@ -1467,7 +1523,7 @@ wheels = [ [[package]] name = "openai" -version = "2.15.0" +version = "2.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1479,9 +1535,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]] @@ -1652,11 +1708,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]] @@ -1855,11 +1911,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]] @@ -1884,19 +1940,19 @@ email = [ [[package]] name = "pydantic-ai" -version = "1.44.0" +version = "1.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "ui", "vertexai"] }, + { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "ui", "vertexai", "xai"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/64/63091cc655b0446e2b2cedf58e19d49b076855c497bc882478b10f8b3e42/pydantic_ai-1.44.0.tar.gz", hash = "sha256:20f378aef29a3fe4bc650c1e5f2b46d0e379b38d2b55d5625c50c84ac234196b", size = 11681, upload-time = "2026-01-17T01:26:07.353Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/30/913d8d3f5271289c1753d0970751143c47acf762d00b11756b25e3e5db34/pydantic_ai-1.48.0.tar.gz", hash = "sha256:d739d7a56125f58a9a8dfbdbb737cb082f9304802f9886ada4195ff76883b15c", size = 11795, upload-time = "2026-01-28T00:09:30.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ba/46c1e3cd61136be75343ce17ff41a23a69ff4df23a3ecff23df282220158/pydantic_ai-1.44.0-py3-none-any.whl", hash = "sha256:d69111a9638f6dcb6b93a9f5db6f1b0a5bdcd6022d9e77ee0928f11d63667dc7", size = 7198, upload-time = "2026-01-17T01:25:56.233Z" }, + { url = "https://files.pythonhosted.org/packages/32/ce/dd0dbaa03f2166e895ef4af74f46780b68c96f17538a0bc28edff2196748/pydantic_ai-1.48.0-py3-none-any.whl", hash = "sha256:da7eacfee0b9534f03ff50d96a66406f7030df59e2626b0100618c0d208b2503", size = 7220, upload-time = "2026-01-28T00:09:20.729Z" }, ] [[package]] name = "pydantic-ai-slim" -version = "1.44.0" +version = "1.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "genai-prices" }, @@ -1907,9 +1963,9 @@ dependencies = [ { name = "pydantic-graph" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/73/122aafb9bcad78042e2799649db745c357fc594e112c0ad0d8d183fdc447/pydantic_ai_slim-1.44.0.tar.gz", hash = "sha256:1bda6dbec4b94d8e52e32eb1e4c1da14f4f0202414306c65e2c3b43bebd82407", size = 378362, upload-time = "2026-01-17T01:26:09.64Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/e1/02d712bb1e93ccd4cf08e154811914da406320cf2ce041941992b642f675/pydantic_ai_slim-1.48.0.tar.gz", hash = "sha256:b386dc9fb6f751aac12599b0efdd3f82ca0c9ec9ae67323bc343d1b4f5f1e7eb", size = 396949, upload-time = "2026-01-28T00:09:33.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/18/2f68a9718843a95c0b4724ab3821ef8f60f7b8ef50e70b3769db3259da81/pydantic_ai_slim-1.44.0-py3-none-any.whl", hash = "sha256:3bf412f43d3ae787350d671f873706ad74603777e04f42411c8fbdf7dced8031", size = 496441, upload-time = "2026-01-17T01:26:00.184Z" }, + { url = "https://files.pythonhosted.org/packages/24/77/42872ccfd21bb8fc701fbe5715ec476de521200284527d04981d32e88389/pydantic_ai_slim-1.48.0-py3-none-any.whl", hash = "sha256:23aa473115f7402876cff448d87ae411dace9c78457640e395f9878882ec2f00", size = 519241, upload-time = "2026-01-28T00:09:24.215Z" }, ] [package.optional-dependencies] @@ -1973,6 +2029,9 @@ vertexai = [ { name = "google-auth" }, { name = "requests" }, ] +xai = [ + { name = "xai-sdk" }, +] [[package]] name = "pydantic-core" @@ -2029,7 +2088,7 @@ wheels = [ [[package]] name = "pydantic-evals" -version = "1.44.0" +version = "1.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2039,14 +2098,14 @@ dependencies = [ { name = "pyyaml" }, { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/0c/3a11101799972833585458caa5c0aa1b1cc8045acaaaec70846cb5dfdb3a/pydantic_evals-1.44.0.tar.gz", hash = "sha256:d93733e1d042c8f312645a969d7496b97d18d0030cb47174e57047ac56a2fc81", size = 47174, upload-time = "2026-01-17T01:26:10.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/a1/50e031c1d11613cb4864d885a2e9ca87ad397b132a570c77b7c4cd2054f3/pydantic_evals-1.48.0.tar.gz", hash = "sha256:cc0d7a2178fcae8696df56a8ed7b44b8eb80991d492b608a83d3dfa80d8e0f35", size = 47191, upload-time = "2026-01-28T00:09:34.69Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/4a/0bb5c78a0c82eaa2fe4b4caf15afa676aa37cad8b3dfeabb7766f7f651df/pydantic_evals-1.44.0-py3-none-any.whl", hash = "sha256:6eed03e42cc0e6f9fd022345dce3db0113614052321037efeb47a408a95f460c", size = 56346, upload-time = "2026-01-17T01:26:02.053Z" }, + { url = "https://files.pythonhosted.org/packages/b2/48/f210b0c3b0600e47295c10e532eaa97e0bde4ed5f9c84a5cd87a9401a069/pydantic_evals-1.48.0-py3-none-any.whl", hash = "sha256:cf7982038407f7d905d95a8c9f693b37dfb515024d63f28013e400100d00c5de", size = 56378, upload-time = "2026-01-28T00:09:26.162Z" }, ] [[package]] name = "pydantic-graph" -version = "1.44.0" +version = "1.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -2054,9 +2113,9 @@ dependencies = [ { name = "pydantic" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/d9/83f1921633634cb1fd5947ebdc48a992cd1e660d9d0ba10f23450af654b8/pydantic_graph-1.44.0.tar.gz", hash = "sha256:81bb14bb47d3313fd7114f3ae9650dab77729451659e0247d298e1800f997c23", size = 58454, upload-time = "2026-01-17T01:26:12.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/da/ed69a409ff559e1088607b5f65b667edbee0dd0169582c013221bfed4711/pydantic_graph-1.48.0.tar.gz", hash = "sha256:de731137208a80dbf1396f23954e7f920da4b8b9d74d02e5cdb154d01fb878c5", size = 58458, upload-time = "2026-01-28T00:09:36.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/e6/f9ed700171435309e214cf19012cf3b391d10ec28786d4a567ee315b1acd/pydantic_graph-1.44.0-py3-none-any.whl", hash = "sha256:5affbfe5779f79946de2c6a6524b98e2344982a129b2a1e19d031e76a5bc1e51", size = 72324, upload-time = "2026-01-17T01:26:04.119Z" }, + { url = "https://files.pythonhosted.org/packages/16/7f/9db05557d9fdb01e888ce5c18875346868b68d94ab600154d74a34279eca/pydantic_graph-1.48.0-py3-none-any.whl", hash = "sha256:47557ffca1972a8cfb1de0c427bf4eba0a080dbd53e966a94e88b55529442485", size = 72345, upload-time = "2026-01-28T00:09:28.15Z" }, ] [[package]] @@ -2176,11 +2235,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]] @@ -2352,15 +2411,15 @@ wheels = [ [[package]] name = "rich" -version = "14.2.0" +version = "14.3.1" 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/a1/84/4831f881aa6ff3c976f6d6809b58cdfa350593ffc0dc3c58f5f6586780fb/rich-14.3.1.tar.gz", hash = "sha256:b8c5f568a3a749f9290ec6bddedf835cec33696bfc1e48bcfecb276c7386e4b8", size = 230125, upload-time = "2026-01-24T21:40:44.847Z" } 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/87/2a/a1810c8627b9ec8c57ec5ec325d306701ae7be50235e8fd81266e002a3cc/rich-14.3.1-py3-none-any.whl", hash = "sha256:da750b1aebbff0b372557426fb3f35ba56de8ef954b3190315eb64076d6fb54e", size = 309952, upload-time = "2026-01-24T21:40:42.969Z" }, ] [[package]] @@ -2456,28 +2515,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]] @@ -2556,14 +2615,14 @@ wheels = [ [[package]] name = "starlette" -version = "0.51.0" +version = "0.52.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/65/5a1fadcc40c5fdc7df421a7506b79633af8f5d5e3a95c3e72acacec644b9/starlette-0.51.0.tar.gz", hash = "sha256:4c4fda9b1bc67f84037d3d14a5112e523509c369d9d47b111b2f984b0cc5ba6c", size = 2647658, upload-time = "2026-01-10T20:23:15.043Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/c4/09985a03dba389d4fe16a9014147a7b02fa76ef3519bf5846462a485876d/starlette-0.51.0-py3-none-any.whl", hash = "sha256:fb460a3d6fd3c958d729fdd96aee297f89a51b0181f16401fe8fd4cb6129165d", size = 74133, upload-time = "2026-01-10T20:23:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, ] [[package]] @@ -2596,7 +2655,7 @@ wheels = [ [[package]] name = "textual" -version = "7.3.0" +version = "7.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", extra = ["linkify"] }, @@ -2606,9 +2665,9 @@ dependencies = [ { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/ee/620c887bfad9d6eba062dfa3b6b0e735e0259102e2667b19f21625ef598d/textual-7.3.0.tar.gz", hash = "sha256:3169e8ba5518a979b0771e60be380ab1a6c344f30a2126e360e6f38d009a3de4", size = 1590692, upload-time = "2026-01-15T16:32:02.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/8d/2fbd6b8652f4cabf9cb0852d7af1aa45b6cad32d0f50735856e8f9e41719/textual-7.4.0.tar.gz", hash = "sha256:1a9598e485492f9a8f033c7ec5e59528df3ab0742fda925681acf78b0fb210de", size = 1592252, upload-time = "2026-01-25T19:57:04.624Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/1f/abeb4e5cb36b99dd37db72beb2a74d58598ccb35aaadf14624ee967d4a6b/textual-7.3.0-py3-none-any.whl", hash = "sha256:db235cecf969c87fe5a9c04d83595f506affc9db81f3a53ab849534d726d330a", size = 716374, upload-time = "2026-01-15T16:31:58.233Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9c/4169ccffed6d53f78e3175eae0cd649990071c6e24b6ad8830812ebab726/textual-7.4.0-py3-none-any.whl", hash = "sha256:41a066cae649654d4ecfe53b8316f5737c0042d1693ce50690b769a7840780ac", size = 717985, upload-time = "2026-01-25T19:57:02.966Z" }, ] [[package]] @@ -2779,11 +2838,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.14" +version = "0.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/6e/62daec357285b927e82263a81f3b4c1790215bc77c42530ce4a69d501a43/wcwidth-0.5.0.tar.gz", hash = "sha256:f89c103c949a693bf563377b2153082bf58e309919dfb7f27b04d862a0089333", size = 246585, upload-time = "2026-01-27T01:31:44.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/f2/3e/45583b67c2ff08ad5a582d316fcb2f11d6cf0a50c7707ac09d212d25bc98/wcwidth-0.5.0-py3-none-any.whl", hash = "sha256:1efe1361b83b0ff7877b81ba57c8562c99cf812158b778988ce17ec061095695", size = 93772, upload-time = "2026-01-27T01:31:43.432Z" }, ] [[package]] @@ -2845,6 +2904,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] +[[package]] +name = "xai-sdk" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/38/751421b0b40050582ab5f5ed56f0074d322668cf72b0374b1b6f898388ac/xai_sdk-1.6.0.tar.gz", hash = "sha256:e0fc9df3a1cab1ec744a9c962d6802f0b27cfd91b4ca8c18f74dd5f4f944f7f7", size = 371793, upload-time = "2026-01-27T12:31:18.526Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/ca/9924cf64c1289ff7725572ca17fb2dea7573233d3cc964a16287260145cf/xai_sdk-1.6.0-py3-none-any.whl", hash = "sha256:b1e828dfb631233bfb3dfc3173ee4e37432710de76094b695f2a89f6cd659ba4", size = 224130, upload-time = "2026-01-27T12:31:17.222Z" }, +] + [[package]] name = "yarl" version = "1.22.0"