Skip to content
Open
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
2 changes: 2 additions & 0 deletions .github/workflows/sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
python -m venv /tmp/uv
/tmp/uv/bin/python -m pip install uv==0.11.3
/tmp/uv/bin/uv sync --extra dev --frozen
/tmp/uv/bin/uv run --extra dev ruff check --output-format=github .
/tmp/uv/bin/uv run --extra dev ruff format --check .
/tmp/uv/bin/uv run --extra dev pytest
'
Expand Down
1 change: 1 addition & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"recommendations": [
"rust-lang.rust-analyzer",
"charliermarsh.ruff",
"tamasfe.even-better-toml",
"vadimcn.vscode-lldb",

Expand Down
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
"editor.defaultFormatter": "tamasfe.even-better-toml",
"editor.formatOnSave": true,
},
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit",
},
},
// Array order for options in ~/.codex/config.toml such as `notify` and the
// `args` for an MCP server is significant, so we disable reordering.
"evenBetterToml.formatter.reorderArrays": false,
Expand Down
4 changes: 3 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ app-server-test-client *args:
cargo build -p codex-cli
cargo run -p codex-app-server-test-client -- --codex-bin ./target/debug/codex "$@"

# format code
# Format Rust and Python SDK code.
fmt:
cargo fmt -- --config imports_granularity=Item 2>/dev/null
uv run --project ../sdk/python --extra dev ruff check --fix --fix-only ../sdk/python
uv run --project ../sdk/python --extra dev ruff format ../sdk/python

fix *args:
cargo clippy --fix --tests --allow-dirty "$@"
Expand Down
18 changes: 4 additions & 14 deletions sdk/python/_runtime_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,9 @@ def _download_release_archive(version: str, temp_root: Path) -> Path:
metadata = _release_metadata(version)
assets = metadata.get("assets")
if not isinstance(assets, list):
raise RuntimeSetupError(
f"Release {release_tag} returned malformed assets metadata."
)
raise RuntimeSetupError(f"Release {release_tag} returned malformed assets metadata.")
asset = next(
(
item
for item in assets
if isinstance(item, dict) and item.get("name") == asset_name
),
(item for item in assets if isinstance(item, dict) and item.get("name") == asset_name),
None,
)
if asset is None:
Expand Down Expand Up @@ -279,9 +273,7 @@ def _extract_runtime_binary(archive_path: Path, temp_root: Path) -> Path:
with zipfile.ZipFile(archive_path) as zip_file:
zip_file.extractall(extract_dir)
else:
raise RuntimeSetupError(
f"Unsupported release archive format: {archive_path.name}"
)
raise RuntimeSetupError(f"Unsupported release archive format: {archive_path.name}")

binary_name = runtime_binary_name()
archive_stem = archive_path.name.removesuffix(".tar.gz").removesuffix(".zip")
Expand All @@ -290,9 +282,7 @@ def _extract_runtime_binary(archive_path: Path, temp_root: Path) -> Path:
for path in extract_dir.rglob("*")
if path.is_file()
and (
path.name == binary_name
or path.name == archive_stem
or path.name.startswith("codex-")
path.name == binary_name or path.name == archive_stem or path.name.startswith("codex-")
)
]
if not candidates:
Expand Down
4 changes: 3 additions & 1 deletion sdk/python/examples/01_quickstart_constructor/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
print("Server:", server_label(codex.metadata))

thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)
result = await thread.run("Say hello in one sentence.")
print("Items:", len(result.items))
print("Text:", result.final_response)
Expand Down
4 changes: 3 additions & 1 deletion sdk/python/examples/02_turn_run/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)
turn = await thread.turn(TextInput("Give 3 bullets about SIMD."))
result = await turn.run()
persisted = await thread.read(include_turns=True)
Expand Down
8 changes: 6 additions & 2 deletions sdk/python/examples/03_turn_stream_events/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
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."))

event_count = 0
Expand All @@ -44,7 +46,9 @@ async def main() -> None:
saw_delta = True
continue
if event.method == "turn/completed":
completed_status = getattr(event.payload.turn.status, "value", str(event.payload.turn.status))
completed_status = getattr(
event.payload.turn.status, "value", str(event.payload.turn.status)
)

if saw_delta:
print()
Expand Down
4 changes: 3 additions & 1 deletion sdk/python/examples/03_turn_stream_events/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
saw_delta = True
continue
if event.method == "turn/completed":
completed_status = getattr(event.payload.turn.status, "value", str(event.payload.turn.status))
completed_status = getattr(
event.payload.turn.status, "value", str(event.payload.turn.status)
)

if saw_delta:
print()
Expand Down
11 changes: 9 additions & 2 deletions sdk/python/examples/05_existing_thread/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
if str(_EXAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_EXAMPLES_ROOT))

from _bootstrap import assistant_text_from_turn, ensure_local_sdk_src, find_turn_by_id, runtime_config
from _bootstrap import (
assistant_text_from_turn,
ensure_local_sdk_src,
find_turn_by_id,
runtime_config,
)

ensure_local_sdk_src()

Expand All @@ -16,7 +21,9 @@

async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
original = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
original = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)

first_turn = await original.turn(TextInput("Tell me one fact about Saturn."))
_ = await first_turn.run()
Expand Down
7 changes: 6 additions & 1 deletion sdk/python/examples/05_existing_thread/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
if str(_EXAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_EXAMPLES_ROOT))

from _bootstrap import assistant_text_from_turn, ensure_local_sdk_src, find_turn_by_id, runtime_config
from _bootstrap import (
assistant_text_from_turn,
ensure_local_sdk_src,
find_turn_by_id,
runtime_config,
)

ensure_local_sdk_src()

Expand Down
16 changes: 12 additions & 4 deletions sdk/python/examples/06_thread_lifecycle_and_controls/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@

async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
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()
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()

reopened = await codex.thread_resume(thread.id)
Expand All @@ -36,15 +40,19 @@ 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(TextInput("Continue in one short sentence."))
).run()
resumed_info = f"{resumed_result.id} {resumed_result.status}"
except Exception as exc:
resumed_info = f"skipped({type(exc).__name__})"

forked_info = "n/a"
try:
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."))).run()
forked_result = await (
await forked.turn(TextInput("Take a different angle in one short sentence."))
).run()
forked_info = f"{forked_result.id} {forked_result.status}"
except Exception as exc:
forked_info = f"skipped({type(exc).__name__})"
Expand Down
5 changes: 3 additions & 2 deletions sdk/python/examples/06_thread_lifecycle_and_controls/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

from openai_codex import Codex, TextInput


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()
Expand Down Expand Up @@ -41,7 +40,9 @@
forked_info = "n/a"
try:
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(
TextInput("Take a different angle in one short sentence.")
).run()
forked_info = f"{forked_result.id} {forked_result.status}"
except Exception as exc:
forked_info = f"skipped({type(exc).__name__})"
Expand Down
4 changes: 3 additions & 1 deletion sdk/python/examples/07_image_and_text/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)
turn = await thread.turn(
[
TextInput("What is in this image? Give 3 bullets."),
Expand Down
8 changes: 6 additions & 2 deletions sdk/python/examples/08_local_image_and_text/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@
async def main() -> None:
with temporary_sample_image_path() as image_path:
async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)

turn = await thread.turn(
[
TextInput("Read this generated local image and summarize the colors/layout in 2 bullets."),
TextInput(
"Read this generated local image and summarize the colors/layout in 2 bullets."
),
LocalImageInput(str(image_path.resolve())),
]
)
Expand Down
4 changes: 3 additions & 1 deletion sdk/python/examples/08_local_image_and_text/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

result = thread.turn(
[
TextInput("Read this generated local image and summarize the colors/layout in 2 bullets."),
TextInput(
"Read this generated local image and summarize the colors/layout in 2 bullets."
),
LocalImageInput(str(image_path.resolve())),
]
).run()
Expand Down
4 changes: 3 additions & 1 deletion sdk/python/examples/10_error_handling_and_retry/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ async def retry_on_overload_async(

async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)

try:
result = await retry_on_overload_async(
Expand Down
4 changes: 3 additions & 1 deletion sdk/python/examples/11_cli_mini_app/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ async def main() -> None:
print("Codex async mini CLI. Type /exit to quit.")

async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)
print("Thread:", thread.id)

while True:
Expand Down
14 changes: 10 additions & 4 deletions sdk/python/examples/12_turn_params_kitchen_sink/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@

async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)

turn = await thread.turn(
TextInput(PROMPT),
Expand All @@ -64,12 +66,16 @@ async def main() -> None:
try:
structured = json.loads(structured_text)
except json.JSONDecodeError as exc:
raise RuntimeError(f"Expected JSON matching OUTPUT_SCHEMA, got: {structured_text!r}") from exc
raise RuntimeError(
f"Expected JSON matching OUTPUT_SCHEMA, got: {structured_text!r}"
) from exc

summary = structured.get("summary")
actions = structured.get("actions")
if not isinstance(summary, str) or not isinstance(actions, list) or not all(
isinstance(action, str) for action in actions
if (
not isinstance(summary, str)
or not isinstance(actions, list)
or not all(isinstance(action, str) for action in actions)
):
raise RuntimeError(
f"Expected structured output with string summary/actions, got: {structured!r}"
Expand Down
14 changes: 10 additions & 4 deletions sdk/python/examples/12_turn_params_kitchen_sink/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,20 @@
try:
structured = json.loads(structured_text)
except json.JSONDecodeError as exc:
raise RuntimeError(f"Expected JSON matching OUTPUT_SCHEMA, got: {structured_text!r}") from exc
raise RuntimeError(
f"Expected JSON matching OUTPUT_SCHEMA, got: {structured_text!r}"
) from exc

summary = structured.get("summary")
actions = structured.get("actions")
if not isinstance(summary, str) or not isinstance(actions, list) or not all(
isinstance(action, str) for action in actions
if (
not isinstance(summary, str)
or not isinstance(actions, list)
or not all(isinstance(action, str) for action in actions)
):
raise RuntimeError(f"Expected structured output with string summary/actions, got: {structured!r}")
raise RuntimeError(
f"Expected structured output with string summary/actions, got: {structured!r}"
)

print("Status:", result.status)
print("summary:", summary)
Expand Down
Loading
Loading