diff --git a/README.md b/README.md index ea5359035..95af7aed9 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,61 @@ uv run workflow/chaining.py --agent post_writer --message "" Add the `--quiet` switch to disable progress and message display and return only the final response - useful for simple automations. +### Agents-as-Tools (child agents as tools) + +Sometimes one agent needs to call other agents as tools. `fast-agent` supports +this via a hybrid *Agents-as-Tools* agent: + +- You declare a BASIC agent with `agents=[...]`. +- At runtime it is instantiated as an internal `AgentsAsToolsAgent`, which: + - Inherits from `McpAgent` (keeps its own MCP servers/tools). + - Exposes each child agent as a tool (`agent__ChildName`). + - Merges MCP tools and agent-tools in a single `list_tools()` surface. + - Supports history/parallel controls: + - `history_mode` (default `fork`; `fork_and_merge` to merge clone history back) + - `max_parallel` (default unlimited), `child_timeout_sec` (default none) + - `max_display_instances` (default 20; collapse progress after top-N) + +Minimal example: + +```python +@fast.agent( + name="NY-Project-Manager", + instruction="Return current time and project status.", + servers=["time"], # MCP server 'time' configured in fastagent.config.yaml +) +@fast.agent( + name="London-Project-Manager", + instruction="Return current time and news.", + servers=["time"], +) +@fast.agent( + name="PMO-orchestrator", + instruction="Get reports. Separate call per topic. NY: {OpenAI, Fast-Agent, Anthropic}, London: Economics", + default=True, + agents=[ + "NY-Project-Manager", + "London-Project-Manager", + ], # children are exposed as tools: agent__NY-Project-Manager, agent__London-Project-Manager + # optional knobs: + # history_mode=HistoryMode.FORK_AND_MERGE to merge clone history back + # max_parallel=8 to cap parallel agent-tools + # child_timeout_sec=600 to bound each child call + # max_display_instances=10 to collapse progress UI after top-N +) +async def main() -> None: + async with fast.run() as agent: + result = await agent("Get PMO report") + print(result) + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +Extended example is available in the repository as +`examples/workflows/agents_as_tools_extended.py`. + ## MCP OAuth (v2.1) For SSE and HTTP MCP servers, OAuth is enabled by default with minimal configuration. A local callback server is used to capture the authorization code, with a paste-URL fallback if the port is unavailable. diff --git a/agetns_as_tools_plan_fix.md b/agetns_as_tools_plan_fix.md new file mode 100644 index 000000000..5f269ab3f --- /dev/null +++ b/agetns_as_tools_plan_fix.md @@ -0,0 +1,371 @@ +# Agents-as-Tools — Fix Plan for Current Implementation + +## 1. Scope + +This document describes how to evolve and harden the current `AgentsAsToolsAgent` implementation in this repo: + +- File: `src/fast_agent/agents/workflow/agents_as_tools_agent.py` +- Wiring: + - `direct_decorators.agent(..., agents=[...])` + - `direct_factory.create_agents_by_type` (BASIC agents with `child_agents`) +- Supporting components: + - `ToolAgent`, `LlmAgent` + - `McpAgent`, `MCPAggregator` + - UI: `RichProgressDisplay`, `ConsoleDisplay`, `history_display`, `usage_display` + - Stats: `UsageAccumulator` + +Goal: keep this implementation **experimental but coherent**, good enough for real workflows and for an upstream-quality PR later. + +--- + +## 2. Recovered Intended Design + +From the module docstring and issue #458: + +- **Concept** + - Parent is a normal tool-calling LLM. + - Each child agent is exposed as a tool: `agent__{child_name}`. + - Parent delegates; it doesn't orchestrate explicitly. + +- **Tool interface** + - `list_tools()` → one tool per child, permissive schema: + - `{ text?: string, json?: object, ... }` + - `call_tool()`: + - Routes tool name → child agent. + - Normalizes arguments to a single `Prompt.user(text)`. + - Executes `child.generate([...])` and returns `CallToolResult`. + +- **Parallelism** + - Parent LLM may emit multiple tool calls in one turn. + - `run_tools()` should: + - Validate tools against `list_tools()`. + - Run all valid calls via `asyncio.gather`. + - Associate each physical tool call with a **virtual instance** index: `[1]`, `[2]`. + +- **Progress panel semantics** (Rich progress, left side) + - Before fan-out: one line per *agent* (parent and children). + - During fan-out: + - Parent line shows `Ready` (waiting on children). + - Each child instance shows its own line, with instance-index-suffixed name: `OriginalName[1]`, `OriginalName[2]`. + - Lines disappear as soon as each instance finishes. + - After fan-in: + - Only base agent lines remain; original names restored. + +- **Chat/log semantics** + - Parent chat should show **tool request/result panels** for each instance. + - Child chat should **not** stream to the panel when invoked as a tool. + - Child **tool usage** (MCP tools, shell, etc.) should still be visible. + +- **MCP initialization semantics** + - Children are real agents (`McpAgent` or similar) with MCP clients & aggregators. + - Multiple instances of the same child **share** one MCP aggregator. + - Parent itself does **not** talk to MCP directly; it only calls children. + +- **Stats semantics** + - Token/tool stats are tracked per *agent* via `UsageAccumulator`. + - Instances are **transient**; they may be visible in progress/chat but stats roll up per agent. + +--- + +## 3. Current Implementation Review + +### 3.1. What's already good + +- **Tool naming & discovery** + - `_make_tool_name(child_name)` → `agent__{child_name}`. + - `list_tools()` returns Tool schemas with the minimal `{ text, json }` interface. + +- **Routing & argument handling** + - `call_tool()` resolves both `agent__Child` and bare `Child`. + - Arguments → `text` precedence, then `json`, then `full args` JSON. + - Child is called via `Prompt.user(...)` + `child.generate([...])`. + +- **Error surfacing** + - If child writes to the `FAST_AGENT_ERROR_CHANNEL`, those blocks are appended to the tool result contents and `CallToolResult.isError` is set. + +- **Parallel fan-out** + - `run_tools()` builds `call_descriptors` and `descriptor_by_id`. + - Uses `asyncio.gather(..., return_exceptions=True)` to execute all calls concurrently. + +- **Instance naming for UI** + - For `pending_count > 1`, collects `original_names[tool_name] = child._name`. + - In `call_with_instance_name()`: + - Computes `instance_name = f"{original}[{instance}]"`. + - Mutates `child._name` and `child._aggregator.agent_name`. + - Emits a synthetic `ProgressEvent(CHATTING, target=instance_name, agent_name=instance_name)` to create a line in the progress panel. + - On completion, hides that line by flipping `task.visible = False` in `RichProgressDisplay`. + +- **Child display suppression** + - `call_tool()` lazily creates: + - `_display_suppression_count: { id(child) -> int }`. + - `_original_display_configs: { id(child) -> ConsoleDisplayConfig }`. + - On first use of a given child, makes a copy of `child.display.config`, sets: + - `logger.show_chat = False` + - `logger.show_tools = True` + - Ensures **children don't spam chat**, but still show their own MCP tool usage. + +- **Top/bottom panels** + - `_show_parallel_tool_calls()` and `_show_parallel_tool_results()` correctly label tools as `tool_name[instance]` in chat panels and bottom status items. + +Overall, the core mechanics of Agents-as-Tools are present and coherent. + +### 3.2. Gaps and fragilities + +1. [x] **Display config restoration logic is incomplete** + + - In `call_tool()` we: + - Always increment `_display_suppression_count[child_id]`. + - In `finally` we **only decrement** the counter, do **not** restore config. + - In `run_tools()` we restore config **only if `pending_count > 1`**: + - For each `child` in `original_names`: + - Delete `_display_suppression_count[child_id]`. + - Restore `display.config` from `_original_display_configs`. + - Problems: + - For a **single tool call** (the most common case!), `pending_count == 1`, so `original_names` is empty and **display configs are never restored**. + - Even for `pending_count > 1`, restoration is decoupled from `_display_suppression_count[child_id]` (no 0→1 / 1→0 semantics). + + **Effect:** once a child is ever used as a tool, its chat may remain permanently suppressed for all subsequent uses, including direct runs, which is surprising. + +2. [x] **Instance naming races on shared child objects** + + - Multiple tool calls to the **same child agent** share a single `child` object and a single `child._aggregator`. + - `call_with_instance_name()` mutates `child._name` and `child._aggregator.agent_name` in each task. + - Under concurrency, whichever task last mutates these fields wins; log lines from the child and from its aggregator may be attributed to the last instance, not this instance. + + **Effect:** progress rows are mostly correct (because we also emit explicit `ProgressEvent`s), but logs and transport stats that come from `MCPAggregator` may mix instance names. + +3. [x] **Direct reliance on private internals of `RichProgressDisplay`** + + - `call_with_instance_name()` accesses: + - `outer_progress_display._taskmap` + - `outer_progress_display._progress.tasks` + - and flips `task.visible = False`. + + **Risk:** this is brittle against internal refactors of the progress UI and difficult to test in isolation. + +4. [x] **`MessageType` import is unused** + + - `from fast_agent.ui.message_primitives import MessageType` is imported but not used. + - Indicates some UI scenarios were planned (e.g. structured tool headers) and not implemented. + +5. [x] **Stats are per-agent only, not per-instance** + + - `UsageAccumulator` is owned by the LLM (via `LlmDecorator.usage_accumulator`). + - Usage is aggregated per **agent** (e.g. `PM-1-DayStatusSummarizer`), not per `[i]` instance. + - This matches the general fast-agent philosophy but does **not** match the stronger requirement separate rows in the stats panel per instance. + + **Current behavior is acceptable**, but the instance-per-row requirement should be documented as **out of scope** for the first implementation. + +6. [x] **Tool availability check and naming** + + - `list_tools()` now merges MCP tools (from `McpAgent`) and agent-tools; names are deduped to avoid collisions. + - `run_tools()` validates against this merged surface so mixed MCP + agent-tool batches work. + +--- + +## 4. Design Decisions to Lock In (for this branch) + +Before making changes, clarify the intended semantics for this repo: + +1. **Child chat visibility** + - When a child agent is used as a tool via `AgentsAsToolsAgent`, its chat is **never** shown. + - When a child is run directly (by the user), its chat **is** shown. + +2. **Instance stats vs agent stats** + - For this implementation, stats remain **per agent**, not per instance. + - Instance-level visibility is provided by: + - Progress panel (per-instance lines). + - Chat log (tool headers `tool_name[i]`). + +3. **MCP reuse model** + - Child MCP aggregators are **shared** between all instances and all parents. + - No per-instance MCP clients. + +4. **Tool namespace composition** + - `AgentsAsToolsAgent.list_tools()` returns a merged surface: MCP tools plus `agent__Child` tools. + - Child tool names are prefixed to avoid collisions; MCP/local tool names remain untouched. + +These decisions simplify the fix plan and keep surface area small. + +--- + +## 5. Step-by-Step Fix Plan + +### 5.1. Fix display suppression and restoration + +**Goal:** implement correct reference counting per-child and always restore display config after the last instance completes, regardless of `pending_count`. + +**Steps:** + +1. [x] **Introduce explicit helpers on `AgentsAsToolsAgent`** + + - Private methods: + - `_ensure_display_maps_initialized()` + - `_suppress_child_display(child)` + - `_release_child_display(child)` + + - Semantics: + - `_suppress_child_display(child)`: + - If `child_id` not in `_display_suppression_count`: + - Snapshot `child.display.config` into `_original_display_configs[child_id]`. + - Install a modified config with `show_chat=False, show_tools=True`. + - Initialize counter to `0`. + - Increment counter. + - `_release_child_display(child)`: + - Decrement counter. + - If counter reaches `0`: + - Restore original config from `_original_display_configs`. + - Delete both entries for this `child_id`. + +2. [x] **Apply helpers in `call_tool()`** + + - Replace direct manipulation with: + - `_suppress_child_display(child)` before `await child.generate(...)`. + - `_release_child_display(child)` in `finally`. + +3. [x] **Remove display restoration from `run_tools()`** + + - The `_display_suppression_count` & `_original_display_configs` clean-up should be **entirely local** to `call_tool()`; `run_tools()` should not know about it. + - This also makes `call_tool()` correct if it's ever used outside of `run_tools()`. + +**Outcome:** display configs are always restored after the last parallel/sequential instance finishes, independent of how many tools or which code path called them. + +--- + +### 5.2. Stabilize instance naming and progress UI + +**Goal:** keep existing UX (progress lines + names `[i]`) but reduce reliance on private internals. + +1. **Add a small public API to `RichProgressDisplay`** + + - In `rich_progress.py`: + - Add methods: + - `def hide_task(self, task_name: str) -> None:` + - Look up `task_id` via `_taskmap.get(task_name)`. + - If found, set `task.visible = False`. + - Optionally `def ensure_task(self, event: ProgressEvent) -> TaskID:` to encapsulate `add_task` + update logic. + + - Refactor `update()` to use `ensure_task()` internally. + +2. [x] **Use the public API in `AgentsAsToolsAgent`** + + - Replace direct access to `_taskmap` and `_progress.tasks` with: + - `outer_progress_display.hide_task(instance_name)`. + +3. **Document expected lifetime** + + - Comment in `AgentsAsToolsAgent`: + - Instance lines are **ephemeral**; they are hidden immediately when each task completes but progress data continues to exist for the duration of the run. + +**Outcome:** same UI behavior, less fragile coupling to UI internals. + +--- + +### 5.3. Reduce naming races (best-effort for experimental phase) + +Completely eliminating races around `child._name` and `child._aggregator.agent_name` would require: + +- Either a per-instance `MCPAggregator`, or +- Making `MCPAggregator` fully stateless in terms of `agent_name`, or +- Augmenting all tool/progress logs with an explicit correlation/instance id. + +That is a larger refactor than we want for the current experimental implementation. Instead, we can apply a **minimal mitigation**: + +1. [x] **Minimize mutation window** + + - In `call_with_instance_name()`: + - Set `child._name` and `child._aggregator.agent_name` **immediately** before `await self.call_tool(...)`. + - Right after the `await`, restore them to the base `original_names[tool_name]` (inside the same task's `try/finally`). + - `run_tools()` should **no longer perform name restoration** for children; it only needs to restore parent-level names (if we ever mutate them) and handle display. + +2. **Clarify known limitation** + + - In the module docstring, add a short Limitations section explaining: + - Under heavy concurrency, some low-level logs from MCP may still show mixed instance names; the progress panel and chat tool headers are the authoritative view. + +**Outcome:** race window is strictly bounded to the duration of a single tool call in a single task; we no longer keep children renamed after the call completes. + +--- + +### 5.4. Explicitly document stats behavior + +**Goal:** align user expectations with current implementation. + +1. **Update README / docs** (or a dedicated experimental note): + + - Describe that: + - Token and tool usage stats are aggregated **per agent**. + - Agents-as-Tools does **not** create per-instance stats rows; instead: + - Per-instance work is visible in the progress panel. + - Tool calls are visible in the history summary as `tool→` / `result→` rows. + +2. **Optionally tag tool results with instance index in content** + + - For debug clarity, `AgentsAsToolsAgent` could prepend a short header block to each `CallToolResult` content: + - e.g. `"[instance 1]"`. + - This would make the instance index visible in `history_display` even outside the UI tool headers. + + This is optional and can be added behind a config flag if needed. + +--- + +### 5.5. Tests and diagnostics + +1. **Unit tests for `AgentsAsToolsAgent`** + + - Scenarios: + - Single tool call to one child. + - Two sequential tool calls in separate turns. + - Two parallel tool calls to **different** children. + - Two parallel tool calls to the **same** child. + - Tool-not-found error path. + - Assertions: + - `list_tools()` returns expected tool names. + - `call_tool()` forwards `text` and `json` correctly. + - Display suppression: + - `child.display.config.logger.show_chat` toggles to False during calls. + - Restored to original after calls (check for all scenarios). + +2. **Integration-style test with a fake `RichProgressDisplay`** + + - Inject a fake progress display with a deterministic in-memory representation. + - Assert that for parallel calls: + - Parent gets a `READY` event. + - Each instance gets a `CHATTING` event with `target=OriginalName[i]`. + - `hide_task()` is called exactly once per instance. + +3. **Manual diagnostic recipe** + + - Document a small `fastagent.config.yaml` example that: + - Defines N children representing mocked projects. + - Defines a parent with `agents: [...]` using Agents-as-Tools. + - Steps to reproduce and visually verify: + - Instance lines in progress panel. + - Tool rows in history summary. + - Stats table showing aggregate per agent. + +--- + +## 6. Future Enhancements (Beyond Fix Plan) + +These are candidates for the from-scratch design rather than this incremental fix: + +- **Per-instance stats** + - Attach a lightweight `InstanceUsage` struct per tool call and aggregate it at run end. + +- **Correlation IDs and structured logging** + - Emit a unique correlation ID for each tool call and propagate it through: + - Parent request → tool_call. + - Child logs and progress events. + - MCPAggregator transport tracking. + +- **Cleaner abstraction boundary** + - Extract an `AgentsAsToolsRuntime` helper that contains **no UI or LLM logic**, only: + - Tool mapping. + - Parallel execution. + - Result collation. + - A separate `AgentsAsToolsDisplayAdapter` layer would handle: + - Progress events. + - Display config changes. + +These ideas are elaborated further in `agetns_as_tools_plan_scratcj.md`. diff --git a/agetns_as_tools_plan_scratch.md b/agetns_as_tools_plan_scratch.md new file mode 100644 index 000000000..80ac412fe --- /dev/null +++ b/agetns_as_tools_plan_scratch.md @@ -0,0 +1,475 @@ +# Agents-as-Tools — From-Scratch Design Plan (Upstream-Oriented) + +## 1. Objectives + +Design a clean, upstream-friendly implementation of the **Agents-as-Tools** pattern for `fast-agent`, starting from the upstream repository semantics: + +- **Model**: a parent LLM agent exposes other agents as callable tools. +- **Behavior**: parent can invoke children in arbitrary order and in parallel, using normal tool-calling. +- **DX**: minimal new concepts; works naturally with existing decorators and config. +- **UX**: integrates with current progress panel, history, and usage stats without introducing ad hoc hacks. + +This plan does **not** assume any existing WIP code; it re-derives the feature from first principles using the current architecture (decorators, factory, MCP, UI, stats). + +--- + +## 2. Conceptual Model + +### 2.1. Roles & responsibilities + +- **Parent agent (Agents-as-Tools orchestrator)** + - A normal LLM agent with tool-calling capability. + - Exposes *child agents* as tools (`agent__ChildName`). + - Delegates the actual work to children; no custom planning. + +- **Child agent(s)** + - Existing agents (typically `McpAgent`-based) with their own MCP servers, skills, tools, etc. + - Own their own `UsageAccumulator`, history, and MCP aggregator. + - Are reused as-is; we do not clone them per instance. + +- **Virtual child instances** + - Logical construct: per tool call, we treat it as an `Instance` of a child with an index `[i]`. + - Instances are used purely for **UI and logging**, not for real objects. + +### 2.2. Key invariants + +- **Single source of truth for child agents** + - One `LlmAgent` object per defined agent name. + - All parents and instances refer to the same child objects. + +- **LLM tool-loop compatibility** + - The parents `generate()` uses the standard `ToolAgent` loop: + - LLM → `stop_reason=TOOL_USE` → `run_tools()` → new USER message. + +- **MCP reuse** + - Each child has exactly one `MCPAggregator` that persists according to its config. + - Instances never create or destroy MCP connections directly. + +- **Stats aggregation per agent** + - Usage summary is per *agent name* (parent + each child), not per instance. + - Instances show up only in progress/historical views. + +[x] ### 2.3. Current implementation snapshot — Detached per-call clones (Nov 2025) + +While §2.3 framed cloning/pooling as optional futures, the active codebase now runs with the **Dedicated child agent per call** strategy so we can guarantee honest per-instance state: + +1. **Clone creation** + - `AgentsAsToolsAgent.run_tools()` calls `child.spawn_detached_instance(name=f"{child}[i]")` before every tool dispatch. + - `spawn_detached_instance` (added to `LlmDecorator`) deep-copies the agent config, re-attaches the same LLM factory/request params, and replays initialization hooks. + +2. **MCP aggregator ownership** + - Each detached clone constructs its own `MCPAggregator`, which in turn acquires a shared `MCPConnectionManager` from context. + - To avoid tearing down the shared TaskGroup, `MCPAggregator` now tracks `_owns_connection_manager`; only the original agent that created the manager performs shutdown on `close()`. + +3. **Lifecycle + cleanup** + - After the tool call completes we `await clone.shutdown()` and merge its `UsageAccumulator` back into the parent child via `child.merge_usage_from(clone)`. + - Progress entries remain visible by emitting `ProgressAction.FINISHED` events instead of hiding tasks, ensuring traceability per instance. + +4. **Implications** + - Logs, MCP events, and progress panel lines now display fully indexed names (for example, `PM-1-DayStatusSummarizer[2]`). + - The CLI *Usage Summary* table still reports a single aggregated row per template agent (for example, `PM-1-DayStatusSummarizer`), not per `[i]` instance. + - Resource cost is higher than the single-object model, but correctness (agent naming, MCP routing, and per-instance traceability in logs/UI) takes priority for the current StratoSpace workflows. + +This snapshot should stay in sync with the actual code to document why the detached-instance path is the default today, even though the plan keeps the door open for lighter reuse models. + +--- + +## 3. High-Level Architecture + +### 3.1. New class: `AgentsAsToolsAgent` + +Location: `src/fast_agent/agents/workflow/agents_as_tools_agent.py`. + +Base class: **`McpAgent`** (inherits `ToolAgent` and manages MCP connections). + +Responsibilities: + +- Adapter between **LLM tool schema**, **child agents**, and **MCP tools**. +- `list_tools()` → MCP tools (from `McpAgent`) plus synthetic tools for children. +- `call_tool()` → executes child agents first; falls back to MCP/local tools. +- `run_tools()` → parallel fan-out for child agents plus integration with the base MCP `run_tools` for mixed batches. + +Constructor: + +```python +class AgentsAsToolsAgent(McpAgent): + def __init__( + self, + config: AgentConfig, + agents: list[LlmAgent], + context: Context | None = None, + **kwargs: Any, + ) -> None: + super().__init__(config=config, context=context, **kwargs) + self._child_agents: dict[str, LlmAgent] = {} + # Maps tool name -> child agent (keys are agent__ChildName) +``` + +### 3.2. Integration points + +1. **Decorators (`direct_decorators.agent`)** + - Add parameter `agents: List[str]` (already present upstream). + - Store `child_agents=agents` in the agent metadata. + +2. **Factory (`direct_factory.create_agents_by_type`)** + - For `AgentType.BASIC`: + - If `child_agents` is non-empty: + - Resolve child names to **already-created** agents. + - Construct `AgentsAsToolsAgent(config, context, agents=child_agents)`. + - Attach LLM. + - Else: create a normal `McpAgent` (as today). + +3. **UI / CLI** + - No CLI flags change. + - New behavior is activated simply by specifying `agents:` in the decorator/config. + +### 3.3. Minimal usage sample (for docs and examples) + +This sample is used as a reference for both local testing and future docs/README updates. +It mirrors the standalone script in the Strato workspace (`fast/agent-as-tools.py`). + +```python +import asyncio +from fast_agent import FastAgent + +fast = FastAgent("Agents-as-Tools demo") + + +@fast.agent( + name="NY-Time", + instruction="Return current time in New York.", + servers=["tm"], # MCP server 'tm' configured in fastagent.config.yaml + model="gpt-5-mini", + tools={"tm": ["get_current_time"]}, +) +@fast.agent( + name="London-Time", + instruction="Return current time in London.", + servers=["tm"], + model="gpt-5-mini", + tools={"tm": ["get_current_time"]}, +) +@fast.agent( + name="time-orchestrator", + instruction="Get current time in New York and London.", + model="gpt-5-mini", + default=True, + agents=[ + "NY-Time", + "London-Time", + ], +) +async def main() -> None: + async with fast.run() as agent: + result = await agent("get time for NY and London") + print(result) + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +Key points: + +- `NY-Time` and `London-Time` are normal MCP-enabled agents using the `tm` server. +- `time-orchestrator` is a BASIC agent with `agents=[...]`; the factory instantiates it + as an `AgentsAsToolsAgent` under the hood, exposing each child as a tool. +- From the LLM's perspective, it simply sees additional tools (`agent__NY-Time`, + `agent__London-Time`) alongside regular MCP tools. + +--- + +## 4. Detailed Design by Concern + +### 4.1. Tool exposure (`list_tools`) + +**Goal:** make each child agent a callable tool with a permissive schema. + +- Tool naming: + - `tool_name = f"agent__{child.name}"`. + - We store the mapping internally, not relying on `child.name` string matching later. + +- Input schema: + - Keep it minimal and robust: + + ```json + { + "type": "object", + "properties": { + "text": { "type": "string", "description": "Plain text input" }, + "json": { "type": "object", "description": "Arbitrary JSON payload" } + }, + "additionalProperties": true + } + ``` + +- Implementation sketch: + - For each child in `self._child_agents`: + - Build an `mcp.Tool`: + - `name = tool_name` + - `description = child.instruction` + - `inputSchema = schema_above`. + +- In the current implementation these child tools are **merged** with the MCP tools exposed by `McpAgent.list_tools()`: `AgentsAsToolsAgent.list_tools()` returns a single combined surface (MCP tools + `agent__Child` tools), adding child tools only when their names do not conflict with existing MCP/local tool names. + +### 4.2. Argument mapping (`call_tool`) + +**Goal:** map tool arguments to a single child **user message**. + +Rules: + +- If `arguments["text"]` is a string → use as-is. +- Else if `"json" in arguments`: + - If it is a dict → `json.dumps` (UTF-8, no ASCII-escaping). + - Else → `str(...)`. +- Else: + - If there are other arguments → `json.dumps(arguments)`. + - Else → empty string. + +Then: + +- Build `PromptMessageExtended.user(input_text)` (or `Prompt.user` helper) and call: + - `child.generate([user_message], request_params=None)`. + +Error handling: + +- Unknown tool name → `CallToolResult(isError=True, content=["Unknown agent-tool: {name}"])`. +- Unhandled exception in child → `CallToolResult(isError=True, content=["Error: {e}"])`. + +Wire error-channel content: + +- If childs response has `channels[FAST_AGENT_ERROR_CHANNEL]`, append those blocks to `CallToolResult.content` and set `isError=True`. + +### 4.3. Display behavior for children + +**Requirement:** when a child is used as a tool: + +- Do **not** show its normal assistant chat blocks. +- Do show its **tool usage** (MCP tools, shell, etc.). + +Design: + +- Define a tiny utility in `AgentsAsToolsAgent`: + + - `self._display_suppression: dict[int, DisplayState]` where `DisplayState` holds: + - `original_config: ConsoleDisplayConfig`. + - `ref_count: int`. + +- Methods: + + - `_suppress_child_display(child: LlmAgent)` + - On first entry for this child: + - Copy `child.display.config` → `original_config`. + - Clone config and set `logger.show_chat = False`, `logger.show_tools = True`. + - Assign cloned config to `child.display.config`. + - Increment `ref_count`. + + - `_release_child_display(child: LlmAgent)` + - Decrement `ref_count`. + - If it reaches 0: + - Restore `child.display.config = original_config`. + - Remove entry from `_display_suppression`. + +- Use these methods in `call_tool()` via `try/finally`. + +Rationale: children can still be run standalone (outside Agents-as-Tools) with full chat visible; we only alter display while they are acting as tools. + +### 4.4. Parallel `run_tools` semantics + +**Goal:** replace `ToolAgent.run_tools` with a parallel implementation that preserves its contract but allows: + +- multiple tool calls per LLM turn; +- concurrent execution via `asyncio.gather`; +- clear UI for each per-call instance. + +#### 4.4.1. Data structures + +- `call_descriptors: list[dict]`: + - `{"id", "tool", "args", "status", "error_message"?}`. + +- `descriptor_by_id: dict[correlation_id -> descriptor]`. +- `tasks: list[Task[CallToolResult]]`. +- `ids_in_order: list[str]` for stable correlation. + +#### 4.4.2. Algorithm (current implementation) + +1. **Validate tool calls** + - Snapshot `available_tools` from `list_tools()`. + - For each `request.tool_calls[correlation_id]`: + - If name not in `available_tools` → create `CallToolResult(isError=True, ...)`, mark descriptor as `status="error"`, skip task. + - Else → `status="pending"`, add to `ids_in_order`. + +2. **Create detached instances and names** + + - For each `correlation_id` in `ids_in_order` assign `instance_index = 1..N`. + - Resolve the template child from `_child_agents`. + - Compute `base_name = child.name` and `instance_name = f"{base_name}[{instance_index}]"`. + - Use `instance_name` consistently for: + - the detached clone (`spawn_detached_instance(name=instance_name)`), + - progress events (`agent_name=instance_name`, `target=instance_name`), + - chat/tool headers (`tool_name[instance_index]`). + +3. **Instance execution wrapper** + + Conceptually (simplified): + + ```python + async def call_with_instance_name(tool_name, tool_args, instance_index) -> CallToolResult: + child = resolve_template_child(tool_name) + base_name = child.name + instance_name = f"{base_name}[{instance_index}]" + + clone = await child.spawn_detached_instance(name=instance_name) + + progress_display.update(ProgressEvent( + action=ProgressAction.CHATTING, + target=instance_name, + agent_name=instance_name, + )) + + try: + # Handles argument → text mapping, display suppression, error channel, etc. + return await self._invoke_child_agent(clone, tool_args) + finally: + await clone.shutdown() + child.merge_usage_from(clone) + progress_display.update(ProgressEvent( + action=ProgressAction.FINISHED, + target=instance_name, + agent_name=instance_name, + details="Completed", + )) + ``` + + - All interaction with the Rich progress panel goes through `ProgressEvent` objects and the shared `progress_display.update(...)` API. + - `RichProgressDisplay.update` is responsible for marking `FINISHED` lines complete without hiding other tasks. + +4. **Parallel execution and UI** + + - For each `correlation_id` with a valid tool call, create a task: + + ```python + tasks.append(asyncio.create_task( + call_with_instance_name(tool_name, tool_args, instance_index=i) + )) + ``` + + - `_show_parallel_tool_calls(call_descriptors)` and `_show_parallel_tool_results(ordered_records)` use `tool_name[i]` labels in the chat panels and bottom status items, but do not touch `RichProgressDisplay` internals. + - `results = await asyncio.gather(*tasks, return_exceptions=True)` collects all results and maps them back to `correlation_id` in input order. + +5. **Finalize** + +- Build ordered `records = [{"descriptor": ..., "result": ...}, ...]` in input order. +- Call `_show_parallel_tool_results(records)`. +- Return `self._finalize_tool_results(tool_results, tool_loop_error)` for consistency with `ToolAgent`. + +6. **Mixed MCP + agent-tools batches** + +- If `request.tool_calls` contains both child-agent tools and regular MCP tools: + - Split `tool_calls` into two subsets: child-agent calls and remaining MCP/local tools. + - Run child-agent calls via the parallel `call_with_instance_name(...)` path described above. + - Delegate the remaining tools to the base `McpAgent.run_tools()` implementation. + - Merge `tool_results` and error text from both branches (using the `FAST_AGENT_ERROR_CHANNEL` error channel) into a single `PromptMessageExtended`. + +### 4.5. Stats and history integration + +- Leave `UsageAccumulator` unchanged. +- Parent and each child agent track their own usage normally. + - In the detached-clone implementation, each clone accrues usage on its own accumulator and then merges it back into the template child. +- History: + - `PromptMessageExtended.tool_results` remains a flat mapping by correlation id. + - `history_display` will show: + - `tool→` and `result→` sections per tool call. + - We can optionally prepend `tool_name[i]` into either: + - the preview text, or + - a dedicated text block in the tool result content. + +No new data model types are needed for stats. + +--- + +## 5. Engineering notes + +In the current implementation `AgentsAsToolsAgent` combines both: + +- core runtime concerns (tool mapping, argument normalization, `run_tools` orchestration), and +- UI wiring (progress events and chat/tool panels). + +This keeps the surface area small and matches the needs of the CLI UI. A future refactor could still extract a pure runtime helper and a separate UI adapter (see §7), but that split is **not** required for the feature to work today. + +--- + +## 6. Implementation Phasing + +### Phase 0 — Skeleton + +- Add `AgentsAsToolsAgent` class with: + - Constructor storing children. + - Basic `list_tools()` and `call_tool()` (no parallelism, no UI tweaks). +- Wire into `direct_factory` for BASIC agents with `child_agents`. +- Provide a minimal example in `examples/` using synchronous tool calls. + +### Phase 1 — Parallel execution + +- Implement `run_tools()` with `asyncio.gather` but **no special UI**: + - Just run calls concurrently and aggregate results. + - Keep the behavior as close as possible to `ToolAgent.run_tools`. + +- Add tests: + - Unit tests for argument mapping and error handling. + - Concurrency tests with fake children that sleep. + +### Phase 2 — UI integration (progress + instance naming) + +- Introduce `AgentsAsToolsDisplayAdapter` to centralize Agents-as-Tools-specific progress behavior. +- Implement instance naming and FINISHED-based progress lines so instances remain visible after completion. +- Suppress child chat via ref-counted display config changes. + +- Manual QA: + - Validate panel behavior with 1, 2, N parallel tasks. + - Validate that parent name & child names are restored. + +### Phase 3 — Documentation & ergonomics + +- Add docs page / section (for example, a `README.md` subsection + "Agents-as-Tools (child agents as tools)"): + - Concept explanation. + - Minimal Python example from §3.3 (NY/London time orchestrator). + - Comparison with Orchestrator / IterativePlanner / Parallel workflows. + +- Keep the code sample in sync with the shipped example script + (currently `fast/agent-as-tools.py` in the Strato workspace, upstream + examples path TBD). + +- Add clear notes about: + - Stats aggregation semantics. + - Reuse of MCP connections. + - Limitations (e.g. no per-instance stats rows). + +--- + +## 7. Potential Future Extensions + +The current implementation is intentionally minimal. The items below are still **future** additions (not implemented as of Nov 2025). + +1. **Recursive Agents-as-Tools** + + - Explicitly document and test scenarios where children are themselves `AgentsAsToolsAgent` instances. + - Ensure nested tool calls remain readable in progress and history views. + +2. **Correlation-friendly logging** + + - Standardize structured log fields for tools (`agent_name`, `instance_name`, `correlation_id`, `tool_name`). + - Make `history_display` able to group tool rows per `(correlation_id, instance)` so parallel runs are easier to inspect. + +## 8. Summary + +This from-scratch plan defines Agents-as-Tools as a **lightweight adapter agent** that: + +- Exposes existing agents as tools. +- Delegates execution to them, preserving their MCP connections and stats. +- Adds a small, well-encapsulated UI layer for: + - Parallel instance progress lines. + - Clear tool call/result labeling (`agent__Child[i]`). + +By keeping a strict separation between core runtime, UI adapter, and factories, the feature remains understandable and testable, and it aligns with fast-agents existing engineering patterns and philosophy. diff --git a/examples/workflows/agents_as_tools_extended.py b/examples/workflows/agents_as_tools_extended.py new file mode 100644 index 000000000..d16ba74c4 --- /dev/null +++ b/examples/workflows/agents_as_tools_extended.py @@ -0,0 +1,73 @@ +"""Agents-as-Tools example: project managers for NY and London. + +Parent agent ("PMO-orchestrator") calls two child agents +("NY-Project-Manager" and "London-Project-Manager") as tools. Each child uses +the ``time`` MCP server for local time and the ``fetch`` MCP server for a short +news-based update on the given topics. + +Defaults: clones fork parent history (no merge-back), no timeout, no parallel cap, +and collapses progress display after the first 20 instances. +To change behavior, pass decorator args such as +`history_mode=HistoryMode.FORK_AND_MERGE`, `child_timeout_sec=600`, +`max_parallel=8`, `max_display_instances=10` +(HistoryMode import: fast_agent.agents.workflow.agents_as_tools_agent). +""" + +import asyncio + +from fast_agent import FastAgent + +# Create the application +fast = FastAgent("Agents-as-Tools demo") + + +@fast.agent( + name="NY-Project-Manager", + instruction=( + "You are a New York project manager. For each given topic, get the " + "current local time in New York and a brief, project-relevant news " + "summary using the 'time' and 'fetch' MCP servers. If a source returns " + "HTTP 403 or is blocked by robots.txt, try up to five alternative " + "public sources before giving up and clearly state any remaining " + "access limits. Hint: Fast-Agent site: https://fast-agent.ai" + ), + servers=[ + "time", + "fetch", + ], # MCP servers 'time' and 'fetch' configured in fastagent.config.yaml +) +@fast.agent( + name="London-Project-Manager", + instruction=( + "You are a London project manager. For each given topic, get the " + "current local time in London and a brief, project-relevant news " + "summary using the 'time' and 'fetch' MCP servers. If a source returns " + "HTTP 403 or is blocked by robots.txt, try up to five alternative " + "public sources before giving up and clearly state any remaining " + "access limits. Hint: BBC: https://www.bbc.com/ and FT: https://www.ft.com/" + ), + servers=["time", "fetch"], +) +@fast.agent( + name="PMO-orchestrator", + instruction=( + "Get project updates from the New York and London project managers. " + "Ask NY-Project-Manager three times about different projects: Anthropic, " + "evalstate/fast-agent, and OpenAI, and London-Project-Manager for economics review. " + "Return a brief, concise combined summary with clear city/time/topic labels." + ), + default=True, + agents=[ + "NY-Project-Manager", + "London-Project-Manager", + ], # children are exposed as tools: agent__NY-Project-Manager, agent__London-Project-Manager + # optional: history_mode="fork_and_merge", child_timeout_sec=600, max_parallel=8, max_display_instances=10 +) +async def main() -> None: + async with fast.run() as agent: + result = await agent("pls send me daily review.") + print(result) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/workflows/agents_as_tools_simple.py b/examples/workflows/agents_as_tools_simple.py new file mode 100644 index 000000000..b8ea9e417 --- /dev/null +++ b/examples/workflows/agents_as_tools_simple.py @@ -0,0 +1,50 @@ +"""Simple Agents-as-Tools PMO example. + +Parent agent ("PMO-orchestrator") calls two child agents ("NY-Project-Manager" +and "London-Project-Manager") as tools. Each child uses the ``time`` MCP +server to include local time in a brief report. + +Defaults: clones fork parent history (no merge-back), no timeout, no parallel cap, +and collapses progress display after the first 20 instances. +If you want merge-back or other limits, pass decorator args: +`history_mode=HistoryMode.FORK_AND_MERGE`, `child_timeout_sec=600`, +`max_parallel=8`, `max_display_instances=10` +(HistoryMode import: fast_agent.agents.workflow.agents_as_tools_agent). +""" + +import asyncio + +from fast_agent import FastAgent + +fast = FastAgent("Agents-as-Tools simple demo") + + +@fast.agent( + name="NY-Project-Manager", + instruction="Return current time and project status.", + servers=["time"], # MCP server 'time' configured in fastagent.config.yaml +) +@fast.agent( + name="London-Project-Manager", + instruction="Return current time and news.", + servers=["time"], +) +@fast.agent( + name="PMO-orchestrator", + instruction="Get reports. Separate call per topic. NY: {OpenAI, Fast-Agent, Anthropic}, London: Economics", + default=True, + agents=[ + "NY-Project-Manager", + "London-Project-Manager", + ], # children are exposed as tools: agent__NY-Project-Manager, agent__London-Project-Manager + # optional: history_mode="fork_and_merge", child_timeout_sec=600, max_parallel=8, max_display_instances=10 +) +async def main() -> None: + async with fast.run() as agent: + result = await agent("Get PMO report") + await agent.interactive() + print(result) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/workflows/fastagent.config.yaml b/examples/workflows/fastagent.config.yaml index 714ef9c6c..c73a5f2e6 100644 --- a/examples/workflows/fastagent.config.yaml +++ b/examples/workflows/fastagent.config.yaml @@ -21,3 +21,6 @@ mcp: fetch: command: "uvx" args: ["mcp-server-fetch"] + time: + command: "uvx" + args: ["mcp-server-time"] diff --git a/src/fast_agent/agents/llm_decorator.py b/src/fast_agent/agents/llm_decorator.py index 7ea63f67b..4d4cad1b6 100644 --- a/src/fast_agent/agents/llm_decorator.py +++ b/src/fast_agent/agents/llm_decorator.py @@ -4,6 +4,7 @@ import json from collections import Counter, defaultdict +from copy import deepcopy from dataclasses import dataclass from typing import ( TYPE_CHECKING, @@ -19,6 +20,8 @@ if TYPE_CHECKING: from rich.text import Text + from fast_agent.agents.llm_agent import LlmAgent + from a2a.types import AgentCard from mcp import ListToolsResult, Tool from mcp.types import ( @@ -187,6 +190,8 @@ def __init__( # Initialize the LLM to None (will be set by attach_llm) self._llm: FastAgentLLMProtocol | None = None self._initialized = False + self._llm_factory_ref: LLMFactoryProtocol | None = None + self._llm_attach_kwargs: dict[str, Any] | None = None @property def context(self) -> Context | None: @@ -257,8 +262,71 @@ async def attach_llm( agent=self, request_params=effective_params, context=self._context, **additional_kwargs ) + # Store attachment details for future cloning + self._llm_factory_ref = llm_factory + attach_kwargs: dict[str, Any] = dict(additional_kwargs) + attach_kwargs["request_params"] = deepcopy(effective_params) + self._llm_attach_kwargs = attach_kwargs + return self._llm + def _clone_constructor_kwargs(self) -> dict[str, Any]: + """Hook for subclasses/mixins to supply constructor kwargs when cloning.""" + return {} + + async def spawn_detached_instance(self, *, name: str | None = None) -> "LlmAgent": + """Create a fresh agent instance with its own MCP/LLM stack.""" + + new_config = deepcopy(self.config) + if name: + new_config.name = name + + constructor_kwargs = self._clone_constructor_kwargs() + clone = type(self)(config=new_config, context=self.context, **constructor_kwargs) + await clone.initialize() + + if self._llm_factory_ref is not None: + if self._llm_attach_kwargs is None: + raise RuntimeError( + "LLM attachment parameters missing despite factory being available" + ) + + attach_kwargs = dict(self._llm_attach_kwargs) + request_params = attach_kwargs.pop("request_params", None) + if request_params is not None: + request_params = deepcopy(request_params) + + await clone.attach_llm( + self._llm_factory_ref, + request_params=request_params, + **attach_kwargs, + ) + + return clone + + def merge_usage_from(self, other: "LlmAgent") -> None: + """Merge LLM usage metrics from another agent instance into this one.""" + + if not hasattr(self, "_llm") or not hasattr(other, "_llm"): + return + + source_llm = getattr(other, "_llm", None) + target_llm = getattr(self, "_llm", None) + if not source_llm or not target_llm: + return + + source_usage = getattr(source_llm, "usage_accumulator", None) + target_usage = getattr(target_llm, "usage_accumulator", None) + if not source_usage or not target_usage: + return + + for turn in source_usage.turns: + try: + target_usage.add_turn(turn.model_copy(deep=True)) + except AttributeError: + # Fallback if turn doesn't provide model_copy + target_usage.add_turn(turn) + async def __call__( self, message: Union[ @@ -915,6 +983,22 @@ def _template_prefix_messages(self) -> list[PromptMessageExtended]: break return prefix + def load_message_history(self, messages: list[PromptMessageExtended] | None) -> None: + """Replace message history with a deep copy of supplied messages (or empty list).""" + msgs = messages or [] + self._message_history = [ + msg.model_copy(deep=True) if hasattr(msg, "model_copy") else msg for msg in msgs + ] + + def append_history(self, messages: list[PromptMessageExtended] | None) -> None: + """Append messages to history as deep copies.""" + if not messages: + return + for msg in messages: + self._message_history.append( + msg.model_copy(deep=True) if hasattr(msg, "model_copy") else msg + ) + def pop_last_message(self) -> PromptMessageExtended | None: """Remove and return the most recent message from the conversation history.""" if self.llm: diff --git a/src/fast_agent/agents/workflow/agents_as_tools_agent.py b/src/fast_agent/agents/workflow/agents_as_tools_agent.py new file mode 100644 index 000000000..5f4d23a7c --- /dev/null +++ b/src/fast_agent/agents/workflow/agents_as_tools_agent.py @@ -0,0 +1,843 @@ +""" +Agents as Tools Pattern Implementation +======================================= + +Overview +-------- +This module implements the "Agents as Tools" pattern, inspired by OpenAI's Agents SDK +(https://openai.github.io/openai-agents-python/tools). It allows child agents to be +exposed as callable tools to a parent agent, enabling hierarchical agent composition +without the complexity of traditional orchestrator patterns. The current implementation +goes a step further by spawning **detached per-call clones** of every child so that each +parallel execution has its own LLM + MCP stack, eliminating name overrides and shared +state hacks. + +Rationale +--------- +Traditional approaches to multi-agent systems often require: +1. Complex orchestration logic with explicit routing rules +2. Iterative planning mechanisms that add cognitive overhead +3. Tight coupling between parent and child agent implementations + +The "Agents as Tools" pattern simplifies this by: +- **Treating agents as first-class tools**: Each child agent becomes a tool that the + parent LLM can call naturally via function calling +- **Delegation, not orchestration**: The parent LLM decides which child agents to invoke + based on its instruction and context, without hardcoded routing logic +- **Parallel execution**: Multiple child agents can run concurrently when the LLM makes + parallel tool calls +- **Clean abstraction**: Child agents expose minimal schemas (text or JSON input), + making them universally composable + +Benefits over iterative_planner/orchestrator: +- Simpler codebase: No custom planning loops or routing tables +- Better LLM utilization: Modern LLMs excel at function calling +- Natural composition: Agents nest cleanly without special handling +- Parallel by default: Leverage asyncio.gather for concurrent execution + +Algorithm +--------- +1. **Initialization** + - `AgentsAsToolsAgent` is itself an `McpAgent` (with its own MCP servers + tools) and receives a list of **child agents**. + - Each child agent is mapped to a synthetic tool name: `agent__{child_name}`. + - Child tool schemas advertise text/json input capabilities. + +2. **Tool Discovery (list_tools)** + - `list_tools()` starts from the base `McpAgent.list_tools()` (MCP + local tools). + - Synthetic child tools `agent__ChildName` are added on top when their names do not collide with existing tools. + - The parent LLM therefore sees a **merged surface**: MCP tools and agent-tools in a single list. + +3. **Tool Execution (call_tool)** + - If the requested tool name resolves to a child agent (either `child_name` or `agent__child_name`): + - Convert tool arguments (text or JSON) to a child user message. + - Execute via detached clones created inside `run_tools` (see below). + - Responses are converted to `CallToolResult` objects (errors propagate as `isError=True`). + - Otherwise, delegate to the base `McpAgent.call_tool` implementation (MCP tools, shell, human-input, etc.). + +4. **Parallel Execution (run_tools)** + - Collect all tool calls from the parent LLM response. + - Partition them into **child-agent tools** and **regular MCP/local tools**. + - Child-agent tools are executed in parallel: + - For each child tool call, spawn a detached clone with its own LLM + MCP aggregator and suffixed name. + - Emit `ProgressAction.CHATTING` / `ProgressAction.FINISHED` events for each instance and keep parent status untouched. + - Merge each clone's usage back into the template child after shutdown. + - Remaining MCP/local tools are delegated to `McpAgent.run_tools()`. + - Child and MCP results (and their error text from `FAST_AGENT_ERROR_CHANNEL`) are merged into a single `PromptMessageExtended` that is returned to the parent LLM. + +Progress Panel Behavior +----------------------- +To provide clear visibility into parallel executions, the progress panel (left status +table) undergoes dynamic updates: + +**Before parallel execution:** +``` +▎▶ Chatting ▎ PM-1-DayStatusSummarizer gpt-5 turn 1 +``` + +**During parallel execution (2+ instances):** +- Parent line stays in whatever lifecycle state it already had; no forced "Ready" flips. +- New lines appear for each detached instance with suffixed names: +``` +▎▶ Chatting ▎ PM-1-DayStatusSummarizer[1] gpt-5 turn 2 +▎▶ Calling tool ▎ PM-1-DayStatusSummarizer[2] tg-ro (list_messages) +``` + +**Key implementation details:** +- Each clone advertises its own `agent_name` (e.g., `OriginalName[instance_number]`). +- MCP progress events originate from the clone's aggregator, so tool activity always shows under the suffixed name. +- Parent status lines remain visible for context while children run. + +**As each instance completes:** +- We emit `ProgressAction.FINISHED` with elapsed time, keeping the line in the panel for auditability. +- Other instances continue showing their independent progress until they also finish. + +**After all parallel executions complete:** +- Finished instance lines remain until the parent agent moves on, giving a full record of what ran. +- Parent and child template names stay untouched because clones carry the suffixed identity. + +- **Instance line visibility**: We now leave finished instance lines visible (marked `FINISHED`) + instead of hiding them immediately, preserving a full audit trail of parallel runs. +- **Chat log separation**: Each parallel instance gets its own tool request/result headers + with instance numbers [1], [2], etc. for traceability. + +Stats and Usage Semantics +------------------------- +- Each detached clone accrues usage on its own `UsageAccumulator`; after shutdown we + call `child.merge_usage_from(clone)` so template agents retain consolidated totals. +- Runtime events (logs, MCP progress, chat headers) use the suffixed clone names, + ensuring per-instance traceability even though usage rolls up to the template. +- The CLI *Usage Summary* table still reports one row per template agent + (for example, `PM-1-DayStatusSummarizer`), not per `[i]` instance; clones are + runtime-only and do not appear as separate agents in that table. + +**Chat log display:** +Tool headers show instance numbers for clarity: +``` +▎▶ orchestrator [tool request - agent__PM-1-DayStatusSummarizer[1]] +▎◀ orchestrator [tool result - agent__PM-1-DayStatusSummarizer[1]] +▎▶ orchestrator [tool request - agent__PM-1-DayStatusSummarizer[2]] +▎◀ orchestrator [tool result - agent__PM-1-DayStatusSummarizer[2]] +``` + +Bottom status bar shows all instances: +``` +| agent__PM-1-DayStatusSummarizer[1] · running | agent__PM-1-DayStatusSummarizer[2] · running | +``` + +Implementation Notes +-------------------- +- **Instance naming**: `run_tools` computes `instance_name = f"{child.name}[i]"` inside the + per-call wrapper and passes it into `spawn_detached_instance`, so the template child object + keeps its original name while each detached clone owns the suffixed identity. +- **Progress event routing**: Because each clone's `MCPAggregator` is constructed with the + suffixed `agent_name`, all MCP/tool progress events naturally use + `PM-1-DayStatusSummarizer[i]` without mutating base agent fields or using `ContextVar` hacks. +- **Display suppression with reference counting**: Multiple parallel instances of the same + child agent share a single agent object. Use reference counting to track active instances: + - `_display_suppression_count[child_id]`: Count of active parallel instances + - `_original_display_configs[child_id]`: Stored original config + - Only modify display config when first instance starts (count 0→1) + - Only restore display config when last instance completes (count 1→0) + - Prevents race condition where early-finishing instances restore config while others run +- **Child agent(s)** + - Existing agents (typically `McpAgent`-based) with their own MCP servers, skills, tools, etc. + - Serve as **templates**; `run_tools` now clones them before every tool call via + `spawn_detached_instance`, so runtime work happens inside short-lived replicas. + +- **Detached instances** + - Each tool call gets an actual cloned agent with suffixed name `Child[i]`. + - Clones own their MCP aggregator/LLM stacks and merge usage back into the template after shutdown. +- **Chat log separation**: Each parallel instance gets its own tool request/result headers + with instance numbers [1], [2], etc. for traceability + +Usage Example +------------- +```python +from fast_agent import FastAgent + +fast = FastAgent("parent") + +# Define child agents +@fast.agent(name="researcher", instruction="Research topics") +async def researcher(): pass + +@fast.agent(name="writer", instruction="Write content") +async def writer(): pass + +# Define parent with agents-as-tools +@fast.agent( + name="coordinator", + instruction="Coordinate research and writing", + agents=["researcher", "writer"], # Exposes children as tools +) +async def coordinator(): pass +``` + +The parent LLM can now naturally call researcher and writer as tools. + +References +---------- +- Design doc: ``agetns_as_tools_plan_scratch.md`` (repo root). +- Docs: [`evalstate/fast-agent-docs`](https://github.com/evalstate/fast-agent-docs) (Agents-as-Tools section). +- OpenAI Agents SDK: +- GitHub Issue: [#458](https://github.com/evalstate/fast-agent/issues/458) +""" + +from __future__ import annotations + +import asyncio +import json +from contextlib import contextmanager, nullcontext +from copy import copy +from dataclasses import dataclass +from enum import Enum +from typing import TYPE_CHECKING, Any + +from mcp import ListToolsResult, Tool +from mcp.types import CallToolResult + +from fast_agent.agents.mcp_agent import McpAgent +from fast_agent.constants import FAST_AGENT_ERROR_CHANNEL +from fast_agent.core.logging.logger import get_logger +from fast_agent.core.prompt import Prompt +from fast_agent.mcp.helpers.content_helpers import get_text, text_content +from fast_agent.types import PromptMessageExtended + +if TYPE_CHECKING: + from fast_agent.agents.agent_types import AgentConfig + from fast_agent.agents.llm_agent import LlmAgent + +logger = get_logger(__name__) + + +class HistoryMode(str, Enum): + """History handling for detached child instances.""" + + SCRATCH = "scratch" + FORK = "fork" + FORK_AND_MERGE = "fork_and_merge" + + @classmethod + def from_input(cls, value: Any | None) -> HistoryMode: + if value is None: + return cls.FORK + if isinstance(value, cls): + return value + try: + return cls(str(value)) + except Exception: + return cls.FORK + + +@dataclass(kw_only=True) +class AgentsAsToolsOptions: + """Configuration knobs for the Agents-as-Tools wrapper. + + Defaults: + - history_mode: fork child history (no merge back) + - max_parallel: None (no cap; caller may set an explicit limit) + - child_timeout_sec: None (no per-child timeout) + - max_display_instances: 20 (show first N lines, collapse the rest) + """ + + history_mode: HistoryMode = HistoryMode.FORK + max_parallel: int | None = None + child_timeout_sec: int | None = None + max_display_instances: int = 20 + + def __post_init__(self) -> None: + self.history_mode = HistoryMode.from_input(self.history_mode) + if self.max_parallel is not None and self.max_parallel <= 0: + raise ValueError("max_parallel must be > 0 when set") + if self.max_display_instances is not None and self.max_display_instances <= 0: + raise ValueError("max_display_instances must be > 0") + if self.child_timeout_sec is not None and self.child_timeout_sec <= 0: + raise ValueError("child_timeout_sec must be > 0 when set") + + +class AgentsAsToolsAgent(McpAgent): + """MCP-enabled agent that exposes child agents as additional tools. + + This hybrid agent: + + - Inherits all MCP behavior from :class:`McpAgent` (servers, MCP tool discovery, local tools). + - Exposes each child agent as an additional synthetic tool (`agent__ChildName`). + - Merges **MCP tools** and **agent-tools** into a single `list_tools()` surface. + - Routes `call_tool()` to child agents when the name matches a child, otherwise delegates + to the base `McpAgent.call_tool` implementation. + - Overrides `run_tools()` to fan out child-agent tools in parallel using detached clones, + while delegating any remaining MCP/local tools to the base `McpAgent.run_tools` and + merging all results into a single tool-loop response. + """ + + def __init__( + self, + config: AgentConfig, + agents: list[LlmAgent], + options: AgentsAsToolsOptions | None = None, + context: Any | None = None, + **kwargs: Any, + ) -> None: + """Initialize AgentsAsToolsAgent. + + Args: + config: Agent configuration for this parent agent (including MCP servers/tools) + agents: List of child agents to expose as tools + context: Optional context for agent execution + **kwargs: Additional arguments passed through to :class:`McpAgent` and its bases + """ + super().__init__(config=config, context=context, **kwargs) + self._options = options or AgentsAsToolsOptions() + self._child_agents: dict[str, LlmAgent] = {} + self._history_merge_locks: dict[int, asyncio.Lock] = {} + self._display_suppression_count: dict[int, int] = {} + self._original_display_configs: dict[int, Any] = {} + + for child in agents: + tool_name = self._make_tool_name(child.name) + if tool_name in self._child_agents: + logger.warning( + f"Duplicate tool name '{tool_name}' for child agent '{child.name}', overwriting" + ) + self._child_agents[tool_name] = child + + def _make_tool_name(self, child_name: str) -> str: + """Generate a tool name for a child agent. + + Args: + child_name: Name of the child agent + + Returns: + Prefixed tool name to avoid collisions with MCP tools + """ + return f"agent__{child_name}" + + async def initialize(self) -> None: + """Initialize this agent and all child agents.""" + await super().initialize() + for agent in self._child_agents.values(): + if not getattr(agent, "initialized", False): + await agent.initialize() + + async def shutdown(self) -> None: + """Shutdown this agent and all child agents.""" + await super().shutdown() + for agent in self._child_agents.values(): + try: + await agent.shutdown() + except Exception as e: + logger.warning(f"Error shutting down child agent {agent.name}: {e}") + + async def list_tools(self) -> ListToolsResult: + """List MCP tools plus child agents exposed as tools.""" + + base = await super().list_tools() + tools = list(base.tools) + existing_names = {tool.name for tool in tools} + + for tool_name, agent in self._child_agents.items(): + if tool_name in existing_names: + continue + + input_schema: dict[str, Any] = { + "type": "object", + "properties": { + "text": {"type": "string", "description": "Plain text input"}, + "json": {"type": "object", "description": "Arbitrary JSON payload"}, + }, + "additionalProperties": True, + } + tools.append( + Tool( + name=tool_name, + description=agent.instruction, + inputSchema=input_schema, + ) + ) + existing_names.add(tool_name) + + return ListToolsResult(tools=tools) + + @contextmanager + def _child_display_suppressed(self, child: LlmAgent): + """Context manager to hide child chat while keeping tool logs visible.""" + child_id = id(child) + count = self._display_suppression_count.get(child_id, 0) + if count == 0: + if ( + hasattr(child, "display") + and child.display + and getattr(child.display, "config", None) + ): + self._original_display_configs[child_id] = child.display.config + temp_config = copy(child.display.config) + if hasattr(temp_config, "logger"): + temp_logger = copy(temp_config.logger) + temp_logger.show_chat = False + temp_logger.show_tools = True + temp_config.logger = temp_logger + child.display.config = temp_config + self._display_suppression_count[child_id] = count + 1 + try: + yield + finally: + self._display_suppression_count[child_id] -= 1 + if self._display_suppression_count[child_id] <= 0: + del self._display_suppression_count[child_id] + original_config = self._original_display_configs.pop(child_id, None) + if original_config is not None and hasattr(child, "display") and child.display: + child.display.config = original_config + + async def _merge_child_history( + self, target: LlmAgent, clone: LlmAgent, start_index: int + ) -> None: + """Append clone history from start_index into target with per-target lock.""" + lock = self._history_merge_locks.setdefault(id(target), asyncio.Lock()) + async with lock: + new_messages = clone.message_history[start_index:] + target.append_history(new_messages) + # Cleanup to avoid unbounded lock map growth + if not lock.locked(): + self._history_merge_locks.pop(id(target), None) + + async def _invoke_child_agent( + self, + child: LlmAgent, + arguments: dict[str, Any] | None = None, + *, + suppress_display: bool = True, + ) -> CallToolResult: + """Shared helper to execute a child agent with standard serialization and display rules.""" + + args = arguments or {} + # Serialize arguments to text input + if isinstance(args.get("text"), str): + input_text = args["text"] + elif "json" in args: + input_text = ( + json.dumps(args["json"], ensure_ascii=False) + if isinstance(args["json"], dict) + else str(args["json"]) + ) + else: + input_text = json.dumps(args, ensure_ascii=False) if args else "" + + child_request = Prompt.user(input_text) + + try: + with self._child_display_suppressed(child) if suppress_display else nullcontext(): + response: PromptMessageExtended = await child.generate([child_request], None) + content_blocks = list(response.content or []) + + error_blocks = None + if response.channels and FAST_AGENT_ERROR_CHANNEL in response.channels: + error_blocks = response.channels.get(FAST_AGENT_ERROR_CHANNEL) or [] + if error_blocks: + content_blocks.extend(error_blocks) + + return CallToolResult( + content=content_blocks, + isError=bool(error_blocks), + ) + except Exception as e: + logger.error(f"Child agent {child.name} failed: {e}") + return CallToolResult(content=[text_content(f"Error: {e}")], isError=True) + + def _resolve_child_agent(self, name: str) -> LlmAgent | None: + return self._child_agents.get(name) or self._child_agents.get(self._make_tool_name(name)) + + async def call_tool( + self, + name: str, + arguments: dict[str, Any] | None = None, + tool_use_id: str | None = None, + ) -> CallToolResult: + """Route tool execution to child agents first, then MCP/local tools. + + The signature matches :meth:`McpAgent.call_tool` so that upstream tooling + can safely pass the LLM's ``tool_use_id`` as a positional argument. + """ + + child = self._resolve_child_agent(name) + if child is not None: + # Child agents don't currently use tool_use_id, they operate via + # a plain PromptMessageExtended tool call. + return await self._invoke_child_agent(child, arguments) + + return await super().call_tool(name, arguments, tool_use_id) + + def _show_parallel_tool_calls(self, descriptors: list[dict[str, Any]]) -> None: + """Display tool call headers for parallel agent execution. + + Args: + descriptors: List of tool call descriptors with metadata + """ + if not descriptors: + return + + status_labels = { + "pending": "running", + "error": "error", + "missing": "missing", + } + + total = len(descriptors) + limit = self._options.max_display_instances or total + + # Show detailed call information for each agent + for i, desc in enumerate(descriptors[:limit], 1): + tool_name = desc.get("tool", "(unknown)") + corr_id = desc.get("id") + args = desc.get("args", {}) + status = desc.get("status", "pending") + + if status == "error": + continue # Skip display for error tools, will show in results + + # Always add individual instance number for clarity + suffix = f"[{i}]" + if corr_id: + suffix = f"[{i}|{corr_id}]" + display_tool_name = f"{tool_name}{suffix}" + + # Build bottom item for THIS instance only (not all instances) + status_label = status_labels.get(status, "pending") + bottom_item = f"{display_tool_name} · {status_label}" + + # Show individual tool call with arguments + self.display.show_tool_call( + name=self.name, + tool_name=display_tool_name, + tool_args=args, + bottom_items=[bottom_item], # Only this instance's label + max_item_length=28, + metadata={"correlation_id": corr_id, "instance_name": display_tool_name}, + type_label="subagent", + ) + if total > limit: + collapsed = total - limit + label = f"[{limit + 1}..{total}]" + self.display.show_tool_call( + name=self.name, + tool_name=label, + tool_args={"collapsed": collapsed}, + bottom_items=[f"{label} · {collapsed} more"], + max_item_length=28, + type_label="subagent", + ) + + def _show_parallel_tool_results(self, records: list[dict[str, Any]]) -> None: + """Display tool result panels for parallel agent execution. + + Args: + records: List of result records with descriptor and result data + """ + if not records: + return + + total = len(records) + limit = self._options.max_display_instances or total + + # Show detailed result for each agent + for i, record in enumerate(records[:limit], 1): + descriptor = record.get("descriptor", {}) + result = record.get("result") + tool_name = descriptor.get("tool", "(unknown)") + corr_id = descriptor.get("id") + + if result: + # Always add individual instance number for clarity + suffix = f"[{i}]" + if corr_id: + suffix = f"[{i}|{corr_id}]" + display_tool_name = f"{tool_name}{suffix}" + + # Show individual tool result with full content + self.display.show_tool_result( + name=self.name, + tool_name=display_tool_name, + type_label="subagent response", + result=result, + ) + if total > limit: + collapsed = total - limit + label = f"[{limit + 1}..{total}]" + self.display.show_tool_result( + name=self.name, + tool_name=label, + type_label="subagent response", + result=CallToolResult( + content=[text_content(f"{collapsed} more results (collapsed)")], + isError=False, + ), + ) + + async def run_tools(self, request: PromptMessageExtended) -> PromptMessageExtended: + """Handle mixed MCP + agent-tool batches.""" + + if not request.tool_calls: + logger.warning("No tool calls found in request", data=request) + return PromptMessageExtended(role="user", tool_results={}) + + child_ids: list[str] = [] + for correlation_id, tool_request in request.tool_calls.items(): + if self._resolve_child_agent(tool_request.params.name): + child_ids.append(correlation_id) + + if not child_ids: + return await super().run_tools(request) + + child_results, child_error = await self._run_child_tools(request, set(child_ids)) + + if len(child_ids) == len(request.tool_calls): + return self._finalize_tool_results(child_results, tool_loop_error=child_error) + + # Execute remaining MCP/local tools via base implementation + remaining_ids = [cid for cid in request.tool_calls.keys() if cid not in child_ids] + mcp_request = PromptMessageExtended( + role=request.role, + content=request.content, + tool_calls={cid: request.tool_calls[cid] for cid in remaining_ids}, + ) + mcp_message = await super().run_tools(mcp_request) + mcp_results = mcp_message.tool_results or {} + mcp_error = self._extract_error_text(mcp_message) + + combined_results = {} + combined_results.update(child_results) + combined_results.update(mcp_results) + + tool_loop_error = child_error or mcp_error + return self._finalize_tool_results(combined_results, tool_loop_error=tool_loop_error) + + async def _run_child_tools( + self, + request: PromptMessageExtended, + target_ids: set[str], + ) -> tuple[dict[str, CallToolResult], str | None]: + """Run only the child-agent tool calls from the request.""" + + if not target_ids: + return {}, None + + tool_results: dict[str, CallToolResult] = {} + tool_loop_error: str | None = None + + try: + listed = await self.list_tools() + available_tools = {t.name for t in listed.tools} + except Exception as exc: + logger.warning(f"Failed to list tools before execution: {exc}") + available_tools = set(self._child_agents.keys()) + + call_descriptors: list[dict[str, Any]] = [] + descriptor_by_id: dict[str, dict[str, Any]] = {} + tasks: list[asyncio.Task] = [] + id_list: list[str] = [] + + for correlation_id, tool_request in request.tool_calls.items(): + if correlation_id not in target_ids: + continue + + tool_name = tool_request.params.name + tool_args = tool_request.params.arguments or {} + + descriptor = { + "id": correlation_id, + "tool": tool_name, + "args": tool_args, + } + call_descriptors.append(descriptor) + descriptor_by_id[correlation_id] = descriptor + + if ( + tool_name not in available_tools + and self._make_tool_name(tool_name) not in available_tools + ): + error_message = f"Tool '{tool_name}' is not available" + tool_results[correlation_id] = CallToolResult( + content=[text_content(error_message)], isError=True + ) + tool_loop_error = tool_loop_error or error_message + descriptor["status"] = "error" + continue + + descriptor["status"] = "pending" + id_list.append(correlation_id) + + max_parallel = self._options.max_parallel + if max_parallel and len(id_list) > max_parallel: + skipped_ids = id_list[max_parallel:] + id_list = id_list[:max_parallel] + skip_msg = f"Skipped {len(skipped_ids)} agent-tool calls (max_parallel={max_parallel})" + tool_loop_error = tool_loop_error or skip_msg + for cid in skipped_ids: + tool_results[cid] = CallToolResult( + content=[text_content(skip_msg)], + isError=True, + ) + descriptor_by_id[cid]["status"] = "error" + descriptor_by_id[cid]["error_message"] = skip_msg + + from fast_agent.event_progress import ProgressAction, ProgressEvent + from fast_agent.ui.progress_display import ( + progress_display as outer_progress_display, + ) + + async def call_with_instance_name( + tool_name: str, tool_args: dict[str, Any], instance: int, correlation_id: str + ) -> CallToolResult: + child = self._resolve_child_agent(tool_name) + if not child: + error_msg = f"Unknown agent-tool: {tool_name}" + return CallToolResult(content=[text_content(error_msg)], isError=True) + + base_name = getattr(child, "_name", child.name) + instance_name = f"{base_name}[{instance}]" + + try: + clone = await child.spawn_detached_instance(name=instance_name) + except Exception as exc: + logger.error( + "Failed to spawn dedicated child instance", + data={ + "tool_name": tool_name, + "agent_name": base_name, + "error": str(exc), + }, + ) + return CallToolResult(content=[text_content(f"Spawn failed: {exc}")], isError=True) + + # Prepare history according to mode + history_mode = self._options.history_mode + base_history = child.message_history + fork_index = len(base_history) + try: + if history_mode == HistoryMode.SCRATCH: + clone.load_message_history([]) + fork_index = 0 + else: + clone.load_message_history(base_history) + except Exception as hist_exc: + logger.warning( + "Failed to load history into clone", + data={"instance_name": instance_name, "error": str(hist_exc)}, + ) + + progress_started = False + try: + outer_progress_display.update( + ProgressEvent( + action=ProgressAction.CHATTING, + target=instance_name, + details="", + agent_name=instance_name, + correlation_id=correlation_id, + instance_name=instance_name, + tool_name=tool_name, + ) + ) + progress_started = True + call_coro = self._invoke_child_agent(clone, tool_args) + timeout = self._options.child_timeout_sec + if timeout: + return await asyncio.wait_for(call_coro, timeout=timeout) + return await call_coro + finally: + try: + await clone.shutdown() + except Exception as shutdown_exc: + logger.warning( + "Error shutting down dedicated child instance", + data={ + "instance_name": instance_name, + "error": str(shutdown_exc), + }, + ) + try: + child.merge_usage_from(clone) + except Exception as merge_exc: + logger.warning( + "Failed to merge usage from child instance", + data={ + "instance_name": instance_name, + "error": str(merge_exc), + }, + ) + if history_mode == HistoryMode.FORK_AND_MERGE: + try: + await self._merge_child_history( + target=child, clone=clone, start_index=fork_index + ) + except Exception as merge_hist_exc: + logger.warning( + "Failed to merge child history", + data={ + "instance_name": instance_name, + "error": str(merge_hist_exc), + }, + ) + if progress_started and instance_name: + outer_progress_display.update( + ProgressEvent( + action=ProgressAction.FINISHED, + target=instance_name, + details="Completed", + agent_name=instance_name, + correlation_id=correlation_id, + instance_name=instance_name, + tool_name=tool_name, + ) + ) + + for i, cid in enumerate(id_list, 1): + tool_name = descriptor_by_id[cid]["tool"] + tool_args = descriptor_by_id[cid]["args"] + tasks.append(asyncio.create_task(call_with_instance_name(tool_name, tool_args, i, cid))) + + self._show_parallel_tool_calls(call_descriptors) + + if tasks: + results = await asyncio.gather(*tasks, return_exceptions=True) + for i, result in enumerate(results): + correlation_id = id_list[i] + if isinstance(result, Exception): + msg = f"Tool execution failed: {result}" + tool_results[correlation_id] = CallToolResult( + content=[text_content(msg)], isError=True + ) + tool_loop_error = tool_loop_error or msg + descriptor_by_id[correlation_id]["status"] = "error" + descriptor_by_id[correlation_id]["error_message"] = msg + else: + tool_results[correlation_id] = result + descriptor_by_id[correlation_id]["status"] = ( + "error" if result.isError else "done" + ) + + ordered_records: list[dict[str, Any]] = [] + for cid in id_list: + result = tool_results.get(cid) + if result is None: + continue + descriptor = descriptor_by_id.get(cid, {}) + ordered_records.append({"descriptor": descriptor, "result": result}) + + self._show_parallel_tool_results(ordered_records) + + return tool_results, tool_loop_error + + def _extract_error_text(self, message: PromptMessageExtended) -> str | None: + if not message.channels: + return None + + error_blocks = message.channels.get(FAST_AGENT_ERROR_CHANNEL) + if not error_blocks: + return None + + for block in error_blocks: + text = get_text(block) + if text: + return text + + return None diff --git a/src/fast_agent/core/direct_decorators.py b/src/fast_agent/core/direct_decorators.py index e857f1824..96dd7b6af 100644 --- a/src/fast_agent/core/direct_decorators.py +++ b/src/fast_agent/core/direct_decorators.py @@ -270,6 +270,7 @@ def agent( instruction_or_kwarg: str | Path | AnyUrl | None = None, *, instruction: str | Path | AnyUrl = DEFAULT_AGENT_INSTRUCTION, + agents: list[str] | None = None, servers: list[str] = [], tools: dict[str, list[str]] | None = None, resources: dict[str, list[str]] | None = None, @@ -282,6 +283,10 @@ def agent( default: bool = False, elicitation_handler: ElicitationFnT | None = None, api_key: str | None = None, + history_mode: Any | None = None, + max_parallel: int | None = None, + child_timeout_sec: int | None = None, + max_display_instances: int | None = None, ) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]: """ Decorator to create and register a standard agent with type-safe signature. @@ -315,6 +320,7 @@ def agent( AgentType.BASIC, name=name, instruction=final_instruction, + child_agents=agents, servers=servers, model=model, use_history=use_history, @@ -327,6 +333,12 @@ def agent( prompts=prompts, skills=skills, api_key=api_key, + agents_as_tools_options={ + "history_mode": history_mode, + "max_parallel": max_parallel, + "child_timeout_sec": child_timeout_sec, + "max_display_instances": max_display_instances, + }, ) diff --git a/src/fast_agent/core/direct_factory.py b/src/fast_agent/core/direct_factory.py index aa0ba093d..8c4d6b131 100644 --- a/src/fast_agent/core/direct_factory.py +++ b/src/fast_agent/core/direct_factory.py @@ -208,33 +208,80 @@ async def create_agents_by_type( # Type-specific initialization based on the Enum type # Note: Above we compared string values from config, here we compare Enum objects directly if agent_type == AgentType.BASIC: - # Create agent with UI support if needed - agent = _create_agent_with_ui_if_needed( - McpAgent, - config, - app_instance.context, - ) + # If BASIC agent declares child_agents, build an Agents-as-Tools wrapper + child_names = agent_data.get("child_agents", []) or [] + if child_names: + # Ensure child agents are already created + child_agents: list[AgentProtocol] = [] + for agent_name in child_names: + if agent_name not in active_agents: + raise AgentConfigError(f"Agent {agent_name} not found") + child_agents.append(active_agents[agent_name]) + + # Import here to avoid circulars at module import time + from fast_agent.agents.workflow.agents_as_tools_agent import ( + AgentsAsToolsAgent, + AgentsAsToolsOptions, + ) + raw_opts = agent_data.get("agents_as_tools_options") or {} + opt_kwargs = {k: v for k, v in raw_opts.items() if v is not None} + options = AgentsAsToolsOptions(**opt_kwargs) + + agent = AgentsAsToolsAgent( + config=config, + context=app_instance.context, + agents=child_agents, # expose children as tools + options=options, + ) - await agent.initialize() + await agent.initialize() - # Attach LLM to the agent - llm_factory = model_factory_func(model=config.model) - await agent.attach_llm( - llm_factory, - request_params=config.default_request_params, - api_key=config.api_key, - ) - result_agents[name] = agent + # Attach LLM to the agent + llm_factory = model_factory_func(model=config.model) + await agent.attach_llm( + llm_factory, + request_params=config.default_request_params, + api_key=config.api_key, + ) + result_agents[name] = agent + + # Log successful agent creation + logger.info( + f"Loaded {name}", + data={ + "progress_action": ProgressAction.LOADED, + "agent_name": name, + "target": name, + }, + ) + else: + # Create agent with UI support if needed + agent = _create_agent_with_ui_if_needed( + McpAgent, + config, + app_instance.context, + ) - # Log successful agent creation - logger.info( - f"Loaded {name}", - data={ - "progress_action": ProgressAction.LOADED, - "agent_name": name, - "target": name, - }, - ) + await agent.initialize() + + # Attach LLM to the agent + llm_factory = model_factory_func(model=config.model) + await agent.attach_llm( + llm_factory, + request_params=config.default_request_params, + api_key=config.api_key, + ) + result_agents[name] = agent + + # Log successful agent creation + logger.info( + f"Loaded {name}", + data={ + "progress_action": ProgressAction.LOADED, + "agent_name": name, + "target": name, + }, + ) elif agent_type == AgentType.CUSTOM: # Get the class to instantiate (support legacy 'agent_class' and new 'cls') diff --git a/src/fast_agent/core/fastagent.py b/src/fast_agent/core/fastagent.py index 84c2d68fa..c687cb2fc 100644 --- a/src/fast_agent/core/fastagent.py +++ b/src/fast_agent/core/fastagent.py @@ -131,6 +131,7 @@ def __init__( (like FastAPI/Uvicorn) that handles its own arguments. quiet: If True, disable progress display, tool and message logging for cleaner output """ + self.args = argparse.Namespace() # Initialize args always self._programmatic_quiet = quiet # Store the programmatic quiet setting self._skills_directory_override = ( @@ -351,6 +352,7 @@ def agent( instruction_or_kwarg: str | Path | AnyUrl | None = None, *, instruction: str | Path | AnyUrl = DEFAULT_AGENT_INSTRUCTION, + agents: list[str] | None = None, servers: list[str] = [], tools: dict[str, list[str]] | None = None, resources: dict[str, list[str]] | None = None, @@ -363,6 +365,10 @@ def agent( default: bool = False, elicitation_handler: ElicitationFnT | None = None, api_key: str | None = None, + history_mode: Any | None = None, + max_parallel: int | None = None, + child_timeout_sec: int | None = None, + max_display_instances: int | None = None, ) -> Callable[ [Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]] ]: ... @@ -467,7 +473,7 @@ def evaluator_optimizer( instruction: str | Path | AnyUrl | None = None, min_rating: str = "GOOD", max_refinements: int = 3, - refinement_instruction: str | None = None, + refinement_instruction: str | None = None, default: bool = False, ) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ... @@ -692,7 +698,9 @@ async def dispose_agent_instance(instance: AgentInstance) -> None: server_name = getattr(self.args, "server_name", None) instance_scope = getattr(self.args, "instance_scope", "shared") - permissions_enabled = getattr(self.args, "permissions_enabled", True) + permissions_enabled = getattr( + self.args, "permissions_enabled", True + ) # Pass skills directory override if configured skills_override = ( diff --git a/src/fast_agent/core/logging/listeners.py b/src/fast_agent/core/logging/listeners.py index 5154aefe0..6165e5bc9 100644 --- a/src/fast_agent/core/logging/listeners.py +++ b/src/fast_agent/core/logging/listeners.py @@ -28,17 +28,72 @@ def convert_log_event(event: Event) -> "ProgressEvent | None": if not isinstance(event_data, dict): return None - progress_action = event_data.get("progress_action") - if not progress_action: + raw_action = event_data.get("progress_action") + if not raw_action: + return None + + # Coerce raw_action (enum or string) into a ProgressAction instance + try: + action = ( + raw_action + if isinstance(raw_action, ProgressAction) + else ProgressAction(str(raw_action)) + ) + except Exception: + # If we cannot coerce, drop this event from progress handling return None # Build target string based on the event type. # Progress display is currently [time] [event] --- [target] [details] namespace = event.namespace agent_name = event_data.get("agent_name") + + # General progress debug logging (including action value and type) + try: + from pathlib import Path + + debug_path = Path.home() / "logs" / "progress_actions_debug.log" + debug_line = ( + "[DEBUG PROGRESS] " + f"namespace={namespace} " + f"action={action.value} " + f"raw_type={type(raw_action).__name__} " + f"agent_name={agent_name} " + f"tool_name={event_data.get('tool_name')} " + f"server_name={event_data.get('server_name')} " + f"model={event_data.get('model')} " + f"tool_event={event_data.get('tool_event')}\n" + ) + debug_path.parent.mkdir(parents=True, exist_ok=True) + with debug_path.open("a", encoding="utf-8") as f: + f.write(debug_line) + except Exception: + pass + + # Temporary diagnostic logging for CALLING_TOOL routing issues + if action == ProgressAction.CALLING_TOOL: + try: + from pathlib import Path + + ct_path = Path.home() / "logs" / "calling_tool_debug.log" + ct_line = ( + "[DEBUG CALLING_TOOL] " + f"namespace={namespace} " + f"agent_name={agent_name} " + f"tool_name={event_data.get('tool_name')} " + f"server_name={event_data.get('server_name')} " + f"model={event_data.get('model')} " + f"tool_event={event_data.get('tool_event')}\n" + ) + ct_path.parent.mkdir(parents=True, exist_ok=True) + with ct_path.open("a", encoding="utf-8") as f: + f.write(ct_line) + except Exception: + pass + target = agent_name details = "" - if progress_action == ProgressAction.FATAL_ERROR: + if action == ProgressAction.FATAL_ERROR: details = event_data.get("error_message", "An error occurred") elif "mcp_aggregator" in namespace: server_name = event_data.get("server_name", "") @@ -50,7 +105,7 @@ def convert_log_event(event: Event) -> "ProgressEvent | None": details = f"{server_name}" # For TOOL_PROGRESS, use progress message if available, otherwise keep default - if progress_action == ProgressAction.TOOL_PROGRESS: + if action == ProgressAction.TOOL_PROGRESS: progress_message = event_data.get("details", "") if progress_message: # Only override if message is non-empty details = progress_message @@ -76,20 +131,20 @@ def convert_log_event(event: Event) -> "ProgressEvent | None": if not target: target = event_data.get("target", "unknown") - # Extract streaming token count for STREAMING actions + # Extract streaming token count for STREAMING/THINKING actions streaming_tokens = None - if progress_action == ProgressAction.STREAMING or progress_action == ProgressAction.THINKING: + if action == ProgressAction.STREAMING or action == ProgressAction.THINKING: streaming_tokens = event_data.get("details", "") # Extract progress data for TOOL_PROGRESS actions progress = None total = None - if progress_action == ProgressAction.TOOL_PROGRESS: + if action == ProgressAction.TOOL_PROGRESS: progress = event_data.get("progress") total = event_data.get("total") return ProgressEvent( - action=ProgressAction(progress_action), + action=action, target=target or "unknown", details=details, agent_name=event_data.get("agent_name"), diff --git a/src/fast_agent/core/validation.py b/src/fast_agent/core/validation.py index ac9246b56..d011302d4 100644 --- a/src/fast_agent/core/validation.py +++ b/src/fast_agent/core/validation.py @@ -208,6 +208,7 @@ def get_agent_dependencies(agent_data: dict[str, Any]) -> set[str]: AgentType.EVALUATOR_OPTIMIZER: ("evaluator", "generator", "eval_optimizer_agents"), AgentType.ITERATIVE_PLANNER: ("child_agents",), AgentType.ORCHESTRATOR: ("child_agents",), + AgentType.BASIC: ("child_agents",), AgentType.PARALLEL: ("fan_out", "fan_in", "parallel_agents"), AgentType.ROUTER: ("router_agents",), } diff --git a/src/fast_agent/event_progress.py b/src/fast_agent/event_progress.py index bc29dfcf9..fc50eb5b7 100644 --- a/src/fast_agent/event_progress.py +++ b/src/fast_agent/event_progress.py @@ -36,6 +36,9 @@ class ProgressEvent(BaseModel): target: str details: str | None = None agent_name: str | None = None + correlation_id: str | None = None + instance_name: str | None = None + tool_name: str | None = None streaming_tokens: str | None = None # Special field for streaming token count progress: float | None = None # Current progress value total: float | None = None # Total value for progress calculation diff --git a/src/fast_agent/mcp/mcp_aggregator.py b/src/fast_agent/mcp/mcp_aggregator.py index 87ac52f74..ae1c4d77a 100644 --- a/src/fast_agent/mcp/mcp_aggregator.py +++ b/src/fast_agent/mcp/mcp_aggregator.py @@ -147,9 +147,12 @@ async def __aenter__(self): manager = MCPConnectionManager(server_registry, context=context) await manager.__aenter__() context._connection_manager = manager + self._owns_connection_manager = True self._persistent_connection_manager = cast( "MCPConnectionManager", context._connection_manager ) + else: + self._persistent_connection_manager = None # Import the display component here to avoid circular imports from fast_agent.ui.console_display import ConsoleDisplay @@ -192,6 +195,8 @@ def __init__( self.connection_persistence = connection_persistence self.agent_name = name self.config = config # Store the config for access in session factory + self._persistent_connection_manager: MCPConnectionManager | None = None + self._owns_connection_manager = False # Store tool execution handler for integration with ACP or other protocols # Default to NoOpToolExecutionHandler if none provided @@ -263,7 +268,7 @@ async def close(self) -> None: if self.connection_persistence and self._persistent_connection_manager: try: # Only attempt cleanup if we own the connection manager - if ( + if self._owns_connection_manager and ( hasattr(self.context, "_connection_manager") and self.context._connection_manager == self._persistent_connection_manager ): @@ -1787,17 +1792,41 @@ async def list_prompts( operation_type="prompts/list", operation_name="", method_name="list_prompts", - error_factory=lambda _: None, + method_args={}, ) + new_tools = result.tools or [] + + # Update tool maps + async with self._tool_map_lock: + # Remove old tools for this server + old_tools = self._server_to_tool_map.get(server_name, []) + for old_tool in old_tools: + if old_tool.namespaced_tool_name in self._namespaced_tool_map: + del self._namespaced_tool_map[old_tool.namespaced_tool_name] + + # Add new tools + self._server_to_tool_map[server_name] = [] + for tool in new_tools: + namespaced_tool_name = create_namespaced_name(server_name, tool.name) + namespaced_tool = NamespacedTool( + tool=tool, + server_name=server_name, + namespaced_tool_name=namespaced_tool_name, + ) - # Get prompts from result - prompts = getattr(result, "prompts", []) - - # Update cache - async with self._prompt_cache_lock: - self._prompt_cache[server_name] = prompts + self._namespaced_tool_map[namespaced_tool_name] = namespaced_tool + self._server_to_tool_map[server_name].append(namespaced_tool) - results[server_name] = prompts + logger.info( + f"Successfully refreshed tools for server '{server_name}'", + data={ + "progress_action": ProgressAction.UPDATED, + "server_name": server_name, + "agent_name": self.agent_name, + "tool_count": len(new_tools), + }, + ) + results[server_name] = new_tools return results # No specific server - check if we can use the cache for all servers @@ -1826,7 +1855,7 @@ async def list_prompts( operation_type="prompts/list", operation_name="", method_name="list_prompts", - error_factory=lambda _: None, + method_args={}, ) prompts = getattr(result, "prompts", []) diff --git a/src/fast_agent/ui/console_display.py b/src/fast_agent/ui/console_display.py index 6c860811a..46ae8335f 100644 --- a/src/fast_agent/ui/console_display.py +++ b/src/fast_agent/ui/console_display.py @@ -537,14 +537,18 @@ def show_tool_result( tool_name: str | None = None, skybridge_config: "SkybridgeServerConfig | None" = None, timing_ms: float | None = None, + type_label: str | None = None, ) -> None: - self._tool_display.show_tool_result( - result, - name=name, - tool_name=tool_name, - skybridge_config=skybridge_config, - timing_ms=timing_ms, - ) + kwargs: dict[str, Any] = { + "name": name, + "tool_name": tool_name, + "skybridge_config": skybridge_config, + "timing_ms": timing_ms, + } + if type_label is not None: + kwargs["type_label"] = type_label + + self._tool_display.show_tool_result(result, **kwargs) def show_tool_call( self, @@ -555,16 +559,19 @@ def show_tool_call( max_item_length: int | None = None, name: str | None = None, metadata: dict[str, Any] | None = None, + type_label: str | None = None, ) -> None: - self._tool_display.show_tool_call( - tool_name, - tool_args, - bottom_items=bottom_items, - highlight_index=highlight_index, - max_item_length=max_item_length, - name=name, - metadata=metadata, - ) + kwargs: dict[str, Any] = { + "bottom_items": bottom_items, + "highlight_index": highlight_index, + "max_item_length": max_item_length, + "name": name, + "metadata": metadata, + } + if type_label is not None: + kwargs["type_label"] = type_label + + self._tool_display.show_tool_call(tool_name, tool_args, **kwargs) async def show_tool_update(self, updated_server: str, agent_name: str | None = None) -> None: await self._tool_display.show_tool_update(updated_server, agent_name=agent_name) diff --git a/src/fast_agent/ui/rich_progress.py b/src/fast_agent/ui/rich_progress.py index c180e40ff..f8582d613 100644 --- a/src/fast_agent/ui/rich_progress.py +++ b/src/fast_agent/ui/rich_progress.py @@ -61,6 +61,16 @@ def resume(self) -> None: self._paused = False self._progress.start() + def hide_task(self, task_name: str) -> None: + """Hide an existing task from the progress display by name.""" + task_id = self._taskmap.get(task_name) + if task_id is None: + return + for task in self._progress.tasks: + if task.id == task_id: + task.visible = False + break + @contextmanager def paused(self): """Context manager for temporarily pausing the display.""" @@ -172,9 +182,6 @@ def update(self, event: ProgressEvent) -> None: details=f" / Elapsed Time {time.strftime('%H:%M:%S', time.gmtime(self._progress.tasks[task_id].elapsed))}", task_name=task_name, ) - for task in self._progress.tasks: - if task.id != task_id: - task.visible = False elif event.action == ProgressAction.FATAL_ERROR: self._progress.update( task_id, @@ -184,8 +191,5 @@ def update(self, event: ProgressEvent) -> None: details=f" / {event.details}", task_name=task_name, ) - for task in self._progress.tasks: - if task.id != task_id: - task.visible = False else: self._progress.reset(task_id) diff --git a/src/fast_agent/ui/tool_display.py b/src/fast_agent/ui/tool_display.py index bad115b2e..a2360e57c 100644 --- a/src/fast_agent/ui/tool_display.py +++ b/src/fast_agent/ui/tool_display.py @@ -34,6 +34,7 @@ def show_tool_result( tool_name: str | None = None, skybridge_config: "SkybridgeServerConfig | None" = None, timing_ms: float | None = None, + type_label: str = "tool result", ) -> None: """Display a tool result in the console.""" config = self._display.config @@ -101,7 +102,7 @@ def show_tool_result( bottom_metadata_items.append("Structured ■") bottom_metadata = bottom_metadata_items or None - right_info = f"[dim]tool result - {status}[/dim]" + right_info = f"[dim]{type_label} - {status}[/dim]" if has_structured: config_map = MESSAGE_CONFIGS[MessageType.TOOL_RESULT] @@ -200,6 +201,7 @@ def show_tool_call( max_item_length: int | None = None, name: str | None = None, metadata: dict[str, Any] | None = None, + type_label: str = "tool call", ) -> None: """Display a tool call header and body.""" config = self._display.config @@ -209,7 +211,7 @@ def show_tool_call( tool_args = tool_args or {} metadata = metadata or {} - right_info = f"[dim]tool request - {tool_name}[/dim]" + right_info = f"[dim]{type_label} - {tool_name}[/dim]" content: Any = tool_args pre_content: Text | None = None truncate_content = True diff --git a/tests/unit/fast_agent/agents/workflow/test_agents_as_tools_agent.py b/tests/unit/fast_agent/agents/workflow/test_agents_as_tools_agent.py new file mode 100644 index 000000000..79cbe6b88 --- /dev/null +++ b/tests/unit/fast_agent/agents/workflow/test_agents_as_tools_agent.py @@ -0,0 +1,150 @@ +import asyncio +from unittest.mock import AsyncMock + +import pytest +from mcp import CallToolRequest, Tool +from mcp.types import CallToolRequestParams + +from fast_agent.agents.agent_types import AgentConfig +from fast_agent.agents.llm_agent import LlmAgent +from fast_agent.agents.workflow.agents_as_tools_agent import ( + AgentsAsToolsAgent, + AgentsAsToolsOptions, +) +from fast_agent.constants import FAST_AGENT_ERROR_CHANNEL +from fast_agent.mcp.helpers.content_helpers import text_content +from fast_agent.types import PromptMessageExtended + + +class FakeChildAgent(LlmAgent): + """Minimal child agent stub for Agents-as-Tools tests.""" + + def __init__(self, name: str, response_text: str = "ok", delay: float = 0): + super().__init__(AgentConfig(name)) + self._response_text = response_text + self._delay = delay + + async def generate(self, messages, request_params=None): + if self._delay: + await asyncio.sleep(self._delay) + return PromptMessageExtended( + role="assistant", + content=[text_content(f"{self._response_text}")], + ) + + async def spawn_detached_instance(self, name: str | None = None): + # Mutate name for instance labelling; reuse self to keep the stub small. + self._name = name or self.name + return self + + +class ErrorChannelChild(FakeChildAgent): + async def generate(self, messages, request_params=None): + return PromptMessageExtended( + role="assistant", + content=[], + channels={FAST_AGENT_ERROR_CHANNEL: [text_content("err-block")]}, + ) + + +class StubNestedAgentsAsTools(AgentsAsToolsAgent): + """Stub AgentsAsToolsAgent that responds without hitting an LLM.""" + + async def generate(self, messages, request_params=None): + return PromptMessageExtended( + role="assistant", + content=[text_content(f"{self.name}-reply")], + ) + + async def spawn_detached_instance(self, name: str | None = None): + self._name = name or self.name + return self + + +@pytest.mark.asyncio +async def test_list_tools_merges_base_and_child(): + child = FakeChildAgent("child") + agent = AgentsAsToolsAgent(AgentConfig("parent"), [child]) + await agent.initialize() + + # Inject a base MCP tool via the filtered MCP path to ensure merge behavior. + base_tool = Tool(name="base_tool", description="base", inputSchema={"type": "object"}) + agent._get_filtered_mcp_tools = AsyncMock(return_value=[base_tool]) + + result = await agent.list_tools() + tool_names = {t.name for t in result.tools} + + assert "base_tool" in tool_names + assert "agent__child" in tool_names + + +@pytest.mark.asyncio +async def test_run_tools_respects_max_parallel_and_timeout(): + fast_child = FakeChildAgent("fast", response_text="fast") + slow_child = FakeChildAgent("slow", response_text="slow", delay=0.05) + + options = AgentsAsToolsOptions(max_parallel=1, child_timeout_sec=0.01) + agent = AgentsAsToolsAgent(AgentConfig("parent"), [fast_child, slow_child], options=options) + await agent.initialize() + + tool_calls = { + "1": CallToolRequest(params=CallToolRequestParams(name="agent__fast", arguments={"text": "hi"})), + "2": CallToolRequest(params=CallToolRequestParams(name="agent__slow", arguments={"text": "hi"})), + } + request = PromptMessageExtended(role="assistant", content=[], tool_calls=tool_calls) + + result_message = await agent.run_tools(request) + assert result_message.tool_results + + fast_result = result_message.tool_results["1"] + slow_result = result_message.tool_results["2"] + + assert not fast_result.isError + # Skipped due to max_parallel cap. + assert slow_result.isError + assert "Skipped" in slow_result.content[0].text + + # Now ensure timeout path yields an error result when a single slow call runs. + request_single = PromptMessageExtended( + role="assistant", + content=[], + tool_calls={"3": CallToolRequest(params=CallToolRequestParams(name="agent__slow", arguments={"text": "hi"}))}, + ) + single_result = await agent.run_tools(request_single) + err_res = single_result.tool_results["3"] + assert err_res.isError + assert any("Tool execution failed" in (block.text or "") for block in err_res.content) + + +@pytest.mark.asyncio +async def test_invoke_child_appends_error_channel(): + child = ErrorChannelChild("err-child") + agent = AgentsAsToolsAgent(AgentConfig("parent"), [child]) + await agent.initialize() + + call_result = await agent._invoke_child_agent(child, {"text": "hi"}) + + assert call_result.isError + texts = [block.text for block in call_result.content if hasattr(block, "text")] + assert "err-block" in texts + + +@pytest.mark.asyncio +async def test_nested_agents_as_tools_preserves_instance_labels(): + leaf = FakeChildAgent("leaf", response_text="leaf-ok") + nested = StubNestedAgentsAsTools(AgentConfig("nested"), [leaf]) + parent = AgentsAsToolsAgent(AgentConfig("parent"), [nested]) + + await nested.initialize() + await parent.initialize() + + tool_calls = { + "1": CallToolRequest(params=CallToolRequestParams(name="agent__nested", arguments={"text": "hi"})), + } + request = PromptMessageExtended(role="assistant", content=[], tool_calls=tool_calls) + + result_message = await parent.run_tools(request) + result = result_message.tool_results["1"] + assert not result.isError + # Reply should include the instance-suffixed nested agent name. + assert any("nested[1]-reply" in (block.text or "") for block in result.content) diff --git a/uv.lock b/uv.lock index 93da7d7ee..de82c8339 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ members = [ [[package]] name = "a2a-sdk" -version = "0.3.16" +version = "0.3.20" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -19,9 +19,9 @@ dependencies = [ { name = "protobuf" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/57/0c5605a956646c3a3fe0a6f0eb2eb1193718b01b5ef3fb7288b20684e67b/a2a_sdk-0.3.16.tar.gz", hash = "sha256:bc579091cfcf18341076379ea7efb361df0aca4822db05db7267d9d7f881e964", size = 228805, upload-time = "2025-11-21T13:34:48.842Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/c1/4c7968e44a318fbfaf82e142b2f63aedcf62ca8da5ee0cea6104a1a29580/a2a_sdk-0.3.20.tar.gz", hash = "sha256:f05bbdf4a8ada6be81dc7e7c73da3add767b20065195d94e8eb6d671d7ea658a", size = 229272, upload-time = "2025-12-03T15:48:22.349Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/e9/2fb9871bb416ae34b3a8f3c08de4bccb0a9b3b1dc0cb9a48940b13c14601/a2a_sdk-0.3.16-py3-none-any.whl", hash = "sha256:e5e1d6f8208985ed42b488dde9721bfb9efdf94e903700bb6c53d599b1433e03", size = 141390, upload-time = "2025-11-21T13:34:47.332Z" }, + { url = "https://files.pythonhosted.org/packages/11/33/719a9331421ee5df0338505548b58b4129a6aca82bba5c8e0593ac8864c7/a2a_sdk-0.3.20-py3-none-any.whl", hash = "sha256:35da261aae28fd22440b61f8eb16a8343b60809e1f7ef028a06d01f17b48a8b9", size = 141547, upload-time = "2025-12-03T15:48:20.812Z" }, ] [[package]] @@ -130,24 +130,23 @@ wheels = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, - { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] [[package]] name = "asttokens" -version = "3.0.0" +version = "3.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, ] [[package]] @@ -161,15 +160,15 @@ wheels = [ [[package]] name = "azure-core" -version = "1.36.0" +version = "1.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/c4/d4ff3bc3ddf155156460bff340bbe9533f99fac54ddea165f35a8619f162/azure_core-1.36.0.tar.gz", hash = "sha256:22e5605e6d0bf1d229726af56d9e92bc37b6e726b141a18be0b4d424131741b7", size = 351139, upload-time = "2025-10-15T00:33:49.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/83/41c9371c8298999c67b007e308a0a3c4d6a59c6908fa9c62101f031f886f/azure_core-1.37.0.tar.gz", hash = "sha256:7064f2c11e4b97f340e8e8c6d923b822978be3016e46b7bc4aa4b337cfb48aee", size = 357620, upload-time = "2025-12-11T20:05:13.518Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/3c/b90d5afc2e47c4a45f4bba00f9c3193b0417fad5ad3bb07869f9d12832aa/azure_core-1.36.0-py3-none-any.whl", hash = "sha256:fee9923a3a753e94a259563429f3644aaf05c486d45b1215d098115102d91d3b", size = 213302, upload-time = "2025-10-15T00:33:51.058Z" }, + { url = "https://files.pythonhosted.org/packages/ee/34/a9914e676971a13d6cc671b1ed172f9804b50a3a80a143ff196e52f4c7ee/azure_core-1.37.0-py3-none-any.whl", hash = "sha256:b3abe2c59e7d6bb18b38c275a5029ff80f98990e7c90a5e646249a56630fcc19", size = 214006, upload-time = "2025-12-11T20:05:14.96Z" }, ] [[package]] @@ -190,48 +189,48 @@ wheels = [ [[package]] name = "boto3" -version = "1.40.70" +version = "1.42.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/12/d5ac34e0536e1914dde28245f014a635056dde0427f6efa09f104d7999f4/boto3-1.40.70.tar.gz", hash = "sha256:191443707b391232ed15676bf6bba7e53caec1e71aafa12ccad2e825c5ee15cc", size = 111638, upload-time = "2025-11-10T20:29:15.199Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/34/64e34fb40903d358a4a3d697e2ee4784a7b52c11e7effbad01967b2d3fc3/boto3-1.42.8.tar.gz", hash = "sha256:e967706af5887339407481562c389c612d5eae641eb854ddd59026d049df740e", size = 112886, upload-time = "2025-12-11T21:54:15.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/cf/e24d08b37cd318754a8e94906c8b34b88676899aad1907ff6942311f13c4/boto3-1.40.70-py3-none-any.whl", hash = "sha256:e8c2f4f4cb36297270f1023ebe5b100333e0e88ab6457a9687d80143d2e15bf9", size = 139358, upload-time = "2025-11-10T20:29:13.512Z" }, + { url = "https://files.pythonhosted.org/packages/96/37/9702c0b8e63aaeb1ad430ece22567b03e58ea41e446d68b92e2cb00e7817/boto3-1.42.8-py3-none-any.whl", hash = "sha256:747acc83488fc80b0e7d1c4ff0c533039ff3ede21bdbd4e89544e25b010b070c", size = 140559, upload-time = "2025-12-11T21:54:14.513Z" }, ] [[package]] name = "botocore" -version = "1.40.70" +version = "1.42.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/c1/8c4c199ae1663feee579a15861e34f10b29da11ae6ea0ad7b6a847ef3823/botocore-1.40.70.tar.gz", hash = "sha256:61b1f2cecd54d1b28a081116fa113b97bf4e17da57c62ae2c2751fe4c528af1f", size = 14444592, upload-time = "2025-11-10T20:29:04.046Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/ea/4be7a4a640d599b5691c7cf27e125155d7d3643ecbe37e32941f412e3de5/botocore-1.42.8.tar.gz", hash = "sha256:4921aa454f82fed0880214eab21126c98a35fe31ede952693356f9c85ce3574b", size = 14861038, upload-time = "2025-12-11T21:54:04.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/d2/507fd0ee4dd574d2bdbdeac5df83f39d2cae1ffe97d4622cca6f6bab39f1/botocore-1.40.70-py3-none-any.whl", hash = "sha256:4a394ad25f5d9f1ef0bed610365744523eeb5c22de6862ab25d8c93f9f6d295c", size = 14106829, upload-time = "2025-11-10T20:29:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/1c/24/a4301564a979368d6f3644f47acc921450b5524b8846e827237d98b04746/botocore-1.42.8-py3-none-any.whl", hash = "sha256:4cb89c74dd9083d16e45868749b999265a91309b2499907c84adeffa0a8df89b", size = 14534173, upload-time = "2025-12-11T21:54:01.143Z" }, ] [[package]] name = "cachetools" -version = "6.2.1" +version = "6.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/7e/b975b5814bd36faf009faebe22c1072a1fa1168db34d285ef0ba071ad78c/cachetools-6.2.1.tar.gz", hash = "sha256:3f391e4bd8f8bf0931169baf7456cc822705f4e2a31f840d218f445b9a854201", size = 31325, upload-time = "2025-10-12T14:55:30.139Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/44/ca1675be2a83aeee1886ab745b28cda92093066590233cc501890eb8417a/cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6", size = 31571, upload-time = "2025-11-13T17:42:51.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl", hash = "sha256:09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701", size = 11280, upload-time = "2025-10-12T14:55:28.382Z" }, + { url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" }, ] [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] @@ -259,11 +258,11 @@ wheels = [ [[package]] name = "cfgv" -version = "3.4.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] @@ -293,14 +292,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -314,37 +313,37 @@ wheels = [ [[package]] name = "coverage" -version = "7.11.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d2/59/9698d57a3b11704c7b89b21d69e9d23ecf80d538cabb536c8b63f4a12322/coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b", size = 815210, upload-time = "2025-11-10T00:13:17.18Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/f6/d8572c058211c7d976f24dab71999a565501fb5b3cdcb59cf782f19c4acb/coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36", size = 216694, upload-time = "2025-11-10T00:11:34.296Z" }, - { url = "https://files.pythonhosted.org/packages/4a/f6/b6f9764d90c0ce1bce8d995649fa307fff21f4727b8d950fa2843b7b0de5/coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e", size = 217065, upload-time = "2025-11-10T00:11:36.281Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8d/a12cb424063019fd077b5be474258a0ed8369b92b6d0058e673f0a945982/coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2", size = 248062, upload-time = "2025-11-10T00:11:37.903Z" }, - { url = "https://files.pythonhosted.org/packages/7f/9c/dab1a4e8e75ce053d14259d3d7485d68528a662e286e184685ea49e71156/coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63", size = 250657, upload-time = "2025-11-10T00:11:39.509Z" }, - { url = "https://files.pythonhosted.org/packages/3f/89/a14f256438324f33bae36f9a1a7137729bf26b0a43f5eda60b147ec7c8c7/coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3", size = 251900, upload-time = "2025-11-10T00:11:41.372Z" }, - { url = "https://files.pythonhosted.org/packages/04/07/75b0d476eb349f1296486b1418b44f2d8780cc8db47493de3755e5340076/coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5", size = 248254, upload-time = "2025-11-10T00:11:43.27Z" }, - { url = "https://files.pythonhosted.org/packages/5a/4b/0c486581fa72873489ca092c52792d008a17954aa352809a7cbe6cf0bf07/coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5", size = 250041, upload-time = "2025-11-10T00:11:45.274Z" }, - { url = "https://files.pythonhosted.org/packages/af/a3/0059dafb240ae3e3291f81b8de00e9c511d3dd41d687a227dd4b529be591/coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7", size = 248004, upload-time = "2025-11-10T00:11:46.93Z" }, - { url = "https://files.pythonhosted.org/packages/83/93/967d9662b1eb8c7c46917dcc7e4c1875724ac3e73c3cb78e86d7a0ac719d/coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5", size = 247828, upload-time = "2025-11-10T00:11:48.563Z" }, - { url = "https://files.pythonhosted.org/packages/4c/1c/5077493c03215701e212767e470b794548d817dfc6247a4718832cc71fac/coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094", size = 249588, upload-time = "2025-11-10T00:11:50.581Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a5/77f64de461016e7da3e05d7d07975c89756fe672753e4cf74417fc9b9052/coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c", size = 219223, upload-time = "2025-11-10T00:11:52.184Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1c/ec51a3c1a59d225b44bdd3a4d463135b3159a535c2686fac965b698524f4/coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2", size = 220033, upload-time = "2025-11-10T00:11:53.871Z" }, - { url = "https://files.pythonhosted.org/packages/01/ec/e0ce39746ed558564c16f2cc25fa95ce6fc9fa8bfb3b9e62855d4386b886/coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944", size = 218661, upload-time = "2025-11-10T00:11:55.597Z" }, - { url = "https://files.pythonhosted.org/packages/46/cb/483f130bc56cbbad2638248915d97b185374d58b19e3cc3107359715949f/coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428", size = 217389, upload-time = "2025-11-10T00:11:57.59Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ae/81f89bae3afef75553cf10e62feb57551535d16fd5859b9ee5a2a97ddd27/coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a", size = 217742, upload-time = "2025-11-10T00:11:59.519Z" }, - { url = "https://files.pythonhosted.org/packages/db/6e/a0fb897041949888191a49c36afd5c6f5d9f5fd757e0b0cd99ec198a324b/coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655", size = 259049, upload-time = "2025-11-10T00:12:01.592Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b6/d13acc67eb402d91eb94b9bd60593411799aed09ce176ee8d8c0e39c94ca/coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7", size = 261113, upload-time = "2025-11-10T00:12:03.639Z" }, - { url = "https://files.pythonhosted.org/packages/ea/07/a6868893c48191d60406df4356aa7f0f74e6de34ef1f03af0d49183e0fa1/coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d", size = 263546, upload-time = "2025-11-10T00:12:05.485Z" }, - { url = "https://files.pythonhosted.org/packages/24/e5/28598f70b2c1098332bac47925806353b3313511d984841111e6e760c016/coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f", size = 258260, upload-time = "2025-11-10T00:12:07.137Z" }, - { url = "https://files.pythonhosted.org/packages/0e/58/58e2d9e6455a4ed746a480c4b9cf96dc3cb2a6b8f3efbee5efd33ae24b06/coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0", size = 261121, upload-time = "2025-11-10T00:12:09.138Z" }, - { url = "https://files.pythonhosted.org/packages/17/57/38803eefb9b0409934cbc5a14e3978f0c85cb251d2b6f6a369067a7105a0/coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739", size = 258736, upload-time = "2025-11-10T00:12:11.195Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f3/f94683167156e93677b3442be1d4ca70cb33718df32a2eea44a5898f04f6/coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71", size = 257625, upload-time = "2025-11-10T00:12:12.843Z" }, - { url = "https://files.pythonhosted.org/packages/87/ed/42d0bf1bc6bfa7d65f52299a31daaa866b4c11000855d753857fe78260ac/coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76", size = 259827, upload-time = "2025-11-10T00:12:15.128Z" }, - { url = "https://files.pythonhosted.org/packages/d3/76/5682719f5d5fbedb0c624c9851ef847407cae23362deb941f185f489c54e/coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c", size = 219897, upload-time = "2025-11-10T00:12:17.274Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/1da511d0ac3d39e6676fa6cc5ec35320bbf1cebb9b24e9ee7548ee4e931a/coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac", size = 220959, upload-time = "2025-11-10T00:12:19.292Z" }, - { url = "https://files.pythonhosted.org/packages/e5/9d/e255da6a04e9ec5f7b633c54c0fdfa221a9e03550b67a9c83217de12e96c/coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc", size = 219234, upload-time = "2025-11-10T00:12:21.251Z" }, - { url = "https://files.pythonhosted.org/packages/19/8f/92bdd27b067204b99f396a1414d6342122f3e2663459baf787108a6b8b84/coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe", size = 208478, upload-time = "2025-11-10T00:13:14.908Z" }, +version = "7.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905, upload-time = "2025-12-08T13:14:38.055Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/cc/bce226595eb3bf7d13ccffe154c3c487a22222d87ff018525ab4dd2e9542/coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf", size = 218297, upload-time = "2025-12-08T13:13:10.977Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9f/73c4d34600aae03447dff3d7ad1d0ac649856bfb87d1ca7d681cfc913f9e/coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a", size = 218673, upload-time = "2025-12-08T13:13:12.562Z" }, + { url = "https://files.pythonhosted.org/packages/63/ab/8fa097db361a1e8586535ae5073559e6229596b3489ec3ef2f5b38df8cb2/coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74", size = 249652, upload-time = "2025-12-08T13:13:13.909Z" }, + { url = "https://files.pythonhosted.org/packages/90/3a/9bfd4de2ff191feb37ef9465855ca56a6f2f30a3bca172e474130731ac3d/coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6", size = 252251, upload-time = "2025-12-08T13:13:15.553Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/b5d8105f016e1b5874af0d7c67542da780ccd4a5f2244a433d3e20ceb1ad/coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b", size = 253492, upload-time = "2025-12-08T13:13:16.849Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b8/0fad449981803cc47a4694768b99823fb23632150743f9c83af329bb6090/coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232", size = 249850, upload-time = "2025-12-08T13:13:18.142Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e9/8d68337c3125014d918cf4327d5257553a710a2995a6a6de2ac77e5aa429/coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971", size = 251633, upload-time = "2025-12-08T13:13:19.56Z" }, + { url = "https://files.pythonhosted.org/packages/55/14/d4112ab26b3a1bc4b3c1295d8452dcf399ed25be4cf649002fb3e64b2d93/coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d", size = 249586, upload-time = "2025-12-08T13:13:20.883Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a9/22b0000186db663b0d82f86c2f1028099ae9ac202491685051e2a11a5218/coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137", size = 249412, upload-time = "2025-12-08T13:13:22.22Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2e/42d8e0d9e7527fba439acdc6ed24a2b97613b1dc85849b1dd935c2cffef0/coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511", size = 251191, upload-time = "2025-12-08T13:13:23.899Z" }, + { url = "https://files.pythonhosted.org/packages/a4/af/8c7af92b1377fd8860536aadd58745119252aaaa71a5213e5a8e8007a9f5/coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1", size = 220829, upload-time = "2025-12-08T13:13:25.182Z" }, + { url = "https://files.pythonhosted.org/packages/58/f9/725e8bf16f343d33cbe076c75dc8370262e194ff10072c0608b8e5cf33a3/coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a", size = 221640, upload-time = "2025-12-08T13:13:26.836Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ff/e98311000aa6933cc79274e2b6b94a2fe0fe3434fca778eba82003675496/coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6", size = 220269, upload-time = "2025-12-08T13:13:28.116Z" }, + { url = "https://files.pythonhosted.org/packages/cf/cf/bbaa2e1275b300343ea865f7d424cc0a2e2a1df6925a070b2b2d5d765330/coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a", size = 218990, upload-time = "2025-12-08T13:13:29.463Z" }, + { url = "https://files.pythonhosted.org/packages/21/1d/82f0b3323b3d149d7672e7744c116e9c170f4957e0c42572f0366dbb4477/coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8", size = 219340, upload-time = "2025-12-08T13:13:31.524Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/fe3fd4702a3832a255f4d43013eacb0ef5fc155a5960ea9269d8696db28b/coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053", size = 260638, upload-time = "2025-12-08T13:13:32.965Z" }, + { url = "https://files.pythonhosted.org/packages/ad/01/63186cb000307f2b4da463f72af9b85d380236965574c78e7e27680a2593/coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071", size = 262705, upload-time = "2025-12-08T13:13:34.378Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a1/c0dacef0cc865f2455d59eed3548573ce47ed603205ffd0735d1d78b5906/coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e", size = 265125, upload-time = "2025-12-08T13:13:35.73Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/82b99223628b61300bd382c205795533bed021505eab6dd86e11fb5d7925/coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493", size = 259844, upload-time = "2025-12-08T13:13:37.69Z" }, + { url = "https://files.pythonhosted.org/packages/cf/2c/89b0291ae4e6cd59ef042708e1c438e2290f8c31959a20055d8768349ee2/coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0", size = 262700, upload-time = "2025-12-08T13:13:39.525Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f9/a5f992efae1996245e796bae34ceb942b05db275e4b34222a9a40b9fbd3b/coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e", size = 260321, upload-time = "2025-12-08T13:13:41.172Z" }, + { url = "https://files.pythonhosted.org/packages/4c/89/a29f5d98c64fedbe32e2ac3c227fbf78edc01cc7572eee17d61024d89889/coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c", size = 259222, upload-time = "2025-12-08T13:13:43.282Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c3/940fe447aae302a6701ee51e53af7e08b86ff6eed7631e5740c157ee22b9/coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e", size = 261411, upload-time = "2025-12-08T13:13:44.72Z" }, + { url = "https://files.pythonhosted.org/packages/eb/31/12a4aec689cb942a89129587860ed4d0fd522d5fda81237147fde554b8ae/coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46", size = 221505, upload-time = "2025-12-08T13:13:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/65/8c/3b5fe3259d863572d2b0827642c50c3855d26b3aefe80bdc9eba1f0af3b0/coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39", size = 222569, upload-time = "2025-12-08T13:13:47.79Z" }, + { url = "https://files.pythonhosted.org/packages/b0/39/f71fa8316a96ac72fc3908839df651e8eccee650001a17f2c78cdb355624/coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e", size = 220841, upload-time = "2025-12-08T13:13:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068, upload-time = "2025-12-08T13:14:36.236Z" }, ] [[package]] @@ -388,6 +387,15 @@ wheels = [ { 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" }, ] +[[package]] +name = "dacite" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/a0/7ca79796e799a3e782045d29bf052b5cde7439a2bbb17f15ff44f7aacc63/dacite-1.9.2.tar.gz", hash = "sha256:6ccc3b299727c7aa17582f0021f6ae14d5de47c7227932c47fec4cdfefd26f09", size = 22420, upload-time = "2025-02-05T09:27:29.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl", hash = "sha256:053f7c3f5128ca2e9aceb66892b1a3c8936d02c686e707bee96e19deef4bc4a0", size = 16600, upload-time = "2025-02-05T09:27:24.345Z" }, +] + [[package]] name = "decorator" version = "5.2.1" @@ -610,7 +618,7 @@ dev = [ [[package]] name = "fastapi" -version = "0.121.1" +version = "0.124.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -618,9 +626,9 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/a4/29e1b861fc9017488ed02ff1052feffa40940cb355ed632a8845df84ce84/fastapi-0.121.1.tar.gz", hash = "sha256:b6dba0538fd15dab6fe4d3e5493c3957d8a9e1e9257f56446b5859af66f32441", size = 342523, upload-time = "2025-11-08T21:48:14.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/b7/4dbca3f9d847ba9876dcb7098c13a4c6c86ee8db148c923fab78e27748d3/fastapi-0.124.2.tar.gz", hash = "sha256:72e188f01f360e2f59da51c8822cbe4bca210c35daaae6321b1b724109101c00", size = 361867, upload-time = "2025-12-10T12:10:10.676Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/fd/2e6f7d706899cc08690c5f6641e2ffbfffe019e8f16ce77104caa5730910/fastapi-0.121.1-py3-none-any.whl", hash = "sha256:2c5c7028bc3a58d8f5f09aecd3fd88a000ccc0c5ad627693264181a3c33aa1fc", size = 109192, upload-time = "2025-11-08T21:48:12.458Z" }, + { url = "https://files.pythonhosted.org/packages/25/c5/8a5231197b81943b2df126cc8ea2083262e004bee3a39cf85a471392d145/fastapi-0.124.2-py3-none-any.whl", hash = "sha256:6314385777a507bb19b34bd064829fddaea0eea54436deb632b5de587554055c", size = 112711, upload-time = "2025-12-10T12:10:08.855Z" }, ] [[package]] @@ -703,23 +711,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/d1/385110a9ae86d91cc14c5282c61fe9f4dc41c0b9f7d423c6ad77038c4448/google_auth-2.43.0-py2.py3-none-any.whl", hash = "sha256:af628ba6fa493f75c7e9dbe9373d148ca9f4399b5ea29976519e0a3848eddd16", size = 223114, upload-time = "2025-11-06T00:13:35.209Z" }, ] +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + [[package]] name = "google-genai" -version = "1.52.0" +version = "1.55.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "google-auth" }, + { name = "distro" }, + { name = "google-auth", extra = ["requests"] }, { name = "httpx" }, { name = "pydantic" }, { name = "requests" }, + { name = "sniffio" }, { name = "tenacity" }, { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/4e/0ad8585d05312074bb69711b2d81cfed69ce0ae441913d57bf169bed20a7/google_genai-1.52.0.tar.gz", hash = "sha256:a74e8a4b3025f23aa98d6a0f84783119012ca6c336fd68f73c5d2b11465d7fc5", size = 258743, upload-time = "2025-11-21T02:18:55.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/7c/19b59750592702305ae211905985ec8ab56f34270af4a159fba5f0214846/google_genai-1.55.0.tar.gz", hash = "sha256:ae9f1318fedb05c7c1b671a4148724751201e8908a87568364a309804064d986", size = 477615, upload-time = "2025-12-11T02:49:28.624Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/66/03f663e7bca7abe9ccfebe6cb3fe7da9a118fd723a5abb278d6117e7990e/google_genai-1.52.0-py3-none-any.whl", hash = "sha256:c8352b9f065ae14b9322b949c7debab8562982f03bf71d44130cd2b798c20743", size = 261219, upload-time = "2025-11-21T02:18:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/3e/86/a5a8e32b2d40b30b5fb20e7b8113fafd1e38befa4d1801abd5ce6991065a/google_genai-1.55.0-py3-none-any.whl", hash = "sha256:98c422762b5ff6e16b8d9a1e4938e8e0ad910392a5422e47f5301498d7f373a1", size = 703389, upload-time = "2025-12-11T02:49:27.105Z" }, ] [[package]] @@ -773,15 +788,15 @@ wheels = [ [[package]] name = "httpx-aiohttp" -version = "0.1.9" +version = "0.1.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "httpx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/f2/9a86ce9bc48cf57dabb3a3160dfed26d8bbe5a2478a51f9d1dbf89f2f1fc/httpx_aiohttp-0.1.9.tar.gz", hash = "sha256:4ee8b22e6f2e7c80cd03be29eff98bfe7d89bd77f021ce0b578ee76b73b4bfe6", size = 206023, upload-time = "2025-10-15T08:52:57.475Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2c/bd4daf3646f8b568dfa572b5339e728bcb87141b03c32d626053f940d032/httpx_aiohttp-0.1.11.tar.gz", hash = "sha256:3e2d3a38a44b655b65ed405a2d3e2cbcb4112eb561cd25e073afebb724e2e632", size = 275819, upload-time = "2025-12-11T19:05:37.456Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/db/5cfa8254a86c34a1ab7fe0dbec9f81bb5ebd831cbdd65aa4be4f37027804/httpx_aiohttp-0.1.9-py3-none-any.whl", hash = "sha256:3dc2845568b07742588710fcf3d72db2cbcdf2acc93376edf85f789c4d8e5fda", size = 6180, upload-time = "2025-10-15T08:52:56.521Z" }, + { url = "https://files.pythonhosted.org/packages/bb/0d/a566181727823779ddf2852d6e9ca6607c60ae503c75682dfccf2718a9df/httpx_aiohttp-0.1.11-py3-none-any.whl", hash = "sha256:4589ef76ed5a4a2240462228c57726679a34e0b255905d94cf7e0c98f89e9caf", size = 6366, upload-time = "2025-12-11T19:05:36.093Z" }, ] [[package]] @@ -847,7 +862,7 @@ wheels = [ [[package]] name = "ipython" -version = "9.7.0" +version = "9.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -861,9 +876,9 @@ dependencies = [ { name = "stack-data" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e", size = 4422115, upload-time = "2025-11-05T12:18:54.646Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/51/a703c030f4928646d390b4971af4938a1b10c9dfce694f0d99a0bb073cb2/ipython-9.8.0.tar.gz", hash = "sha256:8e4ce129a627eb9dd221c41b1d2cdaed4ef7c9da8c17c63f6f578fe231141f83", size = 4424940, upload-time = "2025-12-03T10:18:24.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl", hash = "sha256:bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f", size = 618911, upload-time = "2025-11-05T12:18:52.484Z" }, + { url = "https://files.pythonhosted.org/packages/f1/df/8ee1c5dd1e3308b5d5b2f2dfea323bb2f3827da8d654abb6642051199049/ipython-9.8.0-py3-none-any.whl", hash = "sha256:ebe6d1d58d7d988fbf23ff8ff6d8e1622cfdb194daf4b7b73b792c4ec3b85385", size = 621374, upload-time = "2025-12-03T10:18:22.335Z" }, ] [[package]] @@ -996,7 +1011,7 @@ wheels = [ [[package]] name = "keyring" -version = "25.6.0" +version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jaraco-classes" }, @@ -1006,9 +1021,9 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] [[package]] @@ -1189,7 +1204,7 @@ wheels = [ [[package]] name = "openai" -version = "2.9.0" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1201,9 +1216,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/48/516290f38745cc1e72856f50e8afed4a7f9ac396a5a18f39e892ab89dfc2/openai-2.9.0.tar.gz", hash = "sha256:b52ec65727fc8f1eed2fbc86c8eac0998900c7ef63aa2eb5c24b69717c56fa5f", size = 608202, upload-time = "2025-12-04T18:15:09.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/8c/aa6aea6072f985ace9d6515046b9088ff00c157f9654da0c7b1e129d9506/openai-2.11.0.tar.gz", hash = "sha256:b3da01d92eda31524930b6ec9d7167c535e843918d7ba8a76b1c38f1104f321e", size = 624540, upload-time = "2025-12-11T19:11:58.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/fd/ae2da789cd923dd033c99b8d544071a827c92046b150db01cfa5cea5b3fd/openai-2.9.0-py3-none-any.whl", hash = "sha256:0d168a490fbb45630ad508a6f3022013c155a68fd708069b6a1a01a5e8f0ffad", size = 1030836, upload-time = "2025-12-04T18:15:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/d9251b565fce9f8daeb45611e3e0d2f7f248429e40908dcee3b6fe1b5944/openai-2.11.0-py3-none-any.whl", hash = "sha256:21189da44d2e3d027b08c7a920ba4454b8b7d6d30ae7e64d9de11dbe946d4faa", size = 1064131, upload-time = "2025-12-11T19:11:56.816Z" }, ] [package.optional-dependencies] @@ -1214,46 +1229,46 @@ aiohttp = [ [[package]] name = "opentelemetry-api" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/d8/0f354c375628e048bd0570645b310797299754730079853095bf000fba69/opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12", size = 65242, upload-time = "2025-10-16T08:35:50.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/a2/d86e01c28300bd41bab8f18afd613676e2bd63515417b77636fc1add426f/opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582", size = 65947, upload-time = "2025-10-16T08:35:30.23Z" }, + { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, ] [[package]] name = "opentelemetry-distro" -version = "0.59b0" +version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/73/909d18e3d609c9f72fdfc441dbf2f33d26d29126088de5b3df30f4867f8a/opentelemetry_distro-0.59b0.tar.gz", hash = "sha256:a72703a514e1773d35d1ec01489a5fd1f1e7ce92e93cf459ba60f85b880d0099", size = 2583, upload-time = "2025-10-16T08:39:28.111Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/77/f0b1f2bcf451ec5bc443d53bc7437577c3fc8444b3eb0d416ac5f7558b7b/opentelemetry_distro-0.60b1.tar.gz", hash = "sha256:8b7326b83a55ff7b17bb92225a86e2736a004f6af7aff00cb5d87b2d8e5bc283", size = 2584, upload-time = "2025-12-11T13:36:39.522Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/a5/71d78732d30616b0b57cce416fa49e7f25ce57492eaf66d0b6864c1df35f/opentelemetry_distro-0.59b0-py3-none-any.whl", hash = "sha256:bbe568d84d801d7e1ead320c4521fc37a4c24b3b2cd49a64f6d8a3c10676cea4", size = 3346, upload-time = "2025-10-16T08:38:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/24/70/78a86531495040fcad9569d7daa630eca06d27d37c825a8aad448b7c1c5b/opentelemetry_distro-0.60b1-py3-none-any.whl", hash = "sha256:581104a786f5df252f4dfe725e0ae16337a26da902acb92d8b3e7aee29f0c76e", size = 3343, upload-time = "2025-12-11T13:35:28.462Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/83/dd4660f2956ff88ed071e9e0e36e830df14b8c5dc06722dbde1841accbe8/opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c", size = 20431, upload-time = "2025-10-16T08:35:53.285Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/9e/55a41c9601191e8cd8eb626b54ee6827b9c9d4a46d736f32abc80d8039fc/opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a", size = 18359, upload-time = "2025-10-16T08:35:34.099Z" }, + { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, @@ -1264,14 +1279,14 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/0a/debcdfb029fbd1ccd1563f7c287b89a6f7bef3b2902ade56797bfd020854/opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b", size = 17282, upload-time = "2025-10-16T08:35:54.422Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/77/154004c99fb9f291f74aa0822a2f5bbf565a72d8126b3a1b63ed8e5f83c7/opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b", size = 19579, upload-time = "2025-10-16T08:35:36.269Z" }, + { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.59b0" +version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1279,14 +1294,14 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/ed/9c65cd209407fd807fa05be03ee30f159bdac8d59e7ea16a8fe5a1601222/opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc", size = 31544, upload-time = "2025-10-16T08:39:31.959Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/f5/7a40ff3f62bfe715dad2f633d7f1174ba1a7dd74254c15b2558b3401262a/opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee", size = 33020, upload-time = "2025-10-16T08:38:31.463Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, ] [[package]] name = "opentelemetry-instrumentation-anthropic" -version = "0.49.5" +version = "0.49.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1294,14 +1309,14 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-semantic-conventions-ai" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/2a/e0c868ed209711e5bf418f5e829bd51bc6c2ff5760ea05f705bd43eb7443/opentelemetry_instrumentation_anthropic-0.49.5.tar.gz", hash = "sha256:27ab24154ca5866e1db695aea75a88f05b039472d28ae6dd22055271ed3ea417", size = 14924, upload-time = "2025-11-27T12:58:55.078Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/29/953b7fff0c96aa6c6230bf8dcab3ad24387e934fb538eaf94338fd0d4ce1/opentelemetry_instrumentation_anthropic-0.49.8.tar.gz", hash = "sha256:e03c88dd55ec620fa5bbe0f6d93dc96e7547defd7451a593c094a4976da53149", size = 14925, upload-time = "2025-12-11T20:32:38.718Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/57/76009acbf3cfad6dc48947cbe16e03cb490638146599167b5be7c266ae93/opentelemetry_instrumentation_anthropic-0.49.5-py3-none-any.whl", hash = "sha256:64655ce61c7c47d44ef4212af7d0374ddf1314d70002e18ceef4ba9a01603b89", size = 18459, upload-time = "2025-11-27T12:58:18.561Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6d/873542bd500d8d2975b2d80123bbfcae64ff5abedbb6cd2444bfa15ad4d7/opentelemetry_instrumentation_anthropic-0.49.8-py3-none-any.whl", hash = "sha256:7b1170557be421fa4aa3da58c98752790127ea31139e0c58dabcf1562a58a492", size = 18462, upload-time = "2025-12-11T20:31:58.871Z" }, ] [[package]] name = "opentelemetry-instrumentation-google-genai" -version = "0.4b0" +version = "0.5b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1309,14 +1324,14 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-genai" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/d3/00c67e1f3c070c02fd2ffa9db3eed6f03c32fbe5a08280ce155ec2df9b14/opentelemetry_instrumentation_google_genai-0.4b0.tar.gz", hash = "sha256:743776f6ff4133ad8a84d45af956b7dbb1624c58ac136faa054ec8d4059754ee", size = 47411, upload-time = "2025-10-16T15:13:27.695Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/62/b2506d74f50d4d0f150293d171dc1196c9334ba02c51d6ed19d64d0f76c4/opentelemetry_instrumentation_google_genai-0.5b0.tar.gz", hash = "sha256:1986cd1a69dafdcccee15ae9f114e45ff04954951af0fef8b5482e2930fc0b17", size = 47840, upload-time = "2025-12-11T14:50:48.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/5c/19b701f273da6b730df84073abe0ebe42386c6b6d469fab024039d1df4b4/opentelemetry_instrumentation_google_genai-0.4b0-py3-none-any.whl", hash = "sha256:df2c2af64075bd6253cafb71921a58a2f3554b75eef3a74a55d300e0815626ba", size = 29648, upload-time = "2025-10-16T15:13:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/90/9f/a55591e2b41f6c29c4cf4b459617b03318e6d1e9c06f3b3ce7f22b7da8fc/opentelemetry_instrumentation_google_genai-0.5b0-py3-none-any.whl", hash = "sha256:20467a96d7407affc975e63d1175c21a4d33dd83f5ec162dddde6cea9e8f3995", size = 29531, upload-time = "2025-12-11T14:50:47.323Z" }, ] [[package]] name = "opentelemetry-instrumentation-mcp" -version = "0.49.5" +version = "0.49.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1324,14 +1339,14 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-semantic-conventions-ai" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/60/7478741d87ea87066e3ffb3038b58aa3d0c7ad827402d7f1ffc84db6d8c2/opentelemetry_instrumentation_mcp-0.49.5.tar.gz", hash = "sha256:d00469f01746a1216075fc2527c45522007bfd9f8b60caa49cfc4ce45dd59177", size = 8725, upload-time = "2025-11-27T12:59:06.807Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/86/7a8b4bd935f6c2e281afaa7a326edf75508a9f374db7b5ea0d56fd4ee2ed/opentelemetry_instrumentation_mcp-0.49.8.tar.gz", hash = "sha256:927b46e4735e746244845c136061e77d9ebdefde560c811b881fa431fe8d5783", size = 8727, upload-time = "2025-12-11T20:32:49.844Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/31/9f95fca323f10fc7d1c067f61e207273cac2d048ff0a2ddb6d2a88111ab3/opentelemetry_instrumentation_mcp-0.49.5-py3-none-any.whl", hash = "sha256:4156a4d26a6ee05a459224cb3233842fd35ee434ad59d5b5af392ed2a11e9e3a", size = 10521, upload-time = "2025-11-27T12:58:33.987Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d1/d125024c3b5dcceeef9a06aab40f779b69bbd5e74611d9fb6d8672e2c04e/opentelemetry_instrumentation_mcp-0.49.8-py3-none-any.whl", hash = "sha256:4854bd70a9697a410eac2aab26f14d9fb0d21771626ac1009af9327b3407fde4", size = 10522, upload-time = "2025-12-11T20:32:15.842Z" }, ] [[package]] name = "opentelemetry-instrumentation-openai" -version = "0.49.5" +version = "0.49.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1339,48 +1354,48 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-semantic-conventions-ai" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/1d/38ba5253af41f822ffa921b5edf1eb384ed4a0448ea075a6cb4cdfddde1f/opentelemetry_instrumentation_openai-0.49.5.tar.gz", hash = "sha256:f4f7c76af8fbf32f2d79b26b4517def1d9edc110ba143cf68b511f5feed03c72", size = 27876, upload-time = "2025-11-27T12:59:10.578Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/03/a04b74790ae3c5ea80aa257fae07698a9111ad1c58714ef78eb40f070414/opentelemetry_instrumentation_openai-0.49.8.tar.gz", hash = "sha256:2efe4efea59f2708ef3fc470a10d6db11eb7c48328a2729383d9adef89b6b2da", size = 32254, upload-time = "2025-12-11T20:32:53.415Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/9a/42d9f07a38e1cdc82bf861726a57794638b5095194d5468dbeef2db0d19b/opentelemetry_instrumentation_openai-0.49.5-py3-none-any.whl", hash = "sha256:1062c8f087b2900e2f9d9e035df114ae924e5e0ffbf27f8c60cd4b4fc080df20", size = 37832, upload-time = "2025-11-27T12:58:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/85/e7/36e0d15a1dfb94faf5fcc70721c6706ccbcf58323b31395b857884c0eb91/opentelemetry_instrumentation_openai-0.49.8-py3-none-any.whl", hash = "sha256:2555694d0f009b2d43776d718a7467229d49e04bb2ab78e2a9880d52674b8393", size = 43003, upload-time = "2025-12-11T20:32:20.844Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/14/f0c4f0f6371b9cb7f9fa9ee8918bfd59ac7040c7791f1e6da32a1839780d/opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468", size = 46152, upload-time = "2025-10-16T08:36:01.612Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/6a/82b68b14efca5150b2632f3692d627afa76b77378c4999f2648979409528/opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18", size = 72535, upload-time = "2025-10-16T08:35:45.749Z" }, + { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/cb/f0eee1445161faf4c9af3ba7b848cc22a50a3d3e2515051ad8628c35ff80/opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe", size = 171942, upload-time = "2025-10-16T08:36:02.257Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/2e/e93777a95d7d9c40d270a371392b6d6f1ff170c2a3cb32d6176741b5b723/opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b", size = 132349, upload-time = "2025-10-16T08:35:46.995Z" }, + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.59b0" +version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/bc/8b9ad3802cd8ac6583a4eb7de7e5d7db004e89cb7efe7008f9c8a537ee75/opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0", size = 129861, upload-time = "2025-10-16T08:36:03.346Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/7d/c88d7b15ba8fe5c6b8f93be50fc11795e9fc05386c44afaf6b76fe191f9b/opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed", size = 207954, upload-time = "2025-10-16T08:35:48.054Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, ] [[package]] @@ -1438,11 +1453,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] [[package]] @@ -1456,7 +1471,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.4.0" +version = "4.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -1465,9 +1480,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/49/7845c2d7bf6474efd8e27905b51b11e6ce411708c91e829b93f324de9929/pre_commit-4.4.0.tar.gz", hash = "sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15", size = 197501, upload-time = "2025-11-08T21:12:11.607Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/9b/6a4ffb4ed980519da959e1cf3122fc6cb41211daa58dbae1c73c0e519a37/pre_commit-4.5.0.tar.gz", hash = "sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b", size = 198428, upload-time = "2025-11-22T21:02:42.304Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813", size = 226049, upload-time = "2025-11-08T21:12:10.228Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c4/b2d28e9d2edf4f1713eb3c29307f1a63f3d67cf09bdda29715a36a68921a/pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1", size = 226429, upload-time = "2025-11-22T21:02:40.836Z" }, ] [[package]] @@ -1535,17 +1550,17 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.0" +version = "6.33.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954", size = 443463, upload-time = "2025-10-15T20:39:52.159Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035", size = 425593, upload-time = "2025-10-15T20:39:40.29Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee", size = 436882, upload-time = "2025-10-15T20:39:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455", size = 427521, upload-time = "2025-10-15T20:39:43.803Z" }, - { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90", size = 324445, upload-time = "2025-10-15T20:39:44.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298", size = 339159, upload-time = "2025-10-15T20:39:46.186Z" }, - { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef", size = 323172, upload-time = "2025-10-15T20:39:47.465Z" }, - { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, ] [[package]] @@ -1598,7 +1613,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.4" +version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1606,9 +1621,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [[package]] @@ -1684,7 +1699,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.0" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1693,9 +1708,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/1d/eb34f286b164c5e431a810a38697409cca1112cee04b287bb56ac486730b/pytest-9.0.0.tar.gz", hash = "sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e", size = 1562764, upload-time = "2025-11-08T17:25:33.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/99/cafef234114a3b6d9f3aaed0723b437c40c57bdb7b3e4c3a575bc4890052/pytest-9.0.0-py3-none-any.whl", hash = "sha256:e5ccdf10b0bac554970ee88fc1a4ad0ee5d221f8ef22321f9b7e4584e19d7f96", size = 373364, upload-time = "2025-11-08T17:25:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] @@ -1805,15 +1820,15 @@ wheels = [ [[package]] name = "referencing" -version = "0.36.2" +version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] @@ -1882,39 +1897,39 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.28.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/48/dc/95f074d43452b3ef5d06276696ece4b3b5d696e7c9ad7173c54b1390cd70/rpds_py-0.28.0.tar.gz", hash = "sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea", size = 27419, upload-time = "2025-10-22T22:24:29.327Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/03/ce566d92611dfac0085c2f4b048cd53ed7c274a5c05974b882a908d540a2/rpds_py-0.28.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e9e184408a0297086f880556b6168fa927d677716f83d3472ea333b42171ee3b", size = 366235, upload-time = "2025-10-22T22:22:28.397Z" }, - { url = "https://files.pythonhosted.org/packages/00/34/1c61da1b25592b86fd285bd7bd8422f4c9d748a7373b46126f9ae792a004/rpds_py-0.28.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:edd267266a9b0448f33dc465a97cfc5d467594b600fe28e7fa2f36450e03053a", size = 348241, upload-time = "2025-10-22T22:22:30.171Z" }, - { url = "https://files.pythonhosted.org/packages/fc/00/ed1e28616848c61c493a067779633ebf4b569eccaacf9ccbdc0e7cba2b9d/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85beb8b3f45e4e32f6802fb6cd6b17f615ef6c6a52f265371fb916fae02814aa", size = 378079, upload-time = "2025-10-22T22:22:31.644Z" }, - { url = "https://files.pythonhosted.org/packages/11/b2/ccb30333a16a470091b6e50289adb4d3ec656fd9951ba8c5e3aaa0746a67/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2412be8d00a1b895f8ad827cc2116455196e20ed994bb704bf138fe91a42724", size = 393151, upload-time = "2025-10-22T22:22:33.453Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d0/73e2217c3ee486d555cb84920597480627d8c0240ff3062005c6cc47773e/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf128350d384b777da0e68796afdcebc2e9f63f0e9f242217754e647f6d32491", size = 517520, upload-time = "2025-10-22T22:22:34.949Z" }, - { url = "https://files.pythonhosted.org/packages/c4/91/23efe81c700427d0841a4ae7ea23e305654381831e6029499fe80be8a071/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2036d09b363aa36695d1cc1a97b36865597f4478470b0697b5ee9403f4fe399", size = 408699, upload-time = "2025-10-22T22:22:36.584Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ee/a324d3198da151820a326c1f988caaa4f37fc27955148a76fff7a2d787a9/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8e1e9be4fa6305a16be628959188e4fd5cd6f1b0e724d63c6d8b2a8adf74ea6", size = 385720, upload-time = "2025-10-22T22:22:38.014Z" }, - { url = "https://files.pythonhosted.org/packages/19/ad/e68120dc05af8b7cab4a789fccd8cdcf0fe7e6581461038cc5c164cd97d2/rpds_py-0.28.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0a403460c9dd91a7f23fc3188de6d8977f1d9603a351d5db6cf20aaea95b538d", size = 401096, upload-time = "2025-10-22T22:22:39.869Z" }, - { url = "https://files.pythonhosted.org/packages/99/90/c1e070620042459d60df6356b666bb1f62198a89d68881816a7ed121595a/rpds_py-0.28.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d7366b6553cdc805abcc512b849a519167db8f5e5c3472010cd1228b224265cb", size = 411465, upload-time = "2025-10-22T22:22:41.395Z" }, - { url = "https://files.pythonhosted.org/packages/68/61/7c195b30d57f1b8d5970f600efee72a4fad79ec829057972e13a0370fd24/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b43c6a3726efd50f18d8120ec0551241c38785b68952d240c45ea553912ac41", size = 558832, upload-time = "2025-10-22T22:22:42.871Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3d/06f3a718864773f69941d4deccdf18e5e47dd298b4628062f004c10f3b34/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0cb7203c7bc69d7c1585ebb33a2e6074492d2fc21ad28a7b9d40457ac2a51ab7", size = 583230, upload-time = "2025-10-22T22:22:44.877Z" }, - { url = "https://files.pythonhosted.org/packages/66/df/62fc783781a121e77fee9a21ead0a926f1b652280a33f5956a5e7833ed30/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a52a5169c664dfb495882adc75c304ae1d50df552fbd68e100fdc719dee4ff9", size = 553268, upload-time = "2025-10-22T22:22:46.441Z" }, - { url = "https://files.pythonhosted.org/packages/84/85/d34366e335140a4837902d3dea89b51f087bd6a63c993ebdff59e93ee61d/rpds_py-0.28.0-cp313-cp313-win32.whl", hash = "sha256:2e42456917b6687215b3e606ab46aa6bca040c77af7df9a08a6dcfe8a4d10ca5", size = 217100, upload-time = "2025-10-22T22:22:48.342Z" }, - { url = "https://files.pythonhosted.org/packages/3c/1c/f25a3f3752ad7601476e3eff395fe075e0f7813fbb9862bd67c82440e880/rpds_py-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:e0a0311caedc8069d68fc2bf4c9019b58a2d5ce3cd7cb656c845f1615b577e1e", size = 227759, upload-time = "2025-10-22T22:22:50.219Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d6/5f39b42b99615b5bc2f36ab90423ea404830bdfee1c706820943e9a645eb/rpds_py-0.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:04c1b207ab8b581108801528d59ad80aa83bb170b35b0ddffb29c20e411acdc1", size = 217326, upload-time = "2025-10-22T22:22:51.647Z" }, - { url = "https://files.pythonhosted.org/packages/5c/8b/0c69b72d1cee20a63db534be0df271effe715ef6c744fdf1ff23bb2b0b1c/rpds_py-0.28.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f296ea3054e11fc58ad42e850e8b75c62d9a93a9f981ad04b2e5ae7d2186ff9c", size = 355736, upload-time = "2025-10-22T22:22:53.211Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6d/0c2ee773cfb55c31a8514d2cece856dd299170a49babd50dcffb15ddc749/rpds_py-0.28.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5a7306c19b19005ad98468fcefeb7100b19c79fc23a5f24a12e06d91181193fa", size = 342677, upload-time = "2025-10-22T22:22:54.723Z" }, - { url = "https://files.pythonhosted.org/packages/e2/1c/22513ab25a27ea205144414724743e305e8153e6abe81833b5e678650f5a/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5d9b86aa501fed9862a443c5c3116f6ead8bc9296185f369277c42542bd646b", size = 371847, upload-time = "2025-10-22T22:22:56.295Z" }, - { url = "https://files.pythonhosted.org/packages/60/07/68e6ccdb4b05115ffe61d31afc94adef1833d3a72f76c9632d4d90d67954/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5bbc701eff140ba0e872691d573b3d5d30059ea26e5785acba9132d10c8c31d", size = 381800, upload-time = "2025-10-22T22:22:57.808Z" }, - { url = "https://files.pythonhosted.org/packages/73/bf/6d6d15df80781d7f9f368e7c1a00caf764436518c4877fb28b029c4624af/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5690671cd672a45aa8616d7374fdf334a1b9c04a0cac3c854b1136e92374fe", size = 518827, upload-time = "2025-10-22T22:22:59.826Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d3/2decbb2976cc452cbf12a2b0aaac5f1b9dc5dd9d1f7e2509a3ee00421249/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f1d92ecea4fa12f978a367c32a5375a1982834649cdb96539dcdc12e609ab1a", size = 399471, upload-time = "2025-10-22T22:23:01.968Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2c/f30892f9e54bd02e5faca3f6a26d6933c51055e67d54818af90abed9748e/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d252db6b1a78d0a3928b6190156042d54c93660ce4d98290d7b16b5296fb7cc", size = 377578, upload-time = "2025-10-22T22:23:03.52Z" }, - { url = "https://files.pythonhosted.org/packages/f0/5d/3bce97e5534157318f29ac06bf2d279dae2674ec12f7cb9c12739cee64d8/rpds_py-0.28.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d61b355c3275acb825f8777d6c4505f42b5007e357af500939d4a35b19177259", size = 390482, upload-time = "2025-10-22T22:23:05.391Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f0/886bd515ed457b5bd93b166175edb80a0b21a210c10e993392127f1e3931/rpds_py-0.28.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:acbe5e8b1026c0c580d0321c8aae4b0a1e1676861d48d6e8c6586625055b606a", size = 402447, upload-time = "2025-10-22T22:23:06.93Z" }, - { url = "https://files.pythonhosted.org/packages/42/b5/71e8777ac55e6af1f4f1c05b47542a1eaa6c33c1cf0d300dca6a1c6e159a/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8aa23b6f0fc59b85b4c7d89ba2965af274346f738e8d9fc2455763602e62fd5f", size = 552385, upload-time = "2025-10-22T22:23:08.557Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cb/6ca2d70cbda5a8e36605e7788c4aa3bea7c17d71d213465a5a675079b98d/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7b14b0c680286958817c22d76fcbca4800ddacef6f678f3a7c79a1fe7067fe37", size = 575642, upload-time = "2025-10-22T22:23:10.348Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d4/407ad9960ca7856d7b25c96dcbe019270b5ffdd83a561787bc682c797086/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bcf1d210dfee61a6c86551d67ee1031899c0fdbae88b2d44a569995d43797712", size = 544507, upload-time = "2025-10-22T22:23:12.434Z" }, - { url = "https://files.pythonhosted.org/packages/51/31/2f46fe0efcac23fbf5797c6b6b7e1c76f7d60773e525cb65fcbc582ee0f2/rpds_py-0.28.0-cp313-cp313t-win32.whl", hash = "sha256:3aa4dc0fdab4a7029ac63959a3ccf4ed605fee048ba67ce89ca3168da34a1342", size = 205376, upload-time = "2025-10-22T22:23:13.979Z" }, - { url = "https://files.pythonhosted.org/packages/92/e4/15947bda33cbedfc134490a41841ab8870a72a867a03d4969d886f6594a2/rpds_py-0.28.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7b7d9d83c942855e4fdcfa75d4f96f6b9e272d42fffcb72cd4bb2577db2e2907", size = 215907, upload-time = "2025-10-22T22:23:15.5Z" }, +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, ] [[package]] @@ -1931,53 +1946,53 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/55/cccfca45157a2031dcbb5a462a67f7cf27f8b37d4b3b1cd7438f0f5c1df6/ruff-0.14.4.tar.gz", hash = "sha256:f459a49fe1085a749f15414ca76f61595f1a2cc8778ed7c279b6ca2e1fd19df3", size = 5587844, upload-time = "2025-11-06T22:07:45.033Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/b9/67240254166ae1eaa38dec32265e9153ac53645a6c6670ed36ad00722af8/ruff-0.14.4-py3-none-linux_armv6l.whl", hash = "sha256:e6604613ffbcf2297cd5dcba0e0ac9bd0c11dc026442dfbb614504e87c349518", size = 12606781, upload-time = "2025-11-06T22:07:01.841Z" }, - { url = "https://files.pythonhosted.org/packages/46/c8/09b3ab245d8652eafe5256ab59718641429f68681ee713ff06c5c549f156/ruff-0.14.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d99c0b52b6f0598acede45ee78288e5e9b4409d1ce7f661f0fa36d4cbeadf9a4", size = 12946765, upload-time = "2025-11-06T22:07:05.858Z" }, - { url = "https://files.pythonhosted.org/packages/14/bb/1564b000219144bf5eed2359edc94c3590dd49d510751dad26202c18a17d/ruff-0.14.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9358d490ec030f1b51d048a7fd6ead418ed0826daf6149e95e30aa67c168af33", size = 11928120, upload-time = "2025-11-06T22:07:08.023Z" }, - { url = "https://files.pythonhosted.org/packages/a3/92/d5f1770e9988cc0742fefaa351e840d9aef04ec24ae1be36f333f96d5704/ruff-0.14.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b40d27924f1f02dfa827b9c0712a13c0e4b108421665322218fc38caf615c2", size = 12370877, upload-time = "2025-11-06T22:07:10.015Z" }, - { url = "https://files.pythonhosted.org/packages/e2/29/e9282efa55f1973d109faf839a63235575519c8ad278cc87a182a366810e/ruff-0.14.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5e649052a294fe00818650712083cddc6cc02744afaf37202c65df9ea52efa5", size = 12408538, upload-time = "2025-11-06T22:07:13.085Z" }, - { url = "https://files.pythonhosted.org/packages/8e/01/930ed6ecfce130144b32d77d8d69f5c610e6d23e6857927150adf5d7379a/ruff-0.14.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa082a8f878deeba955531f975881828fd6afd90dfa757c2b0808aadb437136e", size = 13141942, upload-time = "2025-11-06T22:07:15.386Z" }, - { url = "https://files.pythonhosted.org/packages/6a/46/a9c89b42b231a9f487233f17a89cbef9d5acd538d9488687a02ad288fa6b/ruff-0.14.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1043c6811c2419e39011890f14d0a30470f19d47d197c4858b2787dfa698f6c8", size = 14544306, upload-time = "2025-11-06T22:07:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/78/96/9c6cf86491f2a6d52758b830b89b78c2ae61e8ca66b86bf5a20af73d20e6/ruff-0.14.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9f3a936ac27fb7c2a93e4f4b943a662775879ac579a433291a6f69428722649", size = 14210427, upload-time = "2025-11-06T22:07:19.832Z" }, - { url = "https://files.pythonhosted.org/packages/71/f4/0666fe7769a54f63e66404e8ff698de1dcde733e12e2fd1c9c6efb689cb5/ruff-0.14.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95643ffd209ce78bc113266b88fba3d39e0461f0cbc8b55fb92505030fb4a850", size = 13658488, upload-time = "2025-11-06T22:07:22.32Z" }, - { url = "https://files.pythonhosted.org/packages/ee/79/6ad4dda2cfd55e41ac9ed6d73ef9ab9475b1eef69f3a85957210c74ba12c/ruff-0.14.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:456daa2fa1021bc86ca857f43fe29d5d8b3f0e55e9f90c58c317c1dcc2afc7b5", size = 13354908, upload-time = "2025-11-06T22:07:24.347Z" }, - { url = "https://files.pythonhosted.org/packages/b5/60/f0b6990f740bb15c1588601d19d21bcc1bd5de4330a07222041678a8e04f/ruff-0.14.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f911bba769e4a9f51af6e70037bb72b70b45a16db5ce73e1f72aefe6f6d62132", size = 13587803, upload-time = "2025-11-06T22:07:26.327Z" }, - { url = "https://files.pythonhosted.org/packages/c9/da/eaaada586f80068728338e0ef7f29ab3e4a08a692f92eb901a4f06bbff24/ruff-0.14.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76158a7369b3979fa878612c623a7e5430c18b2fd1c73b214945c2d06337db67", size = 12279654, upload-time = "2025-11-06T22:07:28.46Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/b1d0e82cf9bf8aed10a6d45be47b3f402730aa2c438164424783ac88c0ed/ruff-0.14.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f3b8f3b442d2b14c246e7aeca2e75915159e06a3540e2f4bed9f50d062d24469", size = 12357520, upload-time = "2025-11-06T22:07:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/04/f4/53e2b42cc82804617e5c7950b7079d79996c27e99c4652131c6a1100657f/ruff-0.14.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c62da9a06779deecf4d17ed04939ae8b31b517643b26370c3be1d26f3ef7dbde", size = 12719431, upload-time = "2025-11-06T22:07:33.831Z" }, - { url = "https://files.pythonhosted.org/packages/a2/94/80e3d74ed9a72d64e94a7b7706b1c1ebaa315ef2076fd33581f6a1cd2f95/ruff-0.14.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a443a83a1506c684e98acb8cb55abaf3ef725078be40237463dae4463366349", size = 13464394, upload-time = "2025-11-06T22:07:35.905Z" }, - { url = "https://files.pythonhosted.org/packages/54/1a/a49f071f04c42345c793d22f6cf5e0920095e286119ee53a64a3a3004825/ruff-0.14.4-py3-none-win32.whl", hash = "sha256:643b69cb63cd996f1fc7229da726d07ac307eae442dd8974dbc7cf22c1e18fff", size = 12493429, upload-time = "2025-11-06T22:07:38.43Z" }, - { url = "https://files.pythonhosted.org/packages/bc/22/e58c43e641145a2b670328fb98bc384e20679b5774258b1e540207580266/ruff-0.14.4-py3-none-win_amd64.whl", hash = "sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c", size = 13635380, upload-time = "2025-11-06T22:07:40.496Z" }, - { url = "https://files.pythonhosted.org/packages/30/bd/4168a751ddbbf43e86544b4de8b5c3b7be8d7167a2a5cb977d274e04f0a1/ruff-0.14.4-py3-none-win_arm64.whl", hash = "sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb", size = 12663065, upload-time = "2025-11-06T22:07:42.603Z" }, +version = "0.14.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" }, + { url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" }, + { url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" }, + { url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" }, + { url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" }, + { url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" }, + { url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" }, + { url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" }, + { url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" }, ] [[package]] name = "s3transfer" -version = "0.14.0" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/74/8d69dcb7a9efe8baa2046891735e5dfe433ad558ae23d9e3c14c633d1d58/s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125", size = 151547, upload-time = "2025-09-09T19:23:31.089Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/f0/ae7ca09223a81a1d890b2557186ea015f6e0502e9b8cb8e1813f1d8cfa4e/s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", size = 85712, upload-time = "2025-09-09T19:23:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, ] [[package]] name = "secretstorage" -version = "3.4.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "jeepney" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748, upload-time = "2025-09-09T16:42:13.859Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, ] [[package]] @@ -2035,14 +2050,14 @@ wheels = [ [[package]] name = "starlette" -version = "0.49.3" +version = "0.50.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/1a/608df0b10b53b0beb96a37854ee05864d182ddd4b1156a22f1ad3860425a/starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284", size = 2655031, upload-time = "2025-11-01T15:12:26.13Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/e0/021c772d6a662f43b63044ab481dc6ac7592447605b5b35a957785363122/starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f", size = 74340, upload-time = "2025-11-01T15:12:24.387Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] [[package]] @@ -2056,26 +2071,27 @@ wheels = [ [[package]] name = "tensorzero" -version = "2025.11.3" +version = "2025.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "dacite" }, { name = "httpx" }, { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/7b/a48f8ce7ca31df563a5217629b284dfc65c6cac8a960c8d57f0ac1995a24/tensorzero-2025.11.3.tar.gz", hash = "sha256:59c6ecc42755712df0170ffaa37b3e17d14a1864bd2022fdcd1ef838b7d32bb4", size = 1230588, upload-time = "2025-11-11T14:24:16.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/69/31ba5a8c6fb17d5ef9827eba546860f579a25f75d1f67bab4cea95cfca29/tensorzero-2025.12.0.tar.gz", hash = "sha256:be070f1a011d5f9439c599db47e7d6b45092341dfc70976de24221f251554b8e", size = 1488586, upload-time = "2025-12-11T19:45:01.97Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/75/32b65e0b1ada603fc04d25b706db9be067ded81ec1d09b5cbf592fe40ce5/tensorzero-2025.11.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:b37c708a9ad64052a01180e59c7e47bd1cd66626941e22f030ee60afe6daa443", size = 27349017, upload-time = "2025-11-10T16:19:39.995Z" }, - { url = "https://files.pythonhosted.org/packages/25/be/456e9b5545a58680b8eab9ae845fa2bf36399c3ff7eb237ddebaf8f71c8f/tensorzero-2025.11.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f95556807fa0b3bdc122d0242ffdef96281e0ce0f872f67844e919fc9519cdef", size = 30221936, upload-time = "2025-11-10T16:19:34.498Z" }, - { url = "https://files.pythonhosted.org/packages/53/22/42bf8f225e3ffd73f5668b614f2e13223de3f6d892db08e5463c24c557e2/tensorzero-2025.11.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1122f599bc7492383da439e6355e6c6bf39ae6b158b9899c5686a1f1d5293985", size = 30371896, upload-time = "2025-11-10T16:19:37.663Z" }, - { url = "https://files.pythonhosted.org/packages/0a/16/1b53d04d2c37051b3682c9efae8105ffd00ef29cf557c08f7d2a9675323d/tensorzero-2025.11.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ccbcb45949593cfdb7e7653a9b6366beb077f0cd34e04e0297003eae669a66d4", size = 30418456, upload-time = "2025-11-10T16:19:42.416Z" }, - { url = "https://files.pythonhosted.org/packages/28/e8/1c5a31b9e7e3893b64de8e95b6cbd2cbbb5b3276df5f2fce7fdc88f21bec/tensorzero-2025.11.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:10c308dc8b9ec50855592f1cdb4cf7f526957cea447d9382e89a3f7c30f1df8e", size = 30874808, upload-time = "2025-11-10T16:19:44.857Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a1/c7ab519399b8a4cef596a80c5aed3054cdf3ff648a50ea4b363eae3fe2f7/tensorzero-2025.11.3-cp39-abi3-win_amd64.whl", hash = "sha256:61eb11eedd43aeda1eba2a123a417563fa38a2f869f6cbe45c53ad872e9fea76", size = 26506233, upload-time = "2025-11-11T14:24:18.403Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/e8f8e61a026dca6741587c7a21d867659cd779beccf7ca24601d2cb77fce/tensorzero-2025.12.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0968d9e9a0f5d20573708a34dda33db540e505e136098bf82f008f51da61da1c", size = 33394004, upload-time = "2025-12-11T19:44:54.711Z" }, + { url = "https://files.pythonhosted.org/packages/26/6d/e10135f2b7875d12cf2ed4def6b6ac5a350bef41afc5ac5d330915e7a7dc/tensorzero-2025.12.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8602ed5daf2860695eb6ab468cbd46b0be822504765ff66164de548df3feff9f", size = 35749043, upload-time = "2025-12-11T19:44:49.131Z" }, + { url = "https://files.pythonhosted.org/packages/66/0e/e497a38b0acf4f0ebbcfe7ab976cb5dc99ed455281e2bb729017acc09948/tensorzero-2025.12.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67a6d821e59d544584afad8e61d146f798a47c9f25771ee793d65151a4bf075", size = 36003266, upload-time = "2025-12-11T19:44:52.011Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/c1cb003abf705af41aea005768f8999a728c79dd2c7e5584763f08ea2cf5/tensorzero-2025.12.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:74ecdfe4ce7bcdb4e716335542035eb58263cc117ba83b957aa54b0282d5eae5", size = 35942779, upload-time = "2025-12-11T19:44:56.892Z" }, + { url = "https://files.pythonhosted.org/packages/9f/9b/db84f608ed15ebd47bc7026796fe8fc7299306ebf590687b6e49df14a8ad/tensorzero-2025.12.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b94ebd4fdb613d89a860a38ae4d9b661702d3531d6ecee67739054544981ae34", size = 36496902, upload-time = "2025-12-11T19:44:59.994Z" }, + { url = "https://files.pythonhosted.org/packages/8f/bc/1097d7295afc59fb1511966b6434a09c7ee12627ba55d57d6f9bdc4e6386/tensorzero-2025.12.0-cp310-abi3-win_amd64.whl", hash = "sha256:643548debcdbfc7a6f7616aac06b9fc964a5606ec883426638548f0f91c9ae4d", size = 32618400, upload-time = "2025-12-11T19:45:03.477Z" }, ] [[package]] name = "textual" -version = "6.6.0" +version = "6.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", extra = ["linkify"] }, @@ -2085,9 +2101,9 @@ dependencies = [ { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/2f/f0b408f227edca21d1996c1cd0b65309f0cbff44264aa40aded3ff9ce2e1/textual-6.6.0.tar.gz", hash = "sha256:53345166d6b0f9fd028ed0217d73b8f47c3a26679a18ba3b67616dcacb470eec", size = 1579327, upload-time = "2025-11-10T17:50:00.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/8f/aeccf7459e3d71cbca912a27a97f1fcb00735326f90714d22fa540d3848e/textual-6.8.0.tar.gz", hash = "sha256:7efe618ec9197466b8fe536aefabb678edf30658b9dc58a763365d7daed12b62", size = 1581639, upload-time = "2025-12-07T17:53:46.681Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/b3/95ab646b0c908823d71e49ab8b5949ec9f33346cee3897d1af6be28a8d91/textual-6.6.0-py3-none-any.whl", hash = "sha256:5a9484bd15ee8a6fd8ac4ed4849fb25ee56bed2cecc7b8a83c4cd7d5f19515e5", size = 712606, upload-time = "2025-11-10T17:49:58.391Z" }, + { url = "https://files.pythonhosted.org/packages/47/34/4f1bad936ac3ad94c8576b15660d4ce434f7dbd372baa53566a490bcdce3/textual-6.8.0-py3-none-any.whl", hash = "sha256:074d389ba8c6c98c74e2a4fe1493ea3a38f3ee5008697e98f71daa2cf8ab8fda", size = 714378, upload-time = "2025-12-07T17:53:44.501Z" }, ] [[package]] @@ -2184,31 +2200,33 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, ] [[package]] name = "uuid-utils" -version = "0.11.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/ef/b6c1fd4fee3b2854bf9d602530ab8b6624882e2691c15a9c4d22ea8c03eb/uuid_utils-0.11.1.tar.gz", hash = "sha256:7ef455547c2ccb712840b106b5ab006383a9bfe4125ba1c5ab92e47bcbf79b46", size = 19933, upload-time = "2025-10-02T13:32:09.526Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/f5/254d7ce4b3aa4a1a3a4f279e0cc74eec8b4d3a61641d8ffc6e983907f2ca/uuid_utils-0.11.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4bc8cf73c375b9ea11baf70caacc2c4bf7ce9bfd804623aa0541e5656f3dbeaf", size = 581019, upload-time = "2025-10-02T13:31:32.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/e6/f7d14c4e1988d8beb3ac9bd773f370376c704925bdfb07380f5476bb2986/uuid_utils-0.11.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0d2cb3bcc6f5862d08a0ee868b18233bc63ba9ea0e85ea9f3f8e703983558eba", size = 294377, upload-time = "2025-10-02T13:31:34.01Z" }, - { url = "https://files.pythonhosted.org/packages/8e/40/847a9a0258e7a2a14b015afdaa06ee4754a2680db7b74bac159d594eeb18/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463400604f623969f198aba9133ebfd717636f5e34257340302b1c3ff685dc0f", size = 328070, upload-time = "2025-10-02T13:31:35.619Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/c5d342d31860c9b4f481ef31a4056825961f9b462d216555e76dcee580ea/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aef66b935342b268c6ffc1796267a1d9e73135740a10fe7e4098e1891cbcc476", size = 333610, upload-time = "2025-10-02T13:31:37.058Z" }, - { url = "https://files.pythonhosted.org/packages/e1/4b/52edc023ffcb9ab9a4042a58974a79c39ba7a565e683f1fd9814b504cf13/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd65c41b81b762278997de0d027161f27f9cc4058fa57bbc0a1aaa63a63d6d1a", size = 475669, upload-time = "2025-10-02T13:31:38.38Z" }, - { url = "https://files.pythonhosted.org/packages/59/81/ee55ee63264531bb1c97b5b6033ad6ec81b5cd77f89174e9aef3af3d8889/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfac9d5d7522d61accabb8c68448ead6407933415e67e62123ed6ed11f86510", size = 331946, upload-time = "2025-10-02T13:31:39.66Z" }, - { url = "https://files.pythonhosted.org/packages/cf/07/5d4be27af0e9648afa512f0d11bb6d96cb841dd6d29b57baa3fbf55fd62e/uuid_utils-0.11.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:003f48f05c01692d0c1f7e413d194e7299a1a364e0047a4eb904d3478b84eca1", size = 352920, upload-time = "2025-10-02T13:31:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/5b/48/a69dddd9727512b0583b87bfff97d82a8813b28fb534a183c9e37033cfef/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a5c936042120bdc30d62f539165beaa4a6ba7e817a89e5409a6f06dc62c677a9", size = 509413, upload-time = "2025-10-02T13:31:42.547Z" }, - { url = "https://files.pythonhosted.org/packages/66/0d/1b529a3870c2354dd838d5f133a1cba75220242b0061f04a904ca245a131/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:2e16dcdbdf4cd34ffb31ead6236960adb50e6c962c9f4554a6ecfdfa044c6259", size = 529454, upload-time = "2025-10-02T13:31:44.338Z" }, - { url = "https://files.pythonhosted.org/packages/bd/f2/04a3f77c85585aac09d546edaf871a4012052fb8ace6dbddd153b4d50f02/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f8b21fed11b23134502153d652c77c3a37fa841a9aa15a4e6186d440a22f1a0e", size = 498084, upload-time = "2025-10-02T13:31:45.601Z" }, - { url = "https://files.pythonhosted.org/packages/89/08/538b380b4c4b220f3222c970930fe459cc37f1dfc6c8dc912568d027f17d/uuid_utils-0.11.1-cp39-abi3-win32.whl", hash = "sha256:72abab5ab27c1b914e3f3f40f910532ae242df1b5f0ae43f1df2ef2f610b2a8c", size = 174314, upload-time = "2025-10-02T13:31:47.269Z" }, - { url = "https://files.pythonhosted.org/packages/00/66/971ec830094ac1c7d46381678f7138c1805015399805e7dd7769c893c9c8/uuid_utils-0.11.1-cp39-abi3-win_amd64.whl", hash = "sha256:5ed9962f8993ef2fd418205f92830c29344102f86871d99b57cef053abf227d9", size = 179214, upload-time = "2025-10-02T13:31:48.344Z" }, +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/0e/512fb221e4970c2f75ca9dae412d320b7d9ddc9f2b15e04ea8e44710396c/uuid_utils-0.12.0.tar.gz", hash = "sha256:252bd3d311b5d6b7f5dfce7a5857e27bb4458f222586bb439463231e5a9cbd64", size = 20889, upload-time = "2025-12-01T17:29:55.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/43/de5cd49a57b6293b911b6a9a62fc03e55db9f964da7d5882d9edbee1e9d2/uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3b9b30707659292f207b98f294b0e081f6d77e1fbc760ba5b41331a39045f514", size = 603197, upload-time = "2025-12-01T17:29:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/02/fa/5fd1d8c9234e44f0c223910808cde0de43bb69f7df1349e49b1afa7f2baa/uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:add3d820c7ec14ed37317375bea30249699c5d08ff4ae4dbee9fc9bce3bfbf65", size = 305168, upload-time = "2025-12-01T17:29:31.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c6/8633ac9942bf9dc97a897b5154e5dcffa58816ec4dd780b3b12b559ff05c/uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8fce83ecb3b16af29c7809669056c4b6e7cc912cab8c6d07361645de12dd79", size = 340580, upload-time = "2025-12-01T17:29:32.362Z" }, + { url = "https://files.pythonhosted.org/packages/f3/88/8a61307b04b4da1c576373003e6d857a04dade52ab035151d62cb84d5cb5/uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec921769afcb905035d785582b0791d02304a7850fbd6ce924c1a8976380dfc6", size = 346771, upload-time = "2025-12-01T17:29:33.708Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fb/aab2dcf94b991e62aa167457c7825b9b01055b884b888af926562864398c/uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f3b060330f5899a92d5c723547dc6a95adef42433e9748f14c66859a7396664", size = 474781, upload-time = "2025-12-01T17:29:35.237Z" }, + { url = "https://files.pythonhosted.org/packages/5a/7a/dbd5e49c91d6c86dba57158bbfa0e559e1ddf377bb46dcfd58aea4f0d567/uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:908dfef7f0bfcf98d406e5dc570c25d2f2473e49b376de41792b6e96c1d5d291", size = 343685, upload-time = "2025-12-01T17:29:36.677Z" }, + { url = "https://files.pythonhosted.org/packages/1a/19/8c4b1d9f450159733b8be421a4e1fb03533709b80ed3546800102d085572/uuid_utils-0.12.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c6a24148926bd0ca63e8a2dabf4cc9dc329a62325b3ad6578ecd60fbf926506", size = 366482, upload-time = "2025-12-01T17:29:37.979Z" }, + { url = "https://files.pythonhosted.org/packages/82/43/c79a6e45687647f80a159c8ba34346f287b065452cc419d07d2212d38420/uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:64a91e632669f059ef605f1771d28490b1d310c26198e46f754e8846dddf12f4", size = 523132, upload-time = "2025-12-01T17:29:39.293Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a2/b2d75a621260a40c438aa88593827dfea596d18316520a99e839f7a5fb9d/uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:93c082212470bb4603ca3975916c205a9d7ef1443c0acde8fbd1e0f5b36673c7", size = 614218, upload-time = "2025-12-01T17:29:40.315Z" }, + { url = "https://files.pythonhosted.org/packages/13/6b/ba071101626edd5a6dabf8525c9a1537ff3d885dbc210540574a03901fef/uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:431b1fb7283ba974811b22abd365f2726f8f821ab33f0f715be389640e18d039", size = 546241, upload-time = "2025-12-01T17:29:41.656Z" }, + { url = "https://files.pythonhosted.org/packages/01/12/9a942b81c0923268e6d85bf98d8f0a61fcbcd5e432fef94fdf4ce2ef8748/uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2ffd7838c40149100299fa37cbd8bab5ee382372e8e65a148002a37d380df7c8", size = 511842, upload-time = "2025-12-01T17:29:43.107Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a7/c326f5163dd48b79368b87d8a05f5da4668dd228a3f5ca9d79d5fee2fc40/uuid_utils-0.12.0-cp39-abi3-win32.whl", hash = "sha256:487f17c0fee6cbc1d8b90fe811874174a9b1b5683bf2251549e302906a50fed3", size = 179088, upload-time = "2025-12-01T17:29:44.492Z" }, + { url = "https://files.pythonhosted.org/packages/38/92/41c8734dd97213ee1d5ae435cf4499705dc4f2751e3b957fd12376f61784/uuid_utils-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:9598e7c9da40357ae8fffc5d6938b1a7017f09a1acbcc95e14af8c65d48c655a", size = 183003, upload-time = "2025-12-01T17:29:45.47Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/52ab0359618987331a1f739af837d26168a4b16281c9c3ab46519940c628/uuid_utils-0.12.0-cp39-abi3-win_arm64.whl", hash = "sha256:c9bea7c5b2aa6f57937ebebeee4d4ef2baad10f86f1b97b58a3f6f34c14b4e84", size = 182975, upload-time = "2025-12-01T17:29:46.444Z" }, ] [[package]]