Skip to content

Commit 72c656d

Browse files
feat(api): add image edit markup overlays (#4210)
## Summary Adds an optional markup image to guide image edits. ## Changes - Provide a transparent PNG markup overlay when editing an image. - Document that markup must match the first image dimensions and cannot be combined with a mask. - Keep the Realtime example responsive to Ctrl-C while waiting for input, without crashing during interpreter shutdown. On POSIX, the example polls stdin on the main thread instead of using a background reader. Partial input, Unicode and subsequent lines are preserved. Windows retains builtin input. Validation: all eight example input/shutdown scenarios pass under both Pydantic versions, plus180 repeated signal cases. Tests reject fatal interpreter errors and invalid exit statuses; the original two-second deadline is unchanged. The previous daemon-reader implementation fails the strengthened tests. Formatting and lint/type checks passed. Public CI is rerunning on this updated candidate.
1 parent f552fcf commit 72c656d

9 files changed

Lines changed: 331 additions & 25 deletions

File tree

‎.castiron.stats.yml‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
schema_version: 1
2-
generation_id: 093e3a9d-ca04-4b14-b524-cdefc6807fe1
3-
openapi_spec_hash: df8d45213a6894a955338882c36a1d86
4-
openapi_transformed_spec_hash: 050a1c6e01247b5af767e740b8494686
2+
generation_id: ca0448a4-c5bb-4dae-b9a8-ce0695616efc
3+
openapi_spec_hash: 8ca9f63bc016670f3fd9c8265f106c33
4+
openapi_transformed_spec_hash: 2cb5f40bd6fd564475d5b6ef285c9858
55
config_hash: 8a684aec06907ff8ced7809753e16225
6-
codegen_sha: 514ca4482332258ac041a8d62a18c51ad31627e3
7-
codegen_hash: 319a11170cf75d4fb01e168ed07a00b7eecc51b23d5912ff6d9d8552968d127d
6+
codegen_sha: 52a6c414b9565e2a8a3f89f87a4df1a5b20deaea
7+
codegen_hash: 6171d1d87772171c445edcacc04ee274f4d10497fe9da70810d5c778ab57c394

‎api_reference/openapi.transformed.yml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44419,6 +44419,8 @@ components:
4441944419
example: high
4442044420
description: |
4442144421
The quality of the image that will be generated for GPT image models. The GPT image models support `low`, `medium`, and `high`. `gpt-image-2.5-sunburst` and `gpt-image-2.5-flare`, including their `2026-09-08` snapshots, also support `xhigh` and `max`. Defaults to `auto`.
44422+
markup:
44423+
$ref: '#/components/schemas/markup'
4442244424
required:
4442344425
- prompt
4442444426
- image
@@ -46985,6 +46987,9 @@ components:
4698546987
description: Stream partial image results as events.
4698646988
partial_images:
4698746989
$ref: '#/components/schemas/PartialImages'
46990+
markup:
46991+
$ref: '#/components/schemas/ImageRefParam'
46992+
description: A transparent PNG overlaid on the first image, referenced by file ID, HTTPS URL, or base64 data URL. Must match that image's dimensions after orientation and contain transparent pixels. Supports GPT Image 2.5 and 2.6. Cannot be combined with mask.
4698846993
required:
4698946994
- images
4699046995
- prompt
@@ -111105,6 +111110,10 @@ components:
111105111110
- async
111106111111
additionalProperties: false
111107111112
description: The effective settings for compacting the agent's context.
111113+
markup:
111114+
type: string
111115+
format: binary
111116+
description: A transparent PNG overlaid on the first image to guide the edit. Must match that image's dimensions after orientation and contain transparent pixels. Supports GPT Image 2.5 and 2.6. Cannot be combined with mask.
111108111117
SessionSpendControlResource:
111109111118
type: object
111110111119
properties:

‎examples/realtime/realtime.py‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
#!/usr/bin/env -S uv run python
2+
import os
3+
import sys
4+
import codecs
5+
import select
6+
27
from openai import OpenAI
38

49
# Azure OpenAI Realtime Docs
@@ -8,6 +13,33 @@
813
# Entra ID auth: https://learn.microsoft.com/azure/ai-services/openai/how-to/managed-identity
914

1015

16+
def read_input(prompt: str) -> str:
17+
if sys.platform == "win32":
18+
# Windows console input does not support select().
19+
return input(prompt)
20+
21+
# SIGINT may reach a WebSocket thread. Poll on the main thread so Python
22+
# can run its signal handler, without leaving a stdin reader at shutdown.
23+
print(prompt, end="", flush=True)
24+
decoder = codecs.getincrementaldecoder(sys.stdin.encoding)(errors=sys.stdin.errors or "strict")
25+
characters: list[str] = []
26+
while True:
27+
readable, _, _ = select.select([sys.stdin], [], [], 0.1)
28+
if not readable:
29+
continue
30+
# Do not read ahead into the next prompt or block on a partial line.
31+
data = os.read(sys.stdin.fileno(), 1)
32+
character = decoder.decode(data, final=not data)
33+
if character == "\n":
34+
return "".join(characters).removesuffix("\r")
35+
characters.append(character)
36+
if not data:
37+
text = "".join(characters)
38+
if not text:
39+
raise EOFError
40+
return text
41+
42+
1143
def main() -> None:
1244
"""The following example demonstrates how to configure OpenAI to use the Realtime API.
1345
For an audio example, see push_to_talk_app.py and update the client and model parameter accordingly.
@@ -28,7 +60,7 @@ def main() -> None:
2860
}
2961
)
3062
while True:
31-
user_input = input("Enter a message: ")
63+
user_input = read_input("Enter a message: ")
3264
if user_input == "q":
3365
break
3466

‎scripts/castiron/generated-files.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@
547547
},
548548
"src/openai/resources/images.py": {
549549
"mode": "100644",
550-
"sha256": "f8f5376eeda7b45a90179eb287066447fce19ed761d313de5ab4c59df620b659"
550+
"sha256": "211377871db5f2d21633f93122a131023174ea093828c7a957cd0ece49a03b76"
551551
},
552552
"src/openai/resources/live/__init__.py": {
553553
"mode": "100644",
@@ -4543,7 +4543,7 @@
45434543
},
45444544
"src/openai/types/image_edit_params.py": {
45454545
"mode": "100644",
4546-
"sha256": "e45d0c075029142ad45cbb557962a6e065d299687449b97405390a9534d5b7bf"
4546+
"sha256": "a08fc3c5d22c14b3eb969fa91891e3d3dd7088f9e46749aa5bd59c4e5c558df3"
45474547
},
45484548
"src/openai/types/image_edit_partial_image_event.py": {
45494549
"mode": "100644",
@@ -7999,7 +7999,7 @@
79997999
},
80008000
"tests/api_resources/test_images.py": {
80018001
"mode": "100644",
8002-
"sha256": "ebd279e1c7d8c8bd3c8148d1aadbeb7dc1ba43a6e2ee7396653e37563e743f3b"
8002+
"sha256": "ca9dae5fdc1d35f7a7d1dd2356736628b24d3f11c28e4750f7cea920a6c9804c"
80038003
},
80048004
"tests/api_resources/test_live.py": {
80058005
"mode": "100644",

‎src/openai/resources/images.py‎

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def edit(
144144
prompt: str,
145145
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
146146
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
147+
markup: FileTypes | Omit = omit,
147148
mask: FileTypes | Omit = omit,
148149
model: Union[str, ImageModel, None] | Omit = omit,
149150
n: Optional[int] | Omit = omit,
@@ -197,6 +198,10 @@ def edit(
197198
`gpt-image-1.5`; `gpt-image-1-mini` supports only `low`. For `gpt-image-2`, omit
198199
this parameter. Defaults to `low` on supported models.
199200
201+
markup: A transparent PNG overlaid on the first image to guide the edit. Must match that
202+
image's dimensions after orientation and contain transparent pixels. Supports
203+
GPT Image 2.5 and 2.6. Cannot be combined with mask.
204+
200205
mask: An additional image whose fully transparent areas (e.g. where alpha is zero)
201206
indicate where `image` should be edited. If there are multiple images provided,
202207
the mask will be applied on the first image. Must be a valid PNG file, less than
@@ -300,6 +305,7 @@ def edit(
300305
stream: Literal[True],
301306
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
302307
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
308+
markup: FileTypes | Omit = omit,
303309
mask: FileTypes | Omit = omit,
304310
model: Union[str, ImageModel, None] | Omit = omit,
305311
n: Optional[int] | Omit = omit,
@@ -356,6 +362,10 @@ def edit(
356362
`gpt-image-1.5`; `gpt-image-1-mini` supports only `low`. For `gpt-image-2`, omit
357363
this parameter. Defaults to `low` on supported models.
358364
365+
markup: A transparent PNG overlaid on the first image to guide the edit. Must match that
366+
image's dimensions after orientation and contain transparent pixels. Supports
367+
GPT Image 2.5 and 2.6. Cannot be combined with mask.
368+
359369
mask: An additional image whose fully transparent areas (e.g. where alpha is zero)
360370
indicate where `image` should be edited. If there are multiple images provided,
361371
the mask will be applied on the first image. Must be a valid PNG file, less than
@@ -455,6 +465,7 @@ def edit(
455465
stream: bool,
456466
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
457467
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
468+
markup: FileTypes | Omit = omit,
458469
mask: FileTypes | Omit = omit,
459470
model: Union[str, ImageModel, None] | Omit = omit,
460471
n: Optional[int] | Omit = omit,
@@ -511,6 +522,10 @@ def edit(
511522
`gpt-image-1.5`; `gpt-image-1-mini` supports only `low`. For `gpt-image-2`, omit
512523
this parameter. Defaults to `low` on supported models.
513524
525+
markup: A transparent PNG overlaid on the first image to guide the edit. Must match that
526+
image's dimensions after orientation and contain transparent pixels. Supports
527+
GPT Image 2.5 and 2.6. Cannot be combined with mask.
528+
514529
mask: An additional image whose fully transparent areas (e.g. where alpha is zero)
515530
indicate where `image` should be edited. If there are multiple images provided,
516531
the mask will be applied on the first image. Must be a valid PNG file, less than
@@ -609,6 +624,7 @@ def edit(
609624
prompt: str,
610625
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
611626
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
627+
markup: FileTypes | Omit = omit,
612628
mask: FileTypes | Omit = omit,
613629
model: Union[str, ImageModel, None] | Omit = omit,
614630
n: Optional[int] | Omit = omit,
@@ -635,6 +651,7 @@ def edit(
635651
"prompt": prompt,
636652
"background": background,
637653
"input_fidelity": input_fidelity,
654+
"markup": markup,
638655
"mask": mask,
639656
"model": model,
640657
"n": n,
@@ -648,9 +665,11 @@ def edit(
648665
"stream": stream,
649666
"user": user,
650667
},
651-
[["image"], ["image", "<array>"], ["mask"]],
668+
[["image"], ["image", "<array>"], ["markup"], ["mask"]],
669+
)
670+
files = extract_files(
671+
cast(Mapping[str, object], body), paths=[["image"], ["image", "<array>"], ["markup"], ["mask"]]
652672
)
653-
files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["image", "<array>"], ["mask"]])
654673
# It should be noted that the actual Content-Type header that will be
655674
# sent to the server will contain a `boundary` parameter, e.g.
656675
# multipart/form-data; boundary=---abc--
@@ -1301,6 +1320,7 @@ async def edit(
13011320
prompt: str,
13021321
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
13031322
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
1323+
markup: FileTypes | Omit = omit,
13041324
mask: FileTypes | Omit = omit,
13051325
model: Union[str, ImageModel, None] | Omit = omit,
13061326
n: Optional[int] | Omit = omit,
@@ -1354,6 +1374,10 @@ async def edit(
13541374
`gpt-image-1.5`; `gpt-image-1-mini` supports only `low`. For `gpt-image-2`, omit
13551375
this parameter. Defaults to `low` on supported models.
13561376
1377+
markup: A transparent PNG overlaid on the first image to guide the edit. Must match that
1378+
image's dimensions after orientation and contain transparent pixels. Supports
1379+
GPT Image 2.5 and 2.6. Cannot be combined with mask.
1380+
13571381
mask: An additional image whose fully transparent areas (e.g. where alpha is zero)
13581382
indicate where `image` should be edited. If there are multiple images provided,
13591383
the mask will be applied on the first image. Must be a valid PNG file, less than
@@ -1457,6 +1481,7 @@ async def edit(
14571481
stream: Literal[True],
14581482
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
14591483
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
1484+
markup: FileTypes | Omit = omit,
14601485
mask: FileTypes | Omit = omit,
14611486
model: Union[str, ImageModel, None] | Omit = omit,
14621487
n: Optional[int] | Omit = omit,
@@ -1513,6 +1538,10 @@ async def edit(
15131538
`gpt-image-1.5`; `gpt-image-1-mini` supports only `low`. For `gpt-image-2`, omit
15141539
this parameter. Defaults to `low` on supported models.
15151540
1541+
markup: A transparent PNG overlaid on the first image to guide the edit. Must match that
1542+
image's dimensions after orientation and contain transparent pixels. Supports
1543+
GPT Image 2.5 and 2.6. Cannot be combined with mask.
1544+
15161545
mask: An additional image whose fully transparent areas (e.g. where alpha is zero)
15171546
indicate where `image` should be edited. If there are multiple images provided,
15181547
the mask will be applied on the first image. Must be a valid PNG file, less than
@@ -1612,6 +1641,7 @@ async def edit(
16121641
stream: bool,
16131642
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
16141643
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
1644+
markup: FileTypes | Omit = omit,
16151645
mask: FileTypes | Omit = omit,
16161646
model: Union[str, ImageModel, None] | Omit = omit,
16171647
n: Optional[int] | Omit = omit,
@@ -1668,6 +1698,10 @@ async def edit(
16681698
`gpt-image-1.5`; `gpt-image-1-mini` supports only `low`. For `gpt-image-2`, omit
16691699
this parameter. Defaults to `low` on supported models.
16701700
1701+
markup: A transparent PNG overlaid on the first image to guide the edit. Must match that
1702+
image's dimensions after orientation and contain transparent pixels. Supports
1703+
GPT Image 2.5 and 2.6. Cannot be combined with mask.
1704+
16711705
mask: An additional image whose fully transparent areas (e.g. where alpha is zero)
16721706
indicate where `image` should be edited. If there are multiple images provided,
16731707
the mask will be applied on the first image. Must be a valid PNG file, less than
@@ -1766,6 +1800,7 @@ async def edit(
17661800
prompt: str,
17671801
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
17681802
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
1803+
markup: FileTypes | Omit = omit,
17691804
mask: FileTypes | Omit = omit,
17701805
model: Union[str, ImageModel, None] | Omit = omit,
17711806
n: Optional[int] | Omit = omit,
@@ -1792,6 +1827,7 @@ async def edit(
17921827
"prompt": prompt,
17931828
"background": background,
17941829
"input_fidelity": input_fidelity,
1830+
"markup": markup,
17951831
"mask": mask,
17961832
"model": model,
17971833
"n": n,
@@ -1805,9 +1841,11 @@ async def edit(
18051841
"stream": stream,
18061842
"user": user,
18071843
},
1808-
[["image"], ["image", "<array>"], ["mask"]],
1844+
[["image"], ["image", "<array>"], ["markup"], ["mask"]],
1845+
)
1846+
files = extract_files(
1847+
cast(Mapping[str, object], body), paths=[["image"], ["image", "<array>"], ["markup"], ["mask"]]
18091848
)
1810-
files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["image", "<array>"], ["mask"]])
18111849
# It should be noted that the actual Content-Type header that will be
18121850
# sent to the server will contain a `boundary` parameter, e.g.
18131851
# multipart/form-data; boundary=---abc--

‎src/openai/types/image_edit_params.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ class ImageEditParamsBase(TypedDict, total=False):
5151
this parameter. Defaults to `low` on supported models.
5252
"""
5353

54+
markup: FileTypes
55+
"""A transparent PNG overlaid on the first image to guide the edit.
56+
57+
Must match that image's dimensions after orientation and contain transparent
58+
pixels. Supports GPT Image 2.5 and 2.6. Cannot be combined with mask.
59+
"""
60+
5461
mask: FileTypes
5562
"""An additional image whose fully transparent areas (e.g.
5663

‎tests/api_resources/test_images.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def test_method_edit_with_all_params_overload_1(self, client: OpenAI) -> None:
8181
prompt="A cute baby sea otter wearing a beret",
8282
background="transparent",
8383
input_fidelity="high",
84+
markup=b"Example data",
8485
mask=b"Example data",
8586
model="gpt-image-1.5",
8687
n=1,
@@ -139,6 +140,7 @@ def test_method_edit_with_all_params_overload_2(self, client: OpenAI) -> None:
139140
stream=True,
140141
background="transparent",
141142
input_fidelity="high",
143+
markup=b"Example data",
142144
mask=b"Example data",
143145
model="gpt-image-1.5",
144146
n=1,
@@ -360,6 +362,7 @@ async def test_method_edit_with_all_params_overload_1(self, async_client: AsyncO
360362
prompt="A cute baby sea otter wearing a beret",
361363
background="transparent",
362364
input_fidelity="high",
365+
markup=b"Example data",
363366
mask=b"Example data",
364367
model="gpt-image-1.5",
365368
n=1,
@@ -418,6 +421,7 @@ async def test_method_edit_with_all_params_overload_2(self, async_client: AsyncO
418421
stream=True,
419422
background="transparent",
420423
input_fidelity="high",
424+
markup=b"Example data",
421425
mask=b"Example data",
422426
model="gpt-image-1.5",
423427
n=1,

0 commit comments

Comments
 (0)