python-sdk: generated type foundation (all v2 schemas)#13953
python-sdk: generated type foundation (all v2 schemas)#13953shaqayeq-oai merged 10 commits intomainfrom
Conversation
Description: - pin datamodel-code-generator to an exact version and invoke it through the active interpreter - make generate_types own the maintained generated surfaces and regenerate committed artifacts - make sdk/python tests hermetic and regeneration checks idempotent
owenlin0
left a comment
There was a problem hiding this comment.
Can we run datamodel_code_generator against the json schema bundle at codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json instead of over the codex-rs/app-server-protocol/schema/json/v2/ directory? I think that should fix duplicate definitions (e.g. PlanType).
Also, I asked Codex to walk me through the app-server schema -> python pipeline, and it seems like we have these stages:
- Source To Raw Models: app-server json schema -> pydantic types
- Raw Models To Curated Python Shapes: a subset of manually curated types which outputs both TypedDict and Pydantic types
- Raw Models To Public API Signatures: generated Pydantic model -> ergonomic Python method signature
Can we simplify? Codex suggests this:
The simplest functional shape is:
- one canonical generated wire-model layer
- Pydantic only
- no handwritten “curated schema shapes”
- a thin handwritten client/public API on top
That is the direction I’d recommend.Use the bundled schema
codex_app_server_protocol.v2.schemas.jsonas the canonical input, and generate a single Pydantic model namespace from it.The Python-facing models should use snake_case field names, but serialize and validate against camelCase on the wire.
I think that should allow us to delete protocol_types.py and schema_types.py
| ("codex_app_server.generated.v2_all.TurnStartParams", "Personality"): "TurnPersonality", | ||
| ("codex_app_server.generated.v2_all.TurnStartParams", "ReasoningEffort"): "TurnReasoningEffort", | ||
| ("codex_app_server.generated.v2_all.TurnStartParams", "SandboxPolicy"): "TurnSandboxPolicy", | ||
| ("codex_app_server.generated.v2_all.TurnStartParams", "ReasoningSummary"): "TurnReasoningSummary", |
There was a problem hiding this comment.
curious why we need these type aliases, since things like ReasoningSummary is meant to be a shared type across a couple RPCs (same with the others here like Personality etc.)
|
|
||
| class ContextCompactedNotification(BaseModel): | ||
| threadId: str | ||
| turnId: str |
There was a problem hiding this comment.
to make things more pythonic, wonder if it's possible to produce these generated fields as snake_case, but send it over the wire to app-server as camelCase?
sdk/python/README.md
Outdated
| @@ -0,0 +1,77 @@ | |||
| # Codex App Server Python SDK | |||
There was a problem hiding this comment.
probably worth adding (experimental) for now
| source: dict[str, Any] | ||
| status: dict[str, Any] | ||
| turns: list[TurnObject] | ||
| updatedAt: int |
There was a problem hiding this comment.
snake_case would be so nice here
|
|
||
| # Generated by scripts/update_sdk_artifacts.py | ||
|
|
||
| class ThreadObject(TypedDict): |
There was a problem hiding this comment.
curious, why do we need a *Object suffix?
| updatedAt: int | ||
|
|
||
| @dataclass(slots=True, kw_only=True) | ||
| class Thread: |
There was a problem hiding this comment.
What's the difference between ThreadDict vs. Thread (defined here) vs. ThreadObject (defined in protocol_types.py)? Do we need all those types? Can we simplify?
|
|
||
| class AccountLoginCompletedNotification(BaseModel): | ||
| error: Optional[str] = None | ||
| loginId: Optional[str] = None |
There was a problem hiding this comment.
nit: I think str | None = None is more idiomatic these days for optional types
| chatgptAuthTokens = "chatgptAuthTokens" | ||
|
|
||
|
|
||
| class AuthMode(RootModel[Union[AuthMode1, AuthMode2, AuthMode3]]): |
There was a problem hiding this comment.
Curious why AuthMode is generated this way vs. PlanType below (which seems better and is less verbose)
| unlimited: bool | ||
|
|
||
|
|
||
| class PlanType(Enum): |
There was a problem hiding this comment.
Seems like shared enums are not generated once and imported (i.e. we define PlanType multiple times across various files) - possible to fix?
owenlin0
left a comment
There was a problem hiding this comment.
much cleaner, thanks! just some non-blocking comments
| } | ||
| return self.request("turn/start", payload, response_model=TurnStartResponse) | ||
|
|
||
| def turn_text( |
There was a problem hiding this comment.
nit: turn_text seems unnecessary?
| active_thread_id = started.thread.id | ||
| return self.run_text_turn(active_thread_id, text) | ||
|
|
||
| def ask( |
There was a problem hiding this comment.
do we need ask and ask_result? These seem a bit weird
| if notification.method in target_methods: | ||
| return out | ||
|
|
||
| def run_text_turn( |
There was a problem hiding this comment.
what's the use case of run_text_turn? do we need it?
| class TextTurnResult: | ||
| thread_id: str | ||
| turn_id: str | ||
| deltas: list[AgentMessageDeltaNotification] |
There was a problem hiding this comment.
similarly, do we need TextTurnResult? wondering if we can remove.
but if you really do want it, instead of storing deltas could we store a list of agent messages instead? it'd feel a little less weird. (but still unclear of the use case of this)
| def test_codex_event_notifications_are_typed() -> None: | ||
| client = AppServerClient() | ||
| event = client._coerce_notification( | ||
| "codex/event/turn_aborted", |
There was a problem hiding this comment.
FYI we are dropping codex/event/* from app-server API very soon. No need to invest in any support for these
Summary
Foundation PR only (base for PR #3).
This PR contains the SDK runtime foundation and generated artifacts:
sdk/python/bin/(codexorcodex.exeby platform)sdk/python/scripts/update_sdk_artifacts.pysdk/python/src/codex_app_server/generated/protocol_types.pysdk/python/src/codex_app_server/generated/schema_types.pysdk/python/src/codex_app_server/generated/v2_all/*tests/test_contract_generation.py)Release asset behavior
update_sdk_artifacts.pynow:--channel stable|alpha)codexon macOS/Linux,codex.exeon Windows)sdk/python/bin/Scope boundary
Validation
python scripts/update_sdk_artifacts.py --channel stable