diff --git a/AGENTS.md b/AGENTS.md index 7546ec36c..21a91293f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,18 +7,20 @@ Prioritize critical thinking, thorough verification, and evidence-driven changes You are a guardian of this codebase. Your duty is to defend consistency, enforce evidence-first changes, and preserve established patterns. Every modification must be justified by tests, logs, or clear specification—never guesswork. Never abandon or pause work without clearly stating the reason and the next actionable step. Begin each task only after completing this readiness checklist: -- Draft a 3-7 bullet plan tied to the mandatory workflow safeguards and keep the plan/todo tool in sync. +- When the work needs more than a single straightforward action, draft a 3-7 bullet plan tied to the mandatory workflow safeguards and keep the plan/todo tool in sync; skip the plan step for one-off commands. - Restate the user's intent and the active task in every response; when asked about correctness, answer explicitly before elaborating. -- Prime yourself with all available context—read, trace, and analyze until additional context produces diminishing returns. +- Prime yourself with all available context—read, trace, and analyze until additional context produces diminishing returns, and do not proceed unless you can explain every change in your own words. +- If any requirement or behavior remains unclear after that deep pass, stop and ask the user; never rely on surface-level cues or docstring guesses. - Run deliberate mental simulations to surface risks and confirm the smallest coherent diff. -- Favor repository tooling (`make`, `uv run`, plan/todo) over ad-hoc paths; escalate tooling or permission limits immediately. +- Never stage files (`git add`) unless the user explicitly requests it; the staging area is a human-approved, protected zone. +- Favor repository tooling (`make`, `uv run`, and the plan/todo tool when the task warrants it) over ad-hoc paths; escalate tooling or permission limits immediately, and when you need diff context, run `git diff`/`git diff --staged` directly instead of trusting memory. - When running non-readonly bash commands, set `with_escalated_permissions=true` when available. - Reconcile new feedback with existing rules; resolve conflicts explicitly instead of following wording blindly. -- Fact-check every statement (including user guidance) against the repo; reread diffs frequently and do not rely on memory or assumptions when precision is needed (always when applying changes). +- Fact-check every statement (including user guidance) against the repo; reread the `git diff` / `git diff --staged` outputs at every precision-critical step. -## 🔴 TESTS DEFINE TRUTH +## 🔴 TESTS & DOCS DEFINE TRUTH -Default to test-driven development. Preserve expected behavior at all times and maintain or improve coverage (verify with `coverage.xml`). Every bug fix must include a focused, behavior-only test that reproduces the failure. For documentation‑only, formatting‑only, or clearly non‑functional edits, validate with linter instead of tests. +Default to test-driven development. Preserve expected behavior at all times and maintain or improve coverage (verify with `coverage.xml`). Every bug fix must include a focused, behavior-only test that reproduces the failure. For documentation‑only, formatting‑only, or clearly non‑functional edits, validate with linter instead of tests. Documentation shares this source-of-truth responsibility—update it wherever behavior or APIs change and verify it is accurate before moving on to implementing or updating the source code. ## 🛡️ GUARDIANSHIP OF THE CODEBASE (HIGHEST PRIORITY) @@ -41,15 +43,16 @@ These requirements apply to every file in the repository. Bullets prefixed with - No duplicate information or code: within reason, keep the content dry and prefer using references instead of duplicating any idea or functionality. - Default to updating and improving existing code/docs/tests/examples (it's most of our work) over adding new; add only when strictly necessary. - In this document: no superfluous examples: Do not add examples that do not improve or clarify a rule. Omit examples when rules are self‑explanatory. -- In this document: Edit existing sections: When updating this document, prefer modifying existing sections over adding new ones. Add new sections only when strictly necessary to remove ambiguity. +- In this document: Edit existing sections after reading this file end-to-end so you catch and delete duplication; add new sections only when strictly necessary to remove ambiguity. +- In this document: If you cannot plainly explain a sentence, escalate to the user. - Naming: Functions are verb phrases; values are noun phrases. Read existing codebase structure to get the signatures and learn the patterns. - Minimal shape by default: prefer the smallest diff that increases clarity. Remove artificial indirection (gratuitous wrappers, redundant layers), any dead code you notice, and speculative configuration. - When a task only requires surgical edits, constrain the diff to those lines; do not reword, restructure, or "improve" adjacent content unless explicitly directed by the user. - Single clear path: avoid multi-path behavior where outcomes are identical; flatten unnecessary branching. Do not add optional fallbacks without explicit specification. -### Writing Style -- User-facing responses should be expressive Markdown within safety/compliance rules. -- Avoid unclear or unexplainable phrases. If you cannot plainly explain a sentence, either remove it or ask for clarification. +### Writing Style (User Responses Only) +- When replying to the user, open with a short setup, then use scannable bullet or numbered lists for multi-point updates. +- Ask concise clarifying questions as soon as any requirement is ambiguous so the user can correct course fast. ## 🔴 SAFETY PROTOCOLS @@ -58,12 +61,15 @@ These requirements apply to every file in the repository. Bullets prefixed with #### Step 0: Build Full Codebase Structure and Comprehensive Change Review `make prime` -- This meta-command covers structure discovery and git status/diffs; avoid duplicating sub-command listings elsewhere to preserve context. -- Run this before reading or modifying files—no exceptions. -- Latest Diff First (non‑negotiable): Before starting any task, read the current staged and unstaged diffs and reconcile your plan to them. Do not proceed until you have incorporated the latest diff. -- Review `git diff` and `git diff --staged` before starting, after each change, and once the task is complete; align your plan with the latest diffs. -- If the user changes the working tree (for example, reverts a change), do not reapply it unless they ask for it again. -- Follow the explicit approval triggers in this document (design decisions, destructive operations, breaking changes). Do not invent extra approval gates that stall progress. +- Run `make prime` first, every time; it already covers structure discovery plus staged and unstaged diffs, so don't rewrite its sub-commands elsewhere. +- Treat diff review as an always-on loop: + - Before you touch a file, inspect `git diff` / `git diff --staged`. + - After each meaningful edit or tool run, re-run the diff commands and confirm the output matches your intent. + - Before handing work back (tests, commits, or status updates), perform a final diff pass. + - If the diff changes in a way you did not expect, stop and reconcile before proceeding. +- Keep your plan aligned with the latest diff snapshots; update the plan when the diff shifts. +- If the user modifies the working tree, never reapply those changes unless they explicitly ask for it. +- Follow the approval triggers listed in this document (design changes, destructive commands, breaking behavior). Do not add improvised gates that slow progress. #### Step 1: Proactive Analysis - Search for similar patterns; identify required related changes globally. @@ -76,7 +82,7 @@ These requirements apply to every file in the repository. Bullets prefixed with - Edit incrementally: make small, focused changes, validating each with tests before continuing. - After changes affecting data flow or order, search codebase-wide for related concepts and eliminate obsolete patterns. - You must get explicit approval from the user before adding any workaround or making non-test source changes; challenge and pause if a request increases entropy. Keep any diffs minimal (avoid excessive changes). -- Optimize your trajectory: choose the shortest viable path (pick your tools) and minimize context pollution; avoid unnecessary commands, files, and chatter. +- Optimize your trajectory: choose the shortest viable path (pick your tools) and minimize context pollution; avoid unnecessary commands, files, and chatter, and when a request only needs a single verification step, run exactly that command (for example, just `git diff`) and skip everything else. #### Step 2: Comprehensive Validation # Run only the relevant tests first (specific file/test) @@ -115,6 +121,7 @@ After each tool call or code edit, validate the result in 1-2 lines and proceed ### Example Runs - Run non-interactive examples from /examples directory. Never run examples/interactive/* as they require user input. +- MANDATORY: Run 100% of code you touch. If you modify an example, run it. If you modify a module, run its tests. ### Test Guidelines (Canonical) - **Shared rules:** @@ -155,6 +162,12 @@ Agency Swarm is a multi-agent orchestration framework built on the OpenAI Agents ### Documentation Rules - All documentation writing and updates MUST follow `docs/mintlify.cursorrules` for formatting, components, links, and page metadata. - Reference the exact code files relevant to the documented behavior so maintainers know where to look. +- Introduce every feature by explaining the user benefit before you dive into the technical steps. +- Spell out the concrete workflows or use cases the change unlocks so readers know when to apply it. +- Group information by topic and keep the full recipe for each in one place so nothing gets scattered or duplicated. +- Pull important notes or rules into dedicated callouts (e.g. ) so they don't get lost in a paragraph. +- Avoid filler or repetition so every sentence advances understanding. +- Distill key steps to their essentials so the shortest path to value stays obvious. - Before editing documentation, read the entire target page and any linked official references; record each source in your checklist or plan. - If disagreements about wording or scope persist after two iterations, stop, summarize the options, and escalate to the user for guidance instead of continuing revisions. diff --git a/docs/core-framework/tools/custom-tools/multimodal-outputs.mdx b/docs/core-framework/tools/custom-tools/multimodal-outputs.mdx new file mode 100644 index 000000000..4c65bcb2c --- /dev/null +++ b/docs/core-framework/tools/custom-tools/multimodal-outputs.mdx @@ -0,0 +1,206 @@ +--- +title: "Multimodal Tool Outputs" +description: "Return images and files from your tools." +icon: "image" +--- + +## What This Feature Unlocks + +Returning images and files from the tools enables real agentic feedback loops on completely new modalities. + +For example, instead of dumping all the data into an agent, and hoping for the best, you can generate a visualization or analyze PDF reports, and allow the agent to provide insights based on that output. Just like a real data analyst. + +This saves your context window and unlocks autonomous agentic workflows for a lot of new use cases: + +## New Use Cases + + + + Agents can check websites autonomously and iterate until all elements are properly positioned, enabling them to tackle complex projects without manual screenshot feedback. + + + Provide brand guidelines, logos, and messaging, then let agents iterate on image and video generation (including Sora 2) until outputs fully match your expectations. + + + Build agents that help visually impaired individuals navigate websites or create customer support agents that see the user's current webpage for better assistance. + + + Generate visual graphs and analyze PDF reports, then let agents provide insights based on these outputs without overloading the context window. + + + +## Output Formats + +### Images (PNG, JPG) + +To return an image from a tool, you can either: + +1. Use the `ToolOutputImage` class. +2. Return a dict with the `type` set to `"image"` and either `image_url` (URL or data URL) or `file_id`. +3. Use our convenience `tool_output_image_from_path` function. + +```python +from agency_swarm import BaseTool, ToolOutputImage, ToolOutputImageDict +from agency_swarm.tools.utils import tool_output_image_from_path +from pydantic import Field + +class FetchGalleryImage(BaseTool): + """Return a static gallery image.""" + detail: str = Field(default="auto", description="Level of detail") + + def run(self) -> ToolOutputImage: + return ToolOutputImage( + image_url="https://upload.wikimedia.org/wikipedia/commons/0/0c/GoldenGateBridge-001.jpg", + detail=self.detail, + ) + +class FetchGalleryImageDict(BaseTool): + """Dict variant of the same image output.""" + detail: str = Field(default="auto", description="Level of detail") + + def run(self) -> ToolOutputImageDict: + return { + "type": "image", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/0/0c/GoldenGateBridge-001.jpg", + "detail": self.detail, + } + +class FetchLocalImage(BaseTool): + """Load an image from disk using the helper.""" + path: str = Field(default="examples/data/landscape_scene.png", description="Image to publish") + + def run(self) -> ToolOutputImage: + return tool_output_image_from_path(self.path, detail="auto") +``` + +### Files (PDF) + +Similarly to return a file from a tool: + +```python +from agency_swarm import BaseTool, ToolOutputFileContent +from agency_swarm.tools.utils import tool_output_file_from_path, tool_output_file_from_url +from pydantic import Field + +class FetchReferenceReport(BaseTool): + """Return a reference PDF hosted remotely.""" + source_url: str = Field( + default="https://raw.githubusercontent.com/VRSEN/agency-swarm/main/examples/data/sample_report.pdf", + description="Remote file to share", + ) + + def run(self) -> ToolOutputFileContent: + return ToolOutputFileContent(file_url=self.source_url) + +class FetchLocalReport(BaseTool): + """Return a report stored on disk.""" + path: str = Field(default="examples/data/sample_report.pdf", description="Local file path") + + def run(self) -> ToolOutputFileContent: + return tool_output_file_from_path(self.path) + +class FetchRemoteReport(BaseTool): + """Return a remote file using the helper.""" + archive_url: str = Field(default="https://example.com/document.pdf", description="File to expose") + + def run(self) -> ToolOutputFileContent: + return tool_output_file_from_url(self.archive_url) +``` + + +When you choose `file_data`, include `filename` to hint a download name; URL-based outputs rely on the remote server metadata instead. + + + +`tool_output_file_from_path` only supports PDF files. + + + +### Combining Multiple Outputs + +Return multiple outputs by returning a list from `run`. + +```python +from agency_swarm import BaseTool, ToolOutputFileContent, ToolOutputImage, ToolOutputText + +class PrepareShowcase(BaseTool): + """Return rich media and a short description.""" + teaser_a: str = "https://example.com/teaser-a.png" + teaser_b: str = "https://example.com/teaser-b.png" + report_id: str = "file-report-123" + + def run(self) -> list: + return [ + ToolOutputImage(image_url=self.teaser_a), + ToolOutputImage(image_url=self.teaser_b), + ToolOutputText(text="Gallery updated: Teaser A and Teaser B now live."), + ToolOutputFileContent(file_id=self.report_id), + ] +``` + +## Complete Example (Chart generation tool) + +Here's a complete example using `BaseTool`: + +```python +from agency_swarm import Agent, BaseTool, ToolOutputImage +from pydantic import Field +import base64 +import matplotlib.pyplot as plt +import io + +class GenerateChartTool(BaseTool): + """Generate a bar chart from data.""" + + data: list[float] = Field(..., description="Data points for the chart") + labels: list[str] = Field(..., description="Labels for each data point") + + def run(self) -> ToolOutputImage: + """Generate and return the chart as a base64-encoded image.""" + # Create the chart + fig, ax = plt.subplots() + ax.bar(self.labels, self.data) + + # Convert to base64 + buf = io.BytesIO() + plt.savefig(buf, format='png') + buf.seek(0) + image_base64 = base64.b64encode(buf.read()).decode('utf-8') + plt.close() + + # Return in multimodal format + return ToolOutputImage(image_url=f"data:image/png;base64,{image_base64}") + +# Create an agent with the tool +agent = Agent( + name="DataViz", + instructions="You generate charts and visualizations for data analysis.", + tools=[GenerateChartTool] +) +``` + + +`function_tool` decorators and `BaseTool` classes both support multimodal outputs in the exact same way. + + +```python +from agency_swarm import ToolOutputImage, function_tool + +@function_tool +def fetch_gallery_image() -> ToolOutputImage: + return ToolOutputImage( + image_url="https://upload.wikimedia.org/wikipedia/commons/0/0c/GoldenGateBridge-001.jpg", + detail="auto", + ) +``` + +## Tips & Best Practices + +- Base64-encoded images can be large. Use file references for large content. +- Compress screenshots and other visuals before returning them to cut token usage without sacrificing clarity. +- Include the image names in your textual response whenever you return more than one image so the agent can reference them unambiguously. + +## Real Examples + +- [TBD: Include repo from YouTube video] +- [`examples/multimodal_outputs.py`](https://github.com/VRSEN/agency-swarm/blob/main/examples/multimodal_outputs.py) diff --git a/docs/docs.json b/docs/docs.json index 9ec5f1195..f74f75559 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -58,6 +58,7 @@ "pages": [ "core-framework/tools/custom-tools/step-by-step-guide", "core-framework/tools/custom-tools/pydantic-is-all-you-need", + "core-framework/tools/custom-tools/multimodal-outputs", "core-framework/tools/custom-tools/best-practices", "core-framework/tools/custom-tools/configuration" ] diff --git a/examples/data/daily_revenue.png b/examples/data/daily_revenue.png new file mode 100644 index 000000000..b9b63a39b Binary files /dev/null and b/examples/data/daily_revenue.png differ diff --git a/examples/data/daily_revenue_report.pdf b/examples/data/daily_revenue_report.pdf new file mode 100644 index 000000000..087a148f4 Binary files /dev/null and b/examples/data/daily_revenue_report.pdf differ diff --git a/examples/multimodal_outputs.py b/examples/multimodal_outputs.py new file mode 100644 index 000000000..4fcde6b45 --- /dev/null +++ b/examples/multimodal_outputs.py @@ -0,0 +1,69 @@ +""" +Example demonstrating multimodal tool outputs (image + file) using Agency Swarm. +Flow: tools return images or files -> agent reads them -> responds with a description. + +Two BaseTool classes are defined: +1. ``LoadShowcaseImage`` serves a local image via ``tool_output_image_from_path``. +2. ``LoadReferenceReport`` returns a remotely hosted PDF via ``tool_output_file_from_url``. + +Run with a valid OpenAI API key configured in your environment. +""" + +import asyncio +import os +import sys +from pathlib import Path + +# Allow running the example directly from the repository. +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) + +from pydantic import Field + +from agency_swarm import Agency, Agent, BaseTool, ToolOutputFileContent, ToolOutputImage +from agency_swarm.tools.utils import tool_output_file_from_url, tool_output_image_from_path + +DATA_DIR = Path(__file__).resolve().parent / "data" +REFERENCE_PDF_URL = "https://raw.githubusercontent.com/VRSEN/agency-swarm/main/examples/data/daily_revenue_report.pdf" + + +class LoadShowcaseImage(BaseTool): + """Return the latest gallery image as a multimodal output.""" + + path: Path = Field(default=DATA_DIR / "daily_revenue.png", description="Image to publish") + detail: str = Field(default="auto", description="Vision model detail level") + + def run(self) -> ToolOutputImage: + return tool_output_image_from_path(self.path, detail=self.detail) + + +class LoadReferenceReport(BaseTool): + """Return the reference PDF hosted remotely.""" + + source_url: str = Field(default=REFERENCE_PDF_URL, description="Remote PDF to attach") + + def run(self) -> ToolOutputFileContent: + return tool_output_file_from_url(self.source_url) + + +def create_multimodal_agency() -> Agency: + gallery_agent = Agent( + name="GalleryAgent", + description="Provides gallery outputs with narrative context.", + instructions="Call LoadShowcaseImage when asked for the latest gallery image. " + "Use LoadReferenceReport when a supporting document is requested.", + tools=[LoadShowcaseImage, LoadReferenceReport], + model="gpt-5-mini", + ) + return Agency(gallery_agent) + + +async def main() -> None: + agency = create_multimodal_agency() + response = await agency.get_response("Analyze the daily revenue graph, and summarize the supporting report.") + + print("Final response:") + print(response.final_output) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml index 00b33a6be..02f3bdb3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,8 @@ requires-python = ">=3.12" license = "MIT" authors = [{ name = "Vrsen AI Solutions", email = "me@vrsen.ai" }] dependencies = [ - "openai>=1.107.1,<2.0", - "openai-agents==0.3.3", + "openai>=2.2,<3", + "openai-agents==0.4.1", "datamodel-code-generator>=0.33.0,<0.34.0", "docstring-parser>=0.16,<1.0.0", "fastmcp>=2.0.0", diff --git a/src/agency_swarm/__init__.py b/src/agency_swarm/__init__.py index 5f0451e58..36a39a4b3 100644 --- a/src/agency_swarm/__init__.py +++ b/src/agency_swarm/__init__.py @@ -70,7 +70,18 @@ McpAllowedTools, McpRequireApproval, SendMessage, + ToolOutputFileContent, + ToolOutputFileContentDict, + ToolOutputImage, + ToolOutputImageDict, + ToolOutputText, + ToolOutputTextDict, WebSearchTool, + tool_output_file_from_file_id, + tool_output_file_from_path, + tool_output_file_from_url, + tool_output_image_from_file_id, + tool_output_image_from_path, ) from .utils.thread import ThreadManager # noqa: E402 @@ -135,6 +146,17 @@ "Query", "Body", "ResponseIncludable", + "ToolOutputText", + "ToolOutputTextDict", + "ToolOutputImage", + "ToolOutputImageDict", + "ToolOutputFileContent", + "ToolOutputFileContentDict", + "tool_output_image_from_path", + "tool_output_image_from_file_id", + "tool_output_file_from_path", + "tool_output_file_from_url", + "tool_output_file_from_file_id", ] # Conditionally add LitellmModel if available diff --git a/src/agency_swarm/tools/__init__.py b/src/agency_swarm/tools/__init__.py index 9bb2afe7d..0af550a7c 100644 --- a/src/agency_swarm/tools/__init__.py +++ b/src/agency_swarm/tools/__init__.py @@ -7,6 +7,12 @@ HostedMCPTool, ImageGenerationTool, LocalShellTool, + ToolOutputFileContent, + ToolOutputFileContentDict, + ToolOutputImage, + ToolOutputImageDict, + ToolOutputText, + ToolOutputTextDict, WebSearchTool, function_tool, ) @@ -25,7 +31,14 @@ from .concurrency import ToolConcurrencyManager from .send_message import SendMessage, SendMessageHandoff from .tool_factory import ToolFactory -from .utils import validate_openapi_spec +from .utils import ( + tool_output_file_from_file_id, + tool_output_file_from_path, + tool_output_file_from_url, + tool_output_image_from_file_id, + tool_output_image_from_path, + validate_openapi_spec, +) __all__ = [ "BaseTool", @@ -34,6 +47,11 @@ "SendMessage", "SendMessageHandoff", "validate_openapi_spec", + "tool_output_image_from_path", + "tool_output_image_from_file_id", + "tool_output_file_from_path", + "tool_output_file_from_url", + "tool_output_file_from_file_id", # Re-exports from Agents SDK "CodeInterpreterTool", "ComputerTool", @@ -45,6 +63,12 @@ "LocalShellTool", "WebSearchTool", "function_tool", + "ToolOutputText", + "ToolOutputTextDict", + "ToolOutputImage", + "ToolOutputImageDict", + "ToolOutputFileContent", + "ToolOutputFileContentDict", # Tool parameter types from OpenAI "CodeInterpreter", "CodeInterpreterContainer", diff --git a/src/agency_swarm/tools/tool_factory.py b/src/agency_swarm/tools/tool_factory.py index 90e0c979d..e5541719c 100644 --- a/src/agency_swarm/tools/tool_factory.py +++ b/src/agency_swarm/tools/tool_factory.py @@ -17,12 +17,7 @@ from pydantic import BaseModel, ValidationError from .base_tool import BaseTool -from .utils import ( - build_parameter_object_schema, - build_tool_schema, - generate_model_from_schema, - resolve_url, -) +from .utils import build_parameter_object_schema, build_tool_schema, generate_model_from_schema, resolve_url logger = logging.getLogger(__name__) @@ -88,12 +83,12 @@ async def on_invoke_tool(ctx, input_json: str): try: # Call the langchain tool result = tool.run(args) - return str(result) + return result except TypeError: # Try with single argument if direct dict fails (langchain specifics) if len(args) == 1: result = tool.run(list(args.values())[0]) - return str(result) + return result else: return f"Error parsing input for tool '{tool.__class__.__name__}'. Please open an issue on github." except Exception as e: @@ -444,11 +439,9 @@ async def on_invoke_tool(ctx, input_json: str): if ctx is not None: tool_instance._context = ctx if inspect.iscoroutinefunction(tool_instance.run): - result = await tool_instance.run() - else: - # Always run sync run() in a thread for async compatibility - result = await asyncio.to_thread(tool_instance.run) - return str(result) + return await tool_instance.run() + # Always run sync run() in a thread for async compatibility + return await asyncio.to_thread(tool_instance.run) except Exception as e: return f"Error running BaseTool: {e}" diff --git a/src/agency_swarm/tools/utils.py b/src/agency_swarm/tools/utils.py index cb782c613..73a543e84 100644 --- a/src/agency_swarm/tools/utils.py +++ b/src/agency_swarm/tools/utils.py @@ -1,16 +1,17 @@ -from __future__ import annotations - +import base64 import json import logging +import mimetypes from collections.abc import Callable from datetime import date, datetime from decimal import Decimal from enum import Enum +from pathlib import Path from typing import Any, Literal, Optional, Union import httpx import jsonref -from agents import FunctionTool +from agents import FunctionTool, ToolOutputFileContent, ToolOutputImage from agents.run_context import RunContextWrapper from agents.strict_schema import ensure_strict_json_schema from datamodel_code_generator import DataModelType, PythonVersion @@ -19,6 +20,102 @@ logger = logging.getLogger(__name__) +PDF_MIME_TYPE = "application/pdf" + + +def _build_data_url(file_path: Path, mime_type: str) -> str: + encoded_file = base64.b64encode(file_path.read_bytes()).decode("utf-8") + return f"data:{mime_type};base64,{encoded_file}" + + +def _resolve_mime_type(file_path: Path) -> str: + mime_type, _ = mimetypes.guess_type(file_path.name) + if not mime_type: + raise ValueError(f"Unable to determine MIME type for file: {file_path}") + return mime_type + + +def tool_output_image_from_path( + path: str | Path, + *, + detail: Literal["auto", "high", "low"] = "auto", +) -> ToolOutputImage: + """ + Build a ``ToolOutputImage`` from a local image file by returning a data URL. + + Args: + path: Path to the image file on disk. + detail: Optional detail hint to forward to the vision model. + + Raises: + ValueError: If the file type cannot be resolved from the path. + """ + + file_path = Path(path) + mime_type = _resolve_mime_type(file_path) + return ToolOutputImage(image_url=_build_data_url(file_path, mime_type), detail=detail) + + +def tool_output_image_from_file_id( + file_id: str, + *, + detail: Literal["auto", "high", "low"] = "auto", +) -> ToolOutputImage: + """ + Build a ``ToolOutputImage`` from an OpenAI file ID. + + Args: + file_id: openai file id of the image file. + detail: Optional detail hint to forward to the vision model. + """ + + return ToolOutputImage(file_id=file_id, detail=detail) + + +def tool_output_file_from_path(path: str | Path, *, filename: str | None = None) -> ToolOutputFileContent: + """ + Build a ``ToolOutputFileContent`` from a local file by embedding base64 data. + + Args: + path: Path to the file on disk. + filename: Optional filename hint for the client. + + Raises: + ValueError: If the file is not a PDF. + """ + + file_path = Path(path) + if filename and not filename.lower().endswith(".pdf"): + raise ValueError(f"Filename must end with .pdf, got: {filename}") + mime_type = _resolve_mime_type(file_path) + if mime_type != PDF_MIME_TYPE: + raise ValueError("Only PDF files are supported.") + return ToolOutputFileContent( + file_data=_build_data_url(file_path, PDF_MIME_TYPE), filename=filename or file_path.name + ) + + +def tool_output_file_from_url(url: str) -> ToolOutputFileContent: + """ + Build a ``ToolOutputFileContent`` that references an externally hosted file. + + Args: + url: Publicly reachable URL for the file. + """ + + return ToolOutputFileContent(file_url=url) + + +def tool_output_file_from_file_id(file_id: str) -> ToolOutputFileContent: + """ + Build a ``ToolOutputFileContent`` that references an openai file id. + + Args: + file_id: openai file id of the pdf file. + """ + + return ToolOutputFileContent(file_id=file_id) + def from_openapi_schema( schema: str | dict[str, Any], diff --git a/tests/integration/test_multimodal_outputs.py b/tests/integration/test_multimodal_outputs.py new file mode 100644 index 000000000..33b86793c --- /dev/null +++ b/tests/integration/test_multimodal_outputs.py @@ -0,0 +1,86 @@ +"""Integration tests for multimodal attachments.""" + +from pathlib import Path + +import pytest +from pydantic import Field + +from agency_swarm import Agency, Agent, BaseTool, ToolOutputFileContent, ToolOutputImage +from agency_swarm.tools.utils import ( + tool_output_file_from_path, + tool_output_file_from_url, + tool_output_image_from_path, +) + +FILES_DIR = Path(__file__).resolve().parents[1] / "data" / "files" + + +class LoadShowcaseImage(BaseTool): + """Provide the sample PNG so you can identify the function it illustrates.""" + + path: Path = Field(default=FILES_DIR / "test-image.png", description="Image path") + detail: str = Field(default="auto", description="Vision detail level") + + def run(self) -> ToolOutputImage: + return tool_output_image_from_path(self.path, detail=self.detail) + + +class LoadReferenceReportFromUrl(BaseTool): + """Fetch the reference PDF hosted on main for summarisation.""" + + source_url: str = Field( + default="https://raw.githubusercontent.com/VRSEN/agency-swarm/main/tests/data/files/test-pdf.pdf", + description="Remote PDF URL", + ) + + def run(self) -> ToolOutputFileContent: + return tool_output_file_from_url(self.source_url) + + +class LoadReferenceReportFromPath(BaseTool): + """Load the local reference PDF so you can summarise it.""" + + path: Path = Field(default=FILES_DIR / "test-pdf.pdf", description="Local PDF path") + + def run(self) -> ToolOutputFileContent: + return tool_output_file_from_path(self.path) + + +def _build_agency(*tool_types: type[BaseTool]) -> Agency: + agent = Agent( + name="GalleryAgent", + description="Provides gallery outputs with narrative context.", + instructions="Use each tool's description to decide which attachment to load for analysis.", + tools=list(tool_types), + model="gpt-5-mini", + ) + return Agency(agent) + + +@pytest.mark.asyncio +async def test_multimodal_outputs_image_description() -> None: + agency = _build_agency(LoadShowcaseImage) + result = await agency.get_response("Describe the provided diagram image and name the function shown.") + assert isinstance(result.final_output, str) + output = result.final_output.lower() + assert "sum_of_squares" in output or "sum of squares" in output + + +@pytest.mark.asyncio +async def test_multimodal_outputs_remote_pdf() -> None: + agency = _build_agency(LoadReferenceReportFromUrl) + result = await agency.get_response("Summarise the attached PDF and quote its secret phrase.") + assert isinstance(result.final_output, str) + output = result.final_output.lower() + assert "first pdf secret phrase" in output + assert "pdf" in output or "report" in output + + +@pytest.mark.asyncio +async def test_multimodal_outputs_local_pdf() -> None: + agency = _build_agency(LoadReferenceReportFromPath) + result = await agency.get_response("Summarise the attached PDF and quote its secret phrase.") + assert isinstance(result.final_output, str) + output = result.final_output.lower() + assert "first pdf secret phrase" in output + assert "pdf" in output or "report" in output diff --git a/tests/test_agent_modules/send_message/test_deprecated_reminder_override.py b/tests/test_agent_modules/send_message/test_deprecated_reminder_override.py index 66615964c..155821184 100644 --- a/tests/test_agent_modules/send_message/test_deprecated_reminder_override.py +++ b/tests/test_agent_modules/send_message/test_deprecated_reminder_override.py @@ -1,6 +1,5 @@ -from __future__ import annotations - from types import SimpleNamespace +from typing import Self import pytest from agents import ModelSettings @@ -41,7 +40,7 @@ def __init__(self, run_context: _DummyRunContext, input_history: tuple[dict[str, self.run_context = run_context self.input_history = input_history - def clone(self, input_history: tuple[dict[str, object], ...] | None = None) -> _DummyHandoffInputData: + def clone(self, input_history: tuple[dict[str, object], ...] | None = None) -> Self: return _DummyHandoffInputData( self.run_context, input_history if input_history is not None else self.input_history ) diff --git a/tests/test_agent_modules/send_message/test_pending_isolation.py b/tests/test_agent_modules/send_message/test_pending_isolation.py index 3cb5651a6..23fc255ad 100644 --- a/tests/test_agent_modules/send_message/test_pending_isolation.py +++ b/tests/test_agent_modules/send_message/test_pending_isolation.py @@ -1,7 +1,5 @@ """SendMessage isolation regression tests.""" -from __future__ import annotations - import asyncio import json from types import MethodType, SimpleNamespace diff --git a/tests/test_agent_modules/test_tool_factory_core.py b/tests/test_agent_modules/test_tool_factory_core.py index 71ef1032d..951b36d4f 100644 --- a/tests/test_agent_modules/test_tool_factory_core.py +++ b/tests/test_agent_modules/test_tool_factory_core.py @@ -8,7 +8,7 @@ from unittest.mock import MagicMock import pytest -from agents import FunctionTool +from agents import FunctionTool, ToolOutputImage from agency_swarm.tools.base_tool import BaseTool from agency_swarm.tools.tool_factory import ToolFactory @@ -177,6 +177,23 @@ def run(self): assert "Error running BaseTool: Tool execution failed" in callback_result + @pytest.mark.asyncio + async def test_callback_returns_structured_output(self): + """ToolFactory should not stringify structured outputs.""" + + class StructuredTool(BaseTool): + """Tool returning an image output.""" + + def run(self): + return ToolOutputImage(image_url="https://example.com/sample.png") + + result = ToolFactory.adapt_base_tool(StructuredTool) + + mock_ctx = MagicMock() + callback_result = await result.on_invoke_tool(mock_ctx, "{}") + + assert isinstance(callback_result, ToolOutputImage) + @pytest.mark.asyncio async def test_callback_handles_invalid_json(self): """Test callback handling of invalid JSON input.""" diff --git a/tests/test_tools_modules/test_utils.py b/tests/test_tools_modules/test_utils.py new file mode 100644 index 000000000..72be901c6 --- /dev/null +++ b/tests/test_tools_modules/test_utils.py @@ -0,0 +1,75 @@ +""" +Tests for multimodal helper utilities. +""" + +import base64 +from pathlib import Path + +import pytest + +from agency_swarm import ToolOutputFileContent, ToolOutputImage +from agency_swarm.tools.utils import ( + tool_output_file_from_path, + tool_output_file_from_url, + tool_output_image_from_path, +) + + +def _write_png(tmp_path: Path) -> Path: + """Write a 1x1 PNG to disk for image helper tests.""" + png_bytes = base64.b64decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8Xw8AAhABgAP7R7AAAAAASUVORK5CYII=" + ) + image_path = tmp_path / "pixel.png" + image_path.write_bytes(png_bytes) + return image_path + + +def test_tool_output_image_from_path_returns_data_url(tmp_path): + image_path = _write_png(tmp_path) + + result = tool_output_image_from_path(image_path, detail="high") + + assert isinstance(result, ToolOutputImage) + assert result.detail == "high" + assert result.image_url.startswith("data:image/png;base64,") + encoded = result.image_url.split(",", 1)[1] + assert base64.b64decode(encoded) == image_path.read_bytes() + + +def test_tool_output_image_from_path_rejects_unknown_type(tmp_path): + image_path = tmp_path / "pixel" + image_path.write_bytes(_write_png(tmp_path).read_bytes()) + + with pytest.raises(ValueError, match="Unable to determine MIME type"): + tool_output_image_from_path(image_path) + + +def test_tool_output_file_from_path_embeds_file_data(tmp_path): + file_path = tmp_path / "document.pdf" + file_path.write_text("sample pdf content", encoding="utf-8") + + result = tool_output_file_from_path(file_path) + + assert isinstance(result, ToolOutputFileContent) + assert result.filename == "document.pdf" + assert result.file_data is not None + assert result.file_data.startswith("data:application/pdf;base64,") + encoded = result.file_data.split(",", 1)[1] + assert base64.b64decode(encoded.encode("utf-8")).decode("utf-8") == "sample pdf content" + + +def test_tool_output_file_from_path_rejects_non_pdf(tmp_path): + file_path = tmp_path / "document.txt" + file_path.write_text("not a pdf", encoding="utf-8") + + with pytest.raises(ValueError, match="Only PDF files are supported."): + tool_output_file_from_path(file_path) + + +def test_tool_output_file_from_url_returns_remote_reference(): + result = tool_output_file_from_url("https://example.com/archive.zip") + + assert isinstance(result, ToolOutputFileContent) + assert result.file_url == "https://example.com/archive.zip" + assert result.filename is None diff --git a/uv.lock b/uv.lock index bfdacab4b..b12a53b1e 100644 --- a/uv.lock +++ b/uv.lock @@ -95,8 +95,8 @@ requires-dist = [ { name = "litellm", marker = "extra == 'litellm'", specifier = ">=1.67.4.post1,<2" }, { name = "mcp", specifier = ">=1.13.1,<2.0.0" }, { name = "numpy", marker = "python_full_version >= '3.10' and extra == 'voice'", specifier = ">=2.2.0,<3" }, - { name = "openai", specifier = ">=1.107.1,<2.0" }, - { name = "openai-agents", specifier = "==0.3.3" }, + { name = "openai", specifier = ">=2.2,<3" }, + { name = "openai-agents", specifier = "==0.4.1" }, { name = "prompt-toolkit", specifier = ">=3.0.0,<4.0.0" }, { name = "pydantic", specifier = ">=2.11,<3" }, { name = "python-dotenv", specifier = ">=1.1.1,<2.0.0" }, @@ -927,37 +927,70 @@ wheels = [ [[package]] name = "jiter" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604, upload-time = "2025-03-10T21:37:03.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/d7/c55086103d6f29b694ec79156242304adf521577530d9031317ce5338c59/jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11", size = 309203, upload-time = "2025-03-10T21:35:44.852Z" }, - { url = "https://files.pythonhosted.org/packages/b0/01/f775dfee50beb420adfd6baf58d1c4d437de41c9b666ddf127c065e5a488/jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e", size = 319678, upload-time = "2025-03-10T21:35:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/ab/b8/09b73a793714726893e5d46d5c534a63709261af3d24444ad07885ce87cb/jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2", size = 341816, upload-time = "2025-03-10T21:35:47.856Z" }, - { url = "https://files.pythonhosted.org/packages/35/6f/b8f89ec5398b2b0d344257138182cc090302854ed63ed9c9051e9c673441/jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75", size = 364152, upload-time = "2025-03-10T21:35:49.397Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ca/978cc3183113b8e4484cc7e210a9ad3c6614396e7abd5407ea8aa1458eef/jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d", size = 406991, upload-time = "2025-03-10T21:35:50.745Z" }, - { url = "https://files.pythonhosted.org/packages/13/3a/72861883e11a36d6aa314b4922125f6ae90bdccc225cd96d24cc78a66385/jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42", size = 395824, upload-time = "2025-03-10T21:35:52.162Z" }, - { url = "https://files.pythonhosted.org/packages/87/67/22728a86ef53589c3720225778f7c5fdb617080e3deaed58b04789418212/jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc", size = 351318, upload-time = "2025-03-10T21:35:53.566Z" }, - { url = "https://files.pythonhosted.org/packages/69/b9/f39728e2e2007276806d7a6609cda7fac44ffa28ca0d02c49a4f397cc0d9/jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc", size = 384591, upload-time = "2025-03-10T21:35:54.95Z" }, - { url = "https://files.pythonhosted.org/packages/eb/8f/8a708bc7fd87b8a5d861f1c118a995eccbe6d672fe10c9753e67362d0dd0/jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e", size = 520746, upload-time = "2025-03-10T21:35:56.444Z" }, - { url = "https://files.pythonhosted.org/packages/95/1e/65680c7488bd2365dbd2980adaf63c562d3d41d3faac192ebc7ef5b4ae25/jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d", size = 512754, upload-time = "2025-03-10T21:35:58.789Z" }, - { url = "https://files.pythonhosted.org/packages/78/f3/fdc43547a9ee6e93c837685da704fb6da7dba311fc022e2766d5277dfde5/jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06", size = 207075, upload-time = "2025-03-10T21:36:00.616Z" }, - { url = "https://files.pythonhosted.org/packages/cd/9d/742b289016d155f49028fe1bfbeb935c9bf0ffeefdf77daf4a63a42bb72b/jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0", size = 207999, upload-time = "2025-03-10T21:36:02.366Z" }, - { url = "https://files.pythonhosted.org/packages/e7/1b/4cd165c362e8f2f520fdb43245e2b414f42a255921248b4f8b9c8d871ff1/jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7", size = 308197, upload-time = "2025-03-10T21:36:03.828Z" }, - { url = "https://files.pythonhosted.org/packages/13/aa/7a890dfe29c84c9a82064a9fe36079c7c0309c91b70c380dc138f9bea44a/jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b", size = 318160, upload-time = "2025-03-10T21:36:05.281Z" }, - { url = "https://files.pythonhosted.org/packages/6a/38/5888b43fc01102f733f085673c4f0be5a298f69808ec63de55051754e390/jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69", size = 341259, upload-time = "2025-03-10T21:36:06.716Z" }, - { url = "https://files.pythonhosted.org/packages/3d/5e/bbdbb63305bcc01006de683b6228cd061458b9b7bb9b8d9bc348a58e5dc2/jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103", size = 363730, upload-time = "2025-03-10T21:36:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/75/85/53a3edc616992fe4af6814c25f91ee3b1e22f7678e979b6ea82d3bc0667e/jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635", size = 405126, upload-time = "2025-03-10T21:36:10.934Z" }, - { url = "https://files.pythonhosted.org/packages/ae/b3/1ee26b12b2693bd3f0b71d3188e4e5d817b12e3c630a09e099e0a89e28fa/jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4", size = 393668, upload-time = "2025-03-10T21:36:12.468Z" }, - { url = "https://files.pythonhosted.org/packages/11/87/e084ce261950c1861773ab534d49127d1517b629478304d328493f980791/jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d", size = 352350, upload-time = "2025-03-10T21:36:14.148Z" }, - { url = "https://files.pythonhosted.org/packages/f0/06/7dca84b04987e9df563610aa0bc154ea176e50358af532ab40ffb87434df/jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3", size = 384204, upload-time = "2025-03-10T21:36:15.545Z" }, - { url = "https://files.pythonhosted.org/packages/16/2f/82e1c6020db72f397dd070eec0c85ebc4df7c88967bc86d3ce9864148f28/jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5", size = 520322, upload-time = "2025-03-10T21:36:17.016Z" }, - { url = "https://files.pythonhosted.org/packages/36/fd/4f0cd3abe83ce208991ca61e7e5df915aa35b67f1c0633eb7cf2f2e88ec7/jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d", size = 512184, upload-time = "2025-03-10T21:36:18.47Z" }, - { url = "https://files.pythonhosted.org/packages/a0/3c/8a56f6d547731a0b4410a2d9d16bf39c861046f91f57c98f7cab3d2aa9ce/jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53", size = 206504, upload-time = "2025-03-10T21:36:19.809Z" }, - { url = "https://files.pythonhosted.org/packages/f4/1c/0c996fd90639acda75ed7fa698ee5fd7d80243057185dc2f63d4c1c9f6b9/jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7", size = 204943, upload-time = "2025-03-10T21:36:21.536Z" }, - { url = "https://files.pythonhosted.org/packages/78/0f/77a63ca7aa5fed9a1b9135af57e190d905bcd3702b36aca46a01090d39ad/jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001", size = 317281, upload-time = "2025-03-10T21:36:22.959Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/a3a1571712c2bf6ec4c657f0d66da114a63a2e32b7e4eb8e0b83295ee034/jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a", size = 350273, upload-time = "2025-03-10T21:36:24.414Z" }, - { url = "https://files.pythonhosted.org/packages/ee/47/3729f00f35a696e68da15d64eb9283c330e776f3b5789bac7f2c0c4df209/jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf", size = 206867, upload-time = "2025-03-10T21:36:25.843Z" }, +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/68/0357982493a7b20925aece061f7fb7a2678e3b232f8d73a6edb7e5304443/jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc", size = 168385, upload-time = "2025-10-17T11:31:15.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/8b/318e8af2c904a9d29af91f78c1e18f0592e189bbdb8a462902d31fe20682/jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c", size = 305655, upload-time = "2025-10-17T11:29:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/f7/29/6c7de6b5d6e511d9e736312c0c9bfcee8f9b6bef68182a08b1d78767e627/jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d", size = 315645, upload-time = "2025-10-17T11:29:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5f/ef9e5675511ee0eb7f98dd8c90509e1f7743dbb7c350071acae87b0145f3/jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b", size = 348003, upload-time = "2025-10-17T11:29:22.712Z" }, + { url = "https://files.pythonhosted.org/packages/56/1b/abe8c4021010b0a320d3c62682769b700fb66f92c6db02d1a1381b3db025/jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4", size = 365122, upload-time = "2025-10-17T11:29:24.408Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2d/4a18013939a4f24432f805fbd5a19893e64650b933edb057cd405275a538/jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239", size = 488360, upload-time = "2025-10-17T11:29:25.724Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/38124f5d02ac4131f0dfbcfd1a19a0fac305fa2c005bc4f9f0736914a1a4/jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711", size = 376884, upload-time = "2025-10-17T11:29:27.056Z" }, + { url = "https://files.pythonhosted.org/packages/7b/43/59fdc2f6267959b71dd23ce0bd8d4aeaf55566aa435a5d00f53d53c7eb24/jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939", size = 358827, upload-time = "2025-10-17T11:29:28.698Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d0/b3cc20ff5340775ea3bbaa0d665518eddecd4266ba7244c9cb480c0c82ec/jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54", size = 385171, upload-time = "2025-10-17T11:29:30.078Z" }, + { url = "https://files.pythonhosted.org/packages/d2/bc/94dd1f3a61f4dc236f787a097360ec061ceeebebf4ea120b924d91391b10/jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d", size = 518359, upload-time = "2025-10-17T11:29:31.464Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8c/12ee132bd67e25c75f542c227f5762491b9a316b0dad8e929c95076f773c/jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250", size = 509205, upload-time = "2025-10-17T11:29:32.895Z" }, + { url = "https://files.pythonhosted.org/packages/39/d5/9de848928ce341d463c7e7273fce90ea6d0ea4343cd761f451860fa16b59/jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e", size = 205448, upload-time = "2025-10-17T11:29:34.217Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/8002d78637e05009f5e3fb5288f9d57d65715c33b5d6aa20fd57670feef5/jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87", size = 204285, upload-time = "2025-10-17T11:29:35.446Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a2/bb24d5587e4dff17ff796716542f663deee337358006a80c8af43ddc11e5/jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c", size = 188712, upload-time = "2025-10-17T11:29:37.027Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4b/e4dd3c76424fad02a601d570f4f2a8438daea47ba081201a721a903d3f4c/jiter-0.11.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:71b6a920a5550f057d49d0e8bcc60945a8da998019e83f01adf110e226267663", size = 305272, upload-time = "2025-10-17T11:29:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/67/83/2cd3ad5364191130f4de80eacc907f693723beaab11a46c7d155b07a092c/jiter-0.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b3de72e925388453a5171be83379549300db01284f04d2a6f244d1d8de36f94", size = 314038, upload-time = "2025-10-17T11:29:40.563Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3c/8e67d9ba524e97d2f04c8f406f8769a23205026b13b0938d16646d6e2d3e/jiter-0.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc19dd65a2bd3d9c044c5b4ebf657ca1e6003a97c0fc10f555aa4f7fb9821c00", size = 345977, upload-time = "2025-10-17T11:29:42.009Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/489ce64d992c29bccbffabb13961bbb0435e890d7f2d266d1f3df5e917d2/jiter-0.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d58faaa936743cd1464540562f60b7ce4fd927e695e8bc31b3da5b914baa9abd", size = 364503, upload-time = "2025-10-17T11:29:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c0/e321dd83ee231d05c8fe4b1a12caf1f0e8c7a949bf4724d58397104f10f2/jiter-0.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:902640c3103625317291cb73773413b4d71847cdf9383ba65528745ff89f1d14", size = 487092, upload-time = "2025-10-17T11:29:44.835Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/8f24ec49c8d37bd37f34ec0112e0b1a3b4b5a7b456c8efff1df5e189ad43/jiter-0.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30405f726e4c2ed487b176c09f8b877a957f535d60c1bf194abb8dadedb5836f", size = 376328, upload-time = "2025-10-17T11:29:46.175Z" }, + { url = "https://files.pythonhosted.org/packages/7f/70/ded107620e809327cf7050727e17ccfa79d6385a771b7fe38fb31318ef00/jiter-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3217f61728b0baadd2551844870f65219ac4a1285d5e1a4abddff3d51fdabe96", size = 356632, upload-time = "2025-10-17T11:29:47.454Z" }, + { url = "https://files.pythonhosted.org/packages/19/53/c26f7251613f6a9079275ee43c89b8a973a95ff27532c421abc2a87afb04/jiter-0.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1364cc90c03a8196f35f396f84029f12abe925415049204446db86598c8b72c", size = 384358, upload-time = "2025-10-17T11:29:49.377Z" }, + { url = "https://files.pythonhosted.org/packages/84/16/e0f2cc61e9c4d0b62f6c1bd9b9781d878a427656f88293e2a5335fa8ff07/jiter-0.11.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53a54bf8e873820ab186b2dca9f6c3303f00d65ae5e7b7d6bda1b95aa472d646", size = 517279, upload-time = "2025-10-17T11:29:50.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/5c/4cd095eaee68961bca3081acbe7c89e12ae24a5dae5fd5d2a13e01ed2542/jiter-0.11.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7e29aca023627b0e0c2392d4248f6414d566ff3974fa08ff2ac8dbb96dfee92a", size = 508276, upload-time = "2025-10-17T11:29:52.619Z" }, + { url = "https://files.pythonhosted.org/packages/4f/25/f459240e69b0e09a7706d96ce203ad615ca36b0fe832308d2b7123abf2d0/jiter-0.11.1-cp313-cp313-win32.whl", hash = "sha256:f153e31d8bca11363751e875c0a70b3d25160ecbaee7b51e457f14498fb39d8b", size = 205593, upload-time = "2025-10-17T11:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/7c/16/461bafe22bae79bab74e217a09c907481a46d520c36b7b9fe71ee8c9e983/jiter-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:f773f84080b667c69c4ea0403fc67bb08b07e2b7ce1ef335dea5868451e60fed", size = 203518, upload-time = "2025-10-17T11:29:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/7b/72/c45de6e320edb4fa165b7b1a414193b3cae302dd82da2169d315dcc78b44/jiter-0.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:635ecd45c04e4c340d2187bcb1cea204c7cc9d32c1364d251564bf42e0e39c2d", size = 188062, upload-time = "2025-10-17T11:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/4a57922437ca8753ef823f434c2dec5028b237d84fa320f06a3ba1aec6e8/jiter-0.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d892b184da4d94d94ddb4031296931c74ec8b325513a541ebfd6dfb9ae89904b", size = 313814, upload-time = "2025-10-17T11:29:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/76/50/62a0683dadca25490a4bedc6a88d59de9af2a3406dd5a576009a73a1d392/jiter-0.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa22c223a3041dacb2fcd37c70dfd648b44662b4a48e242592f95bda5ab09d58", size = 344987, upload-time = "2025-10-17T11:30:00.208Z" }, + { url = "https://files.pythonhosted.org/packages/da/00/2355dbfcbf6cdeaddfdca18287f0f38ae49446bb6378e4a5971e9356fc8a/jiter-0.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330e8e6a11ad4980cd66a0f4a3e0e2e0f646c911ce047014f984841924729789", size = 356399, upload-time = "2025-10-17T11:30:02.084Z" }, + { url = "https://files.pythonhosted.org/packages/c9/07/c2bd748d578fa933d894a55bff33f983bc27f75fc4e491b354bef7b78012/jiter-0.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:09e2e386ebf298547ca3a3704b729471f7ec666c2906c5c26c1a915ea24741ec", size = 203289, upload-time = "2025-10-17T11:30:03.656Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ee/ace64a853a1acbd318eb0ca167bad1cf5ee037207504b83a868a5849747b/jiter-0.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:fe4a431c291157e11cee7c34627990ea75e8d153894365a3bc84b7a959d23ca8", size = 188284, upload-time = "2025-10-17T11:30:05.046Z" }, + { url = "https://files.pythonhosted.org/packages/8d/00/d6006d069e7b076e4c66af90656b63da9481954f290d5eca8c715f4bf125/jiter-0.11.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0fa1f70da7a8a9713ff8e5f75ec3f90c0c870be6d526aa95e7c906f6a1c8c676", size = 304624, upload-time = "2025-10-17T11:30:06.678Z" }, + { url = "https://files.pythonhosted.org/packages/fc/45/4a0e31eb996b9ccfddbae4d3017b46f358a599ccf2e19fbffa5e531bd304/jiter-0.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:569ee559e5046a42feb6828c55307cf20fe43308e3ae0d8e9e4f8d8634d99944", size = 315042, upload-time = "2025-10-17T11:30:08.87Z" }, + { url = "https://files.pythonhosted.org/packages/e7/91/22f5746f5159a28c76acdc0778801f3c1181799aab196dbea2d29e064968/jiter-0.11.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f69955fa1d92e81987f092b233f0be49d4c937da107b7f7dcf56306f1d3fcce9", size = 346357, upload-time = "2025-10-17T11:30:10.222Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4f/57620857d4e1dc75c8ff4856c90cb6c135e61bff9b4ebfb5dc86814e82d7/jiter-0.11.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:090f4c9d4a825e0fcbd0a2647c9a88a0f366b75654d982d95a9590745ff0c48d", size = 365057, upload-time = "2025-10-17T11:30:11.585Z" }, + { url = "https://files.pythonhosted.org/packages/ce/34/caf7f9cc8ae0a5bb25a5440cc76c7452d264d1b36701b90fdadd28fe08ec/jiter-0.11.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf3d8cedf9e9d825233e0dcac28ff15c47b7c5512fdfe2e25fd5bbb6e6b0cee", size = 487086, upload-time = "2025-10-17T11:30:13.052Z" }, + { url = "https://files.pythonhosted.org/packages/50/17/85b5857c329d533d433fedf98804ebec696004a1f88cabad202b2ddc55cf/jiter-0.11.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa9b1958f9c30d3d1a558b75f0626733c60eb9b7774a86b34d88060be1e67fe", size = 376083, upload-time = "2025-10-17T11:30:14.416Z" }, + { url = "https://files.pythonhosted.org/packages/85/d3/2d9f973f828226e6faebdef034097a2918077ea776fb4d88489949024787/jiter-0.11.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42d1ca16590b768c5e7d723055acd2633908baacb3628dd430842e2e035aa90", size = 357825, upload-time = "2025-10-17T11:30:15.765Z" }, + { url = "https://files.pythonhosted.org/packages/f4/55/848d4dabf2c2c236a05468c315c2cb9dc736c5915e65449ccecdba22fb6f/jiter-0.11.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5db4c2486a023820b701a17aec9c5a6173c5ba4393f26662f032f2de9c848b0f", size = 383933, upload-time = "2025-10-17T11:30:17.34Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6c/204c95a4fbb0e26dfa7776c8ef4a878d0c0b215868011cc904bf44f707e2/jiter-0.11.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4573b78777ccfac954859a6eff45cbd9d281d80c8af049d0f1a3d9fc323d5c3a", size = 517118, upload-time = "2025-10-17T11:30:18.684Z" }, + { url = "https://files.pythonhosted.org/packages/88/25/09956644ea5a2b1e7a2a0f665cb69a973b28f4621fa61fc0c0f06ff40a31/jiter-0.11.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7593ac6f40831d7961cb67633c39b9fef6689a211d7919e958f45710504f52d3", size = 508194, upload-time = "2025-10-17T11:30:20.719Z" }, + { url = "https://files.pythonhosted.org/packages/09/49/4d1657355d7f5c9e783083a03a3f07d5858efa6916a7d9634d07db1c23bd/jiter-0.11.1-cp314-cp314-win32.whl", hash = "sha256:87202ec6ff9626ff5f9351507def98fcf0df60e9a146308e8ab221432228f4ea", size = 203961, upload-time = "2025-10-17T11:30:22.073Z" }, + { url = "https://files.pythonhosted.org/packages/76/bd/f063bd5cc2712e7ca3cf6beda50894418fc0cfeb3f6ff45a12d87af25996/jiter-0.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:a5dd268f6531a182c89d0dd9a3f8848e86e92dfff4201b77a18e6b98aa59798c", size = 202804, upload-time = "2025-10-17T11:30:23.452Z" }, + { url = "https://files.pythonhosted.org/packages/52/ca/4d84193dfafef1020bf0bedd5e1a8d0e89cb67c54b8519040effc694964b/jiter-0.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:5d761f863f912a44748a21b5c4979c04252588ded8d1d2760976d2e42cd8d991", size = 188001, upload-time = "2025-10-17T11:30:24.915Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fa/3b05e5c9d32efc770a8510eeb0b071c42ae93a5b576fd91cee9af91689a1/jiter-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2cc5a3965285ddc33e0cab933e96b640bc9ba5940cea27ebbbf6695e72d6511c", size = 312561, upload-time = "2025-10-17T11:30:26.742Z" }, + { url = "https://files.pythonhosted.org/packages/50/d3/335822eb216154ddb79a130cbdce88fdf5c3e2b43dc5dba1fd95c485aaf5/jiter-0.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b572b3636a784c2768b2342f36a23078c8d3aa6d8a30745398b1bab58a6f1a8", size = 344551, upload-time = "2025-10-17T11:30:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/31/6d/a0bed13676b1398f9b3ba61f32569f20a3ff270291161100956a577b2dd3/jiter-0.11.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad93e3d67a981f96596d65d2298fe8d1aa649deb5374a2fb6a434410ee11915e", size = 363051, upload-time = "2025-10-17T11:30:30.009Z" }, + { url = "https://files.pythonhosted.org/packages/a4/03/313eda04aa08545a5a04ed5876e52f49ab76a4d98e54578896ca3e16313e/jiter-0.11.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83097ce379e202dcc3fe3fc71a16d523d1ee9192c8e4e854158f96b3efe3f2f", size = 485897, upload-time = "2025-10-17T11:30:31.429Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/a1011b9d325e40b53b1b96a17c010b8646013417f3902f97a86325b19299/jiter-0.11.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7042c51e7fbeca65631eb0c332f90c0c082eab04334e7ccc28a8588e8e2804d9", size = 375224, upload-time = "2025-10-17T11:30:33.18Z" }, + { url = "https://files.pythonhosted.org/packages/92/da/1b45026b19dd39b419e917165ff0ea629dbb95f374a3a13d2df95e40a6ac/jiter-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a68d679c0e47649a61df591660507608adc2652442de7ec8276538ac46abe08", size = 356606, upload-time = "2025-10-17T11:30:34.572Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9acb0e54d6a8ba59ce923a180ebe824b4e00e80e56cefde86cc8e0a948be/jiter-0.11.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b0da75dbf4b6ec0b3c9e604d1ee8beaf15bc046fff7180f7d89e3cdbd3bb51", size = 384003, upload-time = "2025-10-17T11:30:35.987Z" }, + { url = "https://files.pythonhosted.org/packages/3f/2b/e5a5fe09d6da2145e4eed651e2ce37f3c0cf8016e48b1d302e21fb1628b7/jiter-0.11.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:69dd514bf0fa31c62147d6002e5ca2b3e7ef5894f5ac6f0a19752385f4e89437", size = 516946, upload-time = "2025-10-17T11:30:37.425Z" }, + { url = "https://files.pythonhosted.org/packages/5f/fe/db936e16e0228d48eb81f9934e8327e9fde5185e84f02174fcd22a01be87/jiter-0.11.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:bb31ac0b339efa24c0ca606febd8b77ef11c58d09af1b5f2be4c99e907b11111", size = 507614, upload-time = "2025-10-17T11:30:38.977Z" }, + { url = "https://files.pythonhosted.org/packages/86/db/c4438e8febfb303486d13c6b72f5eb71cf851e300a0c1f0b4140018dd31f/jiter-0.11.1-cp314-cp314t-win32.whl", hash = "sha256:b2ce0d6156a1d3ad41da3eec63b17e03e296b78b0e0da660876fccfada86d2f7", size = 204043, upload-time = "2025-10-17T11:30:40.308Z" }, + { url = "https://files.pythonhosted.org/packages/36/59/81badb169212f30f47f817dfaabf965bc9b8204fed906fab58104ee541f9/jiter-0.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f4db07d127b54c4a2d43b4cf05ff0193e4f73e0dd90c74037e16df0b29f666e1", size = 204046, upload-time = "2025-10-17T11:30:41.692Z" }, + { url = "https://files.pythonhosted.org/packages/dd/01/43f7b4eb61db3e565574c4c5714685d042fb652f9eef7e5a3de6aafa943a/jiter-0.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:28e4fdf2d7ebfc935523e50d1efa3970043cfaa161674fe66f9642409d001dfe", size = 188069, upload-time = "2025-10-17T11:30:43.23Z" }, + { url = "https://files.pythonhosted.org/packages/a6/bc/950dd7f170c6394b6fdd73f989d9e729bd98907bcc4430ef080a72d06b77/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d", size = 302626, upload-time = "2025-10-17T11:31:09.645Z" }, + { url = "https://files.pythonhosted.org/packages/3a/65/43d7971ca82ee100b7b9b520573eeef7eabc0a45d490168ebb9a9b5bb8b2/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838", size = 297034, upload-time = "2025-10-17T11:31:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/000e1e0c0c67e96557a279f8969487ea2732d6c7311698819f977abae837/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f", size = 337328, upload-time = "2025-10-17T11:31:12.399Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/71408b02c6133153336d29fa3ba53000f1e1a3f78bb2fc2d1a1865d2e743/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960", size = 343697, upload-time = "2025-10-17T11:31:13.773Z" }, ] [[package]] @@ -1378,7 +1411,7 @@ wheels = [ [[package]] name = "openai" -version = "1.107.2" +version = "2.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1390,14 +1423,14 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/66/61b0c63b68df8a22f8763d7d632ea7255edb4021dca1859f4359a5659b85/openai-1.107.2.tar.gz", hash = "sha256:a11fe8d4318e98e94309308dd3a25108dec4dfc1b606f9b1c5706e8d88bdd3cb", size = 564155, upload-time = "2025-09-12T19:52:21.159Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/c7/e42bcd89dfd47fec8a30b9e20f93e512efdbfbb3391b05bbb79a2fb295fa/openai-2.6.0.tar.gz", hash = "sha256:f119faf7fc07d7e558c1e7c32c873e241439b01bd7480418234291ee8c8f4b9d", size = 592904, upload-time = "2025-10-20T17:17:24.588Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/65/e51a77a368eed7b9cc22ce394087ab43f13fa2884724729b716adf2da389/openai-1.107.2-py3-none-any.whl", hash = "sha256:d159d4f3ee3d9c717b248c5d69fe93d7773a80563c8b1ca8e9cad789d3cf0260", size = 946937, upload-time = "2025-09-12T19:52:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0a/58e9dcd34abe273eaeac3807a8483073767b5609d01bb78ea2f048e515a0/openai-2.6.0-py3-none-any.whl", hash = "sha256:f33fa12070fe347b5787a7861c8dd397786a4a17e1c3186e239338dac7e2e743", size = 1005403, upload-time = "2025-10-20T17:17:22.091Z" }, ] [[package]] name = "openai-agents" -version = "0.3.3" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1408,9 +1441,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a4/37/2b4f828840d3ff32d82b813c3371ec9ee26b3b8dc6b4acbb7a4a579f617a/openai_agents-0.3.3.tar.gz", hash = "sha256:b016381a6890e1cb6879eb23c53c35f8c2312be1117f1cd4e4b5e2463150839f", size = 1816230, upload-time = "2025-09-30T23:20:24.22Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/76/52398d0416706daa69b7e79d1d86f728bea4a49b60442006e397564d1366/openai_agents-0.4.1.tar.gz", hash = "sha256:ead3ad58fd918dd7bcbfcb5cd43a27bcd9dfca1e47f444afcf7b62c86f0f2634", size = 1924077, upload-time = "2025-10-22T00:47:12.799Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/59/fd49fd2c3184c0d5fedb8c9c456ae9852154828bca7ee69dce004ea83188/openai_agents-0.3.3-py3-none-any.whl", hash = "sha256:aa2c74e010b923c09f166e63a51fae8c850c62df8581b84bafcbe5bd208d1505", size = 210893, upload-time = "2025-09-30T23:20:22.037Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/df7ecff75ee67779e5159d0ac34e483ef99e658984b5a0706ccbdd68c1bf/openai_agents-0.4.1-py3-none-any.whl", hash = "sha256:d59fa9545625965b270b4d177b58db013730bf1b8c835b473f1e26ebf78a5eb4", size = 215641, upload-time = "2025-10-22T00:47:10.687Z" }, ] [[package]]