codex-python 0.2.3__tar.gz → 0.2.8__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,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 gersmann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: codex-python
3
+ Version: 0.2.8
4
+ Classifier: Programming Language :: Python :: 3
5
+ Classifier: Programming Language :: Python :: 3 :: Only
6
+ Classifier: Programming Language :: Python :: 3.13
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Typing :: Typed
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Dist: pydantic>=2.11.7
11
+ License-File: LICENSE
12
+ Summary: A minimal Python library scaffold for codex-python
13
+ Keywords: codex,library,scaffold
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
16
+ Project-URL: Homepage, https://github.com/gersmann/codex-python
17
+ Project-URL: Repository, https://github.com/gersmann/codex-python
18
+ Project-URL: Issues, https://github.com/gersmann/codex-python/issues
19
+
20
+ # codex-python
21
+
22
+ Native Python bindings for Codex (in‑process execution). Ships as a single package (`codex-python`) with platform wheels that include the native extension.
23
+
24
+ - Python: 3.12–3.13 (CI also attempts 3.14)
25
+ - Import name: `codex`
26
+ - PyPI: https://pypi.org/project/codex-python/
27
+
28
+ ## Install
29
+
30
+ ```
31
+ pip install codex-python
32
+ ```
33
+
34
+ If there’s no prebuilt wheel for your platform/Python, pip will build from source. You’ll need a Rust toolchain and maturin; see “Developing” below.
35
+
36
+ ## Quickstart
37
+
38
+ Run a prompt and collect structured events (typed):
39
+
40
+ ```
41
+ from codex.api import run_exec, CodexClient
42
+ from codex.config import CodexConfig, ApprovalPolicy, SandboxMode
43
+
44
+ cfg = CodexConfig(
45
+ model="gpt-5",
46
+ model_provider="openai",
47
+ approval_policy=ApprovalPolicy.ON_REQUEST,
48
+ sandbox_mode=SandboxMode.WORKSPACE_WRITE,
49
+ )
50
+
51
+ # One-shot
52
+ events = run_exec("Explain this repo", config=cfg)
53
+
54
+ # Conversation (streaming)
55
+ client = CodexClient(config=cfg)
56
+ for ev in client.start_conversation("Add a smoke test"):
57
+ print(ev.id, ev.msg)
58
+ ```
59
+
60
+ Notes
61
+ - `Event.msg` is typed as a union `EventMsg` (also available at `codex.EventMsg`).
62
+ - For raw dict streaming from the native layer, use `codex.native.start_exec_stream`.
63
+
64
+ ## Configuration (Pydantic)
65
+
66
+ Use `CodexConfig` to pass overrides mirrored from Rust `ConfigOverrides`.
67
+
68
+ ```
69
+ from codex.config import CodexConfig, ApprovalPolicy, SandboxMode
70
+
71
+ cfg = CodexConfig(
72
+ model="gpt-5",
73
+ model_provider="openai",
74
+ approval_policy=ApprovalPolicy.ON_REQUEST,
75
+ sandbox_mode=SandboxMode.WORKSPACE_WRITE,
76
+ cwd="/path/to/project",
77
+ include_apply_patch_tool=True,
78
+ )
79
+ ```
80
+
81
+ - `CodexConfig.to_dict()` emits only fields you set, with enums serialized to kebab‑case strings expected by the core.
82
+ - For tests and introspection, `codex.native.preview_config(config_overrides=..., load_default_config=...)` returns a compact snapshot of the effective configuration.
83
+
84
+ ## Troubleshooting
85
+
86
+ - “codex_native extension not installed”
87
+ - Install with `pip install codex-python` (wheel) or build locally (see below).
88
+ - maturin develop fails without a virtualenv
89
+ - Use `python -m venv .venv && source .venv/bin/activate` (or conda), or run `make dev-native` which falls back to build+pip install when no venv is present.
90
+
91
+ ## Developing
92
+
93
+ Prerequisites
94
+ - Python 3.12/3.13
95
+ - Rust toolchain (cargo)
96
+ - maturin (for native builds)
97
+ - uv (optional, for fast Python builds and dev tooling)
98
+
99
+ Common tasks
100
+ - Lint: `make lint` (ruff + mypy)
101
+ - Test: `make test` (pytest)
102
+ - Format: `make fmt`
103
+ - Build native locally: `make dev-native`
104
+ - Generate protocol types from upstream: `make gen-protocol`
105
+
106
+ Protocol types
107
+ - `make gen-protocol` generates TS types (via Codex or cargo) into `.generated/ts` and then writes Pydantic models to `codex/protocol/types.py`.
108
+ - Generated models use `model_config = ConfigDict(extra='allow')` and place it at the end of each class.
109
+
110
+ Releasing
111
+ - Bump `codex/__init__.py` and `crates/codex_native/Cargo.toml` versions.
112
+ - Update `CHANGELOG.md`.
113
+ - Tag and push: `git tag -a vX.Y.Z -m "codex-python X.Y.Z" && git push origin vX.Y.Z`.
114
+ - GitHub Actions (publish.yml) builds native wheels across platforms and an sdist, then publishes them via Trusted Publishing (OIDC).
115
+
116
+ Project layout
117
+ ```
118
+ .
119
+ ├── codex/ # Python package
120
+ ├── crates/codex_native/ # PyO3 native extension
121
+ ├── scripts/ # generators and helpers
122
+ ├── .github/workflows/ # CI, publish, native wheels
123
+ └── Makefile # common tasks
124
+ ```
125
+
126
+ Links
127
+ - Codex repo: https://github.com/openai/codex
128
+ - uv: https://docs.astral.sh/uv/
129
+ - maturin: https://www.maturin.rs/
130
+
@@ -0,0 +1,110 @@
1
+ # codex-python
2
+
3
+ Native Python bindings for Codex (in‑process execution). Ships as a single package (`codex-python`) with platform wheels that include the native extension.
4
+
5
+ - Python: 3.12–3.13 (CI also attempts 3.14)
6
+ - Import name: `codex`
7
+ - PyPI: https://pypi.org/project/codex-python/
8
+
9
+ ## Install
10
+
11
+ ```
12
+ pip install codex-python
13
+ ```
14
+
15
+ If there’s no prebuilt wheel for your platform/Python, pip will build from source. You’ll need a Rust toolchain and maturin; see “Developing” below.
16
+
17
+ ## Quickstart
18
+
19
+ Run a prompt and collect structured events (typed):
20
+
21
+ ```
22
+ from codex.api import run_exec, CodexClient
23
+ from codex.config import CodexConfig, ApprovalPolicy, SandboxMode
24
+
25
+ cfg = CodexConfig(
26
+ model="gpt-5",
27
+ model_provider="openai",
28
+ approval_policy=ApprovalPolicy.ON_REQUEST,
29
+ sandbox_mode=SandboxMode.WORKSPACE_WRITE,
30
+ )
31
+
32
+ # One-shot
33
+ events = run_exec("Explain this repo", config=cfg)
34
+
35
+ # Conversation (streaming)
36
+ client = CodexClient(config=cfg)
37
+ for ev in client.start_conversation("Add a smoke test"):
38
+ print(ev.id, ev.msg)
39
+ ```
40
+
41
+ Notes
42
+ - `Event.msg` is typed as a union `EventMsg` (also available at `codex.EventMsg`).
43
+ - For raw dict streaming from the native layer, use `codex.native.start_exec_stream`.
44
+
45
+ ## Configuration (Pydantic)
46
+
47
+ Use `CodexConfig` to pass overrides mirrored from Rust `ConfigOverrides`.
48
+
49
+ ```
50
+ from codex.config import CodexConfig, ApprovalPolicy, SandboxMode
51
+
52
+ cfg = CodexConfig(
53
+ model="gpt-5",
54
+ model_provider="openai",
55
+ approval_policy=ApprovalPolicy.ON_REQUEST,
56
+ sandbox_mode=SandboxMode.WORKSPACE_WRITE,
57
+ cwd="/path/to/project",
58
+ include_apply_patch_tool=True,
59
+ )
60
+ ```
61
+
62
+ - `CodexConfig.to_dict()` emits only fields you set, with enums serialized to kebab‑case strings expected by the core.
63
+ - For tests and introspection, `codex.native.preview_config(config_overrides=..., load_default_config=...)` returns a compact snapshot of the effective configuration.
64
+
65
+ ## Troubleshooting
66
+
67
+ - “codex_native extension not installed”
68
+ - Install with `pip install codex-python` (wheel) or build locally (see below).
69
+ - maturin develop fails without a virtualenv
70
+ - Use `python -m venv .venv && source .venv/bin/activate` (or conda), or run `make dev-native` which falls back to build+pip install when no venv is present.
71
+
72
+ ## Developing
73
+
74
+ Prerequisites
75
+ - Python 3.12/3.13
76
+ - Rust toolchain (cargo)
77
+ - maturin (for native builds)
78
+ - uv (optional, for fast Python builds and dev tooling)
79
+
80
+ Common tasks
81
+ - Lint: `make lint` (ruff + mypy)
82
+ - Test: `make test` (pytest)
83
+ - Format: `make fmt`
84
+ - Build native locally: `make dev-native`
85
+ - Generate protocol types from upstream: `make gen-protocol`
86
+
87
+ Protocol types
88
+ - `make gen-protocol` generates TS types (via Codex or cargo) into `.generated/ts` and then writes Pydantic models to `codex/protocol/types.py`.
89
+ - Generated models use `model_config = ConfigDict(extra='allow')` and place it at the end of each class.
90
+
91
+ Releasing
92
+ - Bump `codex/__init__.py` and `crates/codex_native/Cargo.toml` versions.
93
+ - Update `CHANGELOG.md`.
94
+ - Tag and push: `git tag -a vX.Y.Z -m "codex-python X.Y.Z" && git push origin vX.Y.Z`.
95
+ - GitHub Actions (publish.yml) builds native wheels across platforms and an sdist, then publishes them via Trusted Publishing (OIDC).
96
+
97
+ Project layout
98
+ ```
99
+ .
100
+ ├── codex/ # Python package
101
+ ├── crates/codex_native/ # PyO3 native extension
102
+ ├── scripts/ # generators and helpers
103
+ ├── .github/workflows/ # CI, publish, native wheels
104
+ └── Makefile # common tasks
105
+ ```
106
+
107
+ Links
108
+ - Codex repo: https://github.com/openai/codex
109
+ - uv: https://docs.astral.sh/uv/
110
+ - maturin: https://www.maturin.rs/
@@ -0,0 +1,34 @@
1
+ """codex
2
+
3
+ Python interface for the Codex CLI.
4
+
5
+ Usage:
6
+ from codex import run_exec
7
+ events = run_exec("explain this codebase to me")
8
+ """
9
+
10
+ from .api import (
11
+ CodexClient,
12
+ CodexError,
13
+ CodexNativeError,
14
+ Conversation,
15
+ run_exec,
16
+ )
17
+ from .config import CodexConfig
18
+ from .event import Event
19
+ from .protocol.types import EventMsg
20
+
21
+ __all__ = [
22
+ "__version__",
23
+ "CodexError",
24
+ "CodexNativeError",
25
+ "CodexClient",
26
+ "Conversation",
27
+ "run_exec",
28
+ "Event",
29
+ "EventMsg",
30
+ "CodexConfig",
31
+ ]
32
+
33
+ # Package version. Kept in sync with Cargo.toml via CI before builds.
34
+ __version__ = "0.2.7"
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable, Iterator, Mapping, Sequence
4
+ from dataclasses import dataclass
5
+
6
+ from .config import CodexConfig
7
+ from .event import Event
8
+ from .native import run_exec_collect as native_run_exec_collect
9
+ from .native import start_exec_stream as native_start_exec_stream
10
+
11
+
12
+ class CodexError(Exception):
13
+ """Base exception for codex-python."""
14
+
15
+
16
+ class CodexNativeError(CodexError):
17
+ """Raised when the native extension is not available or fails."""
18
+
19
+ def __init__(self) -> None:
20
+ super().__init__(
21
+ "codex_native extension not installed or failed to run. "
22
+ "Run `make dev-native` or ensure native wheels are installed."
23
+ )
24
+
25
+
26
+ @dataclass(slots=True)
27
+ class Conversation:
28
+ """A stateful conversation with Codex, streaming events natively."""
29
+
30
+ _stream: Iterable[dict]
31
+
32
+ def __iter__(self) -> Iterator[Event]:
33
+ """Yield `Event` objects from the native stream."""
34
+ for item in self._stream:
35
+ yield Event.model_validate(item)
36
+
37
+
38
+ @dataclass(slots=True)
39
+ class CodexClient:
40
+ """Lightweight, synchronous client for the native Codex core.
41
+
42
+ Provides defaults for repeated invocations and conversation management.
43
+ """
44
+
45
+ config: CodexConfig | None = None
46
+ load_default_config: bool = True
47
+ env: Mapping[str, str] | None = None
48
+ extra_args: Sequence[str] | None = None
49
+
50
+ def start_conversation(
51
+ self,
52
+ prompt: str,
53
+ *,
54
+ config: CodexConfig | None = None,
55
+ load_default_config: bool | None = None,
56
+ ) -> Conversation:
57
+ """Start a new conversation and return a streaming iterator over events."""
58
+ eff_config = config if config is not None else self.config
59
+ eff_load_default_config = (
60
+ load_default_config if load_default_config is not None else self.load_default_config
61
+ )
62
+
63
+ try:
64
+ stream = native_start_exec_stream(
65
+ prompt,
66
+ config_overrides=eff_config.to_dict() if eff_config else None,
67
+ load_default_config=eff_load_default_config,
68
+ )
69
+ return Conversation(_stream=stream)
70
+ except RuntimeError as e:
71
+ raise CodexNativeError() from e
72
+
73
+
74
+ def run_exec(
75
+ prompt: str,
76
+ *,
77
+ config: CodexConfig | None = None,
78
+ load_default_config: bool = True,
79
+ ) -> list[Event]:
80
+ """
81
+ Run a prompt through the native Codex engine and return a list of events.
82
+
83
+ - Raises CodexNativeError if the native extension is unavailable or fails.
84
+ """
85
+ try:
86
+ events = native_run_exec_collect(
87
+ prompt,
88
+ config_overrides=config.to_dict() if config else None,
89
+ load_default_config=load_default_config,
90
+ )
91
+ return [Event.model_validate(e) for e in events]
92
+ except RuntimeError as e:
93
+ raise CodexNativeError() from e
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import Any, cast
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+
9
+ class ApprovalPolicy(str, Enum):
10
+ """Approval policy for executing shell commands.
11
+
12
+ Matches Rust enum `AskForApproval` (serde kebab-case):
13
+ - "untrusted": auto-approve safe read-only commands, ask otherwise
14
+ - "on-failure": sandbox by default; ask only if the sandboxed run fails
15
+ - "on-request": model decides (default)
16
+ - "never": never ask the user
17
+ """
18
+
19
+ UNTRUSTED = "untrusted"
20
+ ON_FAILURE = "on-failure"
21
+ ON_REQUEST = "on-request"
22
+ NEVER = "never"
23
+
24
+
25
+ class SandboxMode(str, Enum):
26
+ """High-level sandbox mode override.
27
+
28
+ Matches Rust enum `SandboxMode` (serde kebab-case):
29
+ - "read-only"
30
+ - "workspace-write"
31
+ - "danger-full-access"
32
+ """
33
+
34
+ READ_ONLY = "read-only"
35
+ WORKSPACE_WRITE = "workspace-write"
36
+ DANGER_FULL_ACCESS = "danger-full-access"
37
+
38
+
39
+ class CodexConfig(BaseModel):
40
+ """Configuration overrides for Codex.
41
+
42
+ This mirrors `codex_core::config::ConfigOverrides` and is intentionally
43
+ conservative: only values present (not None) are passed to the native core.
44
+ """
45
+
46
+ # Model selection
47
+ model: str | None = Field(default=None, description="Model slug, e.g. 'gpt-5' or 'o3'.")
48
+ model_provider: str | None = Field(
49
+ default=None, description="Provider key from config, e.g. 'openai'."
50
+ )
51
+
52
+ # Safety/Execution
53
+ approval_policy: ApprovalPolicy | None = Field(default=None)
54
+ sandbox_mode: SandboxMode | None = Field(default=None)
55
+
56
+ # Environment
57
+ cwd: str | None = Field(default=None, description="Working directory for the session.")
58
+ config_profile: str | None = Field(
59
+ default=None, description="Config profile key to use (from profiles.*)."
60
+ )
61
+ codex_linux_sandbox_exe: str | None = Field(
62
+ default=None, description="Absolute path to codex-linux-sandbox (Linux only)."
63
+ )
64
+
65
+ # UX / features
66
+ base_instructions: str | None = Field(default=None, description="Override base instructions.")
67
+ include_plan_tool: bool | None = Field(default=None)
68
+ include_apply_patch_tool: bool | None = Field(default=None)
69
+ include_view_image_tool: bool | None = Field(default=None)
70
+ show_raw_agent_reasoning: bool | None = Field(default=None)
71
+ tools_web_search_request: bool | None = Field(default=None)
72
+
73
+ def to_dict(self) -> dict[str, Any]:
74
+ """Return overrides as a plain dict with None values removed.
75
+
76
+ Enum fields are emitted as their string values.
77
+ """
78
+ return cast(dict[str, Any], self.model_dump(exclude_none=True))
79
+
80
+ # Pydantic v2 config. `use_enum_values=True` ensures enums dump as strings.
81
+ # Place at end of class, extra='allow' per style.
82
+ model_config = ConfigDict(extra="allow", validate_assignment=True, use_enum_values=True)
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+ from pydantic.config import ConfigDict
5
+
6
+ from .protocol.types import EventMsg
7
+
8
+
9
+ class Event(BaseModel):
10
+ """Protocol event envelope with typed `msg` (union of EventMsg_*)."""
11
+
12
+ id: str
13
+ msg: EventMsg
14
+
15
+ # Allow forward compatibility with additional envelope fields
16
+ model_config = ConfigDict(extra="allow")
@@ -0,0 +1,35 @@
1
+ from typing import Any, cast
2
+
3
+ from codex_native import preview_config as _preview_config
4
+ from codex_native import run_exec_collect as _run_exec_collect
5
+ from codex_native import start_exec_stream as _start_exec_stream
6
+
7
+
8
+ def run_exec_collect(
9
+ prompt: str,
10
+ *,
11
+ config_overrides: dict[str, Any] | None = None,
12
+ load_default_config: bool = True,
13
+ ) -> list[dict]:
14
+ """Run Codex natively (in‑process) and return a list of events as dicts."""
15
+ return cast(list[dict], _run_exec_collect(prompt, config_overrides, load_default_config))
16
+
17
+
18
+ def start_exec_stream(
19
+ prompt: str,
20
+ *,
21
+ config_overrides: dict[str, Any] | None = None,
22
+ load_default_config: bool = True,
23
+ ) -> Any:
24
+ """Return a native streaming iterator over Codex events (dicts)."""
25
+ return _start_exec_stream(prompt, config_overrides, load_default_config)
26
+
27
+
28
+ def preview_config(
29
+ *, config_overrides: dict[str, Any] | None = None, load_default_config: bool = True
30
+ ) -> dict:
31
+ """Return an effective config snapshot (selected fields) from native.
32
+
33
+ Useful for tests to validate override mapping without running Codex.
34
+ """
35
+ return cast(dict, _preview_config(config_overrides, load_default_config))