mirror of
https://github.com/openai/codex.git
synced 2026-05-20 11:12:43 +00:00
[codex] Accept string input for Python turns (#23162)
## Summary
- Allow thread.turn and turn.steer, including async variants, to accept
RunInput so plain strings work alongside typed input objects.
- Export RunInput and update the SDK artifact generator so regenerated
turn methods keep the same signature and normalization.
- Update docs, examples, notebook cells, and tests to use string
shorthand for text-only turns while keeping typed inputs for multimodal
input.
## Validation
- uv run --extra dev ruff format .
- uv run --extra dev ruff check --output-format=github .
- python3 -m py_compile sdk/python/src/openai_codex/__init__.py
sdk/python/src/openai_codex/api.py
sdk/python/src/openai_codex/_inputs.py
sdk/python/scripts/update_sdk_artifacts.py
sdk/python/tests/test_public_api_signatures.py
sdk/python/tests/test_app_server_streaming.py
sdk/python/tests/test_app_server_turn_controls.py
sdk/python/tests/test_real_app_server_integration.py
- python3 -c "import json;
json.load(open('sdk/python/notebooks/sdk_walkthrough.ipynb'))"
- sdk/python/.venv/bin/python -c "import inspect, openai_codex; from
openai_codex import Thread, AsyncThread, TurnHandle, AsyncTurnHandle,
RunInput; funcs=[Thread.run, Thread.turn, AsyncThread.run,
AsyncThread.turn, TurnHandle.steer, AsyncTurnHandle.steer]; assert
all(inspect.signature(fn).parameters['input'].annotation == 'RunInput'
for fn in funcs); assert RunInput is openai_codex.RunInput"
This commit is contained in:
@@ -11,7 +11,7 @@ ensure_local_sdk_src()
|
||||
|
||||
import asyncio
|
||||
|
||||
from openai_codex import AsyncCodex, TextInput
|
||||
from openai_codex import AsyncCodex
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
@@ -19,7 +19,7 @@ async def main() -> None:
|
||||
thread = await codex.thread_start(
|
||||
model="gpt-5.4", config={"model_reasoning_effort": "high"}
|
||||
)
|
||||
turn = await thread.turn(TextInput("Give 3 bullets about SIMD."))
|
||||
turn = await thread.turn("Give 3 bullets about SIMD.")
|
||||
result = await turn.run()
|
||||
|
||||
print("thread_id:", thread.id)
|
||||
|
||||
@@ -9,11 +9,11 @@ from _bootstrap import ensure_local_sdk_src, runtime_config
|
||||
|
||||
ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import Codex, TextInput
|
||||
from openai_codex import Codex
|
||||
|
||||
with Codex(config=runtime_config()) as codex:
|
||||
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
|
||||
result = thread.turn(TextInput("Give 3 bullets about SIMD.")).run()
|
||||
result = thread.turn("Give 3 bullets about SIMD.").run()
|
||||
|
||||
print("thread_id:", thread.id)
|
||||
print("turn_id:", result.id)
|
||||
|
||||
@@ -11,7 +11,7 @@ ensure_local_sdk_src()
|
||||
|
||||
import asyncio
|
||||
|
||||
from openai_codex import AsyncCodex, TextInput
|
||||
from openai_codex import AsyncCodex
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
@@ -19,7 +19,7 @@ async def main() -> None:
|
||||
thread = await codex.thread_start(
|
||||
model="gpt-5.4", config={"model_reasoning_effort": "high"}
|
||||
)
|
||||
turn = await thread.turn(TextInput("Explain SIMD in 3 short bullets."))
|
||||
turn = await thread.turn("Explain SIMD in 3 short bullets.")
|
||||
|
||||
event_count = 0
|
||||
saw_started = False
|
||||
|
||||
@@ -9,11 +9,11 @@ from _bootstrap import ensure_local_sdk_src, runtime_config
|
||||
|
||||
ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import Codex, TextInput
|
||||
from openai_codex import Codex
|
||||
|
||||
with Codex(config=runtime_config()) as codex:
|
||||
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
|
||||
turn = thread.turn(TextInput("Explain SIMD in 3 short bullets."))
|
||||
turn = thread.turn("Explain SIMD in 3 short bullets.")
|
||||
|
||||
event_count = 0
|
||||
saw_started = False
|
||||
|
||||
@@ -11,7 +11,7 @@ ensure_local_sdk_src()
|
||||
|
||||
import asyncio
|
||||
|
||||
from openai_codex import AsyncCodex, TextInput
|
||||
from openai_codex import AsyncCodex
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
@@ -20,12 +20,12 @@ async def main() -> None:
|
||||
model="gpt-5.4", config={"model_reasoning_effort": "high"}
|
||||
)
|
||||
|
||||
first_turn = await original.turn(TextInput("Tell me one fact about Saturn."))
|
||||
first_turn = await original.turn("Tell me one fact about Saturn.")
|
||||
_ = await first_turn.run()
|
||||
print("Created thread:", original.id)
|
||||
|
||||
resumed = await codex.thread_resume(original.id)
|
||||
second_turn = await resumed.turn(TextInput("Continue with one more fact."))
|
||||
second_turn = await resumed.turn("Continue with one more fact.")
|
||||
second = await second_turn.run()
|
||||
print(second.final_response)
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ from _bootstrap import ensure_local_sdk_src, runtime_config
|
||||
|
||||
ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import Codex, TextInput
|
||||
from openai_codex import Codex
|
||||
|
||||
with Codex(config=runtime_config()) as codex:
|
||||
# Create an initial thread and turn so we have a real thread to resume.
|
||||
original = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
|
||||
first = original.turn(TextInput("Tell me one fact about Saturn.")).run()
|
||||
first = original.turn("Tell me one fact about Saturn.").run()
|
||||
print("Created thread:", original.id)
|
||||
|
||||
# Resume the existing thread by ID.
|
||||
resumed = codex.thread_resume(original.id)
|
||||
second = resumed.turn(TextInput("Continue with one more fact.")).run()
|
||||
second = resumed.turn("Continue with one more fact.").run()
|
||||
print(second.final_response)
|
||||
|
||||
@@ -11,7 +11,7 @@ ensure_local_sdk_src()
|
||||
|
||||
import asyncio
|
||||
|
||||
from openai_codex import AsyncCodex, TextInput
|
||||
from openai_codex import AsyncCodex
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
@@ -19,10 +19,8 @@ async def main() -> None:
|
||||
thread = await codex.thread_start(
|
||||
model="gpt-5.4", config={"model_reasoning_effort": "high"}
|
||||
)
|
||||
first = await (
|
||||
await thread.turn(TextInput("One sentence about structured planning."))
|
||||
).run()
|
||||
second = await (await thread.turn(TextInput("Now restate it for a junior engineer."))).run()
|
||||
first = await (await thread.turn("One sentence about structured planning.")).run()
|
||||
second = await (await thread.turn("Now restate it for a junior engineer.")).run()
|
||||
|
||||
reopened = await codex.thread_resume(thread.id)
|
||||
listing_active = await codex.thread_list(limit=20, archived=False)
|
||||
@@ -38,13 +36,11 @@ async def main() -> None:
|
||||
model="gpt-5.4",
|
||||
config={"model_reasoning_effort": "high"},
|
||||
)
|
||||
resumed_result = await (
|
||||
await resumed.turn(TextInput("Continue in one short sentence."))
|
||||
).run()
|
||||
resumed_result = await (await resumed.turn("Continue in one short sentence.")).run()
|
||||
|
||||
forked = await codex.thread_fork(unarchived.id, model="gpt-5.4")
|
||||
forked_result = await (
|
||||
await forked.turn(TextInput("Take a different angle in one short sentence."))
|
||||
await forked.turn("Take a different angle in one short sentence.")
|
||||
).run()
|
||||
|
||||
compact_result = await unarchived.compact()
|
||||
|
||||
@@ -9,12 +9,12 @@ from _bootstrap import ensure_local_sdk_src, runtime_config
|
||||
|
||||
ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import Codex, TextInput
|
||||
from openai_codex import Codex
|
||||
|
||||
with Codex(config=runtime_config()) as codex:
|
||||
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
|
||||
first = thread.turn(TextInput("One sentence about structured planning.")).run()
|
||||
second = thread.turn(TextInput("Now restate it for a junior engineer.")).run()
|
||||
first = thread.turn("One sentence about structured planning.").run()
|
||||
second = thread.turn("Now restate it for a junior engineer.").run()
|
||||
|
||||
reopened = codex.thread_resume(thread.id)
|
||||
listing_active = codex.thread_list(limit=20, archived=False)
|
||||
@@ -30,10 +30,10 @@ with Codex(config=runtime_config()) as codex:
|
||||
model="gpt-5.4",
|
||||
config={"model_reasoning_effort": "high"},
|
||||
)
|
||||
resumed_result = resumed.turn(TextInput("Continue in one short sentence.")).run()
|
||||
resumed_result = resumed.turn("Continue in one short sentence.").run()
|
||||
|
||||
forked = codex.thread_fork(unarchived.id, model="gpt-5.4")
|
||||
forked_result = forked.turn(TextInput("Take a different angle in one short sentence.")).run()
|
||||
forked_result = forked.turn("Take a different angle in one short sentence.").run()
|
||||
|
||||
compact_result = unarchived.compact()
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ from _bootstrap import ensure_local_sdk_src, runtime_config, server_label
|
||||
|
||||
ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import Codex, TextInput
|
||||
from openai_codex import Codex
|
||||
|
||||
with Codex(config=runtime_config()) as codex:
|
||||
print("Server:", server_label(codex.metadata))
|
||||
|
||||
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
|
||||
turn = thread.turn(TextInput("Say hello in one sentence."))
|
||||
turn = thread.turn("Say hello in one sentence.")
|
||||
result = turn.run()
|
||||
|
||||
print("Thread:", thread.id)
|
||||
|
||||
@@ -18,7 +18,6 @@ from openai_codex import (
|
||||
AsyncCodex,
|
||||
JsonRpcError,
|
||||
ServerBusyError,
|
||||
TextInput,
|
||||
is_retryable_error,
|
||||
)
|
||||
from openai_codex.types import TurnStatus
|
||||
@@ -82,7 +81,7 @@ async def main() -> None:
|
||||
|
||||
def _run_turn(thread, prompt: str):
|
||||
async def _inner():
|
||||
turn = await thread.turn(TextInput(prompt))
|
||||
turn = await thread.turn(prompt)
|
||||
return await turn.run()
|
||||
|
||||
return _inner
|
||||
|
||||
@@ -13,7 +13,6 @@ from openai_codex import (
|
||||
Codex,
|
||||
JsonRpcError,
|
||||
ServerBusyError,
|
||||
TextInput,
|
||||
retry_on_overload,
|
||||
)
|
||||
from openai_codex.types import TurnStatus
|
||||
@@ -23,7 +22,7 @@ with Codex(config=runtime_config()) as codex:
|
||||
|
||||
try:
|
||||
result = retry_on_overload(
|
||||
lambda: thread.turn(TextInput("Summarize retry best practices in 3 bullets.")).run(),
|
||||
lambda: thread.turn("Summarize retry best practices in 3 bullets.").run(),
|
||||
max_attempts=3,
|
||||
initial_delay_s=0.25,
|
||||
max_delay_s=2.0,
|
||||
|
||||
@@ -13,7 +13,6 @@ import asyncio
|
||||
|
||||
from openai_codex import (
|
||||
AsyncCodex,
|
||||
TextInput,
|
||||
)
|
||||
from openai_codex.types import (
|
||||
ThreadTokenUsageUpdatedNotification,
|
||||
@@ -51,7 +50,7 @@ async def main() -> None:
|
||||
if user_input in {"/exit", "/quit"}:
|
||||
break
|
||||
|
||||
turn = await thread.turn(TextInput(user_input))
|
||||
turn = await thread.turn(user_input)
|
||||
usage = None
|
||||
status = None
|
||||
error = None
|
||||
|
||||
@@ -11,7 +11,6 @@ ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import (
|
||||
Codex,
|
||||
TextInput,
|
||||
)
|
||||
from openai_codex.types import (
|
||||
ThreadTokenUsageUpdatedNotification,
|
||||
@@ -46,7 +45,7 @@ with Codex(config=runtime_config()) as codex:
|
||||
if user_input in {"/exit", "/quit"}:
|
||||
break
|
||||
|
||||
turn = thread.turn(TextInput(user_input))
|
||||
turn = thread.turn(user_input)
|
||||
usage = None
|
||||
status = None
|
||||
error = None
|
||||
|
||||
@@ -14,7 +14,6 @@ import asyncio
|
||||
|
||||
from openai_codex import (
|
||||
AsyncCodex,
|
||||
TextInput,
|
||||
)
|
||||
from openai_codex.types import (
|
||||
Personality,
|
||||
@@ -49,7 +48,7 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
turn = await thread.turn(
|
||||
TextInput(PROMPT),
|
||||
PROMPT,
|
||||
output_schema=OUTPUT_SCHEMA,
|
||||
personality=Personality.pragmatic,
|
||||
summary=SUMMARY,
|
||||
|
||||
@@ -12,7 +12,6 @@ ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import (
|
||||
Codex,
|
||||
TextInput,
|
||||
)
|
||||
from openai_codex.types import (
|
||||
Personality,
|
||||
@@ -43,7 +42,7 @@ with Codex(config=runtime_config()) as codex:
|
||||
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
|
||||
|
||||
turn = thread.turn(
|
||||
TextInput(PROMPT),
|
||||
PROMPT,
|
||||
output_schema=OUTPUT_SCHEMA,
|
||||
personality=Personality.pragmatic,
|
||||
summary=SUMMARY,
|
||||
|
||||
@@ -13,7 +13,6 @@ import asyncio
|
||||
|
||||
from openai_codex import (
|
||||
AsyncCodex,
|
||||
TextInput,
|
||||
)
|
||||
from openai_codex.types import (
|
||||
Personality,
|
||||
@@ -91,7 +90,7 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
first_turn = await thread.turn(
|
||||
TextInput("Give one short sentence about reliable production releases."),
|
||||
"Give one short sentence about reliable production releases.",
|
||||
model=selected_model.model,
|
||||
effort=selected_effort,
|
||||
)
|
||||
@@ -101,7 +100,7 @@ async def main() -> None:
|
||||
print("items:", len(first.items))
|
||||
|
||||
second_turn = await thread.turn(
|
||||
TextInput("Return JSON for a safe feature-flag rollout plan."),
|
||||
"Return JSON for a safe feature-flag rollout plan.",
|
||||
cwd=str(Path.cwd()),
|
||||
effort=selected_effort,
|
||||
model=selected_model.model,
|
||||
|
||||
@@ -11,7 +11,6 @@ ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import (
|
||||
Codex,
|
||||
TextInput,
|
||||
)
|
||||
from openai_codex.types import (
|
||||
Personality,
|
||||
@@ -88,7 +87,7 @@ with Codex(config=runtime_config()) as codex:
|
||||
)
|
||||
|
||||
first = thread.turn(
|
||||
TextInput("Give one short sentence about reliable production releases."),
|
||||
"Give one short sentence about reliable production releases.",
|
||||
model=selected_model.model,
|
||||
effort=selected_effort,
|
||||
).run()
|
||||
@@ -97,7 +96,7 @@ with Codex(config=runtime_config()) as codex:
|
||||
print("items:", len(first.items))
|
||||
|
||||
second = thread.turn(
|
||||
TextInput("Return JSON for a safe feature-flag rollout plan."),
|
||||
"Return JSON for a safe feature-flag rollout plan.",
|
||||
cwd=str(Path.cwd()),
|
||||
effort=selected_effort,
|
||||
model=selected_model.model,
|
||||
|
||||
@@ -11,7 +11,7 @@ ensure_local_sdk_src()
|
||||
|
||||
import asyncio
|
||||
|
||||
from openai_codex import AsyncCodex, TextInput
|
||||
from openai_codex import AsyncCodex
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
@@ -19,10 +19,8 @@ async def main() -> None:
|
||||
thread = await codex.thread_start(
|
||||
model="gpt-5.4", config={"model_reasoning_effort": "high"}
|
||||
)
|
||||
steer_turn = await thread.turn(
|
||||
TextInput("Count from 1 to 40 with commas, then one summary sentence.")
|
||||
)
|
||||
steer_result = await steer_turn.steer(TextInput("Keep it brief and stop after 10 numbers."))
|
||||
steer_turn = await thread.turn("Count from 1 to 40 with commas, then one summary sentence.")
|
||||
steer_result = await steer_turn.steer("Keep it brief and stop after 10 numbers.")
|
||||
|
||||
steer_event_count = 0
|
||||
steer_completed_status = None
|
||||
@@ -40,7 +38,7 @@ async def main() -> None:
|
||||
steer_preview = "".join(steer_deltas).strip()
|
||||
|
||||
interrupt_turn = await thread.turn(
|
||||
TextInput("Count from 1 to 200 with commas, then one summary sentence.")
|
||||
"Count from 1 to 200 with commas, then one summary sentence."
|
||||
)
|
||||
interrupt_result = await interrupt_turn.interrupt()
|
||||
|
||||
|
||||
@@ -9,14 +9,12 @@ from _bootstrap import ensure_local_sdk_src, runtime_config
|
||||
|
||||
ensure_local_sdk_src()
|
||||
|
||||
from openai_codex import Codex, TextInput
|
||||
from openai_codex import Codex
|
||||
|
||||
with Codex(config=runtime_config()) as codex:
|
||||
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
|
||||
steer_turn = thread.turn(
|
||||
TextInput("Count from 1 to 40 with commas, then one summary sentence.")
|
||||
)
|
||||
steer_result = steer_turn.steer(TextInput("Keep it brief and stop after 10 numbers."))
|
||||
steer_turn = thread.turn("Count from 1 to 40 with commas, then one summary sentence.")
|
||||
steer_result = steer_turn.steer("Keep it brief and stop after 10 numbers.")
|
||||
|
||||
steer_event_count = 0
|
||||
steer_completed_status = None
|
||||
@@ -33,9 +31,7 @@ with Codex(config=runtime_config()) as codex:
|
||||
raise RuntimeError("stream ended without turn/completed")
|
||||
steer_preview = "".join(steer_deltas).strip()
|
||||
|
||||
interrupt_turn = thread.turn(
|
||||
TextInput("Count from 1 to 200 with commas, then one summary sentence.")
|
||||
)
|
||||
interrupt_turn = thread.turn("Count from 1 to 200 with commas, then one summary sentence.")
|
||||
interrupt_result = interrupt_turn.interrupt()
|
||||
|
||||
interrupt_event_count = 0
|
||||
|
||||
@@ -8,6 +8,9 @@ Each example folder contains runnable versions:
|
||||
All examples intentionally use only public SDK exports from `openai_codex`
|
||||
and `openai_codex.types`.
|
||||
|
||||
Examples use plain strings for text-only turns and typed input objects for
|
||||
multimodal or structured input lists.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python `>=3.10`
|
||||
|
||||
Reference in New Issue
Block a user