openai-codex 0.1.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: openai-codex
3
+ Version: 0.1.0b1
4
+ Summary: Python SDK for Codex
5
+ Project-URL: Homepage, https://github.com/openai/codex
6
+ Project-URL: Repository, https://github.com/openai/codex
7
+ Project-URL: Issues, https://github.com/openai/codex/issues
8
+ Project-URL: Documentation, https://github.com/openai/codex/tree/main/sdk/python/docs
9
+ Author: OpenAI
10
+ License-Expression: Apache-2.0
11
+ Keywords: agents,ai,codex,llm,sdk
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: openai-codex-cli-bin==0.132.0
22
+ Requires-Dist: pydantic>=2.12
23
+ Provides-Extra: dev
24
+ Requires-Dist: datamodel-code-generator==0.31.2; extra == 'dev'
25
+ Requires-Dist: pytest>=8.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.15.8; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # OpenAI Codex Python SDK (Beta)
30
+
31
+ Build Python applications that start Codex threads, run turns, stream progress,
32
+ and control workspace access.
33
+
34
+ > [!NOTE]
35
+ > `openai-codex` is in beta. Public APIs may change before `1.0`.
36
+
37
+ ## Install
38
+
39
+ Install the SDK:
40
+
41
+ ```bash
42
+ pip install openai-codex
43
+ ```
44
+
45
+ For reproducible environments, install this release exactly:
46
+
47
+ ```bash
48
+ pip install openai-codex==0.1.0b1
49
+ ```
50
+
51
+ The SDK requires Python `>=3.10` and installs its compatible Codex runtime
52
+ dependency automatically. While beta releases are the only published SDK
53
+ releases, the normal install command selects the latest beta. After a stable
54
+ release exists, use `pip install --pre openai-codex` to explicitly select a
55
+ newer prerelease.
56
+
57
+ ## Quickstart
58
+
59
+ The SDK reuses your existing Codex authentication when one is already
60
+ available:
61
+
62
+ ```python
63
+ from openai_codex import Codex
64
+
65
+ with Codex() as codex:
66
+ thread = codex.thread_start()
67
+ result = thread.run("Explain this repository in three bullets.")
68
+ print(result.final_response)
69
+ ```
70
+
71
+ `thread.run(...)` returns a `TurnResult` containing the final response,
72
+ collected items, and token usage.
73
+
74
+ ## Authentication
75
+
76
+ Existing Codex authentication is reused automatically. To start ChatGPT
77
+ browser login explicitly:
78
+
79
+ ```python
80
+ from openai_codex import Codex
81
+
82
+ with Codex() as codex:
83
+ login = codex.login_chatgpt()
84
+ print(login.auth_url)
85
+ print(login.wait().success)
86
+ ```
87
+
88
+ For device-code login:
89
+
90
+ ```python
91
+ with Codex() as codex:
92
+ login = codex.login_chatgpt_device_code()
93
+ print(login.verification_url, login.user_code)
94
+ login.wait()
95
+ ```
96
+
97
+ For API-key login:
98
+
99
+ ```python
100
+ with Codex() as codex:
101
+ codex.login_api_key("sk-...")
102
+ ```
103
+
104
+ ## Built-In Help
105
+
106
+ Use Python's standard `help(openai_codex)`, `help(Codex)`, or
107
+ `python -m pydoc openai_codex` documentation tools.
108
+
109
+ ## Documentation
110
+
111
+ - [Getting started](https://github.com/openai/codex/blob/main/sdk/python/docs/getting-started.md)
112
+ - [API reference](https://github.com/openai/codex/blob/main/sdk/python/docs/api-reference.md)
113
+ - [FAQ](https://github.com/openai/codex/blob/main/sdk/python/docs/faq.md)
114
+ - [Examples](https://github.com/openai/codex/blob/main/sdk/python/examples/README.md)
115
+
116
+ The package is licensed under the
117
+ [repository Apache License 2.0](https://github.com/openai/codex/blob/main/LICENSE).
@@ -0,0 +1,89 @@
1
+ # OpenAI Codex Python SDK (Beta)
2
+
3
+ Build Python applications that start Codex threads, run turns, stream progress,
4
+ and control workspace access.
5
+
6
+ > [!NOTE]
7
+ > `openai-codex` is in beta. Public APIs may change before `1.0`.
8
+
9
+ ## Install
10
+
11
+ Install the SDK:
12
+
13
+ ```bash
14
+ pip install openai-codex
15
+ ```
16
+
17
+ For reproducible environments, install this release exactly:
18
+
19
+ ```bash
20
+ pip install openai-codex==0.1.0b1
21
+ ```
22
+
23
+ The SDK requires Python `>=3.10` and installs its compatible Codex runtime
24
+ dependency automatically. While beta releases are the only published SDK
25
+ releases, the normal install command selects the latest beta. After a stable
26
+ release exists, use `pip install --pre openai-codex` to explicitly select a
27
+ newer prerelease.
28
+
29
+ ## Quickstart
30
+
31
+ The SDK reuses your existing Codex authentication when one is already
32
+ available:
33
+
34
+ ```python
35
+ from openai_codex import Codex
36
+
37
+ with Codex() as codex:
38
+ thread = codex.thread_start()
39
+ result = thread.run("Explain this repository in three bullets.")
40
+ print(result.final_response)
41
+ ```
42
+
43
+ `thread.run(...)` returns a `TurnResult` containing the final response,
44
+ collected items, and token usage.
45
+
46
+ ## Authentication
47
+
48
+ Existing Codex authentication is reused automatically. To start ChatGPT
49
+ browser login explicitly:
50
+
51
+ ```python
52
+ from openai_codex import Codex
53
+
54
+ with Codex() as codex:
55
+ login = codex.login_chatgpt()
56
+ print(login.auth_url)
57
+ print(login.wait().success)
58
+ ```
59
+
60
+ For device-code login:
61
+
62
+ ```python
63
+ with Codex() as codex:
64
+ login = codex.login_chatgpt_device_code()
65
+ print(login.verification_url, login.user_code)
66
+ login.wait()
67
+ ```
68
+
69
+ For API-key login:
70
+
71
+ ```python
72
+ with Codex() as codex:
73
+ codex.login_api_key("sk-...")
74
+ ```
75
+
76
+ ## Built-In Help
77
+
78
+ Use Python's standard `help(openai_codex)`, `help(Codex)`, or
79
+ `python -m pydoc openai_codex` documentation tools.
80
+
81
+ ## Documentation
82
+
83
+ - [Getting started](https://github.com/openai/codex/blob/main/sdk/python/docs/getting-started.md)
84
+ - [API reference](https://github.com/openai/codex/blob/main/sdk/python/docs/api-reference.md)
85
+ - [FAQ](https://github.com/openai/codex/blob/main/sdk/python/docs/faq.md)
86
+ - [Examples](https://github.com/openai/codex/blob/main/sdk/python/examples/README.md)
87
+
88
+ The package is licensed under the
89
+ [repository Apache License 2.0](https://github.com/openai/codex/blob/main/LICENSE).
@@ -0,0 +1,91 @@
1
+ # Python SDK Examples
2
+
3
+ Each example folder contains runnable versions:
4
+
5
+ - `sync.py` (public sync surface: `Codex`)
6
+ - `async.py` (public async surface: `AsyncCodex`)
7
+
8
+ All examples intentionally use only public SDK exports from `openai_codex`
9
+ and `openai_codex.types`.
10
+
11
+ Examples use plain strings for text-only turns and typed input objects for
12
+ multimodal or structured input lists.
13
+
14
+ ## Prerequisites
15
+
16
+ - Python `>=3.10`
17
+ - Install the SDK for the same Python interpreter you will use to run examples
18
+
19
+ Install the published beta:
20
+
21
+ ```bash
22
+ python -m pip install openai-codex
23
+ ```
24
+
25
+ The SDK installs its pinned `openai-codex-cli-bin` runtime dependency.
26
+ The pinned runtime version comes from the SDK package dependency.
27
+
28
+ ## Run From A Checkout
29
+
30
+ Contributors using these checked-in scripts should install development
31
+ dependencies from `sdk/python`:
32
+
33
+ ```bash
34
+ uv sync --extra dev
35
+ source .venv/bin/activate
36
+ ```
37
+
38
+ The examples bootstrap local SDK imports from `sdk/python/src`. If the pinned
39
+ runtime is not already installed, the bootstrap installs the matching runtime
40
+ package for the active interpreter and cleans up temporary files afterward.
41
+
42
+ ## Run examples
43
+
44
+ From `sdk/python`:
45
+
46
+ ```bash
47
+ python examples/<example-folder>/sync.py
48
+ python examples/<example-folder>/async.py
49
+ ```
50
+
51
+ The checked-in examples use the local SDK source tree automatically.
52
+
53
+ ## Recommended first run
54
+
55
+ ```bash
56
+ python examples/01_quickstart_constructor/sync.py
57
+ python examples/01_quickstart_constructor/async.py
58
+ ```
59
+
60
+ ## Index
61
+
62
+ - `01_quickstart_constructor/`
63
+ - first run / sanity check
64
+ - `02_turn_run/`
65
+ - inspect full turn output fields
66
+ - `03_turn_stream_events/`
67
+ - stream a turn with a small curated event view
68
+ - `04_models_and_metadata/`
69
+ - discover visible models for the connected runtime
70
+ - `05_existing_thread/`
71
+ - resume a real existing thread (created in-script)
72
+ - `06_thread_lifecycle_and_controls/`
73
+ - thread lifecycle + control calls
74
+ - `07_image_and_text/`
75
+ - remote image URL + text multimodal turn
76
+ - `08_local_image_and_text/`
77
+ - local image + text multimodal turn using a generated temporary sample image
78
+ - `09_async_parity/`
79
+ - parity-style sync flow (see async parity in other examples)
80
+ - `10_error_handling_and_retry/`
81
+ - overload retry pattern + typed error handling structure
82
+ - `11_cli_mini_app/`
83
+ - interactive chat loop
84
+ - `12_turn_params_kitchen_sink/`
85
+ - structured output with a curated advanced `turn(...)` configuration
86
+ - `13_model_select_and_turn_params/`
87
+ - list models, pick highest model + highest supported reasoning effort, run turns, print message and usage
88
+ - `14_turn_controls/`
89
+ - separate `steer()` and `interrupt()` demos with concise summaries
90
+ - `15_login_and_account/`
91
+ - browser-login handle lifecycle, cancellation, and account inspection
@@ -0,0 +1,92 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "openai-codex"
7
+ version = "0.1.0b1"
8
+ description = "Python SDK for Codex"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "OpenAI" }]
13
+ keywords = ["codex", "sdk", "llm", "ai", "agents"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ ]
24
+ dependencies = ["pydantic>=2.12", "openai-codex-cli-bin==0.132.0"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/openai/codex"
28
+ Repository = "https://github.com/openai/codex"
29
+ Issues = "https://github.com/openai/codex/issues"
30
+ Documentation = "https://github.com/openai/codex/tree/main/sdk/python/docs"
31
+
32
+ [project.optional-dependencies]
33
+ dev = ["pytest>=8.0", "datamodel-code-generator==0.31.2", "ruff>=0.15.8"]
34
+
35
+ [tool.hatch.build]
36
+ exclude = [
37
+ ".venv/**",
38
+ ".venv2/**",
39
+ ".pytest_cache/**",
40
+ "dist/**",
41
+ "build/**",
42
+ ]
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/openai_codex"]
46
+ include = [
47
+ "src/openai_codex/py.typed",
48
+ ]
49
+
50
+ [tool.hatch.build.targets.sdist]
51
+ include = [
52
+ "src/openai_codex/**",
53
+ "README.md",
54
+ "pyproject.toml",
55
+ ]
56
+
57
+ [tool.pytest.ini_options]
58
+ addopts = "-q"
59
+ testpaths = ["tests"]
60
+
61
+ [tool.ruff]
62
+ target-version = "py310"
63
+ required-version = ">=0.15.8"
64
+ line-length = 100
65
+ extend-exclude = [
66
+ "notebooks/**",
67
+ "src/openai_codex/generated/**",
68
+ ]
69
+
70
+ [tool.ruff.lint]
71
+ select = ["E", "F", "I", "B", "C4"]
72
+ ignore = ["E501"]
73
+ preview = true
74
+ extend-safe-fixes = ["ALL"]
75
+ unfixable = ["F841"]
76
+
77
+ [tool.ruff.lint.per-file-ignores]
78
+ "examples/**/*.py" = ["E402"]
79
+ "tests/test_real_app_server_integration.py" = ["E402"]
80
+
81
+ [tool.ruff.lint.isort]
82
+ combine-as-imports = true
83
+
84
+ [tool.uv]
85
+ exclude-newer = "7 days"
86
+ exclude-newer-package = { openai-codex-cli-bin = "2026-05-20T21:00:00Z" }
87
+ index-strategy = "first-index"
88
+
89
+ [tool.uv.pip]
90
+ exclude-newer = "7 days"
91
+ exclude-newer-package = { openai-codex-cli-bin = "2026-05-20T21:00:00Z" }
92
+ index-strategy = "first-index"
@@ -0,0 +1,93 @@
1
+ """Python SDK for running Codex workflows.
2
+
3
+ Start with :class:`Codex` for synchronous applications or
4
+ :class:`AsyncCodex` for async applications. Most programs create a thread and
5
+ run a turn::
6
+
7
+ from openai_codex import Codex, Sandbox
8
+
9
+ with Codex() as codex:
10
+ thread = codex.thread_start(sandbox=Sandbox.workspace_write)
11
+ result = thread.run("Describe this project.")
12
+ print(result.final_response)
13
+ """
14
+
15
+ from ._version import __version__
16
+ from .api import (
17
+ ApprovalMode,
18
+ AsyncChatgptLoginHandle,
19
+ AsyncCodex,
20
+ AsyncDeviceCodeLoginHandle,
21
+ AsyncThread,
22
+ AsyncTurnHandle,
23
+ ChatgptLoginHandle,
24
+ Codex,
25
+ DeviceCodeLoginHandle,
26
+ ImageInput,
27
+ Input,
28
+ InputItem,
29
+ LocalImageInput,
30
+ MentionInput,
31
+ RunInput,
32
+ Sandbox,
33
+ SkillInput,
34
+ TextInput,
35
+ Thread,
36
+ TurnHandle,
37
+ TurnResult,
38
+ )
39
+ from .client import CodexConfig
40
+ from .errors import (
41
+ CodexError,
42
+ CodexRpcError,
43
+ InternalRpcError,
44
+ InvalidParamsError,
45
+ InvalidRequestError,
46
+ JsonRpcError,
47
+ MethodNotFoundError,
48
+ ParseError,
49
+ RetryLimitExceededError,
50
+ ServerBusyError,
51
+ TransportClosedError,
52
+ is_retryable_error,
53
+ )
54
+ from .retry import retry_on_overload
55
+
56
+ __all__ = [
57
+ "__version__",
58
+ "CodexConfig",
59
+ "Codex",
60
+ "AsyncCodex",
61
+ "ApprovalMode",
62
+ "Sandbox",
63
+ "ChatgptLoginHandle",
64
+ "DeviceCodeLoginHandle",
65
+ "AsyncChatgptLoginHandle",
66
+ "AsyncDeviceCodeLoginHandle",
67
+ "Thread",
68
+ "AsyncThread",
69
+ "TurnHandle",
70
+ "AsyncTurnHandle",
71
+ "TurnResult",
72
+ "Input",
73
+ "InputItem",
74
+ "RunInput",
75
+ "TextInput",
76
+ "ImageInput",
77
+ "LocalImageInput",
78
+ "SkillInput",
79
+ "MentionInput",
80
+ "retry_on_overload",
81
+ "CodexError",
82
+ "TransportClosedError",
83
+ "JsonRpcError",
84
+ "CodexRpcError",
85
+ "ParseError",
86
+ "InvalidRequestError",
87
+ "MethodNotFoundError",
88
+ "InvalidParamsError",
89
+ "InternalRpcError",
90
+ "ServerBusyError",
91
+ "RetryLimitExceededError",
92
+ "is_retryable_error",
93
+ ]
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import NoReturn
5
+
6
+ from .generated.v2_all import (
7
+ ApprovalsReviewer,
8
+ AskForApproval,
9
+ AskForApprovalValue,
10
+ )
11
+
12
+
13
+ class ApprovalMode(str, Enum):
14
+ """High-level approval behavior for escalated permission requests."""
15
+
16
+ deny_all = "deny_all"
17
+ auto_review = "auto_review"
18
+
19
+
20
+ def _approval_mode_settings(
21
+ approval_mode: ApprovalMode,
22
+ ) -> tuple[AskForApproval, ApprovalsReviewer | None]:
23
+ """Map the public approval mode to generated app-server start params."""
24
+ if not isinstance(approval_mode, ApprovalMode):
25
+ supported = ", ".join(mode.value for mode in ApprovalMode)
26
+ raise ValueError(f"approval_mode must be one of: {supported}")
27
+
28
+ match approval_mode:
29
+ case ApprovalMode.auto_review:
30
+ return (
31
+ AskForApproval(root=AskForApprovalValue.on_request),
32
+ ApprovalsReviewer.auto_review,
33
+ )
34
+ case ApprovalMode.deny_all:
35
+ return AskForApproval(root=AskForApprovalValue.never), None
36
+ case _:
37
+ return _assert_never_approval_mode(approval_mode)
38
+
39
+
40
+ def _assert_never_approval_mode(approval_mode: NoReturn) -> NoReturn:
41
+ """Make approval mode mapping exhaustive for static type checkers."""
42
+ raise AssertionError(f"Unhandled approval mode: {approval_mode!r}")
43
+
44
+
45
+ def _approval_mode_override_settings(
46
+ approval_mode: ApprovalMode | None,
47
+ ) -> tuple[AskForApproval | None, ApprovalsReviewer | None]:
48
+ """Map an optional public approval mode to app-server override params."""
49
+ if approval_mode is None:
50
+ return None, None
51
+ return _approval_mode_settings(approval_mode)
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from .models import InitializeResponse, ServerInfo
4
+
5
+
6
+ def _split_user_agent(user_agent: str) -> tuple[str | None, str | None]:
7
+ raw = user_agent.strip()
8
+ if not raw:
9
+ return None, None
10
+ if "/" in raw:
11
+ name, version = raw.split("/", 1)
12
+ return (name or None), (version or None)
13
+ parts = raw.split(maxsplit=1)
14
+ if len(parts) == 2:
15
+ return parts[0], parts[1]
16
+ return raw, None
17
+
18
+
19
+ def validate_initialize_metadata(payload: InitializeResponse) -> InitializeResponse:
20
+ user_agent = (payload.userAgent or "").strip()
21
+ server = payload.serverInfo
22
+
23
+ server_name: str | None = None
24
+ server_version: str | None = None
25
+
26
+ if server is not None:
27
+ server_name = (server.name or "").strip() or None
28
+ server_version = (server.version or "").strip() or None
29
+
30
+ if (server_name is None or server_version is None) and user_agent:
31
+ parsed_name, parsed_version = _split_user_agent(user_agent)
32
+ if server_name is None:
33
+ server_name = parsed_name
34
+ if server_version is None:
35
+ server_version = parsed_version
36
+
37
+ normalized_server_name = (server_name or "").strip()
38
+ normalized_server_version = (server_version or "").strip()
39
+ if not user_agent or not normalized_server_name or not normalized_server_version:
40
+ raise RuntimeError(
41
+ "initialize response missing required metadata "
42
+ f"(user_agent={user_agent!r}, server_name={normalized_server_name!r}, server_version={normalized_server_version!r})"
43
+ )
44
+
45
+ if server is None:
46
+ payload.serverInfo = ServerInfo(
47
+ name=normalized_server_name,
48
+ version=normalized_server_version,
49
+ )
50
+ else:
51
+ server.name = normalized_server_name
52
+ server.version = normalized_server_version
53
+
54
+ return payload
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from .models import JsonObject
6
+
7
+
8
+ @dataclass(slots=True)
9
+ class TextInput:
10
+ """Text supplied to a turn or steering request."""
11
+
12
+ text: str
13
+
14
+
15
+ @dataclass(slots=True)
16
+ class ImageInput:
17
+ """Remote image URL supplied as turn input."""
18
+
19
+ url: str
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class LocalImageInput:
24
+ """Local image path supplied as turn input."""
25
+
26
+ path: str
27
+
28
+
29
+ @dataclass(slots=True)
30
+ class SkillInput:
31
+ """Named skill reference supplied as turn input."""
32
+
33
+ name: str
34
+ path: str
35
+
36
+
37
+ @dataclass(slots=True)
38
+ class MentionInput:
39
+ """Named resource mention supplied as turn input."""
40
+
41
+ name: str
42
+ path: str
43
+
44
+
45
+ InputItem = TextInput | ImageInput | LocalImageInput | SkillInput | MentionInput
46
+ Input = list[InputItem] | InputItem
47
+ RunInput = Input | str
48
+
49
+
50
+ def _to_wire_item(item: InputItem) -> JsonObject:
51
+ if isinstance(item, TextInput):
52
+ return {"type": "text", "text": item.text}
53
+ if isinstance(item, ImageInput):
54
+ return {"type": "image", "url": item.url}
55
+ if isinstance(item, LocalImageInput):
56
+ return {"type": "localImage", "path": item.path}
57
+ if isinstance(item, SkillInput):
58
+ return {"type": "skill", "name": item.name, "path": item.path}
59
+ if isinstance(item, MentionInput):
60
+ return {"type": "mention", "name": item.name, "path": item.path}
61
+ raise TypeError(f"unsupported input item: {type(item)!r}")
62
+
63
+
64
+ def _to_wire_input(input: Input) -> list[JsonObject]:
65
+ if isinstance(input, list):
66
+ return [_to_wire_item(i) for i in input]
67
+ return [_to_wire_item(input)]
68
+
69
+
70
+ def _normalize_run_input(input: RunInput) -> Input:
71
+ if isinstance(input, str):
72
+ return TextInput(input)
73
+ return input