mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Prepare Python SDK beta documentation and package metadata (#24836)
## Why The initial public `openai-codex` beta should read and install like a normal published Python package before a release tag is created. This follows merged PR #24828, which establishes the independent SDK beta release plumbing and exact runtime dependency. ## What changed - Rewrote `sdk/python/README.md` as a compact PyPI-facing beta package page: published installation, one quickstart, short login examples, built-in help, and links to deeper guides. - Updated the getting-started guide, API reference, FAQ, and examples index to present the published beta consistently without repeating onboarding in the package landing page or reference page. - Made `pip install openai-codex` the primary install path while beta releases are the only published SDK releases, with `--pre` documented for opting into prereleases after a stable release exists. - Added curated `help()` / `pydoc` docstrings across the public API and generated public convenience methods through `scripts/update_sdk_artifacts.py`. - Declared the repository `Apache-2.0` license expression and Documentation URL in package metadata, without introducing a duplicated SDK-local license file. - Kept the source distribution focused on installable package material (`src/openai_codex`, `README.md`, and `pyproject.toml`); the repository docs and runnable examples remain linked from the PyPI README. - Built release artifacts in an Alpine container on the Ubuntu runner, matching Python SDK CI and allowing type generation to install the published `musllinux` runtime wheel. - Added `twine check --strict` to the release workflow so malformed PyPI metadata or rendered README content fails before publishing. - Added focused SDK assertions for beta metadata, the exact runtime pin, source distribution contents, and the built-in Python documentation surface. ## Validation - Ran `uv run --frozen --extra dev ruff check scripts/update_sdk_artifacts.py src/openai_codex tests/test_public_api_signatures.py tests/test_artifact_workflow_and_binaries.py` before the final README-only reductions and review-fix follow-ups. - Built `openai_codex-0.1.0b1-py3-none-any.whl` and `openai_codex-0.1.0b1.tar.gz` before the final README-only reductions and review-fix follow-ups. - Ran `python -m twine check --strict` on both built artifacts before the final README-only reductions and review-fix follow-ups. - Verified artifact metadata reports `Apache-2.0` without a duplicated SDK-local license file. - Verified `inspect.getdoc(...)` resolves documentation for the package, `Codex`, `CodexConfig`, and key generated thread methods. - Rebased the documentation/readiness change onto merged PR #24828 without changing the intended SDK or workflow file contents. - Final verification is delegated to online CI for this PR.
This commit is contained in:
committed by
GitHub
Unverified
parent
4d0c4cd058
commit
eb1cc3824c
@@ -1,3 +1,17 @@
|
||||
"""Python SDK for running Codex workflows.
|
||||
|
||||
Start with :class:`Codex` for synchronous applications or
|
||||
:class:`AsyncCodex` for async applications. Most programs create a thread and
|
||||
run a turn::
|
||||
|
||||
from openai_codex import Codex, Sandbox
|
||||
|
||||
with Codex() as codex:
|
||||
thread = codex.thread_start(sandbox=Sandbox.workspace_write)
|
||||
result = thread.run("Describe this project.")
|
||||
print(result.final_response)
|
||||
"""
|
||||
|
||||
from ._version import __version__
|
||||
from .api import (
|
||||
ApprovalMode,
|
||||
|
||||
@@ -7,27 +7,37 @@ from .models import JsonObject
|
||||
|
||||
@dataclass(slots=True)
|
||||
class TextInput:
|
||||
"""Text supplied to a turn or steering request."""
|
||||
|
||||
text: str
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ImageInput:
|
||||
"""Remote image URL supplied as turn input."""
|
||||
|
||||
url: str
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LocalImageInput:
|
||||
"""Local image path supplied as turn input."""
|
||||
|
||||
path: str
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class SkillInput:
|
||||
"""Named skill reference supplied as turn input."""
|
||||
|
||||
name: str
|
||||
path: str
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class MentionInput:
|
||||
"""Named resource mention supplied as turn input."""
|
||||
|
||||
name: str
|
||||
path: str
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ from .models import Notification
|
||||
|
||||
@dataclass(slots=True)
|
||||
class TurnResult:
|
||||
"""Collected result returned after a turn completes."""
|
||||
|
||||
id: str
|
||||
status: TurnStatus
|
||||
error: TurnError | None
|
||||
|
||||
@@ -73,7 +73,11 @@ from .models import InitializeResponse, JsonObject, Notification
|
||||
|
||||
|
||||
class Codex:
|
||||
"""Typed Python client for Codex workflows."""
|
||||
"""Synchronous client for creating threads and running Codex turns.
|
||||
|
||||
The client starts its runtime connection during construction. Use it as a
|
||||
context manager so resources are closed promptly.
|
||||
"""
|
||||
|
||||
def __init__(self, config: CodexConfig | None = None) -> None:
|
||||
self._client = CodexClient(config=config)
|
||||
@@ -143,6 +147,7 @@ class Codex:
|
||||
session_start_source: ThreadStartSource | None = None,
|
||||
thread_source: ThreadSource | None = None,
|
||||
) -> Thread:
|
||||
"""Create a new Codex conversation thread."""
|
||||
approval_policy, approvals_reviewer = _approval_mode_settings(approval_mode)
|
||||
params = ThreadStartParams(
|
||||
approval_policy=approval_policy,
|
||||
@@ -178,6 +183,7 @@ class Codex:
|
||||
source_kinds: list[ThreadSourceKind] | None = None,
|
||||
use_state_db_only: bool | None = None,
|
||||
) -> ThreadListResponse:
|
||||
"""List saved conversation threads."""
|
||||
params = ThreadListParams(
|
||||
archived=archived,
|
||||
cursor=cursor,
|
||||
@@ -207,6 +213,7 @@ class Codex:
|
||||
sandbox: Sandbox | None = None,
|
||||
service_tier: str | None = None,
|
||||
) -> Thread:
|
||||
"""Resume an existing conversation thread by ID."""
|
||||
approval_policy, approvals_reviewer = _approval_mode_override_settings(approval_mode)
|
||||
params = ThreadResumeParams(
|
||||
thread_id=thread_id,
|
||||
@@ -241,6 +248,7 @@ class Codex:
|
||||
service_tier: str | None = None,
|
||||
thread_source: ThreadSource | None = None,
|
||||
) -> Thread:
|
||||
"""Create a new thread from an existing thread."""
|
||||
approval_policy, approvals_reviewer = _approval_mode_override_settings(approval_mode)
|
||||
params = ThreadForkParams(
|
||||
thread_id=thread_id,
|
||||
@@ -261,15 +269,18 @@ class Codex:
|
||||
return Thread(self._client, forked.thread.id)
|
||||
|
||||
def thread_archive(self, thread_id: str) -> ThreadArchiveResponse:
|
||||
"""Archive a stored conversation thread."""
|
||||
return self._client.thread_archive(thread_id)
|
||||
|
||||
def thread_unarchive(self, thread_id: str) -> Thread:
|
||||
"""Restore an archived conversation thread."""
|
||||
unarchived = self._client.thread_unarchive(thread_id)
|
||||
return Thread(self._client, unarchived.thread.id)
|
||||
|
||||
# END GENERATED: Codex.flat_methods
|
||||
|
||||
def models(self, *, include_hidden: bool = False) -> ModelListResponse:
|
||||
"""List available models reported by Codex."""
|
||||
return self._client.model_list(include_hidden=include_hidden)
|
||||
|
||||
|
||||
@@ -376,6 +387,7 @@ class AsyncCodex:
|
||||
session_start_source: ThreadStartSource | None = None,
|
||||
thread_source: ThreadSource | None = None,
|
||||
) -> AsyncThread:
|
||||
"""Create a new Codex conversation thread."""
|
||||
await self._ensure_initialized()
|
||||
approval_policy, approvals_reviewer = _approval_mode_settings(approval_mode)
|
||||
params = ThreadStartParams(
|
||||
@@ -412,6 +424,7 @@ class AsyncCodex:
|
||||
source_kinds: list[ThreadSourceKind] | None = None,
|
||||
use_state_db_only: bool | None = None,
|
||||
) -> ThreadListResponse:
|
||||
"""List saved conversation threads."""
|
||||
await self._ensure_initialized()
|
||||
params = ThreadListParams(
|
||||
archived=archived,
|
||||
@@ -442,6 +455,7 @@ class AsyncCodex:
|
||||
sandbox: Sandbox | None = None,
|
||||
service_tier: str | None = None,
|
||||
) -> AsyncThread:
|
||||
"""Resume an existing conversation thread by ID."""
|
||||
await self._ensure_initialized()
|
||||
approval_policy, approvals_reviewer = _approval_mode_override_settings(approval_mode)
|
||||
params = ThreadResumeParams(
|
||||
@@ -477,6 +491,7 @@ class AsyncCodex:
|
||||
service_tier: str | None = None,
|
||||
thread_source: ThreadSource | None = None,
|
||||
) -> AsyncThread:
|
||||
"""Create a new thread from an existing thread."""
|
||||
await self._ensure_initialized()
|
||||
approval_policy, approvals_reviewer = _approval_mode_override_settings(approval_mode)
|
||||
params = ThreadForkParams(
|
||||
@@ -498,10 +513,12 @@ class AsyncCodex:
|
||||
return AsyncThread(self, forked.thread.id)
|
||||
|
||||
async def thread_archive(self, thread_id: str) -> ThreadArchiveResponse:
|
||||
"""Archive a stored conversation thread."""
|
||||
await self._ensure_initialized()
|
||||
return await self._client.thread_archive(thread_id)
|
||||
|
||||
async def thread_unarchive(self, thread_id: str) -> AsyncThread:
|
||||
"""Restore an archived conversation thread."""
|
||||
await self._ensure_initialized()
|
||||
unarchived = await self._client.thread_unarchive(thread_id)
|
||||
return AsyncThread(self, unarchived.thread.id)
|
||||
@@ -515,6 +532,8 @@ class AsyncCodex:
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Thread:
|
||||
"""Synchronous conversation thread used to run one or more turns."""
|
||||
|
||||
_client: CodexClient
|
||||
id: str
|
||||
|
||||
@@ -532,6 +551,7 @@ class Thread:
|
||||
service_tier: str | None = None,
|
||||
summary: ReasoningSummary | None = None,
|
||||
) -> TurnResult:
|
||||
"""Run a complete turn and collect its final result."""
|
||||
turn = self.turn(
|
||||
input,
|
||||
approval_mode=approval_mode,
|
||||
@@ -565,6 +585,7 @@ class Thread:
|
||||
service_tier: str | None = None,
|
||||
summary: ReasoningSummary | None = None,
|
||||
) -> TurnHandle:
|
||||
"""Start a turn and return a handle for streaming or control."""
|
||||
wire_input = _to_wire_input(_normalize_run_input(input))
|
||||
approval_policy, approvals_reviewer = _approval_mode_override_settings(approval_mode)
|
||||
params = TurnStartParams(
|
||||
@@ -587,6 +608,7 @@ class Thread:
|
||||
# END GENERATED: Thread.flat_methods
|
||||
|
||||
def read(self, *, include_turns: bool = False) -> ThreadReadResponse:
|
||||
"""Read this thread, optionally including its turn history."""
|
||||
return self._client.thread_read(self.id, include_turns=include_turns)
|
||||
|
||||
def set_name(self, name: str) -> ThreadSetNameResponse:
|
||||
@@ -598,6 +620,8 @@ class Thread:
|
||||
|
||||
@dataclass(slots=True)
|
||||
class AsyncThread:
|
||||
"""Asynchronous conversation thread used to run one or more turns."""
|
||||
|
||||
_codex: AsyncCodex
|
||||
id: str
|
||||
|
||||
@@ -615,6 +639,7 @@ class AsyncThread:
|
||||
service_tier: str | None = None,
|
||||
summary: ReasoningSummary | None = None,
|
||||
) -> TurnResult:
|
||||
"""Run a complete turn asynchronously and collect its final result."""
|
||||
turn = await self.turn(
|
||||
input,
|
||||
approval_mode=approval_mode,
|
||||
@@ -648,6 +673,7 @@ class AsyncThread:
|
||||
service_tier: str | None = None,
|
||||
summary: ReasoningSummary | None = None,
|
||||
) -> AsyncTurnHandle:
|
||||
"""Start a turn and return a handle for streaming or control."""
|
||||
await self._codex._ensure_initialized()
|
||||
wire_input = _to_wire_input(_normalize_run_input(input))
|
||||
approval_policy, approvals_reviewer = _approval_mode_override_settings(approval_mode)
|
||||
@@ -675,6 +701,7 @@ class AsyncThread:
|
||||
# END GENERATED: AsyncThread.flat_methods
|
||||
|
||||
async def read(self, *, include_turns: bool = False) -> ThreadReadResponse:
|
||||
"""Read this thread, optionally including its turn history."""
|
||||
await self._codex._ensure_initialized()
|
||||
return await self._codex._client.thread_read(self.id, include_turns=include_turns)
|
||||
|
||||
@@ -689,11 +716,14 @@ class AsyncThread:
|
||||
|
||||
@dataclass(slots=True)
|
||||
class TurnHandle:
|
||||
"""Control and consume a synchronous turn after it has started."""
|
||||
|
||||
_client: CodexClient
|
||||
thread_id: str
|
||||
id: str
|
||||
|
||||
def steer(self, input: RunInput) -> TurnSteerResponse:
|
||||
"""Send additional input to this active turn."""
|
||||
return self._client.turn_steer(
|
||||
self.thread_id,
|
||||
self.id,
|
||||
@@ -701,6 +731,7 @@ class TurnHandle:
|
||||
)
|
||||
|
||||
def interrupt(self) -> TurnInterruptResponse:
|
||||
"""Request interruption of this active turn."""
|
||||
return self._client.turn_interrupt(self.thread_id, self.id)
|
||||
|
||||
def stream(self) -> Iterator[Notification]:
|
||||
@@ -720,6 +751,7 @@ class TurnHandle:
|
||||
self._client.unregister_turn_notifications(self.id)
|
||||
|
||||
def run(self) -> TurnResult:
|
||||
"""Consume the turn stream and return its completed result."""
|
||||
stream = self.stream()
|
||||
try:
|
||||
return _collect_turn_result(stream, turn_id=self.id)
|
||||
@@ -729,11 +761,14 @@ class TurnHandle:
|
||||
|
||||
@dataclass(slots=True)
|
||||
class AsyncTurnHandle:
|
||||
"""Control and consume an asynchronous turn after it has started."""
|
||||
|
||||
_codex: AsyncCodex
|
||||
thread_id: str
|
||||
id: str
|
||||
|
||||
async def steer(self, input: RunInput) -> TurnSteerResponse:
|
||||
"""Send additional input to this active turn."""
|
||||
await self._codex._ensure_initialized()
|
||||
return await self._codex._client.turn_steer(
|
||||
self.thread_id,
|
||||
@@ -742,6 +777,7 @@ class AsyncTurnHandle:
|
||||
)
|
||||
|
||||
async def interrupt(self) -> TurnInterruptResponse:
|
||||
"""Request interruption of this active turn."""
|
||||
await self._codex._ensure_initialized()
|
||||
return await self._codex._client.turn_interrupt(self.thread_id, self.id)
|
||||
|
||||
@@ -763,6 +799,7 @@ class AsyncTurnHandle:
|
||||
self._codex._client.unregister_turn_notifications(self.id)
|
||||
|
||||
async def run(self) -> TurnResult:
|
||||
"""Consume the turn stream and return its completed result."""
|
||||
stream = self.stream()
|
||||
try:
|
||||
return await _collect_async_turn_result(stream, turn_id=self.id)
|
||||
|
||||
@@ -172,6 +172,12 @@ def _resolve_codex_bin(config: "CodexConfig") -> Path:
|
||||
|
||||
@dataclass(slots=True)
|
||||
class CodexConfig:
|
||||
"""Configuration for launching and identifying the local Codex runtime.
|
||||
|
||||
Most callers can use ``Codex()`` without configuration. Set ``codex_bin``
|
||||
only when intentionally using a specific local Codex executable.
|
||||
"""
|
||||
|
||||
codex_bin: str | None = None
|
||||
launch_args_override: tuple[str, ...] | None = None
|
||||
config_overrides: tuple[str, ...] = ()
|
||||
|
||||
@@ -26,23 +26,23 @@ class CodexRpcError(JsonRpcError):
|
||||
|
||||
|
||||
class ParseError(CodexRpcError):
|
||||
pass
|
||||
"""Raised when a request or response cannot be parsed."""
|
||||
|
||||
|
||||
class InvalidRequestError(CodexRpcError):
|
||||
pass
|
||||
"""Raised when the runtime rejects the request shape."""
|
||||
|
||||
|
||||
class MethodNotFoundError(CodexRpcError):
|
||||
pass
|
||||
"""Raised when the requested operation is unavailable."""
|
||||
|
||||
|
||||
class InvalidParamsError(CodexRpcError):
|
||||
pass
|
||||
"""Raised when an operation receives invalid parameters."""
|
||||
|
||||
|
||||
class InternalRpcError(CodexRpcError):
|
||||
pass
|
||||
"""Raised when the runtime reports an internal RPC failure."""
|
||||
|
||||
|
||||
class ServerBusyError(CodexRpcError):
|
||||
|
||||
Reference in New Issue
Block a user