mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Define Python SDK public type surface
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -7,6 +7,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import codex_app_server
|
||||
import codex_app_server.types as public_types
|
||||
from codex_app_server import (
|
||||
AppServerConfig,
|
||||
AsyncCodex,
|
||||
@@ -15,7 +16,7 @@ from codex_app_server import (
|
||||
RunResult,
|
||||
Thread,
|
||||
)
|
||||
from codex_app_server.models import InitializeResponse
|
||||
from codex_app_server.types import InitializeResponse
|
||||
|
||||
EXPECTED_ROOT_EXPORTS = [
|
||||
"__version__",
|
||||
@@ -26,7 +27,6 @@ EXPECTED_ROOT_EXPORTS = [
|
||||
"AsyncThread",
|
||||
"TurnHandle",
|
||||
"AsyncTurnHandle",
|
||||
"InitializeResponse",
|
||||
"RunResult",
|
||||
"Input",
|
||||
"InputItem",
|
||||
@@ -35,30 +35,6 @@ EXPECTED_ROOT_EXPORTS = [
|
||||
"LocalImageInput",
|
||||
"SkillInput",
|
||||
"MentionInput",
|
||||
"ThreadItem",
|
||||
"ThreadTokenUsageUpdatedNotification",
|
||||
"TurnCompletedNotification",
|
||||
"ApprovalsReviewer",
|
||||
"AskForApproval",
|
||||
"Personality",
|
||||
"PlanType",
|
||||
"ReasoningEffort",
|
||||
"ReasoningSummary",
|
||||
"SandboxMode",
|
||||
"SandboxPolicy",
|
||||
"SortDirection",
|
||||
"ThreadListCwdFilter",
|
||||
"ThreadStartParams",
|
||||
"ThreadResumeParams",
|
||||
"ThreadListParams",
|
||||
"ThreadSortKey",
|
||||
"ThreadSource",
|
||||
"ThreadSourceKind",
|
||||
"ThreadForkParams",
|
||||
"ThreadStartSource",
|
||||
"TurnStatus",
|
||||
"TurnStartParams",
|
||||
"TurnSteerParams",
|
||||
"retry_on_overload",
|
||||
"AppServerError",
|
||||
"TransportClosedError",
|
||||
@@ -74,8 +50,43 @@ EXPECTED_ROOT_EXPORTS = [
|
||||
"is_retryable_error",
|
||||
]
|
||||
|
||||
EXPECTED_TYPES_EXPORTS = [
|
||||
"ApprovalsReviewer",
|
||||
"AskForApproval",
|
||||
"InitializeResponse",
|
||||
"JsonObject",
|
||||
"ModelListResponse",
|
||||
"Notification",
|
||||
"Personality",
|
||||
"PlanType",
|
||||
"ReasoningEffort",
|
||||
"ReasoningSummary",
|
||||
"SandboxMode",
|
||||
"SandboxPolicy",
|
||||
"SortDirection",
|
||||
"ThreadArchiveResponse",
|
||||
"ThreadCompactStartResponse",
|
||||
"ThreadItem",
|
||||
"ThreadListCwdFilter",
|
||||
"ThreadListResponse",
|
||||
"ThreadReadResponse",
|
||||
"ThreadSetNameResponse",
|
||||
"ThreadSortKey",
|
||||
"ThreadSource",
|
||||
"ThreadSourceKind",
|
||||
"ThreadStartSource",
|
||||
"ThreadTokenUsage",
|
||||
"ThreadTokenUsageUpdatedNotification",
|
||||
"Turn",
|
||||
"TurnCompletedNotification",
|
||||
"TurnInterruptResponse",
|
||||
"TurnStatus",
|
||||
"TurnSteerResponse",
|
||||
]
|
||||
|
||||
|
||||
def _keyword_only_names(fn: object) -> list[str]:
|
||||
"""Return only user-facing keyword-only parameter names for a public method."""
|
||||
signature = inspect.signature(fn)
|
||||
return [
|
||||
param.name
|
||||
@@ -85,6 +96,7 @@ def _keyword_only_names(fn: object) -> list[str]:
|
||||
|
||||
|
||||
def _assert_no_any_annotations(fn: object) -> None:
|
||||
"""Reject loose annotations on public wrapper methods."""
|
||||
signature = inspect.signature(fn)
|
||||
for param in signature.parameters.values():
|
||||
if param.annotation is Any:
|
||||
@@ -96,14 +108,17 @@ def _assert_no_any_annotations(fn: object) -> None:
|
||||
|
||||
|
||||
def test_root_exports_app_server_config() -> None:
|
||||
"""The root package should expose the process configuration object."""
|
||||
assert AppServerConfig.__name__ == "AppServerConfig"
|
||||
|
||||
|
||||
def test_root_exports_run_result() -> None:
|
||||
"""The root package should expose the common-case run result wrapper."""
|
||||
assert RunResult.__name__ == "RunResult"
|
||||
|
||||
|
||||
def test_package_and_default_client_versions_follow_project_version() -> None:
|
||||
"""The importable package version should stay aligned with pyproject metadata."""
|
||||
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
||||
pyproject = tomllib.loads(pyproject_path.read_text())
|
||||
|
||||
@@ -112,6 +127,7 @@ def test_package_and_default_client_versions_follow_project_version() -> None:
|
||||
|
||||
|
||||
def test_package_includes_py_typed_marker() -> None:
|
||||
"""The wheel should advertise that inline type information is available."""
|
||||
marker = resources.files("codex_app_server").joinpath("py.typed")
|
||||
assert marker.is_file()
|
||||
|
||||
@@ -125,9 +141,21 @@ def test_package_root_exports_only_public_api() -> None:
|
||||
assert {
|
||||
"AppServerClient": hasattr(codex_app_server, "AppServerClient"),
|
||||
"AsyncAppServerClient": hasattr(codex_app_server, "AsyncAppServerClient"),
|
||||
"InitializeResponse": hasattr(codex_app_server, "InitializeResponse"),
|
||||
"ThreadStartParams": hasattr(codex_app_server, "ThreadStartParams"),
|
||||
"TurnStartParams": hasattr(codex_app_server, "TurnStartParams"),
|
||||
"TurnCompletedNotification": hasattr(
|
||||
codex_app_server, "TurnCompletedNotification"
|
||||
),
|
||||
"TurnStatus": hasattr(codex_app_server, "TurnStatus"),
|
||||
} == {
|
||||
"AppServerClient": False,
|
||||
"AsyncAppServerClient": False,
|
||||
"InitializeResponse": False,
|
||||
"ThreadStartParams": False,
|
||||
"TurnStartParams": False,
|
||||
"TurnCompletedNotification": False,
|
||||
"TurnStatus": False,
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +168,44 @@ def test_package_star_import_matches_public_api() -> None:
|
||||
assert exported == set(EXPECTED_ROOT_EXPORTS)
|
||||
|
||||
|
||||
def test_types_module_exports_curated_public_types() -> None:
|
||||
"""The public type module should be the supported place for app-server models."""
|
||||
assert public_types.__all__ == EXPECTED_TYPES_EXPORTS
|
||||
assert {name: hasattr(public_types, name) for name in EXPECTED_TYPES_EXPORTS} == {
|
||||
name: True for name in EXPECTED_TYPES_EXPORTS
|
||||
}
|
||||
|
||||
|
||||
def test_types_star_import_matches_public_types() -> None:
|
||||
"""Star imports from the type module should match its explicit export list."""
|
||||
namespace: dict[str, object] = {}
|
||||
exec("from codex_app_server.types import *", namespace)
|
||||
|
||||
exported = set(namespace) - {"__builtins__"}
|
||||
assert exported == set(EXPECTED_TYPES_EXPORTS)
|
||||
|
||||
|
||||
def test_examples_use_public_import_surfaces() -> None:
|
||||
"""Examples should teach users the public root and type-module imports only."""
|
||||
examples_root = Path(__file__).resolve().parents[1] / "examples"
|
||||
private_import_markers = [
|
||||
"codex_app_server.api",
|
||||
"codex_app_server.client",
|
||||
"codex_app_server.generated",
|
||||
"codex_app_server.models",
|
||||
"codex_app_server.retry",
|
||||
]
|
||||
|
||||
offenders = {
|
||||
str(path.relative_to(examples_root)): marker
|
||||
for path in examples_root.rglob("*.py")
|
||||
for marker in private_import_markers
|
||||
if marker in path.read_text()
|
||||
}
|
||||
|
||||
assert offenders == {}
|
||||
|
||||
|
||||
def test_generated_public_signatures_are_snake_case_and_typed() -> None:
|
||||
"""Generated convenience methods should expose typed Pythonic keyword names."""
|
||||
expected = {
|
||||
@@ -315,6 +381,7 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None:
|
||||
|
||||
|
||||
def test_lifecycle_methods_are_codex_scoped() -> None:
|
||||
"""Lifecycle operations should hang off the client rather than thread objects."""
|
||||
assert hasattr(Codex, "thread_resume")
|
||||
assert hasattr(Codex, "thread_fork")
|
||||
assert hasattr(Codex, "thread_archive")
|
||||
@@ -345,6 +412,7 @@ def test_lifecycle_methods_are_codex_scoped() -> None:
|
||||
|
||||
|
||||
def test_initialize_metadata_parses_user_agent_shape() -> None:
|
||||
"""Initialize metadata should accept the legacy user-agent-only payload shape."""
|
||||
payload = InitializeResponse.model_validate({"userAgent": "codex-cli/1.2.3"})
|
||||
parsed = Codex._validate_initialize(payload)
|
||||
assert parsed is payload
|
||||
@@ -355,6 +423,7 @@ def test_initialize_metadata_parses_user_agent_shape() -> None:
|
||||
|
||||
|
||||
def test_initialize_metadata_requires_non_empty_information() -> None:
|
||||
"""Initialize metadata should fail when the runtime gives no identity signal."""
|
||||
try:
|
||||
Codex._validate_initialize(InitializeResponse.model_validate({}))
|
||||
except RuntimeError as exc:
|
||||
|
||||
Reference in New Issue
Block a user