codex-python 0.1.2__tar.gz → 0.2.0__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.
Files changed (154) hide show
  1. {codex_python-0.1.2 → codex_python-0.2.0}/.github/workflows/ci.yml +11 -1
  2. codex_python-0.2.0/.github/workflows/native-wheels.yml +68 -0
  3. {codex_python-0.1.2 → codex_python-0.2.0}/.gitignore +17 -0
  4. {codex_python-0.1.2 → codex_python-0.2.0}/Makefile +18 -3
  5. {codex_python-0.1.2 → codex_python-0.2.0}/PKG-INFO +69 -30
  6. codex_python-0.2.0/README.md +169 -0
  7. {codex_python-0.1.2 → codex_python-0.2.0}/codex/__init__.py +12 -8
  8. codex_python-0.2.0/codex/api.py +93 -0
  9. codex_python-0.2.0/codex/config.py +82 -0
  10. codex_python-0.2.0/codex/event.py +16 -0
  11. codex_python-0.2.0/codex/native.py +56 -0
  12. {codex_python-0.1.2 → codex_python-0.2.0}/codex/protocol/types.py +413 -289
  13. codex_python-0.2.0/crates/codex_native/Cargo.toml +25 -0
  14. codex_python-0.2.0/crates/codex_native/src/lib.rs +332 -0
  15. {codex_python-0.1.2 → codex_python-0.2.0}/pyproject.toml +2 -0
  16. {codex_python-0.1.2 → codex_python-0.2.0}/scripts/generate_protocol_py.py +2 -2
  17. codex_python-0.1.2/README.md +0 -130
  18. codex_python-0.1.2/codex/api.py +0 -181
  19. codex_python-0.1.2/codex/protocol/runtime.py +0 -80
  20. codex_python-0.1.2/test/AddConversationListenerParams.ts +0 -6
  21. codex_python-0.1.2/test/AddConversationSubscriptionResponse.ts +0 -5
  22. codex_python-0.1.2/test/AgentMessageDeltaEvent.ts +0 -5
  23. codex_python-0.1.2/test/AgentMessageEvent.ts +0 -5
  24. codex_python-0.1.2/test/AgentReasoningDeltaEvent.ts +0 -5
  25. codex_python-0.1.2/test/AgentReasoningEvent.ts +0 -5
  26. codex_python-0.1.2/test/AgentReasoningRawContentDeltaEvent.ts +0 -5
  27. codex_python-0.1.2/test/AgentReasoningRawContentEvent.ts +0 -5
  28. codex_python-0.1.2/test/AgentReasoningSectionBreakEvent.ts +0 -5
  29. codex_python-0.1.2/test/Annotations.ts +0 -9
  30. codex_python-0.1.2/test/ApplyPatchApprovalParams.ts +0 -21
  31. codex_python-0.1.2/test/ApplyPatchApprovalRequestEvent.ts +0 -18
  32. codex_python-0.1.2/test/ApplyPatchApprovalResponse.ts +0 -6
  33. codex_python-0.1.2/test/ArchiveConversationParams.ts +0 -9
  34. codex_python-0.1.2/test/ArchiveConversationResponse.ts +0 -5
  35. codex_python-0.1.2/test/AskForApproval.ts +0 -9
  36. codex_python-0.1.2/test/AudioContent.ts +0 -9
  37. codex_python-0.1.2/test/AuthMode.ts +0 -5
  38. codex_python-0.1.2/test/AuthStatusChangeNotification.ts +0 -10
  39. codex_python-0.1.2/test/BackgroundEventEvent.ts +0 -5
  40. codex_python-0.1.2/test/BlobResourceContents.ts +0 -5
  41. codex_python-0.1.2/test/CallToolResult.ts +0 -10
  42. codex_python-0.1.2/test/CancelLoginChatGptParams.ts +0 -5
  43. codex_python-0.1.2/test/CancelLoginChatGptResponse.ts +0 -5
  44. codex_python-0.1.2/test/ClientRequest.ts +0 -22
  45. codex_python-0.1.2/test/ContentBlock.ts +0 -10
  46. codex_python-0.1.2/test/ContentItem.ts +0 -5
  47. codex_python-0.1.2/test/ConversationHistoryResponseEvent.ts +0 -11
  48. codex_python-0.1.2/test/ConversationId.ts +0 -5
  49. codex_python-0.1.2/test/ConversationSummary.ts +0 -10
  50. codex_python-0.1.2/test/CustomPrompt.ts +0 -5
  51. codex_python-0.1.2/test/EmbeddedResource.ts +0 -13
  52. codex_python-0.1.2/test/EmbeddedResourceResource.ts +0 -7
  53. codex_python-0.1.2/test/ErrorEvent.ts +0 -5
  54. codex_python-0.1.2/test/EventMsg.ts +0 -41
  55. codex_python-0.1.2/test/ExecApprovalRequestEvent.ts +0 -21
  56. codex_python-0.1.2/test/ExecCommandApprovalParams.ts +0 -11
  57. codex_python-0.1.2/test/ExecCommandApprovalResponse.ts +0 -6
  58. codex_python-0.1.2/test/ExecCommandBeginEvent.ts +0 -18
  59. codex_python-0.1.2/test/ExecCommandEndEvent.ts +0 -33
  60. codex_python-0.1.2/test/ExecCommandOutputDeltaEvent.ts +0 -18
  61. codex_python-0.1.2/test/ExecOneOffCommandParams.ts +0 -23
  62. codex_python-0.1.2/test/ExecOutputStream.ts +0 -5
  63. codex_python-0.1.2/test/FileChange.ts +0 -5
  64. codex_python-0.1.2/test/FunctionCallOutputPayload.ts +0 -5
  65. codex_python-0.1.2/test/GetAuthStatusParams.ts +0 -13
  66. codex_python-0.1.2/test/GetAuthStatusResponse.ts +0 -6
  67. codex_python-0.1.2/test/GetHistoryEntryResponseEvent.ts +0 -10
  68. codex_python-0.1.2/test/GetUserAgentResponse.ts +0 -5
  69. codex_python-0.1.2/test/GetUserSavedConfigResponse.ts +0 -6
  70. codex_python-0.1.2/test/GitDiffToRemoteParams.ts +0 -5
  71. codex_python-0.1.2/test/GitDiffToRemoteResponse.ts +0 -6
  72. codex_python-0.1.2/test/GitSha.ts +0 -5
  73. codex_python-0.1.2/test/HistoryEntry.ts +0 -5
  74. codex_python-0.1.2/test/ImageContent.ts +0 -9
  75. codex_python-0.1.2/test/InputItem.ts +0 -5
  76. codex_python-0.1.2/test/InputMessageKind.ts +0 -5
  77. codex_python-0.1.2/test/InterruptConversationParams.ts +0 -6
  78. codex_python-0.1.2/test/InterruptConversationResponse.ts +0 -6
  79. codex_python-0.1.2/test/ListConversationsParams.ts +0 -13
  80. codex_python-0.1.2/test/ListConversationsResponse.ts +0 -11
  81. codex_python-0.1.2/test/ListCustomPromptsResponseEvent.ts +0 -9
  82. codex_python-0.1.2/test/LocalShellAction.ts +0 -6
  83. codex_python-0.1.2/test/LocalShellExecAction.ts +0 -5
  84. codex_python-0.1.2/test/LocalShellStatus.ts +0 -5
  85. codex_python-0.1.2/test/LoginChatGptCompleteNotification.ts +0 -5
  86. codex_python-0.1.2/test/LoginChatGptResponse.ts +0 -9
  87. codex_python-0.1.2/test/LogoutChatGptResponse.ts +0 -5
  88. codex_python-0.1.2/test/McpInvocation.ts +0 -18
  89. codex_python-0.1.2/test/McpListToolsResponseEvent.ts +0 -13
  90. codex_python-0.1.2/test/McpToolCallBeginEvent.ts +0 -10
  91. codex_python-0.1.2/test/McpToolCallEndEvent.ts +0 -15
  92. codex_python-0.1.2/test/NewConversationParams.ts +0 -47
  93. codex_python-0.1.2/test/NewConversationResponse.ts +0 -6
  94. codex_python-0.1.2/test/ParsedCommand.ts +0 -5
  95. codex_python-0.1.2/test/PatchApplyBeginEvent.ts +0 -18
  96. codex_python-0.1.2/test/PatchApplyEndEvent.ts +0 -21
  97. codex_python-0.1.2/test/PlanItemArg.ts +0 -6
  98. codex_python-0.1.2/test/Profile.ts +0 -17
  99. codex_python-0.1.2/test/ReasoningEffort.ts +0 -8
  100. codex_python-0.1.2/test/ReasoningItemContent.ts +0 -5
  101. codex_python-0.1.2/test/ReasoningItemReasoningSummary.ts +0 -5
  102. codex_python-0.1.2/test/ReasoningSummary.ts +0 -10
  103. codex_python-0.1.2/test/RemoveConversationListenerParams.ts +0 -5
  104. codex_python-0.1.2/test/RemoveConversationSubscriptionResponse.ts +0 -5
  105. codex_python-0.1.2/test/RequestId.ts +0 -5
  106. codex_python-0.1.2/test/ResourceLink.ts +0 -11
  107. codex_python-0.1.2/test/ResponseItem.ts +0 -20
  108. codex_python-0.1.2/test/ResumeConversationParams.ts +0 -14
  109. codex_python-0.1.2/test/ResumeConversationResponse.ts +0 -7
  110. codex_python-0.1.2/test/ReviewDecision.ts +0 -8
  111. codex_python-0.1.2/test/Role.ts +0 -8
  112. codex_python-0.1.2/test/SandboxMode.ts +0 -5
  113. codex_python-0.1.2/test/SandboxPolicy.ts +0 -29
  114. codex_python-0.1.2/test/SandboxSettings.ts +0 -8
  115. codex_python-0.1.2/test/SendUserMessageParams.ts +0 -7
  116. codex_python-0.1.2/test/SendUserMessageResponse.ts +0 -5
  117. codex_python-0.1.2/test/SendUserTurnParams.ts +0 -11
  118. codex_python-0.1.2/test/SendUserTurnResponse.ts +0 -5
  119. codex_python-0.1.2/test/ServerNotification.ts +0 -7
  120. codex_python-0.1.2/test/ServerRequest.ts +0 -11
  121. codex_python-0.1.2/test/SessionConfiguredEvent.ts +0 -28
  122. codex_python-0.1.2/test/StepStatus.ts +0 -5
  123. codex_python-0.1.2/test/StreamErrorEvent.ts +0 -5
  124. codex_python-0.1.2/test/TaskCompleteEvent.ts +0 -5
  125. codex_python-0.1.2/test/TaskStartedEvent.ts +0 -5
  126. codex_python-0.1.2/test/TextContent.ts +0 -9
  127. codex_python-0.1.2/test/TextResourceContents.ts +0 -5
  128. codex_python-0.1.2/test/TokenCountEvent.ts +0 -6
  129. codex_python-0.1.2/test/TokenUsage.ts +0 -5
  130. codex_python-0.1.2/test/TokenUsageInfo.ts +0 -6
  131. codex_python-0.1.2/test/Tool.ts +0 -11
  132. codex_python-0.1.2/test/ToolAnnotations.ts +0 -15
  133. codex_python-0.1.2/test/ToolInputSchema.ts +0 -9
  134. codex_python-0.1.2/test/ToolOutputSchema.ts +0 -10
  135. codex_python-0.1.2/test/Tools.ts +0 -8
  136. codex_python-0.1.2/test/TurnAbortReason.ts +0 -5
  137. codex_python-0.1.2/test/TurnAbortedEvent.ts +0 -6
  138. codex_python-0.1.2/test/TurnDiffEvent.ts +0 -5
  139. codex_python-0.1.2/test/UpdatePlanArgs.ts +0 -6
  140. codex_python-0.1.2/test/UserMessageEvent.ts +0 -6
  141. codex_python-0.1.2/test/UserSavedConfig.ts +0 -34
  142. codex_python-0.1.2/test/Verbosity.ts +0 -9
  143. codex_python-0.1.2/test/WebSearchAction.ts +0 -5
  144. codex_python-0.1.2/test/WebSearchBeginEvent.ts +0 -5
  145. codex_python-0.1.2/test/WebSearchEndEvent.ts +0 -5
  146. codex_python-0.1.2/test/index.ts +0 -128
  147. codex_python-0.1.2/test/serde_json/JsonValue.ts +0 -3
  148. codex_python-0.1.2/tests/test_api.py +0 -27
  149. codex_python-0.1.2/tests/test_client.py +0 -37
  150. codex_python-0.1.2/tests/test_version.py +0 -7
  151. {codex_python-0.1.2 → codex_python-0.2.0}/.github/workflows/publish.yml +0 -0
  152. {codex_python-0.1.2 → codex_python-0.2.0}/.pre-commit-config.yaml +0 -0
  153. {codex_python-0.1.2 → codex_python-0.2.0}/LICENSE +0 -0
  154. {codex_python-0.1.2 → codex_python-0.2.0}/codex/py.typed +0 -0
@@ -26,9 +26,19 @@ jobs:
26
26
  with:
27
27
  version: latest
28
28
 
29
+ - name: Set up Rust (for native extension)
30
+ uses: dtolnay/rust-toolchain@stable
31
+
32
+ - name: Install maturin
33
+ run: |
34
+ python -m pip install --upgrade pip
35
+ pip install maturin
36
+
37
+ - name: Build native extension (editable)
38
+ run: make dev-native
39
+
29
40
  - name: Lint
30
41
  run: make lint
31
42
 
32
43
  - name: Test
33
44
  run: make test
34
-
@@ -0,0 +1,68 @@
1
+ name: Build Native Wheels (codex-native)
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch: {}
8
+
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+
13
+ jobs:
14
+ build:
15
+ continue-on-error: ${{ matrix.allow-failure == true }}
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ os: [ubuntu-latest, macos-14, windows-latest]
20
+ python-version: ['3.12', '3.13']
21
+ include:
22
+ - os: ubuntu-latest
23
+ python-version: '3.14'
24
+ allow-failure: true
25
+ runs-on: ${{ matrix.os }}
26
+ steps:
27
+ - name: Checkout repository
28
+ uses: actions/checkout@v4
29
+
30
+ - name: Set up Rust
31
+ uses: dtolnay/rust-toolchain@stable
32
+
33
+ - name: Set up Python
34
+ uses: actions/setup-python@v5
35
+ with:
36
+ python-version: ${{ matrix.python-version }}
37
+
38
+ - name: Install maturin
39
+ run: |
40
+ python -m pip install --upgrade pip
41
+ pip install maturin
42
+
43
+ - name: Build wheels with maturin
44
+ env:
45
+ PYO3_USE_ABI3_FORWARD_COMPATIBILITY: '1'
46
+ run: |
47
+ maturin build -m crates/codex_native/Cargo.toml --release --interpreter python
48
+
49
+ - name: Upload wheels
50
+ uses: actions/upload-artifact@v4
51
+ with:
52
+ name: wheels-${{ matrix.os }}-${{ matrix.python-version }}
53
+ path: crates/codex_native/target/wheels/*.whl
54
+
55
+ publish:
56
+ needs: build
57
+ runs-on: ubuntu-latest
58
+ steps:
59
+ - name: Download all wheels
60
+ uses: actions/download-artifact@v4
61
+ with:
62
+ path: dist
63
+
64
+ - name: Publish to PyPI (Trusted Publishing)
65
+ uses: pypa/gh-action-pypi-publish@release/v1
66
+ with:
67
+ packages-dir: dist
68
+ skip-existing: true
@@ -47,10 +47,27 @@ uv.lock
47
47
  # Generated artifacts
48
48
  # Generated artifacts
49
49
  .generated/
50
+ codex-proj/
51
+ .githooks/
52
+ test/
53
+ tests/
50
54
 
51
55
  # Ignore new markdown files by default
52
56
  *.md
53
57
 
58
+ # Rust build outputs
59
+ target/
60
+ crates/**/target/
61
+ crates/**/Cargo.lock
62
+
54
63
  # Local environment files
55
64
  .env
56
65
  .env.*
66
+ /.idea/inspectionProfiles/profiles_settings.xml
67
+ /.idea/inspectionProfiles/Project_Default.xml
68
+ /.idea/.gitignore
69
+ /.idea/codex-python.iml
70
+ /.idea/misc.xml
71
+ /.idea/modules.xml
72
+ /.idea/ruff.xml
73
+ /.idea/vcs.xml
@@ -22,7 +22,7 @@ lint:
22
22
  uv run --group dev mypy codex
23
23
 
24
24
  test:
25
- uv run --group dev pytest
25
+ @bash -lc 'uv run --group dev pytest -q; ec=$$?; if [ $$ec -eq 5 ]; then echo "No tests collected"; exit 0; else exit $$ec; fi'
26
26
 
27
27
  build:
28
28
  uv build
@@ -55,8 +55,13 @@ gen-protocol:
55
55
  @echo "Generating TypeScript protocol types via codex-proj/codex-rs ..."
56
56
  @mkdir -p .generated/ts
57
57
  @if command -v codex >/dev/null 2>&1; then \
58
- echo "Using 'codex generate-types'"; \
59
- codex generate-types --out .generated/ts; \
58
+ if codex --help | grep -q "generate-ts"; then \
59
+ echo "Using 'codex generate-ts'"; \
60
+ codex generate-ts --out .generated/ts; \
61
+ else \
62
+ echo "'codex' installed but no generate-ts; falling back"; \
63
+ cd codex-proj/codex-rs && cargo run -p codex-protocol-ts -- --out ../../.generated/ts; \
64
+ fi; \
60
65
  else \
61
66
  echo "Using cargo run -p codex-protocol-ts"; \
62
67
  cd codex-proj/codex-rs && cargo run -p codex-protocol-ts -- --out ../../.generated/ts; \
@@ -64,3 +69,13 @@ gen-protocol:
64
69
  @echo "Generating Python bindings ..."
65
70
  @python3 scripts/generate_protocol_py.py .generated/ts
66
71
  @$(MAKE) fmt
72
+
73
+ .PHONY: build-native dev-native
74
+
75
+ build-native:
76
+ @echo "Building native extension with maturin..."
77
+ @maturin build -m crates/codex_native/Cargo.toml --release
78
+
79
+ dev-native:
80
+ @echo "Installing native extension in dev mode..."
81
+ @maturin develop -m crates/codex_native/Cargo.toml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-python
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: A minimal Python library scaffold for codex-python
5
5
  Project-URL: Homepage, https://github.com/gersmann/codex-python
6
6
  Project-URL: Repository, https://github.com/gersmann/codex-python
@@ -41,11 +41,11 @@ Description-Content-Type: text/markdown
41
41
 
42
42
  # codex-python
43
43
 
44
- A minimal Python library scaffold using `uv` with Python 3.13+.
44
+ Python interface to Codex, packaged as a single distribution (`codex-python`). Platform wheels include a native extension for in‑process execution. The library consolidates on native bindings (no subprocess wrapper).
45
45
 
46
46
  ## Quickstart
47
47
 
48
- - Requires Python 3.13+.
48
+ - Requires Python 3.12+.
49
49
  - Package import name: `codex`.
50
50
  - Distribution name (PyPI): `codex-python`.
51
51
 
@@ -56,42 +56,39 @@ A minimal Python library scaffold using `uv` with Python 3.13+.
56
56
 
57
57
  ## Usage
58
58
 
59
- Basic non-interactive execution via Codex CLI:
59
+ Native, non-interactive execution with Pydantic config:
60
60
 
61
61
  ```
62
- from codex import run_exec
62
+ from codex.api import run_exec, CodexClient
63
+ from codex.config import CodexConfig, ApprovalPolicy, SandboxMode
63
64
 
64
- out = run_exec("explain this repo")
65
- print(out)
66
- ```
67
-
68
- Options:
69
-
70
- - Choose model: `run_exec("...", model="gpt-4.1")`
71
- - Full auto: `run_exec("scaffold a cli", full_auto=True)`
72
- - Run in another dir: `run_exec("...", cd="/path/to/project")`
65
+ cfg = CodexConfig(
66
+ model="gpt-5",
67
+ model_provider="openai",
68
+ approval_policy=ApprovalPolicy.ON_REQUEST,
69
+ sandbox_mode=SandboxMode.WORKSPACE_WRITE,
70
+ )
73
71
 
74
- Streaming JSON events (no PyO3 required):
72
+ events = run_exec("explain this repo", config=cfg)
75
73
 
74
+ client = CodexClient(config=cfg)
75
+ for ev in client.start_conversation("add a smoke test"):
76
+ print(ev)
76
77
  ```
77
- from codex.protocol.runtime import stream_exec_events
78
-
79
- for event in stream_exec_events("explain this repo", full_auto=True):
80
- # event is a dict with shape {"id": str, "msg": {...}}
81
- print(event)
82
- ```
83
-
84
- The event payload matches the Pydantic models in `codex.protocol.types` (e.g., `EventMsg`).
85
78
 
86
- Using a client with defaults:
79
+ To stream raw dict events directly from the native layer:
87
80
 
88
81
  ```
89
- from codex import CodexClient
82
+ from codex.native import start_exec_stream
90
83
 
91
- client = CodexClient(model="gpt-4.1", full_auto=True)
92
- print(client.run("explain this repo"))
84
+ for e in start_exec_stream("explain this repo", config_overrides=cfg.to_dict()):
85
+ # each `e` is a dict representing an event envelope
86
+ print(e)
93
87
  ```
94
88
 
89
+ The event payload is typed: `Event.msg` is a union `EventMsg` from `codex.protocol.types`,
90
+ and is also exported at `codex.EventMsg` for convenience.
91
+
95
92
  ### Install uv
96
93
 
97
94
  - macOS (Homebrew): `brew install uv`
@@ -148,8 +145,50 @@ make gen-protocol
148
145
  ```
149
146
 
150
147
  This will:
151
- - run `codex-proj/codex-rs/protocol-ts` to emit TypeScript types under `.generated/ts/`
152
- - convert them to Python `TypedDict`/`Literal` aliases at `codex/protocol/types.py`
148
+ - emit TypeScript types under `.generated/ts/`
149
+ - convert them to Python Pydantic models + Literal unions at `codex/protocol/types.py`
150
+
151
+ ### Native bindings (PyO3)
152
+
153
+ - Install path
154
+
155
+ `pip install codex-python` installs a platform wheel that includes the native extension on supported platforms (Python 3.12/3.13; CI also attempts 3.14).
156
+
157
+ - Build locally (requires Rust + maturin):
158
+
159
+ ```
160
+ make dev-native
161
+ ```
162
+
163
+ - Notes:
164
+ - The native path embeds Codex directly; no subprocess.
165
+ - A helper `codex.native.preview_config(...)` returns a compact snapshot of the effective configuration (selected fields) for tests and introspection.
166
+
167
+ ### Configuration (Pydantic)
168
+
169
+ Use the `CodexConfig` Pydantic model to pass overrides which mirror the Rust `ConfigOverrides`:
170
+
171
+ ```
172
+ from codex.config import CodexConfig, ApprovalPolicy, SandboxMode
173
+ from codex.api import run_exec, CodexClient
174
+
175
+ cfg = CodexConfig(
176
+ model="gpt-5",
177
+ model_provider="openai",
178
+ approval_policy=ApprovalPolicy.ON_REQUEST,
179
+ sandbox_mode=SandboxMode.WORKSPACE_WRITE,
180
+ cwd="/path/to/project",
181
+ include_apply_patch_tool=True,
182
+ )
183
+
184
+ events = run_exec("Explain this project", config=cfg)
185
+
186
+ client = CodexClient(config=cfg)
187
+ for ev in client.start_conversation("Add a test for feature X"):
188
+ print(ev)
189
+ ```
190
+
191
+ `CodexConfig.to_dict()` emits only set (non‑None) fields and serializes enums to their kebab‑case strings to match the native Rust types.
153
192
 
154
193
  ## Project Layout
155
194
 
@@ -168,4 +207,4 @@ Version is managed via `codex/__init__.py` and exposed as `__version__`. The bui
168
207
 
169
208
  ## Python Compatibility
170
209
 
171
- - Requires Python `>=3.13`.
210
+ - Supported: Python 3.12, 3.13. CI attempts 3.14 wheels when available.
@@ -0,0 +1,169 @@
1
+ # codex-python
2
+
3
+ Python interface to Codex, packaged as a single distribution (`codex-python`). Platform wheels include a native extension for in‑process execution. The library consolidates on native bindings (no subprocess wrapper).
4
+
5
+ ## Quickstart
6
+
7
+ - Requires Python 3.12+.
8
+ - Package import name: `codex`.
9
+ - Distribution name (PyPI): `codex-python`.
10
+
11
+ ### Repo
12
+
13
+ - Git: `git@github.com:gersmann/codex-python.git`
14
+ - URL: https://github.com/gersmann/codex-python
15
+
16
+ ## Usage
17
+
18
+ Native, non-interactive execution with Pydantic config:
19
+
20
+ ```
21
+ from codex.api import run_exec, CodexClient
22
+ from codex.config import CodexConfig, ApprovalPolicy, SandboxMode
23
+
24
+ cfg = CodexConfig(
25
+ model="gpt-5",
26
+ model_provider="openai",
27
+ approval_policy=ApprovalPolicy.ON_REQUEST,
28
+ sandbox_mode=SandboxMode.WORKSPACE_WRITE,
29
+ )
30
+
31
+ events = run_exec("explain this repo", config=cfg)
32
+
33
+ client = CodexClient(config=cfg)
34
+ for ev in client.start_conversation("add a smoke test"):
35
+ print(ev)
36
+ ```
37
+
38
+ To stream raw dict events directly from the native layer:
39
+
40
+ ```
41
+ from codex.native import start_exec_stream
42
+
43
+ for e in start_exec_stream("explain this repo", config_overrides=cfg.to_dict()):
44
+ # each `e` is a dict representing an event envelope
45
+ print(e)
46
+ ```
47
+
48
+ The event payload is typed: `Event.msg` is a union `EventMsg` from `codex.protocol.types`,
49
+ and is also exported at `codex.EventMsg` for convenience.
50
+
51
+ ### Install uv
52
+
53
+ - macOS (Homebrew): `brew install uv`
54
+ - Or via install script:
55
+ - Unix/macOS: `curl -LsSf https://astral.sh/uv/install.sh | sh`
56
+ - Windows (PowerShell): `iwr https://astral.sh/uv/install.ps1 -UseBasicParsing | iex`
57
+
58
+ See: https://docs.astral.sh/uv/
59
+
60
+ ### Create a virtual env (optional)
61
+
62
+ ```
63
+ uv python install 3.13
64
+ uv venv --python 3.13
65
+ . .venv/bin/activate # or .venv\Scripts\activate on Windows
66
+ ```
67
+
68
+ ### Build
69
+
70
+ ```
71
+ uv build
72
+ ```
73
+
74
+ Artifacts appear in `dist/` (`.whl` and `.tar.gz`).
75
+
76
+ ### Publish to PyPI
77
+
78
+ - Manual:
79
+
80
+ ```
81
+ export PYPI_API_TOKEN="pypi-XXXX" # create at https://pypi.org/manage/account/token/
82
+ uv publish --token "$PYPI_API_TOKEN"
83
+ ```
84
+
85
+ - GitHub Actions (Trusted Publishing): enable PyPI Trusted Publishing for
86
+ `gersmann/codex-python` and push a tag like `v0.1.0`. No token is needed.
87
+ The workflow at `.github/workflows/publish.yml` requests an OIDC token and
88
+ runs `uv publish --trusted-publishing=always` on `v*` tags.
89
+
90
+ ### Dev tooling
91
+
92
+ - Lint: `make lint` (ruff + mypy)
93
+ - Tests: `make test` (pytest)
94
+ - Format: `make fmt` (ruff formatter)
95
+ - Pre-commit: `uvx pre-commit install && uvx pre-commit run --all-files`
96
+
97
+ ### Protocol bindings (from codex-rs)
98
+
99
+ - Prereq: Rust toolchain (`cargo`) installed.
100
+ - Generate Python types from the upstream protocol with:
101
+
102
+ ```
103
+ make gen-protocol
104
+ ```
105
+
106
+ This will:
107
+ - emit TypeScript types under `.generated/ts/`
108
+ - convert them to Python Pydantic models + Literal unions at `codex/protocol/types.py`
109
+
110
+ ### Native bindings (PyO3)
111
+
112
+ - Install path
113
+
114
+ `pip install codex-python` installs a platform wheel that includes the native extension on supported platforms (Python 3.12/3.13; CI also attempts 3.14).
115
+
116
+ - Build locally (requires Rust + maturin):
117
+
118
+ ```
119
+ make dev-native
120
+ ```
121
+
122
+ - Notes:
123
+ - The native path embeds Codex directly; no subprocess.
124
+ - A helper `codex.native.preview_config(...)` returns a compact snapshot of the effective configuration (selected fields) for tests and introspection.
125
+
126
+ ### Configuration (Pydantic)
127
+
128
+ Use the `CodexConfig` Pydantic model to pass overrides which mirror the Rust `ConfigOverrides`:
129
+
130
+ ```
131
+ from codex.config import CodexConfig, ApprovalPolicy, SandboxMode
132
+ from codex.api import run_exec, CodexClient
133
+
134
+ cfg = CodexConfig(
135
+ model="gpt-5",
136
+ model_provider="openai",
137
+ approval_policy=ApprovalPolicy.ON_REQUEST,
138
+ sandbox_mode=SandboxMode.WORKSPACE_WRITE,
139
+ cwd="/path/to/project",
140
+ include_apply_patch_tool=True,
141
+ )
142
+
143
+ events = run_exec("Explain this project", config=cfg)
144
+
145
+ client = CodexClient(config=cfg)
146
+ for ev in client.start_conversation("Add a test for feature X"):
147
+ print(ev)
148
+ ```
149
+
150
+ `CodexConfig.to_dict()` emits only set (non‑None) fields and serializes enums to their kebab‑case strings to match the native Rust types.
151
+
152
+ ## Project Layout
153
+
154
+ ```
155
+ .
156
+ ├── codex/ # package root (import name: codex)
157
+ │ └── __init__.py # version lives here
158
+ ├── pyproject.toml # PEP 621 metadata, hatchling build backend
159
+ ├── README.md
160
+ └── .gitignore
161
+ ```
162
+
163
+ ## Versioning
164
+
165
+ Version is managed via `codex/__init__.py` and exposed as `__version__`. The build uses Hatch’s version source.
166
+
167
+ ## Python Compatibility
168
+
169
+ - Supported: Python 3.12, 3.13. CI attempts 3.14 wheels when available.
@@ -4,27 +4,31 @@ Python interface for the Codex CLI.
4
4
 
5
5
  Usage:
6
6
  from codex import run_exec
7
- output = run_exec("explain this codebase to me")
7
+ events = run_exec("explain this codebase to me")
8
8
  """
9
9
 
10
10
  from .api import (
11
11
  CodexClient,
12
12
  CodexError,
13
- CodexNotFoundError,
14
- CodexProcessError,
15
- find_binary,
13
+ CodexNativeError,
14
+ Conversation,
16
15
  run_exec,
17
16
  )
17
+ from .config import CodexConfig
18
+ from .event import Event
19
+ from .protocol.types import EventMsg
18
20
 
19
21
  __all__ = [
20
22
  "__version__",
21
23
  "CodexError",
22
- "CodexNotFoundError",
23
- "CodexProcessError",
24
+ "CodexNativeError",
24
25
  "CodexClient",
25
- "find_binary",
26
+ "Conversation",
26
27
  "run_exec",
28
+ "Event",
29
+ "EventMsg",
30
+ "CodexConfig",
27
31
  ]
28
32
 
29
33
  # Managed by Hatch via pyproject.toml [tool.hatch.version]
30
- __version__ = "0.1.2"
34
+ __version__ = "0.2.0"
@@ -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)