[sdk/python] Stop advertising HTTP image URLs (#29464)

## Summary

- use generated image data URLs in the Python SDK examples and notebook
- document HTTP and HTTPS image URLs as deprecated and recommend
`LocalImageInput`
- replace the remote-URL integration test with data-URL coverage

`ImageInput` remains available for data URLs. The SDK does not duplicate
app-server URL validation.

## Testing

- `uv run --frozen --no-sync ruff check --output-format=full .`
- `uv run --frozen --no-sync ruff format --check .`
- full Python SDK test suite with an isolated writable
`CODEX_SQLITE_HOME` (119 passed, 38 skipped)
This commit is contained in:
rka-oai
2026-06-22 11:34:14 -07:00
committed by GitHub
Unverified
parent 5b95745eae
commit 20431d49a0
8 changed files with 37 additions and 23 deletions
+3
View File
@@ -245,6 +245,9 @@ Input = list[InputItem] | InputItem
RunInput = Input | str
```
Use `ImageInput` with a base64-encoded `data:image/...` URL. HTTP and HTTPS image URLs are
deprecated; download remote images and pass their local paths with `LocalImageInput` instead.
Use a plain `str` as shorthand for `TextInput(...)` anywhere a turn input is accepted:
`thread.run("...")`, `thread.turn("...")`, and `turn.steer("...")`.
@@ -5,7 +5,7 @@ _EXAMPLES_ROOT = Path(__file__).resolve().parents[1]
if str(_EXAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_EXAMPLES_ROOT))
from _bootstrap import ensure_local_sdk_src, runtime_config
from _bootstrap import ensure_local_sdk_src, generated_sample_image_data_url, runtime_config
ensure_local_sdk_src()
@@ -13,7 +13,7 @@ import asyncio
from openai_codex import AsyncCodex, ImageInput, TextInput
REMOTE_IMAGE_URL = "https://raw.githubusercontent.com/github/explore/main/topics/python/python.png"
IMAGE_DATA_URL = generated_sample_image_data_url()
async def main() -> None:
@@ -24,7 +24,7 @@ async def main() -> None:
turn = await thread.turn(
[
TextInput("What is in this image? Give 3 bullets."),
ImageInput(REMOTE_IMAGE_URL),
ImageInput(IMAGE_DATA_URL),
]
)
result = await turn.run()
@@ -5,20 +5,20 @@ _EXAMPLES_ROOT = Path(__file__).resolve().parents[1]
if str(_EXAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_EXAMPLES_ROOT))
from _bootstrap import ensure_local_sdk_src, runtime_config
from _bootstrap import ensure_local_sdk_src, generated_sample_image_data_url, runtime_config
ensure_local_sdk_src()
from openai_codex import Codex, ImageInput, TextInput
REMOTE_IMAGE_URL = "https://raw.githubusercontent.com/github/explore/main/topics/python/python.png"
IMAGE_DATA_URL = generated_sample_image_data_url()
with Codex(config=runtime_config()) as codex:
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
result = thread.turn(
[
TextInput("What is in this image? Give 3 bullets."),
ImageInput(REMOTE_IMAGE_URL),
ImageInput(IMAGE_DATA_URL),
]
).run()
+1 -1
View File
@@ -72,7 +72,7 @@ python examples/01_quickstart_constructor/async.py
- `06_thread_lifecycle_and_controls/`
- thread lifecycle + control calls
- `07_image_and_text/`
- remote image URL + text multimodal turn
- image data URL + text multimodal turn
- `08_local_image_and_text/`
- local image + text multimodal turn using a generated temporary sample image
- `09_async_parity/`
+6
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import base64
import contextlib
import importlib.util
import sys
@@ -95,6 +96,11 @@ def _generated_sample_png_bytes() -> bytes:
)
def generated_sample_image_data_url() -> str:
encoded = base64.b64encode(_generated_sample_png_bytes()).decode("ascii")
return f"data:image/png;base64,{encoded}"
@contextlib.contextmanager
def temporary_sample_image_path() -> Iterator[Path]:
with tempfile.TemporaryDirectory(prefix="codex-python-example-image-") as temp_root:
+4 -4
View File
@@ -103,7 +103,7 @@
"outputs": [],
"source": [
"# Cell 2: imports (public only)\n",
"from _bootstrap import server_label\n",
"from _bootstrap import generated_sample_image_data_url, server_label\n",
"from openai_codex import (\n",
" AsyncCodex,\n",
" Codex,\n",
@@ -349,14 +349,14 @@
"metadata": {},
"outputs": [],
"source": [
"# Cell 6: multimodal with remote image\n",
"remote_image_url = 'https://raw.githubusercontent.com/github/explore/main/topics/python/python.png'\n",
"# Cell 6: multimodal with an image data URL\n",
"image_data_url = generated_sample_image_data_url()\n",
"\n",
"with Codex() as codex:\n",
" thread = codex.thread_start(model='gpt-5.4', config={'model_reasoning_effort': 'high'})\n",
" result = thread.turn([\n",
" TextInput('What do you see in this image? 3 bullets.'),\n",
" ImageInput(remote_image_url),\n",
" ImageInput(image_data_url),\n",
" ]).run()\n",
" print('status:', result.status)\n",
" print(result.final_response)\n"
+1 -1
View File
@@ -14,7 +14,7 @@ class TextInput:
@dataclass(slots=True)
class ImageInput:
"""Remote image URL supplied as turn input."""
"""Image data URL supplied as turn input."""
url: str
+16 -11
View File
@@ -1,40 +1,45 @@
from __future__ import annotations
import base64
from app_server_harness import AppServerHarness
from app_server_helpers import TINY_PNG_BYTES
from openai_codex import Codex, ImageInput, LocalImageInput, SkillInput, TextInput
def test_remote_image_input_reaches_responses_api(
def test_data_url_image_input_reaches_responses_api(
tmp_path,
) -> None:
"""Remote image inputs should survive the SDK and app-server boundary."""
remote_image_url = "https://example.com/codex.png"
"""Data URL image inputs should survive the SDK and app-server boundary."""
image_data_url = "data:image/png;base64," + base64.b64encode(TINY_PNG_BYTES).decode("ascii")
with AppServerHarness(tmp_path) as harness:
harness.responses.enqueue_assistant_message(
"remote image received",
response_id="remote-image",
"data URL image received",
response_id="data-url-image",
)
with Codex(config=harness.app_server_config()) as codex:
result = codex.thread_start().run(
[
TextInput("Describe the remote image."),
ImageInput(remote_image_url),
TextInput("Describe the data URL image."),
ImageInput(image_data_url),
]
)
request = harness.responses.single_request()
assert {
"final_response": result.final_response,
"contains_user_prompt": "Describe the remote image." in request.message_input_texts("user"),
"image_urls": request.message_image_urls("user"),
"contains_user_prompt": "Describe the data URL image."
in request.message_input_texts("user"),
"image_url_is_png_data_url": request.message_image_urls("user")[-1].startswith(
"data:image/png;base64,"
),
} == {
"final_response": "remote image received",
"final_response": "data URL image received",
"contains_user_prompt": True,
"image_urls": [remote_image_url],
"image_url_is_png_data_url": True,
}