diff --git a/README.md b/README.md index dbeebf1..afbb83c 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ pip install 'stirrup[all]' # or: uv add 'stirrup[all]' # Individual extras pip install 'stirrup[litellm]' # or: uv add 'stirrup[litellm]' +pip install 'stirrup[anyllm]' # or: uv add 'stirrup[anyllm]' pip install 'stirrup[docker]' # or: uv add 'stirrup[docker]' pip install 'stirrup[e2b]' # or: uv add 'stirrup[e2b]' pip install 'stirrup[mcp]' # or: uv add 'stirrup[mcp]' @@ -164,6 +165,27 @@ agent = Agent( See [LiteLLM Example](https://stirrup.artificialanalysis.ai/examples/#litellm-multi-provider-support) or [Deepseek Example](https://stirrup.artificialanalysis.ai/examples/#openai-compatible-apis-deepseek-vllm-ollama) for complete examples. +### any-llm (Anthropic, Google, etc.) + +```python +# Ensure any-llm is added with: pip install 'stirrup[anyllm]' # or: uv add 'stirrup[anyllm]' +# Create any-llm client for Anthropic Claude +# See https://mozilla-ai.github.io/any-llm/providers/ for all supported providers +client = AnyLLMClient( + model_slug="anthropic/claude-opus-5", + max_tokens=8_192, + context_window_tokens=1_000_000, +) + +# Pass client to Agent - model info comes from client.model_slug +agent = Agent( + client=client, + name="claude_agent", +) +``` + +See [any-llm Example](https://stirrup.artificialanalysis.ai/examples/#any-llm-multi-provider-support) or [Deepseek Example](https://stirrup.artificialanalysis.ai/examples/#openai-compatible-apis-deepseek-vllm-ollama) for complete examples. + ## Default Tools When you create an `Agent` without specifying tools, it uses `default_tools()`: diff --git a/docs/api/clients/anyllm.md b/docs/api/clients/anyllm.md new file mode 100644 index 0000000..261efad --- /dev/null +++ b/docs/api/clients/anyllm.md @@ -0,0 +1,12 @@ +# any-llm Client + +!!! note "Optional Dependency" + any-llm is an optional dependency. Install with: + ```bash + pip install stirrup[anyllm] # or: uv add stirrup[anyllm] + ``` + +`max_tokens` limits provider output. `context_window_tokens` (required) separately +tells the agent when conversation history should be summarized. + +::: stirrup.clients.anyllm_client diff --git a/docs/concepts.md b/docs/concepts.md index 43a9c08..3c570fd 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -268,6 +268,25 @@ Use `LiteLLMClient` for Anthropic, Google, and other providers via [LiteLLM](htt !!! note "LiteLLM Installation" Requires `pip install stirrup[litellm]` (or: `uv add stirrup[litellm]`) +### AnyLLMClient + +Use `AnyLLMClient` for Anthropic, Google, and other providers via [any-llm](https://mozilla-ai.github.io/any-llm/providers/): + +```python +--8<-- "examples/anyllm_example.py:client" +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `model_slug` | `str` | required | Provider/model string (e.g., `"anthropic/claude-opus-5"`) | +| `max_tokens` | `int` | `64_000` | Maximum provider response/output tokens | +| `context_window_tokens` | `int` | required | Context capacity for summarization | +| `reasoning_effort` | `str \| None` | `None` | For reasoning models (e.g. GPT-5.6) | +| `kwargs` | `dict \| None` | `None` | Additional provider-specific arguments | + +!!! note "any-llm Installation" + Requires `pip install stirrup[anyllm]` (or: `uv add stirrup[anyllm]`) + ### Creating Your Own Client Implement the `LLMClient` protocol to create a custom client: diff --git a/docs/examples.md b/docs/examples.md index 661e62e..9745381 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -98,6 +98,17 @@ Use LiteLLM to connect to non-OpenAI providers like Anthropic Claude, Google Gem !!! note Requires `pip install stirrup[litellm]` (or: `uv add stirrup[litellm]`) and the appropriate API key for your chosen provider (e.g., `ANTHROPIC_API_KEY` for Claude). +## any-llm Multi-Provider Support + +Use any-llm to connect to non-OpenAI providers like Anthropic Claude, Google Gemini, and many others. + +```python +--8<-- "examples/anyllm_example.py" +``` + +!!! note + Requires `pip install stirrup[anyllm]` (or: `uv add stirrup[anyllm]`) and the appropriate API key for your chosen provider (e.g., `ANTHROPIC_API_KEY` for Claude). + ## Custom Finish Tool Define structured output with a custom finish tool: diff --git a/docs/extending/clients.md b/docs/extending/clients.md index c6faaa8..8521baf 100644 --- a/docs/extending/clients.md +++ b/docs/extending/clients.md @@ -4,13 +4,14 @@ This guide covers implementing custom LLM clients for Stirrup. ## Built-in Clients -Stirrup provides three built-in clients: +Stirrup provides four built-in clients: | Client | API | Best For | |--------|-----|----------| | [`ChatCompletionsClient`](../api/clients/chat_completions.md) | OpenAI Chat Completions | OpenAI, OpenRouter, vLLM, Ollama, and other OpenAI-compatible APIs | | [`OpenResponsesClient`](../api/clients/open_responses.md) | OpenAI Responses API | Providers implementing the newer Responses API format | | [`LiteLLMClient`](../api/clients/litellm.md) | LiteLLM | Multi-provider support (Anthropic, Google, Azure, etc.) | +| [`AnyLLMClient`](../api/clients/anyllm.md) | any-llm | Multi-provider support (Anthropic, Google, etc.) | ## LLMClient Protocol diff --git a/docs/getting-started.md b/docs/getting-started.md index 9bbd6a9..93a5ddf 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -26,6 +26,7 @@ pip install 'stirrup[all]' # or: uv add 'stirrup[all]' | Extra | Description | |-------|-------------| | `litellm` | Multi-provider support via LiteLLM (Anthropic, Google, etc.) | +| `anyllm` | Multi-provider support via any-llm (Anthropic, Google, etc.) | | `docker` | Docker-based code execution | | `e2b` | E2B cloud sandboxes | | `mcp` | MCP server support | diff --git a/docs/index.md b/docs/index.md index dc26556..3dbaec7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,6 +30,7 @@ pip install 'stirrup[all]' # or: uv add 'stirrup[all]' # Individual extras pip install 'stirrup[litellm]' # or: uv add 'stirrup[litellm]' +pip install 'stirrup[anyllm]' # or: uv add 'stirrup[anyllm]' pip install 'stirrup[docker]' # or: uv add 'stirrup[docker]' pip install 'stirrup[e2b]' # or: uv add 'stirrup[e2b]' pip install 'stirrup[mcp]' # or: uv add 'stirrup[mcp]' @@ -73,6 +74,14 @@ For non-OpenAI providers, change the base URL of the `ChatCompletionsClient`, us See [LiteLLM Example](examples.md#litellm-multi-provider-support) or [Deepseek Example](examples.md#openai-compatible-apis) for complete examples. +### any-llm (Anthropic, Google, etc.) + +```python +--8<-- "examples/anyllm_example.py:client" +``` + +See [any-llm Example](examples.md#any-llm-multi-provider-support) or [Deepseek Example](examples.md#openai-compatible-apis) for complete examples. + ## Default Tools When you create an `Agent` without specifying tools, it uses `default_tools()`: diff --git a/examples/anyllm_example.py b/examples/anyllm_example.py new file mode 100644 index 0000000..42b2c4d --- /dev/null +++ b/examples/anyllm_example.py @@ -0,0 +1,43 @@ +"""Example: Using Agent with any-llm for multi-provider support. + +Demonstrates using any-llm to connect to non-OpenAI providers like Anthropic Claude, +Google Gemini, etc. For any-llm, create the client directly and pass it to the Agent. + +Requires: +- uv pip install stirrup[anyllm] +- ANTHROPIC_API_KEY environment variable (for this example) +""" + +import asyncio + +from stirrup import Agent +from stirrup.clients.anyllm_client import AnyLLMClient + + +async def main() -> None: + """Run an agent using Anthropic Claude via any-llm.""" + # --8<-- [start:client] + # Create any-llm client for Anthropic Claude + # See https://mozilla-ai.github.io/any-llm/providers/ for all supported providers + client = AnyLLMClient( + model_slug="anthropic/claude-opus-5", + max_tokens=8_192, + context_window_tokens=1_000_000, + ) + + # Pass client to Agent - model info comes from client.model_slug + agent = Agent( + client=client, + name="claude_agent", + ) + # --8<-- [end:client] + + async with agent.session(output_dir="./output/anyllm_example") as session: + await session.run( + "What has the temperature been in the last 3 days in San Francisco? " + "Provide a brief summary and output a pdf file with the summary and a graph." + ) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/mkdocs.yml b/mkdocs.yml index 779ef7f..527db6d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -103,6 +103,7 @@ nav: - ChatCompletions: api/clients/chat_completions.md - OpenResponses: api/clients/open_responses.md - LiteLLM: api/clients/litellm.md + - any-llm: api/clients/anyllm.md - Tools: - Overview: api/tools/index.md - Web Tools: api/tools/web.md diff --git a/pyproject.toml b/pyproject.toml index 9667c4e..691ed38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,13 +39,14 @@ dependencies = [ [project.optional-dependencies] litellm = ["litellm>=1.79.3"] +anyllm = ["any-llm-sdk[gemini]>=1.27.0,<1.29.0"] e2b = ["e2b-code-interpreter>=2.3.0"] e2b-throttle = ["stirrup[e2b]", "aiolimiter>=1.1.0"] docker = ["docker>=7.0.0", "python-dotenv>=1.0.0"] mcp = ["mcp>=1.9.0"] browser = ["browser-use>=0.11.3"] slack = ["slack-bolt>=1.20.0"] -all = ["stirrup[litellm,e2b,e2b-throttle,docker,mcp,browser,slack]"] +all = ["stirrup[litellm,anyllm,e2b,e2b-throttle,docker,mcp,browser,slack]"] [project.urls] Homepage = "https://github.com/ArtificialAnalysis/Stirrup" diff --git a/src/stirrup/clients/__init__.py b/src/stirrup/clients/__init__.py index 41e3b17..42a84b0 100644 --- a/src/stirrup/clients/__init__.py +++ b/src/stirrup/clients/__init__.py @@ -8,6 +8,9 @@ For multi-provider support via LiteLLM, install the litellm extra: pip install stirrup[litellm] + +For multi-provider support via any-llm, install the anyllm extra: + pip install stirrup[anyllm] """ from stirrup.clients.chat_completions_client import ChatCompletionsClient diff --git a/src/stirrup/clients/anyllm_client.py b/src/stirrup/clients/anyllm_client.py new file mode 100644 index 0000000..1e8716a --- /dev/null +++ b/src/stirrup/clients/anyllm_client.py @@ -0,0 +1,289 @@ +"""any-llm-based LLM client for multi-provider support. + +This client uses any-llm to provide a unified interface to multiple LLM providers +(OpenAI, Anthropic, Google, etc.) with automatic retries for transient failures. + +Requires the anyllm extra: `pip install stirrup[anyllm]` +""" + +import json +import logging +import warnings +from time import perf_counter +from typing import Any, Literal, cast + +try: + from anthropic.types import Message + from any_llm import AnyLLM + from any_llm.exceptions import ContextLengthExceededError, ProviderError, RateLimitError + from any_llm.providers.anthropic.anthropic import AnthropicProvider + from any_llm.types.completion import ChatCompletion + from any_llm.types.completion import ReasoningEffort as AnyLLMReasoningEffort + from any_llm.utils.exception_handler import convert_exception +except ImportError as e: + raise ImportError( + "Requires installation of the anyllm extra. " + "Install with: `uv pip install stirrup[anyllm]` or `uv add stirrup[anyllm]`" + ) from e + +from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_exponential + +from stirrup.clients.utils import to_openai_messages, to_openai_tools, validate_token_budgets +from stirrup.core.exceptions import ContextOverflowError, OutputTokenLimitError +from stirrup.core.models import ( + AssistantBlock, + AssistantMessage, + ChatMessage, + LLMClient, + ReasoningBlock, + RedactedReasoningBlock, + SignedReasoningBlock, + TextBlock, + TokenUsage, + Tool, + ToolCall, +) + +__all__ = [ + "AnyLLMClient", +] + +LOGGER = logging.getLogger(__name__) + +type ReasoningEffort = AnyLLMReasoningEffort | Literal["default"] + + +class _AnthropicProvider(AnthropicProvider): + """Preserve individual signed/redacted thinking blocks across tool turns.""" + + @staticmethod + def _convert_completion_response(response: Message) -> ChatCompletion: + completion = AnthropicProvider._convert_completion_response(response) # noqa: SLF001 + message = completion.choices[0].message + if message.extra_content is None: + message.extra_content = {} + message.extra_content.setdefault("anthropic", {})["thinking_blocks"] = [ + block.model_dump() for block in response.content if block.type in ("thinking", "redacted_thinking") + ] + return completion + + +def _to_anyllm_messages(messages: list[ChatMessage], provider: str) -> list[dict[str, Any]]: + """Translate the shared chat format's reasoning metadata to any-llm.""" + wire = to_openai_messages(messages, allow_tool_call_signatures=True) + for message in wire: + if provider == "gemini" and message["role"] == "system" and isinstance(message["content"], list): + message["content"] = "".join(part["text"] for part in message["content"]) + for tool_call in message.get("tool_calls", []): + metadata = tool_call.pop("provider_specific_fields", {}) + if signature := metadata.get("thought_signature"): + tool_call["extra_content"] = {"google": {"thought_signature": signature}} + thinking_blocks = message.pop("thinking_blocks", []) + if provider == "anthropic" and message["role"] == "assistant": + # Pass native blocks through any-llm without merging thinking signatures. + content = [*thinking_blocks, *(message.get("content") or [])] + content.extend( + { + "type": "tool_use", + "id": tool_call["id"], + "name": tool_call["function"]["name"], + "input": json.loads(tool_call["function"]["arguments"] or "{}"), + } + for tool_call in message.pop("tool_calls", []) + ) + message["content"] = content + message.pop("reasoning_content", None) + return wire + + +def _parse_thinking_blocks( + thinking_blocks: list[dict[str, Any]] | None, +) -> list[SignedReasoningBlock | RedactedReasoningBlock | ReasoningBlock]: + """Parse native thinking blocks into reasoning blocks, one per entry, in order.""" + parsed: list[SignedReasoningBlock | RedactedReasoningBlock | ReasoningBlock] = [] + for entry in thinking_blocks or []: + if entry.get("type") == "redacted_thinking": + parsed.append(RedactedReasoningBlock(data=entry["data"])) + continue + signature = entry.get("signature") or entry.get("thinking_signature") + content = entry.get("thinking") + if signature is not None: + parsed.append(SignedReasoningBlock(signature=signature, content=content or "")) + elif content is not None: + parsed.append(ReasoningBlock(content=content)) + else: + raise ValueError(f"Signature and content not found in the thinking block response: {entry!r}") + return parsed + + +class AnyLLMClient(LLMClient): + """any-llm-based client supporting multiple LLM providers with unified interface. + + Includes automatic retries for transient failures and token usage tracking. + """ + + def __init__( + self, + model: str | None = None, + max_tokens: int = 64_000, + *, + context_window_tokens: int, + model_slug: str | None = None, + api_key: str | None = None, + reasoning_effort: ReasoningEffort | None = None, + kwargs: dict[str, Any] | None = None, + ) -> None: + """Initialize any-llm client with model configuration and capabilities. + + Args: + model: Model identifier for any-llm (e.g., 'openai:gpt-5.5'). Legacy + 'provider/model' names are also accepted; bare names use OpenAI. + max_tokens: Maximum number of tokens the provider may generate + context_window_tokens: Context capacity used to decide when Agent history + should be summarized. + model_slug: Deprecated. Use model instead. + reasoning_effort: Reasoning effort level for extended thinking models (e.g., 'medium', 'high') + kwargs: Additional arguments to pass to any-llm completion calls. + Provider SDK options can be supplied in ``client_args``. + Keys that collide with the arguments this client sets (including + ``max_tokens``) raise TypeError at request time; other keys pass + through unvalidated. + + Raises: + ValueError: If no model is provided, ``context_window_tokens`` is not + positive, or ``max_tokens`` exceeds it. + """ + if model_slug is not None: + warnings.warn( + "The 'model_slug' parameter is deprecated. Use 'model' instead.", + DeprecationWarning, + stacklevel=2, + ) + if model is None: + model = model_slug + if model is None: + raise ValueError("model is required") + validate_token_budgets(max_tokens, context_window_tokens) + + self._model = model + self._max_tokens = max_tokens + self._context_window_tokens = context_window_tokens + self._reasoning_effort = "auto" if reasoning_effort == "default" else reasoning_effort + self._api_key = api_key + self._provider, separator, self._model_id = model.partition(":" if ":" in model.split("/", 1)[0] else "/") + if not separator: + self._provider, self._model_id = "openai", model + self._kwargs = (kwargs or {}).copy() + self._client_args: dict[str, Any] = self._kwargs.pop("client_args", {}) + if self._provider in ("openai", "anthropic", "openrouter"): + self._client_args.setdefault("max_retries", 0) + self._api_base = self._kwargs.pop("api_base", None) + if self._provider == "anthropic": + self._kwargs.setdefault("timeout", 600) + self._client: AnyLLM | None = None + + @property + def max_tokens(self) -> int: + """Maximum number of tokens the provider may generate.""" + return self._max_tokens + + @property + def context_window_tokens(self) -> int: + """Context capacity used by agents for history summarization.""" + return self._context_window_tokens + + @property + def model_slug(self) -> str: + """Model identifier used by any-llm.""" + return self._model + + @retry( + retry=retry_if_exception_type((ProviderError, RateLimitError)), + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=1, max=10), + ) + async def generate(self, messages: list[ChatMessage], tools: dict[str, Tool]) -> AssistantMessage: + """Generate assistant response with optional tool calls. Retries up to 3 times on timeout/connection errors.""" + request_start_time = perf_counter() + if self._client is None: + if self._provider == "anthropic": + self._client = _AnthropicProvider(api_key=self._api_key, api_base=self._api_base, **self._client_args) + else: + self._client = AnyLLM.create( + self._provider, api_key=self._api_key, api_base=self._api_base, **self._client_args + ) + try: + r = await self._client.acompletion( + model=self._model_id, + messages=cast(Any, _to_anyllm_messages(messages, self._provider)), + tools=to_openai_tools(tools) if tools else None, + tool_choice="auto" if tools else None, + max_tokens=self._max_tokens, + reasoning_effort=self._reasoning_effort, + stream=False, + **self._kwargs, + ) + except (TypeError, ValueError): + raise + except Exception as e: + error = convert_exception(e, self._provider) + if isinstance(error, ContextLengthExceededError): + raise ContextOverflowError(str(error)) from e + if error is e: + raise + raise error from e + request_end_time = perf_counter() + + choice = r.choices[0] + + if choice.finish_reason in ("max_tokens", "length"): + raise OutputTokenLimitError( + model_slug=self.model_slug, + max_tokens=self._max_tokens, + provider_reason=choice.finish_reason, + ) + + msg = choice.message + + # Chat Completions is channel-shaped on the wire — no ordering to preserve — + # so blocks are built in canonical channel order: reasoning → text → tool calls. + extra = msg.extra_content or {} + blocks: list[AssistantBlock] = list(_parse_thinking_blocks(extra.get("anthropic", {}).get("thinking_blocks"))) + if not blocks and msg.reasoning is not None: + blocks.append(ReasoningBlock(content=msg.reasoning.content)) + + content = msg.content or "" + if content: + blocks.append(TextBlock(text=content)) + + blocks.extend( + ToolCall.from_provider( + provider_id=tc.id, + name=tc.function.name, + arguments=tc.function.arguments or "", + signature=(getattr(tc, "extra_content", None) or {}).get("google", {}).get("thought_signature"), + ) + for tc in (msg.tool_calls or []) + if tc.type == "function" + ) + + usage = r.usage + if usage is None: + raise ValueError("Provider response did not include token usage") + input_tokens = usage.prompt_tokens + reasoning_tokens = 0 + if usage.completion_tokens_details: + reasoning_tokens = usage.completion_tokens_details.reasoning_tokens or 0 + output_tokens = usage.completion_tokens + answer_tokens = output_tokens - reasoning_tokens + + return AssistantMessage( + blocks=blocks, + token_usage=TokenUsage( + input=input_tokens, + answer=answer_tokens, + reasoning=reasoning_tokens, + ), + request_start_time=request_start_time, + request_end_time=request_end_time, + ) diff --git a/src/stirrup/utils/logging.py b/src/stirrup/utils/logging.py index f3a3843..6d3a8cd 100644 --- a/src/stirrup/utils/logging.py +++ b/src/stirrup/utils/logging.py @@ -401,6 +401,7 @@ def _configure_logging(self) -> None: # Silence noisy loggers for name in [ "LiteLLM", + "any_llm", "httpx", "httpcore", "openai", diff --git a/tests/test_anyllm_client.py b/tests/test_anyllm_client.py new file mode 100644 index 0000000..4e0822c --- /dev/null +++ b/tests/test_anyllm_client.py @@ -0,0 +1,272 @@ +"""Tests for any-llm client parsing and token-budget behavior.""" + +import json +from importlib import import_module +from types import SimpleNamespace +from typing import Any +from unittest.mock import AsyncMock, MagicMock + +import httpx +import pytest + +pytest.importorskip("any_llm") + +from anthropic import DefaultAsyncHttpxClient +from any_llm.exceptions import ContextLengthExceededError + +from stirrup.clients import anyllm_client as anyllm_module +from stirrup.clients.anyllm_client import AnyLLMClient, _parse_thinking_blocks +from stirrup.core.exceptions import ContextOverflowError, OutputTokenLimitError +from stirrup.core.models import ( + AssistantMessage, + ReasoningBlock, + RedactedReasoningBlock, + SignedReasoningBlock, + ToolCall, + ToolMessage, + UserMessage, +) + + +class TestParseThinkingBlocks: + """Tests for _parse_thinking_blocks: any-llm thinking_blocks → reasoning blocks.""" + + def test_none_and_empty_input(self) -> None: + assert _parse_thinking_blocks(None) == [] + assert _parse_thinking_blocks([]) == [] + + def test_signed_thinking_block(self) -> None: + blocks = _parse_thinking_blocks([{"type": "thinking", "thinking": "Let me think.", "signature": "sig-1"}]) + + assert blocks == [SignedReasoningBlock(signature="sig-1", content="Let me think.")] + + def test_signature_via_thinking_signature_key(self) -> None: + blocks = _parse_thinking_blocks([{"thinking": "hmm", "thinking_signature": "sig-2"}]) + + assert blocks == [SignedReasoningBlock(signature="sig-2", content="hmm")] + + def test_signature_key_takes_precedence(self) -> None: + blocks = _parse_thinking_blocks([{"thinking": "hmm", "signature": "primary", "thinking_signature": "other"}]) + + assert blocks == [SignedReasoningBlock(signature="primary", content="hmm")] + + def test_signature_without_content(self) -> None: + blocks = _parse_thinking_blocks([{"signature": "sig-3"}]) + + assert blocks == [SignedReasoningBlock(signature="sig-3", content="")] + + def test_redacted_thinking_block(self) -> None: + blocks = _parse_thinking_blocks([{"type": "redacted_thinking", "data": "opaque-blob"}]) + + assert blocks == [RedactedReasoningBlock(data="opaque-blob")] + + def test_unsigned_thinking_block(self) -> None: + blocks = _parse_thinking_blocks([{"type": "thinking", "thinking": "plain reasoning"}]) + + assert blocks == [ReasoningBlock(content="plain reasoning")] + + def test_multiple_blocks_preserved_in_order(self) -> None: + blocks = _parse_thinking_blocks( + [ + {"type": "thinking", "thinking": "first", "signature": "sig-a"}, + {"type": "redacted_thinking", "data": "blob"}, + {"type": "thinking", "thinking": "second", "signature": "sig-b"}, + ] + ) + + assert blocks == [ + SignedReasoningBlock(signature="sig-a", content="first"), + RedactedReasoningBlock(data="blob"), + SignedReasoningBlock(signature="sig-b", content="second"), + ] + + def test_entry_without_signature_or_content_raises(self) -> None: + with pytest.raises(ValueError, match="Signature and content not found"): + _parse_thinking_blocks([{"type": "thinking"}]) + + +async def test_idless_anyllm_tool_call_gets_internal_id(monkeypatch: pytest.MonkeyPatch) -> None: + message = { + "content": None, + "tool_calls": [ + SimpleNamespace(type="function", id=None, function=SimpleNamespace(name="lookup", arguments="{}")) + ], + } + choice = MagicMock(finish_reason="stop") + choice.message = SimpleNamespace(**message, extra_content=None, reasoning=None) + usage = MagicMock(prompt_tokens=1, completion_tokens=1, completion_tokens_details=None) + completion = AsyncMock(return_value=SimpleNamespace(choices=[choice], usage=usage)) + monkeypatch.setattr( + anyllm_module.AnyLLM, "create", lambda *_args, **_kwargs: SimpleNamespace(acompletion=completion) + ) + + result = await AnyLLMClient(model="test", context_window_tokens=76_543).generate( + [UserMessage(content="prompt")], {} + ) + + [tool_call] = result.blocks + assert isinstance(tool_call, ToolCall) + assert tool_call.tool_call_id + assert not tool_call.has_provider_tool_call_id + + +async def test_anyllm_passes_back_tool_call_signatures(monkeypatch: pytest.MonkeyPatch) -> None: + message = {"content": None, "tool_calls": []} + choice = MagicMock(finish_reason="stop") + choice.message = SimpleNamespace(**message, extra_content=None, reasoning=None) + usage = MagicMock(prompt_tokens=1, completion_tokens=1, completion_tokens_details=None) + completion = AsyncMock(return_value=SimpleNamespace(choices=[choice], usage=usage)) + monkeypatch.setattr( + anyllm_module.AnyLLM, "create", lambda *_args, **_kwargs: SimpleNamespace(acompletion=completion) + ) + history = AssistantMessage( + blocks=[ToolCall(tool_call_id="call_1", name="lookup", arguments="{}", signature="google-signature")] + ) + + await AnyLLMClient(model="test", context_window_tokens=76_543).generate([history], {}) + + [wire_call] = completion.call_args.kwargs["messages"][0]["tool_calls"] + assert wire_call["extra_content"] == {"google": {"thought_signature": "google-signature"}} + + +@pytest.mark.parametrize("finish_reason", ["length", "max_tokens"]) +async def test_output_limit_is_forwarded_and_reported_without_retry( + monkeypatch: pytest.MonkeyPatch, + finish_reason: str, +) -> None: + provider_call = AsyncMock(return_value=SimpleNamespace(choices=[SimpleNamespace(finish_reason=finish_reason)])) + monkeypatch.setattr( + anyllm_module.AnyLLM, "create", lambda *_args, **_kwargs: SimpleNamespace(acompletion=provider_call) + ) + client = AnyLLMClient( + model="test/provider-model", + max_tokens=789, + context_window_tokens=76_543, + ) + + with pytest.raises(OutputTokenLimitError) as exc_info: + await client.generate([], {}) + + assert exc_info.value.model_slug == "test/provider-model" + assert exc_info.value.max_tokens == 789 + assert exc_info.value.provider_reason == finish_reason + provider_call.assert_awaited_once() + provider_request = provider_call.await_args + assert provider_request is not None + assert provider_request.kwargs["max_tokens"] == 789 + + +async def test_context_window_rejection_surfaces_as_context_overflow(monkeypatch: pytest.MonkeyPatch) -> None: + provider_call = AsyncMock( + side_effect=ContextLengthExceededError( + message="boom", + provider_name="test", + ) + ) + monkeypatch.setattr( + anyllm_module.AnyLLM, "create", lambda *_args, **_kwargs: SimpleNamespace(acompletion=provider_call) + ) + client = AnyLLMClient(model="test/provider-model", max_tokens=8_192, context_window_tokens=76_543) + + with pytest.raises(ContextOverflowError): + await client.generate([], {}) + + +async def test_anthropic_thinking_blocks_survive_tool_round_trip() -> None: + transport = httpx if issubclass(DefaultAsyncHttpxClient, httpx.AsyncClient) else import_module("httpx2") + blocks = [ + {"type": "thinking", "thinking": "first", "signature": "sig-a"}, + {"type": "redacted_thinking", "data": "opaque"}, + {"type": "thinking", "thinking": "second", "signature": "sig-b"}, + {"type": "tool_use", "id": "toolu_1", "name": "lookup", "input": {}}, + ] + requests: list[dict[str, Any]] = [] + + def respond(request: httpx.Request) -> httpx.Response: + requests.append(json.loads(request.content)) + return transport.Response( + 200, + json={ + "id": "msg-test", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-6", + "content": blocks, + "stop_reason": "tool_use", + "stop_sequence": None, + "usage": {"input_tokens": 10, "output_tokens": 5}, + }, + ) + + async with transport.AsyncClient(transport=transport.MockTransport(respond)) as http_client: + client = AnyLLMClient( + model="anthropic:claude-sonnet-4-6", + api_key="test-key", + context_window_tokens=128_000, + kwargs={"client_args": {"http_client": http_client}}, + ) + prompt = UserMessage(content="Look it up") + result = await client.generate([prompt], {}) + assert result.blocks == [ + SignedReasoningBlock(content="first", signature="sig-a"), + RedactedReasoningBlock(data="opaque"), + SignedReasoningBlock(content="second", signature="sig-b"), + ToolCall(tool_call_id="toolu_1", name="lookup", arguments="{}"), + ] + await client.generate([prompt, result, ToolMessage(tool_call_id="toolu_1", content="42")], {}) + + assert requests[1]["messages"][1]["content"] == blocks + assert requests[1]["messages"][2]["content"][0]["tool_use_id"] == "toolu_1" + + +async def test_gemini_tool_signature_survives_tool_round_trip() -> None: + pytest.importorskip("google.genai") + signature = "Z2VtaW5pLXNpZw==" + function_call = {"name": "lookup", "args": {}} + requests: list[dict[str, Any]] = [] + + def respond(request: httpx.Request) -> httpx.Response: + requests.append(json.loads(request.content)) + return httpx.Response( + 200, + json={ + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + {"functionCall": function_call, "thoughtSignature": signature}, + ], + }, + "finishReason": "STOP", + } + ], + "usageMetadata": {"promptTokenCount": 10, "candidatesTokenCount": 5, "totalTokenCount": 15}, + }, + ) + + async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as http_client: + client = AnyLLMClient( + model="gemini:gemini-3-flash-preview", + api_key="test-key", + context_window_tokens=128_000, + kwargs={"client_args": {"http_options": {"httpx_async_client": http_client}}}, + ) + prompt = UserMessage(content="Look it up") + result = await client.generate([prompt], {}) + [tool_call] = result.blocks + assert isinstance(tool_call, ToolCall) + assert (tool_call.name, tool_call.arguments, tool_call.signature) == ("lookup", "{}", signature) + await client.generate( + [ + prompt, + result, + ToolMessage(tool_call_id=tool_call.tool_call_id, name="lookup", content="42"), + ], + {}, + ) + + [part] = requests[1]["contents"][1]["parts"] + assert part["functionCall"] == function_call + assert part["thoughtSignature"] == signature + assert requests[1]["contents"][2]["parts"][0]["functionResponse"]["name"] == "lookup" diff --git a/uv.lock b/uv.lock index df0911b..2c197c7 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.13'", @@ -146,7 +146,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.76.0" +version = "0.125.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -158,9 +158,33 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/be/d11abafaa15d6304826438170f7574d750218f49a106c54424a40cef4494/anthropic-0.76.0.tar.gz", hash = "sha256:e0cae6a368986d5cf6df743dfbb1b9519e6a9eee9c6c942ad8121c0b34416ffe", size = 495483, upload-time = "2026-01-13T18:41:14.908Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/f8/6f0560884b5363848347bd640b6c1d04abc25e7aa61787a232f790c6b60a/anthropic-0.125.0.tar.gz", hash = "sha256:e0cdd336580cb7411c1cdab69f80973e9bf4bff7f8e08141811d46307d45c682", size = 1112593, upload-time = "2026-08-19T22:00:42.837Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/70/7b0fd9c1a738f59d3babe2b4212031c34ab7d0fda4ffef15b58a55c5bcea/anthropic-0.76.0-py3-none-any.whl", hash = "sha256:81efa3113901192af2f0fe977d3ec73fdadb1e691586306c4256cd6d5ccc331c", size = 390309, upload-time = "2026-01-13T18:41:13.483Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1a/b1bd30cda3790557e8791bec5922a6ec8fabb6fa8b008c76a39cf7be6152/anthropic-0.125.0-py3-none-any.whl", hash = "sha256:3486013602eca76d8b12540764e53654f02cf4951110bca86cf06e67428a9f21", size = 1184067, upload-time = "2026-08-19T22:00:44.596Z" }, +] + +[[package]] +name = "any-llm-sdk" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anthropic" }, + { name = "httpx" }, + { name = "openai" }, + { name = "openresponses-types" }, + { name = "pydantic" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/81/736798361148759c1350237757545f3b0be36bad2dd5a04ffab94709abfb/any_llm_sdk-1.27.0.tar.gz", hash = "sha256:9e18f1c270c2f0a125b209e5f975c5396d81f838f87bc9d98da240397d56e3ed", size = 210469, upload-time = "2026-09-03T14:27:58.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/81/a1c6d1e7bfe0192948d08b6edd6a17bb1ca732d24a8b3862dee94a969a0e/any_llm_sdk-1.27.0-py3-none-any.whl", hash = "sha256:3e670d7c61874cf72ce7fef211ee31115aa7480d2114cda3235a46e3c9ade383", size = 265044, upload-time = "2026-09-03T14:27:57.545Z" }, +] + +[package.optional-dependencies] +gemini = [ + { name = "google-cloud-storage" }, + { name = "google-genai" }, ] [[package]] @@ -925,6 +949,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/21/fb96db432d187b07756e62971c4d89bdef70259e4cfa76ee32bcc0ac97d1/google_auth_oauthlib-1.2.4-py3-none-any.whl", hash = "sha256:0e922eea5f2baacaf8867febb782e46e7b153236c21592ed76ab3ddb77ffd772", size = 19193, upload-time = "2026-01-15T22:03:09.046Z" }, ] +[[package]] +name = "google-cloud-core" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/71/d6081acadf55d4233271c39860f0d140ef61fbdb4bceb2075e9c2905d947/google_cloud_core-2.8.0.tar.gz", hash = "sha256:365f8e4518ae81c8101b8dea5fc1c32a960badedb8b511f19db2843cbbd285d2", size = 36605, upload-time = "2026-09-29T19:25:59.275Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/ed/1b09640a565e5d34d4e517463f65a67a11f9dcdeb6b6e8220f2d06aaf489/google_cloud_core-2.8.0-py3-none-any.whl", hash = "sha256:e235b0952f7ffe7b9c71a4cf96b506d9cfb557e22557c412f0df9b7068b5d007", size = 31048, upload-time = "2026-09-29T19:25:35.361Z" }, +] + +[[package]] +name = "google-cloud-storage" +version = "3.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "google-resumable-media" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/d8/9d6444c1bc301bd4bad36cc91306524e8aba670cbefcc0d2fc184c88fbea/google_cloud_storage-3.17.1.tar.gz", hash = "sha256:b24df37900f5e0a93518bd7d3a2a7b7b48d8a1e72add8eb57d53a38b8b2b0640", size = 17355234, upload-time = "2026-10-15T17:49:57.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/77/4376bb26f24966008a9522c61bae025d7030baabf443f20c4b795fb58308/google_cloud_storage-3.17.1-py3-none-any.whl", hash = "sha256:287c7919d7bcda710eae6f6b60c756e89140475d9a4daec01b6174e737488ca8", size = 345706, upload-time = "2026-10-15T17:49:27.497Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/25/9cb0c1c31c45b893eb8f11ae70b3f4309432d59b5acaebca5dbe791729a4/google_crc32c-1.9.0.tar.gz", hash = "sha256:7b8c84c3d159ab6817fe3f74e6e6cef099c3f95dcec3abc0d8afb1404642efbe", size = 14857, upload-time = "2026-09-24T21:39:32.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/5d/0730e1b3a14d054d1466f2fec88dadf978509c749a3d96d8b069cc56d38a/google_crc32c-1.9.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:53fdafef58e230d0c946ab5f8446d123d9f548230a73b29c8b41c9546f268bc1", size = 31961, upload-time = "2026-09-24T21:19:01.724Z" }, + { url = "https://files.pythonhosted.org/packages/dd/32/d085abaf2fd907121975b92245bb3480fb8be40c37d03f9d6c41857f84c3/google_crc32c-1.9.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:8b91f41645b15a720357183fa5716682ada441873e3c462c15f9714be36f146b", size = 31804, upload-time = "2026-09-24T21:22:25.81Z" }, + { url = "https://files.pythonhosted.org/packages/94/78/dd1935432337e5da7af391a6fc9f161c1c8e9b9002a402b9190135fe1b59/google_crc32c-1.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:16865b477d7941712cb0e0aad8ad4815e984fb5fc16d3fdaef7d986e26e53c95", size = 37775, upload-time = "2026-09-24T21:38:09.249Z" }, + { url = "https://files.pythonhosted.org/packages/9e/43/9db03635bb10188d93dcbab9baa2a8670a0da4e868b4370cdbd98d65fed8/google_crc32c-1.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3abb18297d9ef0ab120531838be0e6d68c9fa876570e11c229c48f2edac23ce7", size = 36347, upload-time = "2026-09-24T21:38:10.141Z" }, + { url = "https://files.pythonhosted.org/packages/cf/eb/94dee516c846bd9382c3f566d8f8e5fb9e90599e45afeb697f9fc2533528/google_crc32c-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fb63a8d7fa2e95dcff1ca16af2f4d88b526fa5ff72d1696285884ac2d49b6963", size = 35097, upload-time = "2026-09-24T21:39:28.934Z" }, + { url = "https://files.pythonhosted.org/packages/3f/34/cb484e8b6174f130f8c6dc79c733a9dd8869b410ad6511fb6104c46b973a/google_crc32c-1.9.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:f1dc17d987ddcc5eba12a7ce48f0eb93141dea236b170c1101151396edf2f0cf", size = 31960, upload-time = "2026-09-24T21:19:02.454Z" }, + { url = "https://files.pythonhosted.org/packages/af/25/3e8e567bd48448e225ea27318ccf2b94e05124e7b8b97b13eaec9e127199/google_crc32c-1.9.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f894a2877650b56201d26a012a257b76d54a68834dc3913a93830ca8a047b075", size = 31803, upload-time = "2026-09-24T21:22:27.008Z" }, + { url = "https://files.pythonhosted.org/packages/f0/18/bee0dd59ae622482dc6463636c79e4bde7c954d061c859c9256362c9931a/google_crc32c-1.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4488f1553a9ab7e86cdedc833374a7e904031803b995dc0bd0be48c271fa6556", size = 37776, upload-time = "2026-09-24T21:38:11.056Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b6/e76e80fed5f2558273c7839e622f98095c9b36c719c7147e38e3c055cb70/google_crc32c-1.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0568b17ed90ac596f29400d99e243fd0cc6276766183def888d1bf8d1dc13827", size = 36350, upload-time = "2026-09-24T21:38:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/87/34/165542bfa99dfef91a76471cc48cce74b8ff4e295722896087ab2b8e8611/google_crc32c-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:8583ec21d56b565d68ab2963cc7e21b3b271247c29b04286068255ef65f221bd", size = 35090, upload-time = "2026-09-24T21:39:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/43ea41f4061a1cad87b2b6559c98e960e45bf551fe66f83d833b98aaf0c9/google_crc32c-1.9.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6a3b2c8a343c570ed8100a7627c20badfd92c6caa2067093a86be45af27f5b1b", size = 31963, upload-time = "2026-09-24T21:19:03.208Z" }, + { url = "https://files.pythonhosted.org/packages/45/d2/a968c0c29ccd2b0c980ff4f9e3f7035cee28c23a1c57541825cc8221858c/google_crc32c-1.9.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:13179f7e3282617923e957b8e54b8f9c3968030f48640a9f47fd7c5c38c4a215", size = 31805, upload-time = "2026-09-24T21:22:27.917Z" }, + { url = "https://files.pythonhosted.org/packages/03/73/388e493d6c3e252e37165d22efe5a1361f872a24425391b999822861b23a/google_crc32c-1.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:265233aff33d835f5b909584fe36ab29647b598c271b661a300001099109e53e", size = 37925, upload-time = "2026-09-24T21:38:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/36/190d32caa363ef25d685f422ed1bbf93ff1140fb22fd4d90f24cec209977/google_crc32c-1.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dee799544cae42a42b17a88e38b59cf2c271051dc001da2117a8ff240ffa0548", size = 36495, upload-time = "2026-09-24T21:38:14.211Z" }, + { url = "https://files.pythonhosted.org/packages/d3/fd/81cefea6adae7bd92abb23d4567d199f6485a20ec0a305ca5fa04c52b9c5/google_crc32c-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:af73200fa9791ccd380f3598235dba8d82b8af0905df045b3dc60b59836e8ddd", size = 35535, upload-time = "2026-09-24T21:39:30.52Z" }, + { url = "https://files.pythonhosted.org/packages/c5/18/19d4f17f3f33f8fdffcb3e1e69219d6f7ec2c359c160867b04dac1d0a64d/google_crc32c-1.9.0-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:e6e8be8a94436079cb5340f6d495d9d7ba30124d8b952703994c739c7c06e236", size = 31977, upload-time = "2026-09-24T21:19:03.976Z" }, + { url = "https://files.pythonhosted.org/packages/81/b4/8010372c4b46f2ee2352dfdb630c397570cd85522a315df024ad2f9459aa/google_crc32c-1.9.0-cp315-cp315-macosx_12_0_x86_64.whl", hash = "sha256:f2b64641bca27497b986b9d87883014035aa904cb4fa333407c6752b3afee9ba", size = 31809, upload-time = "2026-09-24T21:22:29.1Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f8/7e33845d6b90ce1cf37cfabf25cb859277c7d3533ef1b6b1e1ca58581549/google_crc32c-1.9.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f97c3806dcea41c29c04965347b0e12481561b75e0045dc7a4f69d75dec5d9b1", size = 38359, upload-time = "2026-09-24T21:38:14.983Z" }, + { url = "https://files.pythonhosted.org/packages/36/ff/556b2423f449a7515af6b8222a4d7833cbe09ff3e8d2f0b80471f5f6d02e/google_crc32c-1.9.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0abe7e202c25909869c35672ab0f2fe748a7acf276eb78577332a7c38999740f", size = 36858, upload-time = "2026-09-24T21:38:15.799Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/4733f1b7c921d04a2bb9b9916cf66498bf7ad0860a06289413830da83192/google_crc32c-1.9.0-cp315-cp315-win_amd64.whl", hash = "sha256:5695c8b9327e040b2aba12c6659b0acb5995314ef0af0192da66e662e011103b", size = 35534, upload-time = "2026-09-24T21:39:31.337Z" }, +] + [[package]] name = "google-genai" version = "1.59.0" @@ -946,6 +1028,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/53/6d00692fe50d73409b3406ae90c71bc4499c8ae7fac377ba16e283da917c/google_genai-1.59.0-py3-none-any.whl", hash = "sha256:59fc01a225d074fe9d1e626c3433da292f33249dadce4deb34edea698305a6df", size = 719099, upload-time = "2026-01-15T20:29:44.604Z" }, ] +[[package]] +name = "google-resumable-media" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/64/df6a482d5aa39d7f7be186d892377d605cae6c88525fd851d456e8bbe9c9/google_resumable_media-2.11.0.tar.gz", hash = "sha256:febd83686752799661b4de575f0b993c5c25c349a5362556fc4d7be164056a37", size = 2164914, upload-time = "2026-09-29T19:26:13.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/2e/4f0a152f2e576e496f31ba1c3c62ed174a8878d06008916a7edc58b1bb28/google_resumable_media-2.11.0-py3-none-any.whl", hash = "sha256:f43d15e6a7f818f762eaead0f369c551f8275a4179c9d6225d0d259f49b87b5d", size = 81515, upload-time = "2026-09-29T19:25:47.31Z" }, +] + [[package]] name = "googleapis-common-protos" version = "1.72.0" @@ -1929,7 +2023,7 @@ wheels = [ [[package]] name = "openai" -version = "2.9.0" +version = "2.54.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1941,9 +2035,21 @@ 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/50/9a/8c75e8c8a5b407a0586faeb2afac91674ff955c191ecc1d6d3b6669f6788/openai-2.54.0.tar.gz", hash = "sha256:e3e6f8bc1ba30ddf381ace1a14340eed381cb984a1a59bd0f34b5be3b5d49cfa", size = 1100285, upload-time = "2026-08-11T18:46:59.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/a8/bb76c7356de8ad57f59d5ff993d434df0607f07f08bcc9c9a5c275e399c0/openai-2.54.0-py3-none-any.whl", hash = "sha256:89089789197ccdb87f173a03145ed1598d00795220c93e96cf712b1cbf5e5f2b", size = 1660351, upload-time = "2026-08-11T18:46:56.684Z" }, +] + +[[package]] +name = "openresponses-types" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/eb/cb4a855a27c6cf04af2d47c896b6549040a86bb75e6a59cb687a8bcf19aa/openresponses_types-2.4.0.tar.gz", hash = "sha256:cd55e7fa17230d8edd46e254ef34666ca6f250b7efa3712a110aac546c7dc8d5", size = 22101, upload-time = "2026-08-05T12:22:46.167Z" } 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/5b/ef/2605d7219b0fa648e66ab15043bae492f4e8d3c4de4fdad85fc5eadb95a3/openresponses_types-2.4.0-py3-none-any.whl", hash = "sha256:186406592bc115bf12d9ee88320f8051ab1ade0a8fa8941fd1e44b0be16684f1", size = 15097, upload-time = "2026-08-05T12:22:45.044Z" }, ] [[package]] @@ -2430,165 +2536,166 @@ version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, - { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-accessibility", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-accounts", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-addressbook" }, - { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-adservices", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-adsupport", marker = "(platform_release >= '18.0' and sys_platform != 'darwin') or (platform_release >= '18' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-applescriptkit" }, - { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-applescriptobjc", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-applicationservices" }, - { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-arkit", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-apptrackingtransparency", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-arkit", marker = "(platform_release >= '25.0' and sys_platform != 'darwin') or (platform_release >= '25' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-audiovideobridging", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-authenticationservices", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-automator" }, - { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4'" }, - { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-avfoundation", marker = "(platform_release >= '11.0' and sys_platform != 'darwin') or (platform_release >= '11' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-avkit", marker = "(platform_release >= '13.0' and sys_platform != 'darwin') or (platform_release >= '13' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-avrouting", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-backgroundassets", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-browserenginekit", marker = "(platform_release >= '23.4' and sys_platform != 'darwin') or (platform_release >= '23.4' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-businesschat", marker = "(platform_release >= '18.0' and sys_platform != 'darwin') or (platform_release >= '18' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-calendarstore", marker = "(platform_release >= '9.0' and sys_platform != 'darwin') or (platform_release >= '9' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-callkit", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-carbon" }, { name = "pyobjc-framework-cfnetwork" }, - { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-compositorservices", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-cinematic", marker = "(platform_release >= '23.0' and sys_platform != 'darwin') or (platform_release >= '23' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-classkit", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-cloudkit", marker = "(platform_release >= '14.0' and sys_platform != 'darwin') or (platform_release >= '14' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-collaboration", marker = "(platform_release >= '9.0' and sys_platform != 'darwin') or (platform_release >= '9' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-colorsync", marker = "(platform_release >= '17.0' and sys_platform != 'darwin') or (platform_release >= '17' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-compositorservices", marker = "(platform_release >= '25.0' and sys_platform != 'darwin') or (platform_release >= '25' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-contacts", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-contactsui", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-coreaudio" }, { name = "pyobjc-framework-coreaudiokit" }, - { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-corebluetooth", marker = "(platform_release >= '14.0' and sys_platform != 'darwin') or (platform_release >= '14' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-corehaptics", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-corelocation", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-coremedia", marker = "(platform_release >= '11.0' and sys_platform != 'darwin') or (platform_release >= '11' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-coremediaio", marker = "(platform_release >= '11.0' and sys_platform != 'darwin') or (platform_release >= '11' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-coremidi" }, - { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-coreml", marker = "(platform_release >= '17.0' and sys_platform != 'darwin') or (platform_release >= '17' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-coremotion", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-coreservices" }, - { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-corespotlight", marker = "(platform_release >= '17.0' and sys_platform != 'darwin') or (platform_release >= '17' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-corewlan", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-cryptotokenkit", marker = "(platform_release >= '14.0' and sys_platform != 'darwin') or (platform_release >= '14' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-datadetection", marker = "(platform_release >= '21.0' and sys_platform != 'darwin') or (platform_release >= '21' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-devicecheck", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-devicediscoveryextension", marker = "(platform_release >= '24.0' and sys_platform != 'darwin') or (platform_release >= '24' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-dictionaryservices", marker = "(platform_release >= '9.0' and sys_platform != 'darwin') or (platform_release >= '9' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-discrecording" }, { name = "pyobjc-framework-discrecordingui" }, { name = "pyobjc-framework-diskarbitration" }, { name = "pyobjc-framework-dvdplayback" }, - { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-eventkit", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-exceptionhandling" }, - { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-fskit", marker = "platform_release >= '24.4'" }, - { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-gamesave", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-executionpolicy", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-extensionkit", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-externalaccessory", marker = "(platform_release >= '17.0' and sys_platform != 'darwin') or (platform_release >= '17' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-fileprovider", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-fileproviderui", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-findersync", marker = "(platform_release >= '14.0' and sys_platform != 'darwin') or (platform_release >= '14' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-fsevents", marker = "(platform_release >= '9.0' and sys_platform != 'darwin') or (platform_release >= '9' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-fskit", marker = "(platform_release >= '24.4' and sys_platform != 'darwin') or (platform_release >= '24.4' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-gamecenter", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-gamecontroller", marker = "(platform_release >= '13.0' and sys_platform != 'darwin') or (platform_release >= '13' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-gamekit", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-gameplaykit", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-gamesave", marker = "(platform_release >= '25.0' and sys_platform != 'darwin') or (platform_release >= '25' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-healthkit", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-imagecapturecore", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-inputmethodkit", marker = "(platform_release >= '9.0' and sys_platform != 'darwin') or (platform_release >= '9' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-installerplugins" }, - { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-instantmessage", marker = "(platform_release >= '9.0' and sys_platform != 'darwin') or (platform_release >= '9' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-intents", marker = "(platform_release >= '16.0' and sys_platform != 'darwin') or (platform_release >= '16' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-intentsui", marker = "(platform_release >= '21.0' and sys_platform != 'darwin') or (platform_release >= '21' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-iobluetooth" }, { name = "pyobjc-framework-iobluetoothui" }, - { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-iosurface", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-ituneslibrary", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-kernelmanagement", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-latentsemanticmapping" }, { name = "pyobjc-framework-launchservices" }, - { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-network", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-libdispatch", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-libxpc", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-linkpresentation", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-localauthentication", marker = "(platform_release >= '14.0' and sys_platform != 'darwin') or (platform_release >= '14' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-localauthenticationembeddedui", marker = "(platform_release >= '21.0' and sys_platform != 'darwin') or (platform_release >= '21' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-mailkit", marker = "(platform_release >= '21.0' and sys_platform != 'darwin') or (platform_release >= '21' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-mapkit", marker = "(platform_release >= '13.0' and sys_platform != 'darwin') or (platform_release >= '13' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-mediaaccessibility", marker = "(platform_release >= '13.0' and sys_platform != 'darwin') or (platform_release >= '13' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-mediaextension", marker = "(platform_release >= '24.0' and sys_platform != 'darwin') or (platform_release >= '24' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-medialibrary", marker = "(platform_release >= '13.0' and sys_platform != 'darwin') or (platform_release >= '13' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-mediaplayer", marker = "(platform_release >= '16.0' and sys_platform != 'darwin') or (platform_release >= '16' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-mediatoolbox", marker = "(platform_release >= '13.0' and sys_platform != 'darwin') or (platform_release >= '13' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-metal", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-metalfx", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-metalkit", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-metalperformanceshaders", marker = "(platform_release >= '17.0' and sys_platform != 'darwin') or (platform_release >= '17' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-metrickit", marker = "(platform_release >= '21.0' and sys_platform != 'darwin') or (platform_release >= '21' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-mlcompute", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-modelio", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-multipeerconnectivity", marker = "(platform_release >= '14.0' and sys_platform != 'darwin') or (platform_release >= '14' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-naturallanguage", marker = "(platform_release >= '18.0' and sys_platform != 'darwin') or (platform_release >= '18' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-netfs", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-network", marker = "(platform_release >= '18.0' and sys_platform != 'darwin') or (platform_release >= '18' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-networkextension", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-notificationcenter", marker = "(platform_release >= '14.0' and sys_platform != 'darwin') or (platform_release >= '14' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-opendirectory", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-osakit" }, - { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-oslog", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-passkit", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-pencilkit", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-phase", marker = "(platform_release >= '21.0' and sys_platform != 'darwin') or (platform_release >= '21' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-photos", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-photosui", marker = "(platform_release >= '15.0' and sys_platform != 'darwin') or (platform_release >= '15' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-preferencepanes" }, - { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-pubsub", marker = "platform_release >= '9' and platform_release < '18' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-pushkit", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-quartz" }, - { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4'" }, + { name = "pyobjc-framework-quicklookthumbnailing", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-replaykit", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-safariservices", marker = "(platform_release >= '16.0' and sys_platform != 'darwin') or (platform_release >= '16' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-safetykit", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-scenekit", marker = "(platform_release >= '11.0' and sys_platform != 'darwin') or (platform_release >= '11' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-screencapturekit", marker = "(platform_release >= '21.4' and sys_platform != 'darwin') or (platform_release >= '21.4' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-screensaver" }, - { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-screentime", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-scriptingbridge", marker = "(platform_release >= '9.0' and sys_platform != 'darwin') or (platform_release >= '9' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-searchkit" }, { name = "pyobjc-framework-security" }, { name = "pyobjc-framework-securityfoundation" }, { name = "pyobjc-framework-securityinterface" }, - { name = "pyobjc-framework-securityui", marker = "platform_release >= '24.4'" }, - { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-social", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-securityui", marker = "(platform_release >= '24.4' and sys_platform != 'darwin') or (platform_release >= '24.4' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-sensitivecontentanalysis", marker = "(platform_release >= '23.0' and sys_platform != 'darwin') or (platform_release >= '23' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-servicemanagement", marker = "(platform_release >= '10.0' and sys_platform != 'darwin') or (platform_release >= '10' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-sharedwithyou", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-sharedwithyoucore", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-shazamkit", marker = "(platform_release >= '21.0' and sys_platform != 'darwin') or (platform_release >= '21' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-social", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-soundanalysis", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-speech", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-spritekit", marker = "(platform_release >= '13.0' and sys_platform != 'darwin') or (platform_release >= '13' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-storekit", marker = "(platform_release >= '11.0' and sys_platform != 'darwin') or (platform_release >= '11' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-symbols", marker = "(platform_release >= '23.0' and sys_platform != 'darwin') or (platform_release >= '23' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-syncservices" }, { name = "pyobjc-framework-systemconfiguration" }, - { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-systemextensions", marker = "(platform_release >= '19.0' and sys_platform != 'darwin') or (platform_release >= '19' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-threadnetwork", marker = "(platform_release >= '22.0' and sys_platform != 'darwin') or (platform_release >= '22' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-usernotifications", marker = "(platform_release >= '18.0' and sys_platform != 'darwin') or (platform_release >= '18' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-usernotificationsui", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-videosubscriberaccount", marker = "(platform_release >= '18.0' and sys_platform != 'darwin') or (platform_release >= '18' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-videotoolbox", marker = "(platform_release >= '12.0' and sys_platform != 'darwin') or (platform_release >= '12' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-virtualization", marker = "(platform_release >= '20.0' and sys_platform != 'darwin') or (platform_release >= '20' and sys_platform == 'darwin')" }, + { name = "pyobjc-framework-vision", marker = "(platform_release >= '17.0' and sys_platform != 'darwin') or (platform_release >= '17' and sys_platform == 'darwin')" }, { name = "pyobjc-framework-webkit" }, ] sdist = { url = "https://files.pythonhosted.org/packages/17/06/d77639ba166cc09aed2d32ae204811b47bc5d40e035cdc9bff7fff72ec5f/pyobjc-12.1.tar.gz", hash = "sha256:686d6db3eb3182fac9846b8ce3eedf4c7d2680b21b8b8d6e6df054a17e92a12d", size = 11345, upload-time = "2025-11-14T10:07:28.155Z" } @@ -4494,6 +4601,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/7b/8ceec1ab0446224d685e243e2770c5a5c92285bcab0b9324dbe7a893ae5a/pyobjc_framework_preferencepanes-12.1-py2.py3-none-any.whl", hash = "sha256:1b3af9db9e0cfed8db28c260b2cf9a22c15fda5f0ff4c26157b17f99a0e29bbf", size = 4797, upload-time = "2025-11-14T09:59:03.998Z" }, ] +[[package]] +name = "pyobjc-framework-pubsub" +version = "12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/b6/199b873535d523cda4cbd107859055c7b926950d67fd24e435f524d9c467/pyobjc_framework_pubsub-12.1.tar.gz", hash = "sha256:dc9dea4b2e82eb8e3370b399587b6b3bb1a8b9f9361178a83d608ec82cac0d89", size = 15450, upload-time = "2025-11-14T10:18:55.588Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/f1/27bd9c8219857286a50ef53bdd252c0ba8c5908e955dff860c0fca639075/pyobjc_framework_pubsub-12.1-py2.py3-none-any.whl", hash = "sha256:6bf254217645a493edd82090dbe9ab3e4ec97b9d1416a6f126ce3c2f7d7389af", size = 5225, upload-time = "2025-11-14T09:59:05.699Z" }, +] + [[package]] name = "pyobjc-framework-pushkit" version = "12.1" @@ -5666,6 +5786,7 @@ dependencies = [ [package.optional-dependencies] all = [ { name = "aiolimiter" }, + { name = "any-llm-sdk", extra = ["gemini"] }, { name = "browser-use" }, { name = "docker" }, { name = "e2b-code-interpreter" }, @@ -5674,6 +5795,9 @@ all = [ { name = "python-dotenv" }, { name = "slack-bolt" }, ] +anyllm = [ + { name = "any-llm-sdk", extra = ["gemini"] }, +] browser = [ { name = "browser-use" }, ] @@ -5713,6 +5837,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "aiolimiter", marker = "extra == 'e2b-throttle'", specifier = ">=1.1.0" }, + { name = "any-llm-sdk", extras = ["gemini"], marker = "extra == 'anyllm'", specifier = ">=1.27.0,<1.29.0" }, { name = "anyio", specifier = ">=4.1.0" }, { name = "browser-use", marker = "extra == 'browser'", specifier = ">=0.11.3" }, { name = "docker", marker = "extra == 'docker'", specifier = ">=7.0.0" }, @@ -5730,11 +5855,11 @@ requires-dist = [ { name = "rich", specifier = ">=13.0.0" }, { name = "slack-bolt", marker = "extra == 'slack'", specifier = ">=1.20.0" }, { name = "stirrup", extras = ["e2b"], marker = "extra == 'e2b-throttle'" }, - { name = "stirrup", extras = ["litellm", "e2b", "e2b-throttle", "docker", "mcp", "browser", "slack"], marker = "extra == 'all'" }, + { name = "stirrup", extras = ["litellm", "anyllm", "e2b", "e2b-throttle", "docker", "mcp", "browser", "slack"], marker = "extra == 'all'" }, { name = "tenacity", specifier = ">=5.0.0" }, { name = "trafilatura", specifier = ">=1.9.0" }, ] -provides-extras = ["litellm", "e2b", "e2b-throttle", "docker", "mcp", "browser", "slack", "all"] +provides-extras = ["litellm", "anyllm", "e2b", "e2b-throttle", "docker", "mcp", "browser", "slack", "all"] [package.metadata.requires-dev] dev = [