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
27 changes: 25 additions & 2 deletions sdk/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ when you intentionally want to run against a specific local app-server binary.
## Quickstart

```python
from openai_codex import Codex
from openai_codex import Codex, Sandbox

with Codex() as codex:
# Call login_api_key(...) first when this app-server session is not
# already authenticated.
thread = codex.thread_start(model="gpt-5")
thread = codex.thread_start(model="gpt-5", sandbox=Sandbox.workspace_write)
result = thread.run("Say hello in one sentence.")
print(result.final_response)
print(len(result.items))
Expand All @@ -38,6 +38,29 @@ with Codex() as codex:
`final_response` is `None` when the turn completes without a final-answer or
phase-less assistant message item.

## Sandbox

Use the same enum when creating a thread or changing its sandbox for a turn:

```python
from openai_codex import Codex, Sandbox

with Codex() as codex:
thread = codex.thread_start(sandbox=Sandbox.workspace_write)
thread.run("Make the requested change.")
review = thread.run("Review the diff only.", sandbox=Sandbox.read_only)
```

Available presets:

- `Sandbox.read_only`: read files without allowing writes.
- `Sandbox.workspace_write`: the normal default for projects with a recorded trust decision; read files and write inside the workspace and configured writable roots.
- `Sandbox.full_access`: run without filesystem access restrictions.

When `sandbox=` is omitted, app-server uses its configured default. A sandbox
passed to `run(...)` or `turn(...)` applies to that turn and subsequent turns
on the thread.

## Login

Use the auth helper that matches your app:
Expand Down
42 changes: 32 additions & 10 deletions sdk/python/docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ from openai_codex import (
Codex,
AsyncCodex,
ApprovalMode,
Sandbox,
ChatgptLoginHandle,
DeviceCodeLoginHandle,
AsyncChatgptLoginHandle,
Expand Down Expand Up @@ -63,10 +64,10 @@ Properties/methods:
- `login_chatgpt_device_code() -> DeviceCodeLoginHandle`
- `account(*, refresh_token: bool = False) -> GetAccountResponse`
- `logout() -> None`
- `thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox=None) -> Thread`
- `thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Thread`
- `thread_list(*, archived=None, cursor=None, cwd=None, limit=None, model_providers=None, sort_key=None, source_kinds=None) -> ThreadListResponse`
- `thread_resume(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, personality=None, sandbox=None) -> Thread`
- `thread_fork(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, sandbox=None) -> Thread`
- `thread_resume(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Thread`
- `thread_fork(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, sandbox: Sandbox | None = None) -> Thread`
- `thread_archive(thread_id: str) -> ThreadArchiveResponse`
- `thread_unarchive(thread_id: str) -> Thread`
- `models(*, include_hidden: bool = False) -> ModelListResponse`
Expand Down Expand Up @@ -103,10 +104,10 @@ Properties/methods:
- `login_chatgpt_device_code() -> Awaitable[AsyncDeviceCodeLoginHandle]`
- `account(*, refresh_token: bool = False) -> Awaitable[GetAccountResponse]`
- `logout() -> Awaitable[None]`
- `thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox=None) -> Awaitable[AsyncThread]`
- `thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Awaitable[AsyncThread]`
- `thread_list(*, archived=None, cursor=None, cwd=None, limit=None, model_providers=None, sort_key=None, source_kinds=None) -> Awaitable[ThreadListResponse]`
- `thread_resume(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, personality=None, sandbox=None) -> Awaitable[AsyncThread]`
- `thread_fork(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, sandbox=None) -> Awaitable[AsyncThread]`
- `thread_resume(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Awaitable[AsyncThread]`
- `thread_fork(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, sandbox: Sandbox | None = None) -> Awaitable[AsyncThread]`
- `thread_archive(thread_id: str) -> Awaitable[ThreadArchiveResponse]`
- `thread_unarchive(thread_id: str) -> Awaitable[AsyncThread]`
- `models(*, include_hidden: bool = False) -> Awaitable[ModelListResponse]`
Expand Down Expand Up @@ -148,16 +149,16 @@ attempt. API-key login completes synchronously and does not return a handle.

### Thread

- `run(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox_policy=None, service_tier=None, summary=None) -> TurnResult`
- `turn(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox_policy=None, service_tier=None, summary=None) -> TurnHandle`
- `run(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, summary=None) -> TurnResult`
- `turn(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, summary=None) -> TurnHandle`
- `read(*, include_turns: bool = False) -> ThreadReadResponse`
- `set_name(name: str) -> ThreadSetNameResponse`
- `compact() -> ThreadCompactStartResponse`

### AsyncThread

- `run(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox_policy=None, service_tier=None, summary=None) -> Awaitable[TurnResult]`
- `turn(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox_policy=None, service_tier=None, summary=None) -> Awaitable[AsyncTurnHandle]`
- `run(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, summary=None) -> Awaitable[TurnResult]`
- `turn(input: str | Input, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, summary=None) -> Awaitable[AsyncTurnHandle]`
- `read(*, include_turns: bool = False) -> Awaitable[ThreadReadResponse]`
- `set_name(name: str) -> Awaitable[ThreadSetNameResponse]`
- `compact() -> Awaitable[ThreadCompactStartResponse]`
Expand All @@ -182,6 +183,27 @@ phase-less assistant message item.
Use `turn(...)` when you need low-level turn control (`stream()`, `steer()`,
`interrupt()`) before collecting the turn result.

## Sandbox

Use `sandbox=` consistently on thread lifecycle methods and turns:

```python
from openai_codex import Codex, Sandbox

with Codex() as codex:
thread = codex.thread_start(sandbox=Sandbox.workspace_write)
result = thread.run("Review the diff only.", sandbox=Sandbox.read_only)
```

Presets:

- `Sandbox.read_only`: read files without allowing writes.
- `Sandbox.workspace_write`: the normal default for projects with a recorded trust decision; read files and write inside the workspace and configured writable roots.
- `Sandbox.full_access`: run without filesystem access restrictions.

When `sandbox=` is omitted, app-server uses its configured default. A sandbox
passed to `run(...)` or `turn(...)` applies to that turn and subsequent turns.

## TurnHandle / AsyncTurnHandle

### TurnHandle
Expand Down
21 changes: 20 additions & 1 deletion sdk/python/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,26 @@ If you are migrating older code, update these names:
- `sortKey` -> `sort_key`
- `sourceKinds` -> `source_kinds`
- `outputSchema` -> `output_schema`
- `sandboxPolicy` -> `sandbox_policy`

## How do I choose sandbox access?

Use the same `sandbox=` keyword for threads and turns:

```python
from openai_codex import Sandbox

thread = codex.thread_start(sandbox=Sandbox.workspace_write)
result = thread.run("Review only.", sandbox=Sandbox.read_only)
```

The presets are:

- `Sandbox.read_only`: read files without allowing writes.
- `Sandbox.workspace_write`: the normal default for projects with a recorded trust decision; read files and write inside the workspace and configured writable roots.
- `Sandbox.full_access`: run without filesystem access restrictions.

When `sandbox=` is omitted, app-server uses its configured default. A turn
sandbox override applies to that turn and subsequent turns.

## Why only `thread_start(...)` and `thread_resume(...)`?

Expand Down
40 changes: 33 additions & 7 deletions sdk/python/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Existing Codex auth state is reused automatically. To authenticate from the SDK,
use the flow that fits your app:

```python
from openai_codex import Codex
from openai_codex import Codex, Sandbox

with Codex() as codex:
codex.login_api_key("sk-...")
Expand Down Expand Up @@ -58,7 +58,11 @@ with Codex() as codex:
server = codex.metadata.serverInfo
print("Server:", None if server is None else server.name, None if server is None else server.version)

thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = codex.thread_start(
model="gpt-5.4",
config={"model_reasoning_effort": "high"},
sandbox=Sandbox.workspace_write,
)
result = thread.run("Say hello in one sentence.")

print("Thread:", thread.id)
Expand All @@ -76,7 +80,29 @@ What happened:
- use `thread.turn(...)` when you need a `TurnHandle` for streaming, steering, or interrupting before collecting `TurnResult`
- one client can consume multiple active turns concurrently; turn streams are routed by turn ID

## 4) Continue the same thread (multi-turn)
## 4) Change sandbox access

Use one enum for the initial sandbox and for later turn overrides:

```python
from openai_codex import Codex, Sandbox

with Codex() as codex:
thread = codex.thread_start(sandbox=Sandbox.workspace_write)
thread.run("Make the requested changes.")
review = thread.run("Review the diff only.", sandbox=Sandbox.read_only)
```

Available presets:

- `Sandbox.read_only`: read files without allowing writes.
- `Sandbox.workspace_write`: the normal default for projects with a recorded trust decision; read files and write inside the workspace and configured writable roots.
- `Sandbox.full_access`: run without filesystem access restrictions.

When `sandbox=` is omitted, app-server uses its configured default. A turn
override also becomes the sandbox for subsequent turns on that thread.

## 5) Continue the same thread (multi-turn)

```python
from openai_codex import Codex
Expand All @@ -91,7 +117,7 @@ with Codex() as codex:
print("second:", second.final_response)
```

## 5) Async parity
## 6) Async parity

Use `async with AsyncCodex()` as the normal async entrypoint. `AsyncCodex`
initializes lazily, and context entry makes startup/shutdown explicit.
Expand All @@ -111,7 +137,7 @@ async def main() -> None:
asyncio.run(main())
```

## 6) Resume an existing thread
## 7) Resume an existing thread

```python
from openai_codex import Codex
Expand All @@ -124,7 +150,7 @@ with Codex() as codex:
print(result.final_response)
```

## 7) Public app-server types
## 8) Public app-server types

The convenience wrappers live at the package root. Public app-server value and
event types live under:
Expand All @@ -133,7 +159,7 @@ event types live under:
from openai_codex.types import ThreadReadResponse, Turn, TurnStatus
```

## 8) Next stops
## 9) Next stops

- API surface and signatures: `docs/api-reference.md`
- Common decisions/pitfalls: `docs/faq.md`
Expand Down
11 changes: 2 additions & 9 deletions sdk/python/examples/13_model_select_and_turn_params/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@

from openai_codex import (
AsyncCodex,
Sandbox,
)
from openai_codex.types import (
Personality,
ReasoningEffort,
ReasoningSummary,
SandboxPolicy,
)

REASONING_RANK = {
Expand Down Expand Up @@ -67,13 +67,6 @@ def _pick_highest_turn_effort(model) -> ReasoningEffort:
"additionalProperties": False,
}

SANDBOX_POLICY = SandboxPolicy.model_validate(
{
"type": "readOnly",
"access": {"type": "fullAccess"},
}
)


async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
Expand Down Expand Up @@ -106,7 +99,7 @@ async def main() -> None:
model=selected_model.model,
output_schema=OUTPUT_SCHEMA,
personality=Personality.pragmatic,
sandbox_policy=SANDBOX_POLICY,
sandbox=Sandbox.read_only,
summary=ReasoningSummary.model_validate("concise"),
)
second = await second_turn.run()
Expand Down
12 changes: 2 additions & 10 deletions sdk/python/examples/13_model_select_and_turn_params/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

from openai_codex import (
Codex,
Sandbox,
)
from openai_codex.types import (
Personality,
ReasoningEffort,
ReasoningSummary,
SandboxPolicy,
)

REASONING_RANK = {
Expand Down Expand Up @@ -65,14 +65,6 @@ def _pick_highest_turn_effort(model) -> ReasoningEffort:
"additionalProperties": False,
}

SANDBOX_POLICY = SandboxPolicy.model_validate(
{
"type": "readOnly",
"access": {"type": "fullAccess"},
}
)


with Codex(config=runtime_config()) as codex:
models = codex.models(include_hidden=True)
selected_model = _pick_highest_model(models.data)
Expand Down Expand Up @@ -102,7 +94,7 @@ def _pick_highest_turn_effort(model) -> ReasoningEffort:
model=selected_model.model,
output_schema=OUTPUT_SCHEMA,
personality=Personality.pragmatic,
sandbox_policy=SANDBOX_POLICY,
sandbox=Sandbox.read_only,
summary=ReasoningSummary.model_validate("concise"),
).run()

Expand Down
14 changes: 6 additions & 8 deletions sdk/python/notebooks/sdk_walkthrough.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,11 @@
"source": [
"# Cell 5b: one turn with most optional turn params\n",
"from pathlib import Path\n",
"from openai_codex import (\n",
"from openai_codex import Sandbox\n",
"from openai_codex.types import (\n",
" Personality,\n",
" ReasoningEffort,\n",
" ReasoningSummary,\n",
" SandboxPolicy,\n",
")\n",
"\n",
"output_schema = {\n",
Expand All @@ -237,7 +237,6 @@
" 'additionalProperties': False,\n",
"}\n",
"\n",
"sandbox_policy = SandboxPolicy.model_validate({'type': 'readOnly', 'access': {'type': 'fullAccess'}})\n",
"summary = ReasoningSummary.model_validate('concise')\n",
"\n",
"with Codex() as codex:\n",
Expand All @@ -249,7 +248,7 @@
" model='gpt-5.4',\n",
" output_schema=output_schema,\n",
" personality=Personality.pragmatic,\n",
" sandbox_policy=sandbox_policy,\n",
" sandbox=Sandbox.read_only,\n",
" summary=summary,\n",
" )\n",
" result = turn.run()\n",
Expand All @@ -266,11 +265,11 @@
"source": [
"# Cell 5c: choose highest model + highest supported reasoning, then run turns\n",
"from pathlib import Path\n",
"from openai_codex import (\n",
"from openai_codex import Sandbox\n",
"from openai_codex.types import (\n",
" Personality,\n",
" ReasoningEffort,\n",
" ReasoningSummary,\n",
" SandboxPolicy,\n",
")\n",
"\n",
"reasoning_rank = {\n",
Expand Down Expand Up @@ -310,7 +309,6 @@
" 'required': ['summary', 'actions'],\n",
" 'additionalProperties': False,\n",
"}\n",
"sandbox_policy = SandboxPolicy.model_validate({'type': 'readOnly', 'access': {'type': 'fullAccess'}})\n",
"\n",
"with Codex() as codex:\n",
" models = codex.models(include_hidden=True)\n",
Expand All @@ -337,7 +335,7 @@
" model=selected_model.model,\n",
" output_schema=output_schema,\n",
" personality=Personality.pragmatic,\n",
" sandbox_policy=sandbox_policy,\n",
" sandbox=Sandbox.read_only,\n",
" summary=ReasoningSummary.model_validate('concise'),\n",
" ).run()\n",
" print('agent.message.params:', second.final_response)\n",
Expand Down
Loading
Loading