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.
- {codex_python-0.1.2 → codex_python-0.2.0}/.github/workflows/ci.yml +11 -1
- codex_python-0.2.0/.github/workflows/native-wheels.yml +68 -0
- {codex_python-0.1.2 → codex_python-0.2.0}/.gitignore +17 -0
- {codex_python-0.1.2 → codex_python-0.2.0}/Makefile +18 -3
- {codex_python-0.1.2 → codex_python-0.2.0}/PKG-INFO +69 -30
- codex_python-0.2.0/README.md +169 -0
- {codex_python-0.1.2 → codex_python-0.2.0}/codex/__init__.py +12 -8
- codex_python-0.2.0/codex/api.py +93 -0
- codex_python-0.2.0/codex/config.py +82 -0
- codex_python-0.2.0/codex/event.py +16 -0
- codex_python-0.2.0/codex/native.py +56 -0
- {codex_python-0.1.2 → codex_python-0.2.0}/codex/protocol/types.py +413 -289
- codex_python-0.2.0/crates/codex_native/Cargo.toml +25 -0
- codex_python-0.2.0/crates/codex_native/src/lib.rs +332 -0
- {codex_python-0.1.2 → codex_python-0.2.0}/pyproject.toml +2 -0
- {codex_python-0.1.2 → codex_python-0.2.0}/scripts/generate_protocol_py.py +2 -2
- codex_python-0.1.2/README.md +0 -130
- codex_python-0.1.2/codex/api.py +0 -181
- codex_python-0.1.2/codex/protocol/runtime.py +0 -80
- codex_python-0.1.2/test/AddConversationListenerParams.ts +0 -6
- codex_python-0.1.2/test/AddConversationSubscriptionResponse.ts +0 -5
- codex_python-0.1.2/test/AgentMessageDeltaEvent.ts +0 -5
- codex_python-0.1.2/test/AgentMessageEvent.ts +0 -5
- codex_python-0.1.2/test/AgentReasoningDeltaEvent.ts +0 -5
- codex_python-0.1.2/test/AgentReasoningEvent.ts +0 -5
- codex_python-0.1.2/test/AgentReasoningRawContentDeltaEvent.ts +0 -5
- codex_python-0.1.2/test/AgentReasoningRawContentEvent.ts +0 -5
- codex_python-0.1.2/test/AgentReasoningSectionBreakEvent.ts +0 -5
- codex_python-0.1.2/test/Annotations.ts +0 -9
- codex_python-0.1.2/test/ApplyPatchApprovalParams.ts +0 -21
- codex_python-0.1.2/test/ApplyPatchApprovalRequestEvent.ts +0 -18
- codex_python-0.1.2/test/ApplyPatchApprovalResponse.ts +0 -6
- codex_python-0.1.2/test/ArchiveConversationParams.ts +0 -9
- codex_python-0.1.2/test/ArchiveConversationResponse.ts +0 -5
- codex_python-0.1.2/test/AskForApproval.ts +0 -9
- codex_python-0.1.2/test/AudioContent.ts +0 -9
- codex_python-0.1.2/test/AuthMode.ts +0 -5
- codex_python-0.1.2/test/AuthStatusChangeNotification.ts +0 -10
- codex_python-0.1.2/test/BackgroundEventEvent.ts +0 -5
- codex_python-0.1.2/test/BlobResourceContents.ts +0 -5
- codex_python-0.1.2/test/CallToolResult.ts +0 -10
- codex_python-0.1.2/test/CancelLoginChatGptParams.ts +0 -5
- codex_python-0.1.2/test/CancelLoginChatGptResponse.ts +0 -5
- codex_python-0.1.2/test/ClientRequest.ts +0 -22
- codex_python-0.1.2/test/ContentBlock.ts +0 -10
- codex_python-0.1.2/test/ContentItem.ts +0 -5
- codex_python-0.1.2/test/ConversationHistoryResponseEvent.ts +0 -11
- codex_python-0.1.2/test/ConversationId.ts +0 -5
- codex_python-0.1.2/test/ConversationSummary.ts +0 -10
- codex_python-0.1.2/test/CustomPrompt.ts +0 -5
- codex_python-0.1.2/test/EmbeddedResource.ts +0 -13
- codex_python-0.1.2/test/EmbeddedResourceResource.ts +0 -7
- codex_python-0.1.2/test/ErrorEvent.ts +0 -5
- codex_python-0.1.2/test/EventMsg.ts +0 -41
- codex_python-0.1.2/test/ExecApprovalRequestEvent.ts +0 -21
- codex_python-0.1.2/test/ExecCommandApprovalParams.ts +0 -11
- codex_python-0.1.2/test/ExecCommandApprovalResponse.ts +0 -6
- codex_python-0.1.2/test/ExecCommandBeginEvent.ts +0 -18
- codex_python-0.1.2/test/ExecCommandEndEvent.ts +0 -33
- codex_python-0.1.2/test/ExecCommandOutputDeltaEvent.ts +0 -18
- codex_python-0.1.2/test/ExecOneOffCommandParams.ts +0 -23
- codex_python-0.1.2/test/ExecOutputStream.ts +0 -5
- codex_python-0.1.2/test/FileChange.ts +0 -5
- codex_python-0.1.2/test/FunctionCallOutputPayload.ts +0 -5
- codex_python-0.1.2/test/GetAuthStatusParams.ts +0 -13
- codex_python-0.1.2/test/GetAuthStatusResponse.ts +0 -6
- codex_python-0.1.2/test/GetHistoryEntryResponseEvent.ts +0 -10
- codex_python-0.1.2/test/GetUserAgentResponse.ts +0 -5
- codex_python-0.1.2/test/GetUserSavedConfigResponse.ts +0 -6
- codex_python-0.1.2/test/GitDiffToRemoteParams.ts +0 -5
- codex_python-0.1.2/test/GitDiffToRemoteResponse.ts +0 -6
- codex_python-0.1.2/test/GitSha.ts +0 -5
- codex_python-0.1.2/test/HistoryEntry.ts +0 -5
- codex_python-0.1.2/test/ImageContent.ts +0 -9
- codex_python-0.1.2/test/InputItem.ts +0 -5
- codex_python-0.1.2/test/InputMessageKind.ts +0 -5
- codex_python-0.1.2/test/InterruptConversationParams.ts +0 -6
- codex_python-0.1.2/test/InterruptConversationResponse.ts +0 -6
- codex_python-0.1.2/test/ListConversationsParams.ts +0 -13
- codex_python-0.1.2/test/ListConversationsResponse.ts +0 -11
- codex_python-0.1.2/test/ListCustomPromptsResponseEvent.ts +0 -9
- codex_python-0.1.2/test/LocalShellAction.ts +0 -6
- codex_python-0.1.2/test/LocalShellExecAction.ts +0 -5
- codex_python-0.1.2/test/LocalShellStatus.ts +0 -5
- codex_python-0.1.2/test/LoginChatGptCompleteNotification.ts +0 -5
- codex_python-0.1.2/test/LoginChatGptResponse.ts +0 -9
- codex_python-0.1.2/test/LogoutChatGptResponse.ts +0 -5
- codex_python-0.1.2/test/McpInvocation.ts +0 -18
- codex_python-0.1.2/test/McpListToolsResponseEvent.ts +0 -13
- codex_python-0.1.2/test/McpToolCallBeginEvent.ts +0 -10
- codex_python-0.1.2/test/McpToolCallEndEvent.ts +0 -15
- codex_python-0.1.2/test/NewConversationParams.ts +0 -47
- codex_python-0.1.2/test/NewConversationResponse.ts +0 -6
- codex_python-0.1.2/test/ParsedCommand.ts +0 -5
- codex_python-0.1.2/test/PatchApplyBeginEvent.ts +0 -18
- codex_python-0.1.2/test/PatchApplyEndEvent.ts +0 -21
- codex_python-0.1.2/test/PlanItemArg.ts +0 -6
- codex_python-0.1.2/test/Profile.ts +0 -17
- codex_python-0.1.2/test/ReasoningEffort.ts +0 -8
- codex_python-0.1.2/test/ReasoningItemContent.ts +0 -5
- codex_python-0.1.2/test/ReasoningItemReasoningSummary.ts +0 -5
- codex_python-0.1.2/test/ReasoningSummary.ts +0 -10
- codex_python-0.1.2/test/RemoveConversationListenerParams.ts +0 -5
- codex_python-0.1.2/test/RemoveConversationSubscriptionResponse.ts +0 -5
- codex_python-0.1.2/test/RequestId.ts +0 -5
- codex_python-0.1.2/test/ResourceLink.ts +0 -11
- codex_python-0.1.2/test/ResponseItem.ts +0 -20
- codex_python-0.1.2/test/ResumeConversationParams.ts +0 -14
- codex_python-0.1.2/test/ResumeConversationResponse.ts +0 -7
- codex_python-0.1.2/test/ReviewDecision.ts +0 -8
- codex_python-0.1.2/test/Role.ts +0 -8
- codex_python-0.1.2/test/SandboxMode.ts +0 -5
- codex_python-0.1.2/test/SandboxPolicy.ts +0 -29
- codex_python-0.1.2/test/SandboxSettings.ts +0 -8
- codex_python-0.1.2/test/SendUserMessageParams.ts +0 -7
- codex_python-0.1.2/test/SendUserMessageResponse.ts +0 -5
- codex_python-0.1.2/test/SendUserTurnParams.ts +0 -11
- codex_python-0.1.2/test/SendUserTurnResponse.ts +0 -5
- codex_python-0.1.2/test/ServerNotification.ts +0 -7
- codex_python-0.1.2/test/ServerRequest.ts +0 -11
- codex_python-0.1.2/test/SessionConfiguredEvent.ts +0 -28
- codex_python-0.1.2/test/StepStatus.ts +0 -5
- codex_python-0.1.2/test/StreamErrorEvent.ts +0 -5
- codex_python-0.1.2/test/TaskCompleteEvent.ts +0 -5
- codex_python-0.1.2/test/TaskStartedEvent.ts +0 -5
- codex_python-0.1.2/test/TextContent.ts +0 -9
- codex_python-0.1.2/test/TextResourceContents.ts +0 -5
- codex_python-0.1.2/test/TokenCountEvent.ts +0 -6
- codex_python-0.1.2/test/TokenUsage.ts +0 -5
- codex_python-0.1.2/test/TokenUsageInfo.ts +0 -6
- codex_python-0.1.2/test/Tool.ts +0 -11
- codex_python-0.1.2/test/ToolAnnotations.ts +0 -15
- codex_python-0.1.2/test/ToolInputSchema.ts +0 -9
- codex_python-0.1.2/test/ToolOutputSchema.ts +0 -10
- codex_python-0.1.2/test/Tools.ts +0 -8
- codex_python-0.1.2/test/TurnAbortReason.ts +0 -5
- codex_python-0.1.2/test/TurnAbortedEvent.ts +0 -6
- codex_python-0.1.2/test/TurnDiffEvent.ts +0 -5
- codex_python-0.1.2/test/UpdatePlanArgs.ts +0 -6
- codex_python-0.1.2/test/UserMessageEvent.ts +0 -6
- codex_python-0.1.2/test/UserSavedConfig.ts +0 -34
- codex_python-0.1.2/test/Verbosity.ts +0 -9
- codex_python-0.1.2/test/WebSearchAction.ts +0 -5
- codex_python-0.1.2/test/WebSearchBeginEvent.ts +0 -5
- codex_python-0.1.2/test/WebSearchEndEvent.ts +0 -5
- codex_python-0.1.2/test/index.ts +0 -128
- codex_python-0.1.2/test/serde_json/JsonValue.ts +0 -3
- codex_python-0.1.2/tests/test_api.py +0 -27
- codex_python-0.1.2/tests/test_client.py +0 -37
- codex_python-0.1.2/tests/test_version.py +0 -7
- {codex_python-0.1.2 → codex_python-0.2.0}/.github/workflows/publish.yml +0 -0
- {codex_python-0.1.2 → codex_python-0.2.0}/.pre-commit-config.yaml +0 -0
- {codex_python-0.1.2 → codex_python-0.2.0}/LICENSE +0 -0
- {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
|
-
|
59
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
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
|
-
|
79
|
+
To stream raw dict events directly from the native layer:
|
87
80
|
|
88
81
|
```
|
89
|
-
from codex import
|
82
|
+
from codex.native import start_exec_stream
|
90
83
|
|
91
|
-
|
92
|
-
|
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
|
-
-
|
152
|
-
- convert them to Python
|
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
|
-
-
|
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
|
-
|
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
|
-
|
14
|
-
|
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
|
-
"
|
23
|
-
"CodexProcessError",
|
24
|
+
"CodexNativeError",
|
24
25
|
"CodexClient",
|
25
|
-
"
|
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.
|
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)
|