codex-python-sdk 0.1.0__py3-none-any.whl

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,333 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import threading
5
+ from typing import Any, Iterator
6
+
7
+ from .async_client import AsyncCodexAgenticClient
8
+ from .errors import CodexAgenticError
9
+ from .types import AgentResponse, ResponseEvent
10
+
11
+
12
+ class CodexAgenticClient:
13
+ """Synchronous facade over :class:`AsyncCodexAgenticClient`.
14
+
15
+ This wrapper owns an internal event loop and exposes blocking APIs for
16
+ scripts and notebooks that prefer non-async usage.
17
+ """
18
+
19
+ def __init__(self, **kwargs: Any) -> None:
20
+ self._loop = asyncio.new_event_loop()
21
+ self._client = AsyncCodexAgenticClient(**kwargs)
22
+ self._loop_started = threading.Event()
23
+ self._loop_thread = threading.Thread(target=self._loop_thread_main, name="codex-agentic-client-loop", daemon=True)
24
+ self._loop_thread.start()
25
+ self._loop_started.wait()
26
+ self._closed = False
27
+
28
+ def _loop_thread_main(self) -> None:
29
+ asyncio.set_event_loop(self._loop)
30
+ self._loop_started.set()
31
+ self._loop.run_forever()
32
+
33
+ def close(self) -> None:
34
+ if self._closed:
35
+ return
36
+ try:
37
+ self._run(self._client.close())
38
+ finally:
39
+ try:
40
+ if not self._loop.is_closed():
41
+ self._loop.call_soon_threadsafe(self._loop.stop)
42
+ finally:
43
+ self._loop_thread.join(timeout=2.0)
44
+ # If the loop thread didn't exit, we cannot safely close the loop here.
45
+ if (not self._loop_thread.is_alive()) and (not self._loop.is_running()) and (not self._loop.is_closed()):
46
+ self._loop.close()
47
+ self._closed = True
48
+
49
+ def __enter__(self) -> "CodexAgenticClient":
50
+ try:
51
+ self._run(self._client.connect())
52
+ except Exception:
53
+ try:
54
+ self.close()
55
+ except Exception:
56
+ # Preserve the original connection failure.
57
+ pass
58
+ raise
59
+ return self
60
+
61
+ def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
62
+ self.close()
63
+
64
+ def _run(self, coro: Any) -> Any:
65
+ if self._closed:
66
+ raise CodexAgenticError("Client is closed.")
67
+ if threading.current_thread() is self._loop_thread:
68
+ raise CodexAgenticError("Cannot block on sync client from its own loop thread.")
69
+ fut = asyncio.run_coroutine_threadsafe(coro, self._loop)
70
+ return fut.result()
71
+
72
+ def thread_start(
73
+ self,
74
+ *,
75
+ params: dict[str, Any] | None = None,
76
+ ) -> dict[str, Any]:
77
+ """Create a new thread."""
78
+
79
+ return self._run(self._client.thread_start(params=params))
80
+
81
+ def thread_read(self, thread_id: str, *, include_turns: bool = False) -> dict[str, Any]:
82
+ """Read one thread by id."""
83
+
84
+ return self._run(self._client.thread_read(thread_id, include_turns=include_turns))
85
+
86
+ def thread_list(self, limit: int = 50, *, sort_key: str = "updated_at") -> list[dict[str, Any]]:
87
+ """List available threads."""
88
+
89
+ return self._run(self._client.thread_list(limit=limit, sort_key=sort_key))
90
+
91
+ def thread_archive(self, thread_id: str) -> dict[str, Any]:
92
+ """Archive one thread."""
93
+
94
+ return self._run(self._client.thread_archive(thread_id))
95
+
96
+ def thread_fork(
97
+ self,
98
+ thread_id: str,
99
+ *,
100
+ params: dict[str, Any] | None = None,
101
+ ) -> dict[str, Any]:
102
+ return self._run(self._client.thread_fork(thread_id, params=params))
103
+
104
+ def thread_name_set(self, thread_id: str, name: str) -> dict[str, Any]:
105
+ return self._run(self._client.thread_name_set(thread_id, name))
106
+
107
+ def thread_unarchive(self, thread_id: str) -> dict[str, Any]:
108
+ return self._run(self._client.thread_unarchive(thread_id))
109
+
110
+ def thread_compact_start(self, thread_id: str) -> dict[str, Any]:
111
+ return self._run(self._client.thread_compact_start(thread_id))
112
+
113
+ def thread_rollback(self, thread_id: str, num_turns: int) -> dict[str, Any]:
114
+ return self._run(self._client.thread_rollback(thread_id, num_turns))
115
+
116
+ def thread_loaded_list(self, *, limit: int | None = None, cursor: str | None = None) -> dict[str, Any]:
117
+ return self._run(self._client.thread_loaded_list(limit=limit, cursor=cursor))
118
+
119
+ def skills_list(
120
+ self,
121
+ *,
122
+ cwds: list[str] | None = None,
123
+ force_reload: bool | None = None,
124
+ ) -> dict[str, Any]:
125
+ return self._run(self._client.skills_list(cwds=cwds, force_reload=force_reload))
126
+
127
+ def skills_remote_read(self) -> dict[str, Any]:
128
+ return self._run(self._client.skills_remote_read())
129
+
130
+ def skills_remote_write(self, hazelnut_id: str, is_preload: bool) -> dict[str, Any]:
131
+ return self._run(self._client.skills_remote_write(hazelnut_id, is_preload))
132
+
133
+ def app_list(self, *, limit: int | None = None, cursor: str | None = None) -> dict[str, Any]:
134
+ return self._run(self._client.app_list(limit=limit, cursor=cursor))
135
+
136
+ def skills_config_write(self, path: str, enabled: bool) -> dict[str, Any]:
137
+ return self._run(self._client.skills_config_write(path, enabled))
138
+
139
+ def turn_interrupt(self, thread_id: str, turn_id: str) -> dict[str, Any]:
140
+ return self._run(self._client.turn_interrupt(thread_id, turn_id))
141
+
142
+ def turn_steer(self, thread_id: str, turn_id: str, prompt: str) -> dict[str, Any]:
143
+ return self._run(self._client.turn_steer(thread_id, turn_id, prompt))
144
+
145
+ def review_start(
146
+ self,
147
+ thread_id: str,
148
+ target: dict[str, Any],
149
+ *,
150
+ delivery: str | None = None,
151
+ ) -> dict[str, Any]:
152
+ return self._run(self._client.review_start(thread_id, target, delivery=delivery))
153
+
154
+ def model_list(self, *, limit: int | None = None, cursor: str | None = None) -> dict[str, Any]:
155
+ return self._run(self._client.model_list(limit=limit, cursor=cursor))
156
+
157
+ def account_rate_limits_read(self) -> dict[str, Any]:
158
+ return self._run(self._client.account_rate_limits_read())
159
+
160
+ def account_read(self, *, refresh_token: bool | None = None) -> dict[str, Any]:
161
+ return self._run(self._client.account_read(refresh_token=refresh_token))
162
+
163
+ def command_exec(
164
+ self,
165
+ command: list[str],
166
+ *,
167
+ cwd: str | None = None,
168
+ timeout_ms: int | None = None,
169
+ sandbox_policy: dict[str, Any] | None = None,
170
+ ) -> dict[str, Any]:
171
+ return self._run(
172
+ self._client.command_exec(
173
+ command,
174
+ cwd=cwd,
175
+ timeout_ms=timeout_ms,
176
+ sandbox_policy=sandbox_policy,
177
+ )
178
+ )
179
+
180
+ def config_read(self, *, cwd: str | None = None, include_layers: bool = False) -> dict[str, Any]:
181
+ return self._run(self._client.config_read(cwd=cwd, include_layers=include_layers))
182
+
183
+ def config_value_write(
184
+ self,
185
+ key_path: str,
186
+ value: Any,
187
+ *,
188
+ merge_strategy: str = "upsert",
189
+ file_path: str | None = None,
190
+ expected_version: str | None = None,
191
+ ) -> dict[str, Any]:
192
+ return self._run(
193
+ self._client.config_value_write(
194
+ key_path,
195
+ value,
196
+ merge_strategy=merge_strategy,
197
+ file_path=file_path,
198
+ expected_version=expected_version,
199
+ )
200
+ )
201
+
202
+ def config_batch_write(
203
+ self,
204
+ edits: list[dict[str, Any]],
205
+ *,
206
+ file_path: str | None = None,
207
+ expected_version: str | None = None,
208
+ ) -> dict[str, Any]:
209
+ return self._run(
210
+ self._client.config_batch_write(
211
+ edits,
212
+ file_path=file_path,
213
+ expected_version=expected_version,
214
+ )
215
+ )
216
+
217
+ def config_requirements_read(self) -> dict[str, Any]:
218
+ return self._run(self._client.config_requirements_read())
219
+
220
+ def config_mcp_server_reload(self) -> dict[str, Any]:
221
+ return self._run(self._client.config_mcp_server_reload())
222
+
223
+ def mcp_server_status_list(self, *, limit: int | None = None, cursor: str | None = None) -> dict[str, Any]:
224
+ return self._run(self._client.mcp_server_status_list(limit=limit, cursor=cursor))
225
+
226
+ def mcp_server_oauth_login(
227
+ self,
228
+ name: str,
229
+ *,
230
+ scopes: list[str] | None = None,
231
+ timeout_secs: int | None = None,
232
+ ) -> dict[str, Any]:
233
+ return self._run(self._client.mcp_server_oauth_login(name, scopes=scopes, timeout_secs=timeout_secs))
234
+
235
+ def fuzzy_file_search(
236
+ self,
237
+ query: str,
238
+ *,
239
+ roots: list[str] | None = None,
240
+ cancellation_token: str | None = None,
241
+ ) -> dict[str, Any]:
242
+ return self._run(
243
+ self._client.fuzzy_file_search(
244
+ query,
245
+ roots=roots,
246
+ cancellation_token=cancellation_token,
247
+ )
248
+ )
249
+
250
+ def responses_events(
251
+ self,
252
+ *,
253
+ prompt: str,
254
+ session_id: str | None = None,
255
+ thread_params: dict[str, Any] | None = None,
256
+ turn_params: dict[str, Any] | None = None,
257
+ ) -> Iterator[ResponseEvent]:
258
+ """Run one prompt and yield structured events."""
259
+
260
+ async_iter = self._client.responses_events(
261
+ prompt=prompt,
262
+ session_id=session_id,
263
+ thread_params=thread_params,
264
+ turn_params=turn_params,
265
+ )
266
+ try:
267
+ while True:
268
+ try:
269
+ event = self._run(async_iter.__anext__())
270
+ except StopAsyncIteration:
271
+ break
272
+ else:
273
+ yield event
274
+ finally:
275
+ aclose = getattr(async_iter, "aclose", None)
276
+ if callable(aclose):
277
+ try:
278
+ self._run(aclose())
279
+ except Exception:
280
+ pass
281
+
282
+ def responses_stream_text(
283
+ self,
284
+ *,
285
+ prompt: str,
286
+ session_id: str | None = None,
287
+ thread_params: dict[str, Any] | None = None,
288
+ turn_params: dict[str, Any] | None = None,
289
+ ) -> Iterator[str]:
290
+ """Run one prompt and yield text deltas only."""
291
+
292
+ async_iter = self._client.responses_stream_text(
293
+ prompt=prompt,
294
+ session_id=session_id,
295
+ thread_params=thread_params,
296
+ turn_params=turn_params,
297
+ )
298
+ try:
299
+ while True:
300
+ try:
301
+ chunk = self._run(async_iter.__anext__())
302
+ except StopAsyncIteration:
303
+ break
304
+ else:
305
+ yield chunk
306
+ finally:
307
+ aclose = getattr(async_iter, "aclose", None)
308
+ if callable(aclose):
309
+ try:
310
+ self._run(aclose())
311
+ except Exception:
312
+ pass
313
+
314
+ def responses_create(
315
+ self,
316
+ *,
317
+ prompt: str,
318
+ session_id: str | None = None,
319
+ thread_params: dict[str, Any] | None = None,
320
+ turn_params: dict[str, Any] | None = None,
321
+ include_events: bool = False,
322
+ ) -> AgentResponse:
323
+ """Run one prompt and return final aggregated response."""
324
+
325
+ return self._run(
326
+ self._client.responses_create(
327
+ prompt=prompt,
328
+ session_id=session_id,
329
+ thread_params=thread_params,
330
+ turn_params=turn_params,
331
+ include_events=include_events,
332
+ )
333
+ )
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+ from ._shared import utc_now
7
+
8
+
9
+ @dataclass
10
+ class AgentResponse:
11
+ """Aggregated final response for one prompt execution.
12
+
13
+ Attributes:
14
+ text: Final assistant text (message completion or merged deltas).
15
+ session_id: Thread id used by app-server.
16
+ request_id: Optional request id extracted from notifications.
17
+ tool_name: Backend label, currently always ``"app-server"``.
18
+ raw: Raw payload from the last observed event.
19
+ events: Optional full event list when ``include_events=True`` is used.
20
+ """
21
+
22
+ text: str
23
+ session_id: str
24
+ request_id: str | None
25
+ tool_name: str
26
+ raw: Any
27
+ events: list["ResponseEvent"] | None = None
28
+
29
+
30
+ @dataclass
31
+ class ResponseEvent:
32
+ """Normalized streaming event produced from app-server notifications."""
33
+
34
+ type: str
35
+ phase: str
36
+ text_delta: str | None = None
37
+ message_text: str | None = None
38
+ request_id: str | None = None
39
+ session_id: str | None = None
40
+ turn_id: str | None = None
41
+ item_id: str | None = None
42
+ summary_index: int | None = None
43
+ thread_name: str | None = None
44
+ token_usage: dict[str, Any] | None = None
45
+ plan: list[dict[str, Any]] | None = None
46
+ diff: str | None = None
47
+ raw: dict[str, Any] = field(default_factory=dict)
48
+ timestamp: str = field(default_factory=lambda: utc_now())
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.4
2
+ Name: codex-python-sdk
3
+ Version: 0.1.0
4
+ Summary: Python wrapper for Codex app-server JSON-RPC interface
5
+ Author: Henry_spdcoding
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/spdcoding/codex-python-sdk
8
+ Project-URL: Repository, https://github.com/spdcoding/codex-python-sdk
9
+ Project-URL: Issues, https://github.com/spdcoding/codex-python-sdk/issues
10
+ Project-URL: Documentation, https://github.com/spdcoding/codex-python-sdk/blob/HEAD/docs/tutorial.md
11
+ Project-URL: Codex-App-Server, https://developers.openai.com/codex/app-server/
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: <4.0,>=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest>=8.2; extra == "test"
23
+ Requires-Dist: pytest-timeout; extra == "test"
24
+ Requires-Dist: rich; extra == "test"
25
+ Provides-Extra: build
26
+ Requires-Dist: setuptools>=61; extra == "build"
27
+ Requires-Dist: wheel; extra == "build"
28
+ Requires-Dist: build; extra == "build"
29
+ Requires-Dist: twine; extra == "build"
30
+ Dynamic: license-file
31
+
32
+ # codex-python-sdk
33
+
34
+ [GitHub](https://github.com/spdcoding/codex-python-sdk) | [English](./README.md) | [简体中文](./README.zh-CN.md) | [Docs](./docs)
35
+
36
+ Production-focused Python SDK for running Codex agents through `codex app-server`.
37
+
38
+ `codex-python-sdk` gives you a stable Python interface over Codex JSON-RPC so you can automate agent workflows, stream structured runtime events, and enforce runtime policy from your own applications.
39
+
40
+ ## Why This SDK
41
+
42
+ - Script-first API: built for automation pipelines, not only interactive CLI sessions.
43
+ - Sync + async parity: same mental model and similar method names in both clients.
44
+ - Structured streaming: consume normalized `ResponseEvent` objects for observability and UI.
45
+ - Predictable failures: explicit error types such as `NotAuthenticatedError` and `SessionNotFoundError`.
46
+ - Policy control: approval/file-change/tool-input/tool-call hooks and policy engine integration.
47
+ - Thin protocol wrapper: close to `codex app-server` behavior, easier to reason about and debug.
48
+
49
+ ## 30-Second Quick Start
50
+
51
+ ```python
52
+ from codex_python_sdk import create_client
53
+
54
+ with create_client() as client:
55
+ result = client.responses_create(prompt="Reply with exactly: READY")
56
+ print(result.session_id)
57
+ print(result.text)
58
+ ```
59
+
60
+ ## Core Workflows
61
+
62
+ ### Stream events (for logs/UI)
63
+
64
+ ```python
65
+ from codex_python_sdk import create_client, render_exec_style_events
66
+
67
+ with create_client() as client:
68
+ events = client.responses_events(prompt="Summarize this repository")
69
+ render_exec_style_events(events)
70
+ ```
71
+
72
+ ### Async flow
73
+
74
+ ```python
75
+ import asyncio
76
+ from codex_python_sdk import create_async_client
77
+
78
+
79
+ async def main() -> None:
80
+ async with create_async_client() as client:
81
+ result = await client.responses_create(prompt="Reply with exactly: ASYNC_READY")
82
+ print(result.text)
83
+
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ ### Smoke and demo runner
89
+
90
+ ```bash
91
+ # Quick health check (default mode)
92
+ codex-python-sdk-demo --mode smoke
93
+
94
+ # Stable API showcase
95
+ codex-python-sdk-demo --mode demo
96
+
97
+ # Demo + unstable remote/interrupt/compact paths
98
+ codex-python-sdk-demo --mode full
99
+ ```
100
+
101
+ Note: the demo runner uses permissive hooks (`accept` for command/file approvals and empty tool-input answers) so it can run unattended.
102
+ Use stricter hooks or policy engines in production.
103
+
104
+ ## Mental Model: How It Works
105
+
106
+ `codex app-server` is Codex CLI's local JSON-RPC runtime over stdio.
107
+
108
+ One `responses_create(prompt=...)` call is essentially:
109
+
110
+ 1. `create_client()` creates a sync facade (`CodexAgenticClient`).
111
+ 2. Sync call forwards to `AsyncCodexAgenticClient` via a dedicated event-loop thread.
112
+ 3. `connect()` starts `codex app-server` and performs `initialize/initialized`.
113
+ 4. `_request(method, params)` handles all JSON-RPC request/response plumbing.
114
+ 5. `responses_events()` streams notifications; `responses_create()` aggregates them into final text.
115
+
116
+ For a deeper walkthrough, see `docs/core_mechanism.md`.
117
+
118
+ ## Safety Defaults (Important)
119
+
120
+ Default behavior without hooks/policy:
121
+ - Command approval: `accept`
122
+ - File change approval: `accept`
123
+ - Tool user input: empty answers
124
+ - Tool call: failure response with explanatory text
125
+
126
+ This is convenient for unattended demos, but not production-safe.
127
+
128
+ Recommended safer setup: enable LLM-judge policy with strict fallback decisions.
129
+
130
+ ```python
131
+ from codex_python_sdk import PolicyJudgeConfig, create_client
132
+
133
+ rubric = {
134
+ "system_rubric": "Allow read-only operations. Decline unknown write operations.",
135
+ "use_llm_judge": True,
136
+ }
137
+
138
+ judge_cfg = PolicyJudgeConfig(
139
+ timeout_seconds=8.0,
140
+ model="gpt-5",
141
+ effort="low",
142
+ fallback_command_decision="decline",
143
+ fallback_file_change_decision="decline",
144
+ )
145
+
146
+ with create_client(
147
+ policy_rubric=rubric,
148
+ policy_judge_config=judge_cfg,
149
+ ) as client:
150
+ result = client.responses_create(prompt="Show git status.")
151
+ print(result.text)
152
+ ```
153
+
154
+ Note: LLM-judge requires a real Codex runtime/account; for deterministic local tests, use `RuleBasedPolicyEngine`.
155
+
156
+ ## Install
157
+
158
+ ### Prerequisites
159
+
160
+ - Python `3.9+` (recommended: `3.12`)
161
+ - `uv` (recommended for development workflows)
162
+ - `codex` CLI installed and runnable
163
+ - Authentication completed via `codex login`
164
+
165
+ ### Install from PyPI
166
+
167
+ ```bash
168
+ uv add codex-python-sdk
169
+ uv run codex-python-sdk-demo --help
170
+ ```
171
+
172
+ or
173
+
174
+ ```bash
175
+ pip install codex-python-sdk
176
+ codex-python-sdk-demo --help
177
+ ```
178
+
179
+ ### Developer setup (for contributors)
180
+
181
+ ```bash
182
+ ./uv-sync.sh
183
+ ```
184
+
185
+ This bootstraps a local `.venv` and installs project/test/build dependencies.
186
+
187
+ ## API Snapshot
188
+
189
+ Factory:
190
+ - `create_client(**kwargs) -> CodexAgenticClient`
191
+ - `create_async_client(**kwargs) -> AsyncCodexAgenticClient`
192
+
193
+ High-frequency response APIs:
194
+ - `responses_create(...) -> AgentResponse`
195
+ - `responses_events(...) -> Iterator[ResponseEvent] / AsyncIterator[ResponseEvent]`
196
+ - `responses_stream_text(...) -> Iterator[str] / AsyncIterator[str]`
197
+
198
+ Thread basics:
199
+ - `thread_start`, `thread_read`, `thread_list`, `thread_archive`
200
+
201
+ Account basics:
202
+ - `account_read`, `account_rate_limits_read`
203
+
204
+ ## Documentation Map
205
+
206
+ English:
207
+ - `docs/tutorial.md`: practical workflows and end-to-end usage
208
+ - `docs/core_mechanism.md`: architecture-level core control flow
209
+ - `docs/config.md`: server/thread/turn configuration model
210
+ - `docs/api.md`: full API reference (sync + async)
211
+ - `docs/policy.md`: hooks and policy engine integration
212
+ - `docs/app_server.md`: app-server concepts and protocol mapping
213
+
214
+ 简体中文:
215
+ - `docs/zh/tutorial.md`
216
+ - `docs/zh/core_mechanism.md`
217
+ - `docs/zh/config.md`
218
+ - `docs/zh/api.md`
219
+ - `docs/zh/policy.md`
220
+ - `docs/zh/app_server.md`
221
+
222
+ ## Notes
223
+
224
+ - After `AppServerConnectionError`, recreate the client instead of relying on implicit reconnect behavior.
225
+ - Internal app-server `stderr` buffering keeps only the latest 500 lines in SDK-captured diagnostics.
226
+ - When using low-level server request handlers, method names must be exactly `item`, `tool`, or `requestUserInput`.
227
+ - Policy LLM-judge parsing is strict JSON-only: judge output must be a pure JSON object; embedded JSON snippets in free text are rejected.
228
+ - Invalid command/file policy decision values (allowed: `accept`, `acceptForSession`, `decline`, `cancel`) raise `CodexAgenticError`.
229
+
230
+ ## Development
231
+
232
+ ```bash
233
+ ./uv-sync.sh
234
+ uv run python3 -m pytest -q -m "not real"
235
+ ```
236
+
237
+ ## Release
238
+
239
+ ```bash
240
+ # Default: test + build + twine check (no upload)
241
+ ./build.sh
242
+
243
+ # Build only
244
+ ./build.sh build
245
+
246
+ # Release to pypi (upload enabled explicitly)
247
+ TWINE_UPLOAD=1 ./build.sh release --repo pypi
248
+
249
+ # Release to testpypi
250
+ TWINE_UPLOAD=1 ./build.sh release --repo testpypi
251
+
252
+ # Upload existing artifacts only
253
+ ./build.sh upload --repo pypi
254
+
255
+ # Help
256
+ ./build.sh help
257
+ ```
258
+
259
+ Recommended upload auth: `~/.pypirc` with API token.
260
+
261
+ ## Project Layout
262
+
263
+ - `codex_python_sdk/`: SDK source code
264
+ - `codex_python_sdk/examples/`: runnable demo code
265
+ - `tests/`: unit and real-runtime integration tests
266
+ - `uv-sync.sh`: dev environment bootstrap
267
+ - `build.sh`: build/release script
268
+
269
+ ## Error Types
270
+
271
+ - `CodexAgenticError`: base SDK error
272
+ - `AppServerConnectionError`: app-server transport/setup failure
273
+ - `SessionNotFoundError`: unknown thread/session id
274
+ - `NotAuthenticatedError`: auth unavailable or invalid
@@ -0,0 +1,17 @@
1
+ codex_python_sdk/__init__.py,sha256=aD7oFvTq-ZFk5RWlF_nd0ycXr0JXltz-LmeXReC7YmQ,1410
2
+ codex_python_sdk/_shared.py,sha256=0YwwZ1KeM9AvUJ8v_o3mjeFeUbtKk9j3WJZvUbGks_g,3028
3
+ codex_python_sdk/async_client.py,sha256=Wmc39b8jpHJi8mARVFRSAANriXaX6n0bjtFqj2hsFA0,54972
4
+ codex_python_sdk/errors.py,sha256=ndPfykdV4xFRZDppxyKlFCjHzXlP-SVifIlAExIEnJI,493
5
+ codex_python_sdk/factory.py,sha256=BenAH6fWAgI1HVvGpWDV_2dCYyz2K_XxLf9dLI2ipQo,597
6
+ codex_python_sdk/policy.py,sha256=63ssVhPQ-AGGbDY5-xMpBlRVFkPv7QlyDjpiktEzaVM,23115
7
+ codex_python_sdk/renderer.py,sha256=doE_adm-WpJ_C1ETbHoVhMfxIi19IYH_xuES7TMcY-s,23654
8
+ codex_python_sdk/sync_client.py,sha256=8kiym7uEveHBhl0savtDkAFo_oHARTTzJtKYcps8CKI,11508
9
+ codex_python_sdk/types.py,sha256=Kahi-p3WSOEeXG5ep0gCji8oG6__1GJ4wYcmE92Lmnc,1435
10
+ codex_python_sdk/examples/__init__.py,sha256=PHh4jqD7i0aL8W8hXThczKcT4xsNZiXanBxOH5IOW5I,36
11
+ codex_python_sdk/examples/demo_smoke.py,sha256=BHWVSbTSvbMKnVdnJNOPivzMS-SfHwf7SN2HkevEFoA,13569
12
+ codex_python_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=ww_lp8EcdsoJ9-K-8xXnJ_n8SX6FNiv7tKE8jk2Gzh0,1072
13
+ codex_python_sdk-0.1.0.dist-info/METADATA,sha256=dJRAGTgKZxaOgNoSZiskqMEbh2FzTVSZNXZk_zSD97E,8441
14
+ codex_python_sdk-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
15
+ codex_python_sdk-0.1.0.dist-info/entry_points.txt,sha256=YiQUxrLaU3Mwjp8X4kMNpCkhY1HXMN2TJIuYyQWt-l8,84
16
+ codex_python_sdk-0.1.0.dist-info/top_level.txt,sha256=Et4uySZ0xrY_Fog06H-5_jyj1ZOoTwKIfVvyYOeZQVI,17
17
+ codex_python_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codex-python-sdk-demo = codex_python_sdk.examples.demo_smoke:main