Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sdk/python/docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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("...")`.

Expand Down
6 changes: 3 additions & 3 deletions sdk/python/examples/07_image_and_text/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
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()

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:
Expand All @@ -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()
Expand Down
6 changes: 3 additions & 3 deletions sdk/python/examples/07_image_and_text/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
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()

Expand Down
2 changes: 1 addition & 1 deletion sdk/python/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/`
Expand Down
6 changes: 6 additions & 0 deletions sdk/python/examples/_bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import base64
import contextlib
import importlib.util
import sys
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions sdk/python/notebooks/sdk_walkthrough.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/src/openai_codex/_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 16 additions & 11 deletions sdk/python/tests/test_app_server_inputs.py
Original file line number Diff line number Diff line change
@@ -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,
}


Expand Down
Loading