Add high-level Python SDK approval mode

Expose approval_mode with deny_all and auto_review options on the high-level Python SDK, and map those choices to generated app-server approval params internally.

Update examples, docs, notebooks, and public API tests to use the new mode instead of raw generated approval fields.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-05-10 11:44:34 +03:00
parent 7edbdc555c
commit ffe6e44a03
11 changed files with 314 additions and 159 deletions

View File

@@ -18,11 +18,11 @@ ensure_local_sdk_src()
import asyncio
from openai_codex import (
ApprovalMode,
AsyncCodex,
TextInput,
)
from openai_codex.types import (
AskForApproval,
Personality,
ReasoningSummary,
)
@@ -46,16 +46,18 @@ PROMPT = (
"Analyze a safe rollout plan for enabling a feature flag in production. "
"Return JSON matching the requested schema."
)
APPROVAL_POLICY = AskForApproval.never
APPROVAL_MODE = ApprovalMode.auto_review
async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = await codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)
turn = await thread.turn(
TextInput(PROMPT),
approval_policy=APPROVAL_POLICY,
approval_mode=APPROVAL_MODE,
output_schema=OUTPUT_SCHEMA,
personality=Personality.pragmatic,
summary=SUMMARY,
@@ -67,12 +69,16 @@ async def main() -> None:
try:
structured = json.loads(structured_text)
except json.JSONDecodeError as exc:
raise RuntimeError(f"Expected JSON matching OUTPUT_SCHEMA, got: {structured_text!r}") from exc
raise RuntimeError(
f"Expected JSON matching OUTPUT_SCHEMA, got: {structured_text!r}"
) from exc
summary = structured.get("summary")
actions = structured.get("actions")
if not isinstance(summary, str) or not isinstance(actions, list) or not all(
isinstance(action, str) for action in actions
if (
not isinstance(summary, str)
or not isinstance(actions, list)
or not all(isinstance(action, str) for action in actions)
):
raise RuntimeError(
f"Expected structured output with string summary/actions, got: {structured!r}"
@@ -83,7 +89,9 @@ async def main() -> None:
print("actions:")
for action in actions:
print("-", action)
print("Items:", 0 if persisted_turn is None else len(persisted_turn.items or []))
print(
"Items:", 0 if persisted_turn is None else len(persisted_turn.items or [])
)
if __name__ == "__main__":

View File

@@ -16,11 +16,11 @@ from _bootstrap import (
ensure_local_sdk_src()
from openai_codex import (
ApprovalMode,
Codex,
TextInput,
)
from openai_codex.types import (
AskForApproval,
Personality,
ReasoningSummary,
)
@@ -44,14 +44,16 @@ PROMPT = (
"Analyze a safe rollout plan for enabling a feature flag in production. "
"Return JSON matching the requested schema."
)
APPROVAL_POLICY = AskForApproval.never
APPROVAL_MODE = ApprovalMode.auto_review
with Codex(config=runtime_config()) as codex:
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
thread = codex.thread_start(
model="gpt-5.4", config={"model_reasoning_effort": "high"}
)
turn = thread.turn(
TextInput(PROMPT),
approval_policy=APPROVAL_POLICY,
approval_mode=APPROVAL_MODE,
output_schema=OUTPUT_SCHEMA,
personality=Personality.pragmatic,
summary=SUMMARY,
@@ -63,14 +65,20 @@ with Codex(config=runtime_config()) as codex:
try:
structured = json.loads(structured_text)
except json.JSONDecodeError as exc:
raise RuntimeError(f"Expected JSON matching OUTPUT_SCHEMA, got: {structured_text!r}") from exc
raise RuntimeError(
f"Expected JSON matching OUTPUT_SCHEMA, got: {structured_text!r}"
) from exc
summary = structured.get("summary")
actions = structured.get("actions")
if not isinstance(summary, str) or not isinstance(actions, list) or not all(
isinstance(action, str) for action in actions
if (
not isinstance(summary, str)
or not isinstance(actions, list)
or not all(isinstance(action, str) for action in actions)
):
raise RuntimeError(f"Expected structured output with string summary/actions, got: {structured!r}")
raise RuntimeError(
f"Expected structured output with string summary/actions, got: {structured!r}"
)
print("Status:", result.status)
print("summary:", summary)

View File

@@ -5,18 +5,23 @@ _EXAMPLES_ROOT = Path(__file__).resolve().parents[1]
if str(_EXAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_EXAMPLES_ROOT))
from _bootstrap import assistant_text_from_turn, ensure_local_sdk_src, find_turn_by_id, runtime_config
from _bootstrap import (
assistant_text_from_turn,
ensure_local_sdk_src,
find_turn_by_id,
runtime_config,
)
ensure_local_sdk_src()
import asyncio
from openai_codex import (
ApprovalMode,
AsyncCodex,
TextInput,
)
from openai_codex.types import (
AskForApproval,
Personality,
ReasoningEffort,
ReasoningSummary,
@@ -36,11 +41,16 @@ PREFERRED_MODEL = "gpt-5.4"
def _pick_highest_model(models):
visible = [m for m in models if not m.hidden] or models
preferred = next((m for m in visible if m.model == PREFERRED_MODEL or m.id == PREFERRED_MODEL), None)
preferred = next(
(m for m in visible if m.model == PREFERRED_MODEL or m.id == PREFERRED_MODEL),
None,
)
if preferred is not None:
return preferred
known_names = {m.id for m in visible} | {m.model for m in visible}
top_candidates = [m for m in visible if not (m.upgrade and m.upgrade in known_names)]
top_candidates = [
m for m in visible if not (m.upgrade and m.upgrade in known_names)
]
pool = top_candidates or visible
return max(pool, key=lambda m: (m.model, m.id))
@@ -75,7 +85,7 @@ SANDBOX_POLICY = SandboxPolicy.model_validate(
"access": {"type": "fullAccess"},
}
)
APPROVAL_POLICY = AskForApproval.never
APPROVAL_MODE = ApprovalMode.auto_review
async def main() -> None:
@@ -102,11 +112,16 @@ async def main() -> None:
first_persisted_turn = find_turn_by_id(persisted.thread.turns, first.id)
print("agent.message:", assistant_text_from_turn(first_persisted_turn))
print("items:", 0 if first_persisted_turn is None else len(first_persisted_turn.items or []))
print(
"items:",
0
if first_persisted_turn is None
else len(first_persisted_turn.items or []),
)
second_turn = await thread.turn(
TextInput("Return JSON for a safe feature-flag rollout plan."),
approval_policy=APPROVAL_POLICY,
approval_mode=APPROVAL_MODE,
cwd=str(Path.cwd()),
effort=selected_effort,
model=selected_model.model,
@@ -120,7 +135,12 @@ async def main() -> None:
second_persisted_turn = find_turn_by_id(persisted.thread.turns, second.id)
print("agent.message.params:", assistant_text_from_turn(second_persisted_turn))
print("items.params:", 0 if second_persisted_turn is None else len(second_persisted_turn.items or []))
print(
"items.params:",
0
if second_persisted_turn is None
else len(second_persisted_turn.items or []),
)
if __name__ == "__main__":

View File

@@ -5,16 +5,21 @@ _EXAMPLES_ROOT = Path(__file__).resolve().parents[1]
if str(_EXAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_EXAMPLES_ROOT))
from _bootstrap import assistant_text_from_turn, ensure_local_sdk_src, find_turn_by_id, runtime_config
from _bootstrap import (
assistant_text_from_turn,
ensure_local_sdk_src,
find_turn_by_id,
runtime_config,
)
ensure_local_sdk_src()
from openai_codex import (
ApprovalMode,
Codex,
TextInput,
)
from openai_codex.types import (
AskForApproval,
Personality,
ReasoningEffort,
ReasoningSummary,
@@ -34,11 +39,16 @@ PREFERRED_MODEL = "gpt-5.4"
def _pick_highest_model(models):
visible = [m for m in models if not m.hidden] or models
preferred = next((m for m in visible if m.model == PREFERRED_MODEL or m.id == PREFERRED_MODEL), None)
preferred = next(
(m for m in visible if m.model == PREFERRED_MODEL or m.id == PREFERRED_MODEL),
None,
)
if preferred is not None:
return preferred
known_names = {m.id for m in visible} | {m.model for m in visible}
top_candidates = [m for m in visible if not (m.upgrade and m.upgrade in known_names)]
top_candidates = [
m for m in visible if not (m.upgrade and m.upgrade in known_names)
]
pool = top_candidates or visible
return max(pool, key=lambda m: (m.model, m.id))
@@ -73,7 +83,7 @@ SANDBOX_POLICY = SandboxPolicy.model_validate(
"access": {"type": "fullAccess"},
}
)
APPROVAL_POLICY = AskForApproval.never
APPROVAL_MODE = ApprovalMode.auto_review
with Codex(config=runtime_config()) as codex:
@@ -102,7 +112,7 @@ with Codex(config=runtime_config()) as codex:
second = thread.turn(
TextInput("Return JSON for a safe feature-flag rollout plan."),
approval_policy=APPROVAL_POLICY,
approval_mode=APPROVAL_MODE,
cwd=str(Path.cwd()),
effort=selected_effort,
model=selected_model.model,