[6/8] Add high-level Python SDK approval mode (#21910)

## Why

The high-level SDK should expose the approval behavior it actually
supports instead of leaking generated app-server routing fields. New
work should have two clear choices: default auto review, or explicitly
deny escalated permission requests. Existing threads and subsequent
turns should preserve their current approval behavior unless the caller
passes an override.

## What

- Add the public `ApprovalMode` enum with `auto_review` and `deny_all`.
- Default new thread creation to `ApprovalMode.auto_review`.
- Preserve existing approval settings by default for resume, fork, run,
and turn helpers.
- Remove raw `approval_policy` / `approvals_reviewer` kwargs from
high-level SDK wrappers.
- Update generated wrapper output, docs, examples, notebooks, and tests
for the high-level approval mode API.

## Stack

1. #21891 `[1/8]` Pin Python SDK runtime dependency
2. #21893 `[2/8]` Generate Python SDK types from pinned runtime
3. #21895 `[3/8]` Run Python SDK tests in CI
4. #21896 `[4/8]` Define Python SDK public API surface
5. #21905 `[5/8]` Rename Python SDK package to `openai-codex`
6. This PR `[6/8]` Add high-level Python SDK approval mode
7. #22014 `[7/8]` Add Python SDK app-server integration harness
8. #22021 `[8/8]` Add Python SDK Ruff formatting

## Verification

- Added approval-mode mapping/default tests for new threads, existing
threads, forks, resumes, and subsequent turns.

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-05-12 01:02:43 +03:00
committed by GitHub
parent f1b84fac63
commit 2b90c37069
11 changed files with 403 additions and 94 deletions

View File

@@ -881,6 +881,34 @@ def _kw_signature_lines(fields: list[PublicFieldSpec]) -> list[str]:
return lines
def _approval_mode_start_signature_lines() -> list[str]:
"""Return the approval mode kwarg for new threads."""
return [" approval_mode: ApprovalMode = ApprovalMode.auto_review,"]
def _approval_mode_override_signature_lines() -> list[str]:
"""Return the optional approval mode kwarg for override-style helpers."""
return [" approval_mode: ApprovalMode | None = None,"]
def _approval_mode_assignment_line(
helper_name: str, *, indent: str = " "
) -> str:
"""Return the local mapping from public mode to app-server params."""
return (
f"{indent}approval_policy, approvals_reviewer = "
f"{helper_name}(approval_mode)"
)
def _approval_mode_model_arg_lines(*, indent: str = " ") -> list[str]:
"""Return app-server approval params derived from ApprovalMode."""
return [
f"{indent}approval_policy=approval_policy,",
f"{indent}approvals_reviewer=approvals_reviewer,",
]
def _model_arg_lines(
fields: list[PublicFieldSpec], *, indent: str = " "
) -> list[str]:
@@ -908,9 +936,12 @@ def _render_codex_block(
" def thread_start(",
" self,",
" *,",
*_approval_mode_start_signature_lines(),
*_kw_signature_lines(thread_start_fields),
" ) -> Thread:",
_approval_mode_assignment_line("_approval_mode_settings"),
" params = ThreadStartParams(",
*_approval_mode_model_arg_lines(),
*_model_arg_lines(thread_start_fields),
" )",
" started = self._client.thread_start(params)",
@@ -930,10 +961,13 @@ def _render_codex_block(
" self,",
" thread_id: str,",
" *,",
*_approval_mode_override_signature_lines(),
*_kw_signature_lines(resume_fields),
" ) -> Thread:",
_approval_mode_assignment_line("_approval_mode_override_settings"),
" params = ThreadResumeParams(",
" thread_id=thread_id,",
*_approval_mode_model_arg_lines(),
*_model_arg_lines(resume_fields),
" )",
" resumed = self._client.thread_resume(thread_id, params)",
@@ -943,10 +977,13 @@ def _render_codex_block(
" self,",
" thread_id: str,",
" *,",
*_approval_mode_override_signature_lines(),
*_kw_signature_lines(fork_fields),
" ) -> Thread:",
_approval_mode_assignment_line("_approval_mode_override_settings"),
" params = ThreadForkParams(",
" thread_id=thread_id,",
*_approval_mode_model_arg_lines(),
*_model_arg_lines(fork_fields),
" )",
" forked = self._client.thread_fork(thread_id, params)",
@@ -972,10 +1009,13 @@ def _render_async_codex_block(
" async def thread_start(",
" self,",
" *,",
*_approval_mode_start_signature_lines(),
*_kw_signature_lines(thread_start_fields),
" ) -> AsyncThread:",
" await self._ensure_initialized()",
_approval_mode_assignment_line("_approval_mode_settings"),
" params = ThreadStartParams(",
*_approval_mode_model_arg_lines(),
*_model_arg_lines(thread_start_fields),
" )",
" started = await self._client.thread_start(params)",
@@ -996,11 +1036,14 @@ def _render_async_codex_block(
" self,",
" thread_id: str,",
" *,",
*_approval_mode_override_signature_lines(),
*_kw_signature_lines(resume_fields),
" ) -> AsyncThread:",
" await self._ensure_initialized()",
_approval_mode_assignment_line("_approval_mode_override_settings"),
" params = ThreadResumeParams(",
" thread_id=thread_id,",
*_approval_mode_model_arg_lines(),
*_model_arg_lines(resume_fields),
" )",
" resumed = await self._client.thread_resume(thread_id, params)",
@@ -1010,11 +1053,14 @@ def _render_async_codex_block(
" self,",
" thread_id: str,",
" *,",
*_approval_mode_override_signature_lines(),
*_kw_signature_lines(fork_fields),
" ) -> AsyncThread:",
" await self._ensure_initialized()",
_approval_mode_assignment_line("_approval_mode_override_settings"),
" params = ThreadForkParams(",
" thread_id=thread_id,",
*_approval_mode_model_arg_lines(),
*_model_arg_lines(fork_fields),
" )",
" forked = await self._client.thread_fork(thread_id, params)",
@@ -1040,12 +1086,15 @@ def _render_thread_block(
" self,",
" input: Input,",
" *,",
*_approval_mode_override_signature_lines(),
*_kw_signature_lines(turn_fields),
" ) -> TurnHandle:",
" wire_input = _to_wire_input(input)",
_approval_mode_assignment_line("_approval_mode_override_settings"),
" params = TurnStartParams(",
" thread_id=self.id,",
" input=wire_input,",
*_approval_mode_model_arg_lines(),
*_model_arg_lines(turn_fields),
" )",
" turn = self._client.turn_start(self.id, wire_input, params=params)",
@@ -1062,13 +1111,16 @@ def _render_async_thread_block(
" self,",
" input: Input,",
" *,",
*_approval_mode_override_signature_lines(),
*_kw_signature_lines(turn_fields),
" ) -> AsyncTurnHandle:",
" await self._codex._ensure_initialized()",
" wire_input = _to_wire_input(input)",
_approval_mode_assignment_line("_approval_mode_override_settings"),
" params = TurnStartParams(",
" thread_id=self.id,",
" input=wire_input,",
*_approval_mode_model_arg_lines(),
*_model_arg_lines(turn_fields),
" )",
" turn = await self._codex._client.turn_start(",
@@ -1092,9 +1144,11 @@ def generate_public_api_flat_methods() -> None:
if src_dir_str not in sys.path:
sys.path.insert(0, src_dir_str)
approval_fields = {"approval_policy", "approvals_reviewer"}
thread_start_fields = _load_public_fields(
"openai_codex.generated.v2_all",
"ThreadStartParams",
exclude=approval_fields,
)
thread_list_fields = _load_public_fields(
"openai_codex.generated.v2_all",
@@ -1103,17 +1157,17 @@ def generate_public_api_flat_methods() -> None:
thread_resume_fields = _load_public_fields(
"openai_codex.generated.v2_all",
"ThreadResumeParams",
exclude={"thread_id"},
exclude={"thread_id", *approval_fields},
)
thread_fork_fields = _load_public_fields(
"openai_codex.generated.v2_all",
"ThreadForkParams",
exclude={"thread_id"},
exclude={"thread_id", *approval_fields},
)
turn_start_fields = _load_public_fields(
"openai_codex.generated.v2_all",
"TurnStartParams",
exclude={"thread_id", "input"},
exclude={"thread_id", "input", *approval_fields},
)
source = public_api_path.read_text()