From 66edf33d6c4656276810e6ae76eab13d5e75807a Mon Sep 17 00:00:00 2001 From: Luke Parke <5702154+LukasParke@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:09:37 -0500 Subject: [PATCH] fix: bind the code-interpreter image type across the openrouter 1.1.40 rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit openrouter 1.1.40 renamed components.OutputImage to components.CodeInterpreterImageOutput with no back-compat alias. This package re-exports that symbol by name from __init__.py and declares `openrouter>=1.1,<2`, so any resolution at or above 1.1.40 raises ImportError on `import openrouter_agent` — the whole package, not just the renamed type. That is the state of the published 0.8.0 wheel today: a plain `pip install openrouter-agent-sdk` resolves the newest 1.1.x and cannot be imported. CI missed it because every job except `build` runs through uv.lock, which pins 1.1.22; `build` installs the wheel unpinned and is the one job that sees what users get. Bind whichever name the installed SDK provides. The two models are structurally identical (type, url) and this package only re-exports the type, so OutputImage and OutputInputImage stay stable for consumers on both sides of the boundary. Verified against 1.1.22, 1.1.39, 1.1.40 and 1.1.90; the full unit suite also passes against 1.1.90, so the rename was the only incompatibility in the range. Co-Authored-By: Claude Opus 5 --- src/openrouter_agent/__init__.py | 16 ++++++++++++++- tests/unit/test_sdk_compat_exports.py | 28 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_sdk_compat_exports.py diff --git a/src/openrouter_agent/__init__.py b/src/openrouter_agent/__init__.py index 5df8a08..5036ce4 100644 --- a/src/openrouter_agent/__init__.py +++ b/src/openrouter_agent/__init__.py @@ -39,7 +39,6 @@ OpenResponsesResult, OutputFileSearchCallItem, OutputFunctionCallItem, - OutputImage, OutputImageGenerationCallItem, OutputItems, OutputMessage, @@ -235,6 +234,21 @@ class BeforeCreateRequestHook(Protocol): def before_create_request(self, hook_ctx: BeforeCreateRequestContext, request: Any) -> Any: ... +# openrouter 1.1.40 renamed OutputImage to CodeInterpreterImageOutput with no +# back-compat alias, and this package's constraint (openrouter>=1.1,<2) spans +# both. The two models are structurally identical (type, url) and this package +# only re-exports the type, so bind whichever name the installed SDK provides +# to keep OutputImage / OutputInputImage stable for consumers. +# Both ignores are load-bearing across the range, not just today: mypy resolves +# against the locked SDK, so exactly one of these two imports is unresolvable at +# any given pin, and which one flips when the lock crosses 1.1.40. +try: + from openrouter.components import OutputImage # type: ignore[attr-defined] +except ImportError: # openrouter >= 1.1.40 + from openrouter.components import ( # type: ignore[assignment, no-redef] + CodeInterpreterImageOutput as OutputImage, + ) + OutputInputImage = OutputImage __all__ = [ diff --git a/tests/unit/test_sdk_compat_exports.py b/tests/unit/test_sdk_compat_exports.py new file mode 100644 index 0000000..5631743 --- /dev/null +++ b/tests/unit/test_sdk_compat_exports.py @@ -0,0 +1,28 @@ +"""Guards the public re-exports that the generated SDK has renamed under us. + +The package constraint is ``openrouter>=1.1,<2``, so a plain install resolves +whatever 1.1.x is newest. A symbol this package re-exports by name can vanish +inside that range -- openrouter 1.1.40 renamed ``OutputImage`` -- and the +failure mode is an ImportError at ``import openrouter_agent``, i.e. the whole +package, not just the renamed type. +""" + +from __future__ import annotations + +import openrouter_agent + + +def test_output_image_is_exported_under_both_names() -> None: + assert openrouter_agent.OutputImage is openrouter_agent.OutputInputImage + + +def test_output_image_has_the_code_interpreter_image_shape() -> None: + fields = openrouter_agent.OutputImage.model_fields + + assert set(fields) == {"type", "url"} + + +def test_declared_public_api_is_fully_importable() -> None: + missing = [name for name in openrouter_agent.__all__ if not hasattr(openrouter_agent, name)] + + assert missing == []