Refactor image content to use a shared ImageReference type (#45543)

## What changed

Represent images in `ContentItem` and `FunctionCallOutputContentItem` with `ImageReference::Inline`, flattened to preserve the existing `image_url` wire format. Update image producers and consumers and regenerate app-server schemas and SDK artifacts.

Preserve the Python SDK's `InputImageContentItem` and `InputImageFunctionCallOutputContentItem` class names during generation.

## Testing

Add a regression test for stable Python image class names and adapt existing image tests to the shared representation.

GitOrigin-RevId: c38a780ac3314c2ac2deb3afc1b93b94b6f93fec
This commit is contained in:
Krish Chainani
2026-09-14 23:25:09 +00:00
committed by copyberry
parent 6ae5e71458
commit 5a66d460d3
79 changed files with 994 additions and 312 deletions
@@ -575,6 +575,7 @@ def generate_v2_all(schema_dir: Path) -> None:
],
cwd=sdk_root(),
)
_preserve_inline_image_class_names(out_path)
_require_nullable_chatgpt_account_email(out_path)
_preserve_reasoning_effort_enum(out_path)
_preserve_thread_source_enum(out_path)
@@ -582,6 +583,23 @@ def generate_v2_all(schema_dir: Path) -> None:
_normalize_generated_timestamps(out_path)
def _preserve_inline_image_class_names(out_path: Path) -> None:
"""Keep the public class names used before ImageReference was introduced."""
source = out_path.read_text()
stable_names = {
"ImageUrlContentItem": "InputImageContentItem",
"ImageUrlFunctionCallOutputContentItem": "InputImageFunctionCallOutputContentItem",
}
for generated_name, stable_name in stable_names.items():
if source.count(f"class {generated_name}(") != 1:
raise RuntimeError(f"Generated SDK is missing a unique {generated_name} class")
if re.search(rf"\b{re.escape(stable_name)}\b", source):
raise RuntimeError(f"Generated SDK already defines {stable_name}")
source = re.sub(rf"\b{re.escape(generated_name)}\b", stable_name, source)
out_path.write_text(source)
def _require_nullable_chatgpt_account_email(out_path: Path) -> None:
"""Preserve required-but-nullable email semantics in the generated SDK model."""
source = out_path.read_text()
@@ -7784,8 +7784,8 @@ class InputImageContentItem(BaseModel):
populate_by_name=True,
)
detail: ImageDetail | None = None
image_url: str
type: Annotated[Literal["input_image"], Field(title="InputImageContentItemType")]
image_url: str
class ContentItem(
@@ -7796,9 +7796,7 @@ class ContentItem(
model_config = ConfigDict(
populate_by_name=True,
)
root: (
InputTextContentItem | InputImageContentItem | InputAudioContentItem | OutputTextContentItem
)
root: InputTextContentItem | InputImageContentItem | InputAudioContentItem | OutputTextContentItem
class ExperimentalFeature(BaseModel):
@@ -7980,10 +7978,10 @@ class InputImageFunctionCallOutputContentItem(BaseModel):
populate_by_name=True,
)
detail: ImageDetail | None = None
image_url: str
type: Annotated[
Literal["input_image"], Field(title="InputImageFunctionCallOutputContentItemType")
]
image_url: str
class FunctionCallOutputContentItem(
@@ -536,6 +536,20 @@ def test_generated_chatgpt_account_email_is_required_nullable() -> None:
ChatgptAccount.model_validate({"planType": "pro", "type": "chatgpt"})
def test_generated_inline_image_class_names_remain_stable() -> None:
"""Keep the existing Python class names when image references expand."""
from openai_codex.generated.v2_all import (
InputImageContentItem,
InputImageFunctionCallOutputContentItem,
)
assert InputImageContentItem.__name__ == "InputImageContentItem"
assert (
InputImageFunctionCallOutputContentItem.__name__
== "InputImageFunctionCallOutputContentItem"
)
def test_runtime_package_template_has_no_checked_in_binaries() -> None:
runtime_root = ROOT.parent / "python-runtime" / "src" / "codex_cli_bin"
assert sorted(