From 45134c04632b971776d88edfdea71c051ebe4161 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Wed, 9 Sep 2026 03:14:45 +0000 Subject: [PATCH] Generate Python SDK types from repository app-server schemas (#44032) ## Why Keep Python protocol models aligned with the checked-in app-server schemas and preserve reviewed generated artifacts when staging SDK releases. ## What changed - Generate SDK types from the schema directory configured in `pyproject.toml`, with a `--schema-dir` override, instead of invoking the pinned runtime binary. - Refresh Python artifacts through `just write-app-server-schema` for standard repository exports. Skip SDK updates for scratch and experimental exports. - Regenerate protocol models and notification dispatch, deriving the known payload union from the registry so `Notification.payload` covers every registered event. - Explicitly allowlist convenience API parameters so new protocol fields do not silently expand method signatures. Preserve existing approval path wrappers. - Stage SDK releases using checked-in generated files without regenerating them. ## Testing Add coverage for schema selection, refresh gating and failure handling, release artifact preservation, notification payload typing, and approval path compatibility. Update the generation drift test to use repository schemas. GitOrigin-RevId: fab350b07cf170258b91fbafa8da384aeb2e3bd7 --- .../scripts/write_schema_fixtures.py | 28 +- justfile | 4 +- sdk/python/docs/api-reference.md | 17 + sdk/python/pyproject.toml | 3 + sdk/python/scripts/update_sdk_artifacts.py | 308 +-- .../generated/notification_registry.py | 109 +- .../src/openai_codex/generated/v2_all.py | 2153 +++++++++++++++-- sdk/python/src/openai_codex/models.py | 99 +- .../test_artifact_workflow_and_binaries.py | 198 +- sdk/python/tests/test_client_rpc_methods.py | 69 + sdk/python/tests/test_contract_generation.py | 51 +- 11 files changed, 2437 insertions(+), 602 deletions(-) diff --git a/codex-rs/app-server-protocol/scripts/write_schema_fixtures.py b/codex-rs/app-server-protocol/scripts/write_schema_fixtures.py index f23da4fc86..65432a219e 100644 --- a/codex-rs/app-server-protocol/scripts/write_schema_fixtures.py +++ b/codex-rs/app-server-protocol/scripts/write_schema_fixtures.py @@ -29,7 +29,8 @@ def main() -> None: args = parser.parse_args() workspace_root = Path(__file__).resolve().parents[2] - schema_root = args.schema_root or workspace_root / "app-server-protocol" / "schema" + repository_schema_root = workspace_root / "app-server-protocol" / "schema" + schema_root = args.schema_root or repository_schema_root env = os.environ.copy() env["CODEX_APP_SERVER_SCHEMA_ROOT"] = str(schema_root) @@ -54,6 +55,31 @@ def main() -> None: check=True, ) + # Scratch exports and experimental-only bundles do not update checked-in SDK code. + if ( + not args.experimental + and schema_root.resolve() == repository_schema_root.resolve() + ): + sdk_root = workspace_root.parent / "sdk" / "python" + subprocess.run( + [ + "uv", + "run", + "--project", + str(sdk_root), + "--frozen", + "--only-group", + "test", + "python", + str(sdk_root / "scripts" / "update_sdk_artifacts.py"), + "generate-types", + "--schema-dir", + str(schema_root / "json"), + ], + cwd=workspace_root, + check=True, + ) + if __name__ == "__main__": main() diff --git a/justfile b/justfile index f85f1c0338..7a93f11957 100644 --- a/justfile +++ b/justfile @@ -173,9 +173,9 @@ build-for-release: write-config-schema: cargo run -p codex-config-schema --bin codex-write-config-schema -# Regenerate vendored app-server protocol schema artifacts. +# Regenerate app-server protocol schemas and the Python SDK derived from them. write-app-server-schema *args: - cargo run -p codex-app-server-protocol --bin write_schema_fixtures -- {args} + {{ python }} app-server-protocol/scripts/write_schema_fixtures.py {args} [no-cd] write-hooks-schema: diff --git a/sdk/python/docs/api-reference.md b/sdk/python/docs/api-reference.md index fbf1abb49c..46c76f92a8 100644 --- a/sdk/python/docs/api-reference.md +++ b/sdk/python/docs/api-reference.md @@ -268,6 +268,23 @@ from openai_codex.types import ( ) ``` +### Notifications and generated models + +Known notifications have typed `Notification.payload` values, including +authentication recovery, thread queue/project changes, thread reversion, and +realtime item updates. The `Notification.payload` type covers every registered +event. Unknown methods and payloads that fail validation still produce +`UnknownNotification`, with the raw data in +`.params`. When an event gains a typed payload, read its named fields instead +of `.params`. + +Returned models include the current CLI's thread metadata, richer turn errors, +and `functionCallOutput` history items. Code that imports generated +`HookMetadata` directly must access the handler through `.root`, inspect its +`handler_type`, and then read the fields for that handler. For example, only a +`"command"` handler has a `command` field. This reflects the app-server's +separate command, MCP tool, prompt, and agent hook variants. + ## Retry + errors ```python diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 88bf8bac48..e7f88396f0 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -36,6 +36,9 @@ dev = [ { include-group = "format" }, ] +[tool.codex.codegen] +schema-dir = "../../codex-rs/app-server-protocol/schema/json" + [tool.pytest.ini_options] addopts = "-q" testpaths = ["tests"] diff --git a/sdk/python/scripts/update_sdk_artifacts.py b/sdk/python/scripts/update_sdk_artifacts.py index 7dc4eddf9f..76537dc900 100755 --- a/sdk/python/scripts/update_sdk_artifacts.py +++ b/sdk/python/scripts/update_sdk_artifacts.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 import argparse -import importlib -import importlib.metadata +import importlib.util import json import platform import re @@ -41,13 +40,8 @@ def python_runtime_root() -> Path: return repo_root() / "sdk" / "python-runtime" -def sdk_pyproject_path() -> Path: - """Return the SDK pyproject file that owns package pins and versions.""" - return sdk_root() / "pyproject.toml" - - def schema_bundle_path(schema_dir: Path) -> Path: - """Return the aggregate v2 schema bundle emitted by the runtime binary.""" + """Return the aggregate v2 app-server schema bundle.""" return schema_dir / "codex_app_server_protocol.v2.schemas.json" @@ -75,67 +69,6 @@ def run_python_module(module: str, args: list[str], cwd: Path) -> None: run([sys.executable, "-m", module, *args], cwd) -def current_sdk_version() -> str: - match = re.search( - r'^version = "([^"]+)"$', - sdk_pyproject_path().read_text(), - flags=re.MULTILINE, - ) - if match is None: - raise RuntimeError("Could not determine Python SDK version from pyproject.toml") - return match.group(1) - - -def pinned_runtime_version() -> str: - """Read the exact runtime package pin used for schema generation.""" - pyproject_text = sdk_pyproject_path().read_text() - match = re.search(r"(?ms)^dependencies = \[(.*?)\]$", pyproject_text) - if match is None: - raise RuntimeError("Could not find dependencies array in sdk/python/pyproject.toml") - - pins = re.findall( - rf'"{re.escape(RUNTIME_DISTRIBUTION_NAME)}==([^"]+)"', - match.group(1), - ) - if len(pins) != 1: - raise RuntimeError( - f"Expected exactly one {RUNTIME_DISTRIBUTION_NAME} dependency pin " - "in sdk/python/pyproject.toml" - ) - return normalize_codex_version(pins[0]) - - -def pinned_runtime_codex_path() -> Path: - """Return the bundled Codex binary from the installed pinned runtime wheel.""" - expected_version = pinned_runtime_version() - try: - installed_version = importlib.metadata.version(RUNTIME_DISTRIBUTION_NAME) - except importlib.metadata.PackageNotFoundError as exc: - raise RuntimeError( - f"Install {RUNTIME_DISTRIBUTION_NAME}=={expected_version} before " - "generating Python SDK types." - ) from exc - - normalized_installed_version = normalize_codex_version(installed_version) - if normalized_installed_version != expected_version: - raise RuntimeError( - f"Expected {RUNTIME_DISTRIBUTION_NAME}=={expected_version}, " - f"but found {installed_version}." - ) - - try: - from codex_cli_bin import bundled_codex_path - except ImportError as exc: - raise RuntimeError( - f"Installed {RUNTIME_DISTRIBUTION_NAME} package does not expose bundled_codex_path." - ) from exc - - codex_path = bundled_codex_path() - if not codex_path.exists(): - raise RuntimeError(f"Pinned Codex runtime binary not found at {codex_path}.") - return codex_path - - def _copy_package_tree(src: Path, dst: Path) -> None: if dst.exists(): if dst.is_dir(): @@ -527,29 +460,25 @@ def _make_chatgpt_account_email_nullable(schema: dict[str, Any]) -> None: raise RuntimeError("Schema bundle is missing the ChatGPT account variant") -def generate_schema_from_pinned_runtime(schema_dir: Path) -> Path: - """Generate app-server schemas by invoking the installed pinned runtime binary.""" - codex_path = pinned_runtime_codex_path() - if schema_dir.exists(): - shutil.rmtree(schema_dir) - schema_dir.mkdir(parents=True) - run( - [ - str(codex_path), - "app-server", - "generate-json-schema", - "--out", - str(schema_dir), - ], - cwd=sdk_root(), - ) - return schema_dir +def _preserve_guardian_approval_path_wrappers(schema: dict[str, Any]) -> None: + """Preserve the path wrappers accepted by the existing Python API.""" + definitions = schema.get("definitions", {}) + if not isinstance(definitions, dict): + return + for variant in definitions.get("GuardianApprovalReviewAction", {}).get("oneOf", []): + properties = variant.get("properties", {}) + kind = properties.get("type", {}).get("enum") + if kind in (["command"], ["applyPatch"]): + properties["cwd"] = {"$ref": "#/definitions/AbsolutePathBuf"} + if kind == ["applyPatch"]: + properties["files"]["items"] = {"$ref": "#/definitions/AbsolutePathBuf"} def _normalized_schema_bundle_text(schema_dir: Path) -> str: """Normalize the schema bundle before feeding it to the Python type generator.""" schema = json.loads(schema_bundle_path(schema_dir).read_text()) _make_chatgpt_account_email_nullable(schema) + _preserve_guardian_approval_path_wrappers(schema) definitions = schema.get("definitions", {}) if isinstance(definitions, dict): for definition in definitions.values(): @@ -562,7 +491,7 @@ def _normalized_schema_bundle_text(schema_dir: Path) -> str: def generate_v2_all(schema_dir: Path) -> None: - """Regenerate the Pydantic v2 protocol model module from runtime schemas.""" + """Regenerate the Pydantic v2 protocol model module from app-server schemas.""" out_path = sdk_root() / "src" / "openai_codex" / "generated" / "v2_all.py" out_dir = out_path.parent old_package_dir = out_dir / "v2_all" @@ -803,10 +732,12 @@ def _type_tuple_source(class_names: list[str]) -> str: def generate_notification_registry(schema_dir: Path) -> None: - """Regenerate notification dispatch metadata from the runtime notification schema.""" + """Regenerate notification dispatch metadata from the app-server notification schema.""" out = sdk_root() / "src" / "openai_codex" / "generated" / "notification_registry.py" specs = _notification_specs(schema_dir) class_names = sorted({class_name for _, class_name in specs}) + if not class_names: + raise RuntimeError("Schema did not contain any supported notification payloads") direct_turn_id_types, nested_turn_types = _notification_turn_id_specs( schema_dir, specs, @@ -818,6 +749,8 @@ def generate_notification_registry(schema_dir: Path) -> None: "", "from __future__ import annotations", "", + "from typing import TypeAlias", + "", "from pydantic import BaseModel", "", ] @@ -827,7 +760,11 @@ def generate_notification_registry(schema_dir: Path) -> None: lines.extend( [ "", - "NOTIFICATION_MODELS: dict[str, type[BaseModel]] = {", + "KnownNotificationPayload: TypeAlias = (", + " " + "\n | ".join(class_names), + ")", + "", + "NOTIFICATION_MODELS: dict[str, type[KnownNotificationPayload]] = {", ] ) for method, class_name in specs: @@ -871,6 +808,78 @@ FIELD_ANNOTATION_OVERRIDES: dict[str, str] = { # Keep public API typed without falling back to `Any`. "config": "JsonObject", "output_schema": "JsonObject", + "sandbox": "Sandbox", + "sandbox_policy": "Sandbox", +} + +PUBLIC_FIELD_NAMES = { + "sandbox_policy": "sandbox", +} + +# Adding a protocol field must not silently add a public SDK parameter. These +# reviewed wire fields define the convenience API; protocol models stay complete. +PUBLIC_METHOD_FIELDS = { + "ThreadStartParams": ( + "base_instructions", + "config", + "cwd", + "developer_instructions", + "ephemeral", + "model", + "model_provider", + "personality", + "sandbox", + "service_name", + "service_tier", + "session_start_source", + "thread_source", + ), + "ThreadListParams": ( + "archived", + "cursor", + "cwd", + "limit", + "model_providers", + "search_term", + "section_id", + "sort_direction", + "sort_key", + "source_kinds", + "use_state_db_only", + ), + "ThreadResumeParams": ( + "base_instructions", + "config", + "cwd", + "developer_instructions", + "model", + "model_provider", + "personality", + "sandbox", + "service_tier", + ), + "ThreadForkParams": ( + "base_instructions", + "config", + "cwd", + "developer_instructions", + "ephemeral", + "model", + "model_provider", + "sandbox", + "service_tier", + "thread_source", + ), + "TurnStartParams": ( + "cwd", + "effort", + "model", + "output_schema", + "personality", + "sandbox_policy", + "service_tier", + "summary", + ), } @@ -884,10 +893,9 @@ class PublicFieldSpec: @dataclass(frozen=True) class CliOps: - generate_types: Callable[[], None] + generate_types: Callable[[Path], None] stage_python_sdk_package: Callable[[Path, str], Path] stage_python_runtime_package: Callable[[Path, str, Path, str | None], Path] - current_sdk_version: Callable[[], str] def _annotation_to_source(annotation: Any) -> str: @@ -926,20 +934,15 @@ def _camel_to_snake(name: str) -> str: return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", head).lower() -def _load_public_fields( - module_name: str, class_name: str, *, exclude: set[str] | None = None -) -> list[PublicFieldSpec]: - """Load generated model fields used to render the ergonomic public methods.""" - exclude = exclude or set() - if module_name == "openai_codex.generated.v2_all": - module = _load_generated_v2_all_module() - else: - module = importlib.import_module(module_name) +def _load_public_fields(class_name: str) -> list[PublicFieldSpec]: + """Load only the protocol fields deliberately exposed by the public SDK.""" + module = _load_generated_v2_all_module() model = getattr(module, class_name) fields: list[PublicFieldSpec] = [] - for name, field in model.model_fields.items(): - if name in exclude: - continue + for name in PUBLIC_METHOD_FIELDS[class_name]: + if name not in model.model_fields: + raise RuntimeError(f"Public SDK field {class_name}.{name} is missing from the schema") + field = model.model_fields[name] required = field.is_required() annotation = _annotation_to_source(field.annotation) override = FIELD_ANNOTATION_OVERRIDES.get(name) @@ -948,12 +951,12 @@ def _load_public_fields( fields.append( PublicFieldSpec( wire_name=name, - py_name=name, + py_name=PUBLIC_FIELD_NAMES.get(name, name), annotation=annotation, required=required, ) ) - return fields + return sorted(fields, key=lambda field: field.py_name) def _load_generated_v2_all_module() -> types.ModuleType: @@ -1013,32 +1016,6 @@ def _model_arg_lines(fields: list[PublicFieldSpec], *, indent: str = " return lines -def _replace_public_sandbox_field( - fields: list[PublicFieldSpec], *, wire_name: str -) -> list[PublicFieldSpec]: - """Expose stable wire sandbox settings through one public enum parameter.""" - public_fields: list[PublicFieldSpec] = [] - replaced = False - for field in fields: - if field.wire_name != wire_name: - public_fields.append(field) - continue - if replaced: - raise RuntimeError(f"Found more than one generated sandbox field named {wire_name}") - public_fields.append( - PublicFieldSpec( - wire_name=wire_name, - py_name="sandbox", - annotation="Sandbox | None", - required=False, - ) - ) - replaced = True - if not replaced: - raise RuntimeError(f"Could not find generated sandbox field named {wire_name}") - return public_fields - - def _replace_generated_block(source: str, block_name: str, body: str) -> str: start_tag = f" # BEGIN GENERATED: {block_name}" end_tag = f" # END GENERATED: {block_name}" @@ -1282,37 +1259,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_start_fields = _replace_public_sandbox_field(thread_start_fields, wire_name="sandbox") - thread_list_fields = _load_public_fields( - "openai_codex.generated.v2_all", - "ThreadListParams", - ) - thread_resume_fields = _load_public_fields( - "openai_codex.generated.v2_all", - "ThreadResumeParams", - exclude={"thread_id", *approval_fields}, - ) - thread_resume_fields = _replace_public_sandbox_field(thread_resume_fields, wire_name="sandbox") - thread_fork_fields = _load_public_fields( - "openai_codex.generated.v2_all", - "ThreadForkParams", - exclude={"thread_id", "last_turn_id", *approval_fields}, - ) - thread_fork_fields = _replace_public_sandbox_field(thread_fork_fields, wire_name="sandbox") - turn_start_fields = _load_public_fields( - "openai_codex.generated.v2_all", - "TurnStartParams", - # Keep the wire model current without exposing this app-server field - # through the ergonomic Python API yet. - exclude={"thread_id", "input", "client_user_message_id", *approval_fields}, - ) - turn_start_fields = _replace_public_sandbox_field(turn_start_fields, wire_name="sandbox_policy") + thread_start_fields = _load_public_fields("ThreadStartParams") + thread_list_fields = _load_public_fields("ThreadListParams") + thread_resume_fields = _load_public_fields("ThreadResumeParams") + thread_fork_fields = _load_public_fields("ThreadForkParams") + turn_start_fields = _load_public_fields("TurnStartParams") source = public_api_path.read_text() source = _replace_generated_block( @@ -1357,22 +1308,22 @@ def generate_types_from_schema_dir(schema_dir: Path) -> None: generate_public_api_flat_methods() -def generate_types() -> None: - """Generate schemas from the pinned runtime and then refresh SDK artifacts.""" - with tempfile.TemporaryDirectory(prefix="codex-python-schema-") as td: - schema_dir = generate_schema_from_pinned_runtime(Path(td) / "schema") - generate_types_from_schema_dir(schema_dir) - - def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Single SDK maintenance entrypoint") subparsers = parser.add_subparsers(dest="command", required=True) - subparsers.add_parser("generate-types", help="Regenerate Python protocol-derived types") + generate_types_parser = subparsers.add_parser( + "generate-types", help="Regenerate Python types from the repository's app-server schemas" + ) + generate_types_parser.add_argument( + "--schema-dir", + type=Path, + help="App-server JSON schema directory (defaults to tool.codex.codegen.schema-dir)", + ) stage_sdk_parser = subparsers.add_parser( "stage-sdk", - help="Stage a releasable SDK package while preserving its reviewed runtime pin", + help="Stage a releasable SDK package from its reviewed generated files and runtime pin", ) stage_sdk_parser.add_argument( "staging_dir", @@ -1427,18 +1378,25 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: def default_cli_ops() -> CliOps: return CliOps( - generate_types=generate_types, + generate_types=generate_types_from_schema_dir, stage_python_sdk_package=stage_python_sdk_package, stage_python_runtime_package=stage_python_runtime_package, - current_sdk_version=current_sdk_version, ) def run_command(args: argparse.Namespace, ops: CliOps) -> None: if args.command == "generate-types": - ops.generate_types() + schema_dir = args.schema_dir + if schema_dir is None: + try: + import tomllib + except ModuleNotFoundError: + import tomli as tomllib + + pyproject = tomllib.loads((sdk_root() / "pyproject.toml").read_text()) + schema_dir = sdk_root() / pyproject["tool"]["codex"]["codegen"]["schema-dir"] + ops.generate_types(schema_dir.resolve()) elif args.command == "stage-sdk": - ops.generate_types() ops.stage_python_sdk_package( args.staging_dir, normalize_codex_version(args.sdk_version), diff --git a/sdk/python/src/openai_codex/generated/notification_registry.py b/sdk/python/src/openai_codex/generated/notification_registry.py index 037be5d199..4b2af11c8a 100644 --- a/sdk/python/src/openai_codex/generated/notification_registry.py +++ b/sdk/python/src/openai_codex/generated/notification_registry.py @@ -3,6 +3,8 @@ from __future__ import annotations +from typing import TypeAlias + from pydantic import BaseModel from .v2_all import AccountLoginCompletedNotification @@ -10,6 +12,7 @@ from .v2_all import AccountRateLimitsUpdatedNotification from .v2_all import AccountUpdatedNotification from .v2_all import AgentMessageDeltaNotification from .v2_all import AppListUpdatedNotification +from .v2_all import AuthRecoveryNotification from .v2_all import CommandExecOutputDeltaNotification from .v2_all import CommandExecutionOutputDeltaNotification from .v2_all import ConfigWarningNotification @@ -31,6 +34,7 @@ from .v2_all import ItemCompletedNotification from .v2_all import ItemGuardianApprovalReviewCompletedNotification from .v2_all import ItemGuardianApprovalReviewStartedNotification from .v2_all import ItemStartedNotification +from .v2_all import McpServerEventStreamNotification from .v2_all import McpServerOauthLoginCompletedNotification from .v2_all import McpServerStatusUpdatedNotification from .v2_all import McpToolCallProgressNotification @@ -40,12 +44,14 @@ from .v2_all import ModelVerificationNotification from .v2_all import PlanDeltaNotification from .v2_all import ProcessExitedNotification from .v2_all import ProcessOutputDeltaNotification +from .v2_all import ProjectChangedNotification from .v2_all import ReasoningSummaryPartAddedNotification from .v2_all import ReasoningSummaryTextDeltaNotification from .v2_all import ReasoningTextDeltaNotification from .v2_all import RemoteControlStatusChangedNotification from .v2_all import ServerRequestResolvedNotification from .v2_all import SkillsChangedNotification +from .v2_all import StrictReviewRequiredNotification from .v2_all import TerminalInteractionNotification from .v2_all import ThreadArchivedNotification from .v2_all import ThreadClosedNotification @@ -53,14 +59,20 @@ from .v2_all import ThreadDeletedNotification from .v2_all import ThreadGoalClearedNotification from .v2_all import ThreadGoalUpdatedNotification from .v2_all import ThreadNameUpdatedNotification +from .v2_all import ThreadProjectUpdatedNotification +from .v2_all import ThreadQueueChangedNotification from .v2_all import ThreadRealtimeClosedNotification from .v2_all import ThreadRealtimeErrorNotification from .v2_all import ThreadRealtimeItemAddedNotification +from .v2_all import ThreadRealtimeItemCompletedNotification +from .v2_all import ThreadRealtimeItemStartedNotification +from .v2_all import ThreadRealtimeItemTranscriptDeltaNotification from .v2_all import ThreadRealtimeOutputAudioDeltaNotification from .v2_all import ThreadRealtimeSdpNotification from .v2_all import ThreadRealtimeStartedNotification from .v2_all import ThreadRealtimeTranscriptDeltaNotification from .v2_all import ThreadRealtimeTranscriptDoneNotification +from .v2_all import ThreadRevertedNotification from .v2_all import ThreadSettingsUpdatedNotification from .v2_all import ThreadStartedNotification from .v2_all import ThreadStatusChangedNotification @@ -75,11 +87,94 @@ from .v2_all import WarningNotification from .v2_all import WindowsSandboxSetupCompletedNotification from .v2_all import WindowsWorldWritableWarningNotification -NOTIFICATION_MODELS: dict[str, type[BaseModel]] = { +KnownNotificationPayload: TypeAlias = ( + AccountLoginCompletedNotification + | AccountRateLimitsUpdatedNotification + | AccountUpdatedNotification + | AgentMessageDeltaNotification + | AppListUpdatedNotification + | AuthRecoveryNotification + | CommandExecOutputDeltaNotification + | CommandExecutionOutputDeltaNotification + | ConfigWarningNotification + | ContextCompactedNotification + | DeprecationNoticeNotification + | EnvironmentConnectionNotification + | ErrorNotification + | ExternalAgentConfigImportCompletedNotification + | ExternalAgentConfigImportProgressNotification + | FileChangeOutputDeltaNotification + | FileChangePatchUpdatedNotification + | FsChangedNotification + | FuzzyFileSearchSessionCompletedNotification + | FuzzyFileSearchSessionUpdatedNotification + | GuardianWarningNotification + | HookCompletedNotification + | HookStartedNotification + | ItemCompletedNotification + | ItemGuardianApprovalReviewCompletedNotification + | ItemGuardianApprovalReviewStartedNotification + | ItemStartedNotification + | McpServerEventStreamNotification + | McpServerOauthLoginCompletedNotification + | McpServerStatusUpdatedNotification + | McpToolCallProgressNotification + | ModelReroutedNotification + | ModelSafetyBufferingUpdatedNotification + | ModelVerificationNotification + | PlanDeltaNotification + | ProcessExitedNotification + | ProcessOutputDeltaNotification + | ProjectChangedNotification + | ReasoningSummaryPartAddedNotification + | ReasoningSummaryTextDeltaNotification + | ReasoningTextDeltaNotification + | RemoteControlStatusChangedNotification + | ServerRequestResolvedNotification + | SkillsChangedNotification + | StrictReviewRequiredNotification + | TerminalInteractionNotification + | ThreadArchivedNotification + | ThreadClosedNotification + | ThreadDeletedNotification + | ThreadGoalClearedNotification + | ThreadGoalUpdatedNotification + | ThreadNameUpdatedNotification + | ThreadProjectUpdatedNotification + | ThreadQueueChangedNotification + | ThreadRealtimeClosedNotification + | ThreadRealtimeErrorNotification + | ThreadRealtimeItemAddedNotification + | ThreadRealtimeItemCompletedNotification + | ThreadRealtimeItemStartedNotification + | ThreadRealtimeItemTranscriptDeltaNotification + | ThreadRealtimeOutputAudioDeltaNotification + | ThreadRealtimeSdpNotification + | ThreadRealtimeStartedNotification + | ThreadRealtimeTranscriptDeltaNotification + | ThreadRealtimeTranscriptDoneNotification + | ThreadRevertedNotification + | ThreadSettingsUpdatedNotification + | ThreadStartedNotification + | ThreadStatusChangedNotification + | ThreadTokenUsageUpdatedNotification + | ThreadUnarchivedNotification + | TurnCompletedNotification + | TurnDiffUpdatedNotification + | TurnModerationMetadataNotification + | TurnPlanUpdatedNotification + | TurnStartedNotification + | WarningNotification + | WindowsSandboxSetupCompletedNotification + | WindowsWorldWritableWarningNotification +) + +NOTIFICATION_MODELS: dict[str, type[KnownNotificationPayload]] = { "account/login/completed": AccountLoginCompletedNotification, "account/rateLimits/updated": AccountRateLimitsUpdatedNotification, "account/updated": AccountUpdatedNotification, "app/list/updated": AppListUpdatedNotification, + "autoApprovalReview/strictReviewRequired": StrictReviewRequiredNotification, "command/exec/outputDelta": CommandExecOutputDeltaNotification, "configWarning": ConfigWarningNotification, "deprecationNotice": DeprecationNoticeNotification, @@ -106,13 +201,17 @@ NOTIFICATION_MODELS: dict[str, type[BaseModel]] = { "item/reasoning/summaryTextDelta": ReasoningSummaryTextDeltaNotification, "item/reasoning/textDelta": ReasoningTextDeltaNotification, "item/started": ItemStartedNotification, + "mcpServer/event/stream/notification": McpServerEventStreamNotification, "mcpServer/oauthLogin/completed": McpServerOauthLoginCompletedNotification, "mcpServer/startupStatus/updated": McpServerStatusUpdatedNotification, "model/rerouted": ModelReroutedNotification, "model/safetyBuffering/updated": ModelSafetyBufferingUpdatedNotification, "model/verification": ModelVerificationNotification, + "modelProvider/authRecoveryCompleted": AuthRecoveryNotification, + "modelProvider/authRecoveryStarted": AuthRecoveryNotification, "process/exited": ProcessExitedNotification, "process/outputDelta": ProcessOutputDeltaNotification, + "project/changed": ProjectChangedNotification, "remoteControl/status/changed": RemoteControlStatusChangedNotification, "serverRequest/resolved": ServerRequestResolvedNotification, "skills/changed": SkillsChangedNotification, @@ -125,14 +224,20 @@ NOTIFICATION_MODELS: dict[str, type[BaseModel]] = { "thread/goal/cleared": ThreadGoalClearedNotification, "thread/goal/updated": ThreadGoalUpdatedNotification, "thread/name/updated": ThreadNameUpdatedNotification, + "thread/project/updated": ThreadProjectUpdatedNotification, + "thread/queue/changed": ThreadQueueChangedNotification, "thread/realtime/closed": ThreadRealtimeClosedNotification, "thread/realtime/error": ThreadRealtimeErrorNotification, + "thread/realtime/item/completed": ThreadRealtimeItemCompletedNotification, + "thread/realtime/item/started": ThreadRealtimeItemStartedNotification, + "thread/realtime/item/transcript/delta": ThreadRealtimeItemTranscriptDeltaNotification, "thread/realtime/itemAdded": ThreadRealtimeItemAddedNotification, "thread/realtime/outputAudio/delta": ThreadRealtimeOutputAudioDeltaNotification, "thread/realtime/sdp": ThreadRealtimeSdpNotification, "thread/realtime/started": ThreadRealtimeStartedNotification, "thread/realtime/transcript/delta": ThreadRealtimeTranscriptDeltaNotification, "thread/realtime/transcript/done": ThreadRealtimeTranscriptDoneNotification, + "thread/reverted": ThreadRevertedNotification, "thread/settings/updated": ThreadSettingsUpdatedNotification, "thread/started": ThreadStartedNotification, "thread/status/changed": ThreadStatusChangedNotification, @@ -150,6 +255,7 @@ NOTIFICATION_MODELS: dict[str, type[BaseModel]] = { DIRECT_TURN_ID_NOTIFICATION_TYPES: tuple[type[BaseModel], ...] = ( AgentMessageDeltaNotification, + AuthRecoveryNotification, CommandExecutionOutputDeltaNotification, ContextCompactedNotification, ErrorNotification, @@ -169,6 +275,7 @@ DIRECT_TURN_ID_NOTIFICATION_TYPES: tuple[type[BaseModel], ...] = ( ReasoningSummaryPartAddedNotification, ReasoningSummaryTextDeltaNotification, ReasoningTextDeltaNotification, + StrictReviewRequiredNotification, TerminalInteractionNotification, ThreadGoalUpdatedNotification, ThreadTokenUsageUpdatedNotification, diff --git a/sdk/python/src/openai_codex/generated/v2_all.py b/sdk/python/src/openai_codex/generated/v2_all.py index 68215cfdf6..dc33688b25 100644 --- a/sdk/python/src/openai_codex/generated/v2_all.py +++ b/sdk/python/src/openai_codex/generated/v2_all.py @@ -102,6 +102,13 @@ class AdditionalNetworkPermissions(BaseModel): enabled: bool | None = None +class AgentMessageDelivery(RootModel[Literal["async"]]): + model_config = ConfigDict( + populate_by_name=True, + ) + root: Literal["async"] + + class AgentMessageDeltaNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -146,6 +153,11 @@ class AgentPath(RootModel[str]): root: str +class AllowDenyRequirement(Enum): + allow = "allow" + deny = "deny" + + class AnalyticsConfig(BaseModel): model_config = ConfigDict( extra="allow", @@ -166,6 +178,13 @@ class AppBranding(BaseModel): website: str | None = None +class AppLinksConfig(BaseModel): + pass + model_config = ConfigDict( + populate_by_name=True, + ) + + class AppReview(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -314,6 +333,13 @@ class AppsReadParams(BaseModel): description="When true, include display-only public tool summaries in the returned metadata.", ), ] = None + thread_id: Annotated[ + str | None, + Field( + alias="threadId", + description="Optional loaded thread id used to evaluate effective app configuration.", + ), + ] = None class AskForApprovalValue(Enum): @@ -348,6 +374,15 @@ class AskForApproval(RootModel[AskForApprovalValue | GranularAskForApproval]): root: AskForApprovalValue | GranularAskForApproval +class AsyncUserInputQuestion(BaseModel): + model_config = ConfigDict( + extra="forbid", + populate_by_name=True, + ) + options: list[str] | None = None + title: str + + class AuthMode(Enum): apikey = "apikey" chatgpt = "chatgpt" @@ -356,6 +391,17 @@ class AuthMode(Enum): agent_identity = "agentIdentity" personal_access_token = "personalAccessToken" bedrock_api_key = "bedrockApiKey" + bedrock_access_keys = "bedrockAccessKeys" + + +class AuthRecoveryNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + message: str + provider: str + thread_id: Annotated[str, Field(alias="threadId")] + turn_id: Annotated[str, Field(alias="turnId")] class AutoCompactTokenLimitScope(Enum): @@ -375,11 +421,58 @@ class AutoReviewDecisionSource(RootModel[Literal["agent"]]): ] +class AutoReviewRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + ignore_rules: Annotated[list[str] | None, Field(alias="ignoreRules")] = None + required_on_models: Annotated[list[str] | None, Field(alias="requiredOnModels")] = None + + +class BrowserUseAccessApprovalLifetime(Enum): + turn = "turn" + thread = "thread" + + +class BrowserUseOriginPolicy(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + access: AllowDenyRequirement | None = None + access_approval_lifetime: Annotated[ + BrowserUseAccessApprovalLifetime | None, Field(alias="accessApprovalLifetime") + ] = None + auto_review: Annotated[AllowDenyRequirement | None, Field(alias="autoReview")] = None + downloads: AllowDenyRequirement | None = None + full_cdp_access: Annotated[AllowDenyRequirement | None, Field(alias="fullCdpAccess")] = None + persistent_approval: Annotated[bool | None, Field(alias="persistentApproval")] = None + uploads: AllowDenyRequirement | None = None + + +class BrowserUseOriginPolicyConfig(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + access: AllowDenyRequirement | None = None + downloads: AllowDenyRequirement | None = None + full_cdp_access: AllowDenyRequirement | None = None + uploads: AllowDenyRequirement | None = None + + class BrowserUseRequirements(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + allow_global_persistent_approval: Annotated[ + bool | None, Field(alias="allowGlobalPersistentApproval") + ] = None + allow_history_access: Annotated[bool | None, Field(alias="allowHistoryAccess")] = None + allow_webmcp: Annotated[bool | None, Field(alias="allowWebmcp")] = None + default_origin_policy: Annotated[ + BrowserUseOriginPolicy | None, Field(alias="defaultOriginPolicy") + ] = None disable_auto_review: Annotated[bool | None, Field(alias="disableAutoReview")] = None + origins: dict[str, Any] | None = None class ByteRange(BaseModel): @@ -423,6 +516,13 @@ class CapabilityRootLocation(RootModel[EnvironmentCapabilityRootLocation]): ] +class CliAuthCredentialsStoreMode(Enum): + file = "file" + keyring = "keyring" + auto = "auto" + ephemeral = "ephemeral" + + class ClientInfo(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -436,8 +536,10 @@ class CodexErrorInfoValue(Enum): context_window_exceeded = "contextWindowExceeded" session_budget_exceeded = "sessionBudgetExceeded" usage_limit_exceeded = "usageLimitExceeded" + rate_limit_exceeded = "rateLimitExceeded" server_overloaded = "serverOverloaded" cyber_policy = "cyberPolicy" + misalignment_policy_violation = "misalignmentPolicyViolation" internal_server_error = "internalServerError" unauthorized = "unauthorized" bad_request = "badRequest" @@ -534,12 +636,17 @@ class CollabAgentTool(Enum): resume_agent = "resumeAgent" wait = "wait" close_agent = "closeAgent" + send_message = "sendMessage" + followup_task = "followupTask" + interrupt_agent = "interruptAgent" + list_agents = "listAgents" class CollabAgentToolCallStatus(Enum): in_progress = "inProgress" completed = "completed" failed = "failed" + interrupted = "interrupted" class ListFilesCommandAction(BaseModel): @@ -689,11 +796,58 @@ class CommandMigration(BaseModel): name: str -class ComputerUseRequirements(BaseModel): +class ComputerUseMacosConfig(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - allow_locked_computer_use: Annotated[bool | None, Field(alias="allowLockedComputerUse")] = None + bundle_ids: dict[str, Any] | None = None + + +class ComputerUseMacosRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + bundle_ids: Annotated[dict[str, Any] | None, Field(alias="bundleIds")] = None + + +class ComputerUseWindowsExeConfig(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + access: AllowDenyRequirement + binary_name: str | None = None + product_name: str + publisher_name: str + + +class ComputerUseWindowsExeRequirement(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + access: AllowDenyRequirement + binary_name: Annotated[str | None, Field(alias="binaryName")] = None + product_name: Annotated[str, Field(alias="productName")] + publisher_name: Annotated[str, Field(alias="publisherName")] + + +class ComputerUseWindowsRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + aumids: dict[str, Any] | None = None + exes: list[ComputerUseWindowsExeRequirement] | None = None + + +class PackagedDefaultsConfigLayerSource(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + file: Annotated[ + AbsolutePathBuf, Field(description="Path to the packaged default configuration file.") + ] + type: Annotated[ + Literal["packagedDefaults"], Field(title="PackagedDefaultsConfigLayerSourceType") + ] class MdmConfigLayerSource(BaseModel): @@ -791,7 +945,8 @@ class LegacyManagedConfigTomlFromMdmConfigLayerSource(BaseModel): class ConfigLayerSource( RootModel[ - MdmConfigLayerSource + PackagedDefaultsConfigLayerSource + | MdmConfigLayerSource | SystemConfigLayerSource | EnterpriseManagedConfigLayerSource | UserConfigLayerSource @@ -805,7 +960,8 @@ class ConfigLayerSource( populate_by_name=True, ) root: ( - MdmConfigLayerSource + PackagedDefaultsConfigLayerSource + | MdmConfigLayerSource | SystemConfigLayerSource | EnterpriseManagedConfigLayerSource | UserConfigLayerSource @@ -849,6 +1005,18 @@ class CommandConfiguredHookHandler(BaseModel): type: Annotated[Literal["command"], Field(title="CommandConfiguredHookHandlerType")] +class McpToolConfiguredHookHandler(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + input: dict[str, Any] + server: str + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + timeout_sec: Annotated[int | None, Field(alias="timeoutSec", ge=0)] = None + tool: str + type: Annotated[Literal["mcp_tool"], Field(title="McpToolConfiguredHookHandlerType")] + + class PromptConfiguredHookHandler(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -865,13 +1033,21 @@ class AgentConfiguredHookHandler(BaseModel): class ConfiguredHookHandler( RootModel[ - CommandConfiguredHookHandler | PromptConfiguredHookHandler | AgentConfiguredHookHandler + CommandConfiguredHookHandler + | McpToolConfiguredHookHandler + | PromptConfiguredHookHandler + | AgentConfiguredHookHandler ] ): model_config = ConfigDict( populate_by_name=True, ) - root: CommandConfiguredHookHandler | PromptConfiguredHookHandler | AgentConfiguredHookHandler + root: ( + CommandConfiguredHookHandler + | McpToolConfiguredHookHandler + | PromptConfiguredHookHandler + | AgentConfiguredHookHandler + ) class ConfiguredHookMatcherGroup(BaseModel): @@ -978,6 +1154,12 @@ class CreditsSnapshot(BaseModel): unlimited: bool +class CyberAccessProgram(Enum): + standard = "standard" + daybreak_blue = "daybreakBlue" + daybreak_red = "daybreakRed" + + class DeprecationNoticeNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -1648,14 +1830,37 @@ class GetAccountParams(BaseModel): ] = None -class GetAccountTokenUsageResponse(BaseModel): +class GetAccountRateLimitsParams(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - daily_usage_buckets: Annotated[ - list[AccountTokenUsageDailyBucket] | None, Field(alias="dailyUsageBuckets") + exclude_reset_credit_details: Annotated[ + bool | None, + Field( + alias="excludeResetCreditDetails", + description="Skip the separate reset-credit detail lookup for background usage polls. The usage response still includes the available count; omitted/false preserves detailed reads.", + ), + ] = None + supports_luna_reserve: Annotated[ + bool | None, + Field( + alias="supportsLunaReserve", + description="The client supports automatic Luna Reserve fallback. For eligible ChatGPT CLI users, allow the backend to record experiment exposure after ordinary usage is blocked.", + ), + ] = None + + +class GetAccountTokenUsageParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + thread_id: Annotated[ + str | None, + Field( + alias="threadId", + description="When present, read estimated usage for this thread instead of account-wide token activity.", + ), ] = None - summary: AccountTokenUsageSummary class GitInfo(BaseModel): @@ -1749,6 +1954,7 @@ class HookEventName(Enum): subagent_start = "subagentStart" subagent_stop = "subagentStop" stop = "stop" + interrupt = "interrupt" class HookExecutionMode(Enum): @@ -1758,6 +1964,7 @@ class HookExecutionMode(Enum): class HookHandlerType(Enum): command = "command" + mcp_tool = "mcpTool" prompt = "prompt" agent = "agent" @@ -1836,6 +2043,33 @@ class ImageDetail(Enum): original = "original" +class UsageLimitExceededImageGenerationFailure(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + limit_id: Annotated[str, Field(alias="limitId")] + resets_at: Annotated[int | None, Field(alias="resetsAt")] = None + type: Annotated[ + Literal["usageLimitExceeded"], Field(title="UsageLimitExceededImageGenerationFailureType") + ] + + +class ImageGenerationFailure(RootModel[UsageLimitExceededImageGenerationFailure]): + model_config = ConfigDict( + populate_by_name=True, + ) + root: UsageLimitExceededImageGenerationFailure + + +class InAppBrowserRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + allow_external_browser_settings_import: Annotated[ + bool | None, Field(alias="allowExternalBrowserSettingsImport") + ] = None + + class InitializeCapabilities(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2011,6 +2245,20 @@ class AmazonBedrockLoginAccountParams(BaseModel): ] +class AmazonBedrockAccessKeysLoginAccountParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + access_key_id: Annotated[str, Field(alias="accessKeyId")] + region: str + secret_access_key: Annotated[str, Field(alias="secretAccessKey")] + session_token: Annotated[str | None, Field(alias="sessionToken")] = None + type: Annotated[ + Literal["amazonBedrockAccessKeys"], + Field(title="AmazonBedrockAccessKeysv2::LoginAccountParamsType"), + ] + + class ApiKeyLoginAccountResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2110,6 +2358,7 @@ class ManagedHooksRequirements(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + interrupt: Annotated[list[ConfiguredHookMatcherGroup] | None, Field(alias="Interrupt")] = [] permission_request: Annotated[ list[ConfiguredHookMatcherGroup], Field(alias="PermissionRequest") ] @@ -2211,11 +2460,45 @@ class McpResourceReadParams(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + connector_id: Annotated[str | None, Field(alias="connectorId")] = None + origin_call_id: Annotated[ + str | None, + Field( + alias="originCallId", + description="Originating MCP tool call used to select the resource's app.", + ), + ] = None server: str thread_id: Annotated[str | None, Field(alias="threadId")] = None uri: str +class McpServerConnectionStatus(Enum): + not_started = "notStarted" + starting = "starting" + connected = "connected" + authentication_required = "authenticationRequired" + failed = "failed" + cancelled = "cancelled" + disabled = "disabled" + + +class McpServerEventNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + method: str + params: Any + + +class McpServerEventStreamNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + notification: McpServerEventNotification + subscription_id: Annotated[str, Field(alias="subscriptionId")] + + class McpServerInfo(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2235,6 +2518,12 @@ class McpServerMigration(BaseModel): name: str +class McpServerOauthClientRegistration(Enum): + auto = "auto" + cimd = "cimd" + dcr = "dcr" + + class McpServerOauthLoginCompletedNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2249,6 +2538,13 @@ class McpServerOauthLoginParams(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + client_registration: Annotated[ + McpServerOauthClientRegistration | None, + Field( + alias="clientRegistration", + description="Registration strategy for this login only; omission selects automatic discovery.", + ), + ] = None name: str scopes: list[str] | None = None thread_id: Annotated[str | None, Field(alias="threadId")] = None @@ -2385,6 +2681,13 @@ class MessagePhase(Enum): final_answer = "final_answer" +class MisalignmentSteer(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + message: str + + class ModeKind(Enum): plan = "plan" default = "default" @@ -2480,6 +2783,13 @@ class ModelUpgradeInfo(BaseModel): migration_markdown: Annotated[str | None, Field(alias="migrationMarkdown")] = None model: str model_link: Annotated[str | None, Field(alias="modelLink")] = None + retirement_at: Annotated[ + int | None, + Field( + alias="retirementAt", + description="Informational Unix timestamp for this upgrade's scheduled retirement, if known.", + ), + ] = None upgrade_copy: Annotated[str | None, Field(alias="upgradeCopy")] = None @@ -2524,6 +2834,12 @@ class MultiAgentMode(RootModel[MultiAgentModeValue | CustomMultiAgentMode]): ] +class MultiAgentVersion(Enum): + disabled = "disabled" + v1 = "v1" + v2 = "v2" + + class NetworkAccess(Enum): restricted = "restricted" enabled = "enabled" @@ -2605,6 +2921,24 @@ class NonSteerableTurnKind(Enum): compact = "compact" +class NullableGetAccountRateLimitsParams(RootModel[GetAccountRateLimitsParams | None]): + model_config = ConfigDict( + populate_by_name=True, + ) + root: Annotated[ + GetAccountRateLimitsParams | None, Field(title="Nullable_GetAccountRateLimitsParams") + ] + + +class NullableGetAccountTokenUsageParams(RootModel[GetAccountTokenUsageParams | None]): + model_config = ConfigDict( + populate_by_name=True, + ) + root: Annotated[ + GetAccountTokenUsageParams | None, Field(title="Nullable_GetAccountTokenUsageParams") + ] + + class PatchApplyStatus(Enum): in_progress = "inProgress" completed = "completed" @@ -2710,6 +3044,8 @@ class PlanType(str, Enum): enterprise_cbp_usage_based = "enterprise_cbp_usage_based" enterprise = "enterprise" edu = "edu" + edu_plus = "edu_plus" + edu_pro = "edu_pro" unknown = "unknown" @classmethod @@ -2751,6 +3087,13 @@ class PluginInstallParams(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + install_attempt_id: Annotated[ + str | None, + Field( + alias="installAttemptId", + description="Client-generated identifier used to correlate one installation attempt.", + ), + ] = None marketplace_path: Annotated[AbsolutePathBuf | None, Field(alias="marketplacePath")] = None plugin_name: Annotated[str, Field(alias="pluginName")] remote_marketplace_name: Annotated[str | None, Field(alias="remoteMarketplaceName")] = None @@ -2901,6 +3244,64 @@ class PluginReadParams(BaseModel): remote_marketplace_name: Annotated[str | None, Field(alias="remoteMarketplaceName")] = None +class PluginReconcileChangedPlugin(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + has_apps: Annotated[bool, Field(alias="hasApps")] + has_hooks: Annotated[bool, Field(alias="hasHooks")] + has_mcps: Annotated[bool, Field(alias="hasMcps")] + has_skills: Annotated[ + bool, + Field( + alias="hasSkills", + description="Whether either bundle declares skill roots; not a validated inventory of enabled skills.", + ), + ] + id: Annotated[ + str, Field(description="Local plugin ID (`name@marketplace`), matching `PluginSummary.id`.") + ] + + +class PluginReconcileParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + reason: Annotated[ + str | None, + Field( + description="Optional client-provided reason recorded with the reconciliation attempt." + ), + ] = None + + +class PluginReconcileResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + changed_plugins: Annotated[ + list[PluginReconcileChangedPlugin], + Field( + alias="changedPlugins", + description="Plugins affected by bundle changes, enablement changes, or removals. Installed-state changes compare against the previous cached snapshot, including cached reinstalls. Removal hints survive cache cleanup failures; unchanged plugins are omitted.", + ), + ] + failed_materialization_remote_plugin_ids: Annotated[ + list[str], + Field( + alias="failedMaterializationRemotePluginIds", + description="Subset of failures for which the requested bundle could not be materialized. A previously cached version may still be available.", + ), + ] + failed_remote_plugin_ids: Annotated[ + list[str], + Field( + alias="failedRemotePluginIds", + description="Backend remote plugin IDs whose bundle or identity update failed.", + ), + ] + + class PluginSearchScope(Enum): global_ = "global" workspace = "workspace" @@ -3129,6 +3530,32 @@ class ProcessTerminalSize(BaseModel): rows: Annotated[int, Field(description="Terminal height in character cells.", ge=0)] +class ProjectChangeType(Enum): + created = "created" + updated = "updated" + deleted = "deleted" + + +class ProjectChangedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + change_type: Annotated[ProjectChangeType, Field(alias="changeType")] + project_id: Annotated[str, Field(alias="projectId")] + + +class ProjectRoot(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + path: AbsolutePathBuf + + +class ProjectSortKey(Enum): + position = "position" + recency_at = "recencyAt" + + class RateLimitReachedType(Enum): rate_limit_reached = "rate_limit_reached" workspace_owner_credits_depleted = "workspace_owner_credits_depleted" @@ -3562,6 +3989,14 @@ class OtherResponseItem(BaseModel): type: Annotated[Literal["other"], Field(title="OtherResponseItemType")] +class ResponseUsageMetadata(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + amount: str | None = None + metadata: Any | None = None + + class SearchResponsesApiWebSearchAction(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -3798,6 +4233,40 @@ class SendAddCreditsNudgeEmailResponse(BaseModel): status: AddCreditsNudgeEmailStatus +class ServerDiagnosticsGauge(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + name: str + value: Annotated[int, Field(ge=0)] + + +class ServerDiagnosticsProcess(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: Annotated[int, Field(ge=0)] + physical_footprint_bytes: Annotated[int | None, Field(alias="physicalFootprintBytes", ge=0)] = ( + None + ) + resident_memory_bytes: Annotated[int | None, Field(alias="residentMemoryBytes", ge=0)] = None + + +class ProjectChangedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[Literal["project/changed"], Field(title="Project/changedNotificationMethod")] + params: ProjectChangedNotification + + class ThreadEnvironmentConnectedServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -3971,6 +4440,24 @@ class McpServerStartupStatusUpdatedServerNotification(BaseModel): params: McpServerStatusUpdatedNotification +class McpServerEventStreamNotificationServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["mcpServer/event/stream/notification"], + Field(title="McpServer/event/stream/notificationNotificationMethod"), + ] + params: McpServerEventStreamNotification + + class RemoteControlStatusChangedServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4107,6 +4594,42 @@ class ModelVerificationServerNotification(BaseModel): params: ModelVerificationNotification +class ModelProviderAuthRecoveryStartedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["modelProvider/authRecoveryStarted"], + Field(title="ModelProvider/authRecoveryStartedNotificationMethod"), + ] + params: AuthRecoveryNotification + + +class ModelProviderAuthRecoveryCompletedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["modelProvider/authRecoveryCompleted"], + Field(title="ModelProvider/authRecoveryCompletedNotificationMethod"), + ] + params: AuthRecoveryNotification + + class ModelSafetyBufferingUpdatedServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4370,10 +4893,26 @@ class SpendControlLimitSnapshot(BaseModel): used: str +class StrictReviewRequiredNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + started_at_ms: Annotated[ + int, + Field( + alias="startedAtMs", + description="Unix timestamp (in milliseconds) when this review started.", + ), + ] + thread_id: Annotated[str, Field(alias="threadId")] + turn_id: Annotated[str, Field(alias="turnId")] + + class SubAgentActivityKind(Enum): started = "started" interacted = "interacted" interrupted = "interrupted" + completed = "completed" class SubAgentSourceValue(Enum): @@ -4530,6 +5069,17 @@ class ThreadDeletedNotification(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] +class ThreadEnvironment(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + cwd: LegacyAppPathString + environment_id: Annotated[str, Field(alias="environmentId")] + runtime_workspace_roots: Annotated[ + list[LegacyAppPathString], Field(alias="runtimeWorkspaceRoots") + ] + + class ThreadExtra(BaseModel): pass model_config = ConfigDict( @@ -4717,6 +5267,7 @@ class ImageGenerationThreadItem(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + failure: ImageGenerationFailure | None = None id: str result: str revised_prompt: Annotated[str | None, Field(alias="revisedPrompt")] = None @@ -4752,6 +5303,34 @@ class ContextCompactionThreadItem(BaseModel): type: Annotated[Literal["contextCompaction"], Field(title="ContextCompactionThreadItemType")] +class ThreadItemsListParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + cursor: Annotated[ + str | None, + Field( + description="Opaque cursor to pass to the next call to continue after the last item." + ), + ] = None + limit: Annotated[int | None, Field(description="Optional item page size.", ge=0)] = None + sort_direction: Annotated[ + SortDirection | None, + Field( + alias="sortDirection", + description="Optional item pagination direction; defaults to ascending.", + ), + ] = None + thread_id: Annotated[str, Field(alias="threadId")] + turn_id: Annotated[ + str | None, + Field( + alias="turnId", + description="Optional turn id to filter by. When omitted, returns items across the thread.", + ), + ] = None + + class ThreadListCwdFilter(RootModel[str | list[str]]): model_config = ConfigDict( populate_by_name=True, @@ -4839,6 +5418,21 @@ class ThreadNameUpdatedNotification(BaseModel): thread_name: Annotated[str | None, Field(alias="threadName")] = None +class ThreadProjectUpdatedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + project_id: Annotated[str | None, Field(alias="projectId")] = None + thread_id: Annotated[str, Field(alias="threadId")] + + +class ThreadQueueChangedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + thread_id: Annotated[str, Field(alias="threadId")] + + class ThreadReadParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4847,7 +5441,7 @@ class ThreadReadParams(BaseModel): bool | None, Field( alias="includeTurns", - description="When true, include turns and their items from rollout history.", + description="When true, include turns and their items from rollout history. Full-history hydration is deprecated for paginated threads; prefer a metadata-only read and page with `thread/turns/list` and `thread/items/list`.", ), ] = None thread_id: Annotated[str, Field(alias="threadId")] @@ -4864,6 +5458,56 @@ class ThreadRealtimeAudioChunk(BaseModel): samples_per_channel: Annotated[int | None, Field(alias="samplesPerChannel", ge=0)] = None +class WholeItemThreadRealtimeBemItemPresentation(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + type: Annotated[ + Literal["wholeItem"], Field(title="WholeItemThreadRealtimeBemItemPresentationType") + ] + + +class InlineMarkdownThreadRealtimeBemItemPresentation(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + type: Annotated[ + Literal["inlineMarkdown"], + Field(title="InlineMarkdownThreadRealtimeBemItemPresentationType"), + ] + + +class InlineVisualizationThreadRealtimeBemItemPresentation(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + index: Annotated[int, Field(ge=0)] + type: Annotated[ + Literal["inlineVisualization"], + Field(title="InlineVisualizationThreadRealtimeBemItemPresentationType"), + ] + + +class ThreadRealtimeBemItemPresentation( + RootModel[ + WholeItemThreadRealtimeBemItemPresentation + | InlineMarkdownThreadRealtimeBemItemPresentation + | InlineVisualizationThreadRealtimeBemItemPresentation + ] +): + model_config = ConfigDict( + populate_by_name=True, + ) + root: Annotated[ + WholeItemThreadRealtimeBemItemPresentation + | InlineMarkdownThreadRealtimeBemItemPresentation + | InlineVisualizationThreadRealtimeBemItemPresentation, + Field( + description="EXPERIMENTAL - how an existing agent item appears in a realtime conversation." + ), + ] + + class ThreadRealtimeClosedNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4888,6 +5532,32 @@ class ThreadRealtimeInitialItem(BaseModel): text: str +class RealtimeSessionStartedThreadRealtimeItem(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: str + realtime_session_id: Annotated[str, Field(alias="realtimeSessionId")] + type: Annotated[ + Literal["realtimeSessionStarted"], + Field(title="RealtimeSessionStartedThreadRealtimeItemType"), + ] + + +class BemItemPromotedThreadRealtimeItem(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: str + realtime_session_id: Annotated[str, Field(alias="realtimeSessionId")] + item_id: str + presentation: ThreadRealtimeBemItemPresentation + turn_id: str + type: Annotated[ + Literal["bemItemPromoted"], Field(title="BemItemPromotedThreadRealtimeItemType") + ] + + class ThreadRealtimeItemAddedNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4896,6 +5566,15 @@ class ThreadRealtimeItemAddedNotification(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] +class ThreadRealtimeItemTranscriptDeltaNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + delta: str + item_id: Annotated[str, Field(alias="itemId")] + thread_id: Annotated[str, Field(alias="threadId")] + + class ThreadRealtimeOutputAudioDeltaNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4912,6 +5591,11 @@ class ThreadRealtimeSdpNotification(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] +class ThreadRealtimeSessionOutcome(Enum): + ended = "ended" + failed = "failed" + + class WebsocketThreadRealtimeStartTransport(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4932,14 +5616,36 @@ class WebrtcThreadRealtimeStartTransport(BaseModel): type: Annotated[Literal["webrtc"], Field(title="WebrtcThreadRealtimeStartTransportType")] +class ExistingCallThreadRealtimeStartTransport(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + call_id: Annotated[ + str, + Field( + alias="callId", + description="Identifier of a realtime call already created and negotiated by the client.", + ), + ] + type: Annotated[ + Literal["existingCall"], Field(title="ExistingCallThreadRealtimeStartTransportType") + ] + + class ThreadRealtimeStartTransport( - RootModel[WebsocketThreadRealtimeStartTransport | WebrtcThreadRealtimeStartTransport] + RootModel[ + WebsocketThreadRealtimeStartTransport + | WebrtcThreadRealtimeStartTransport + | ExistingCallThreadRealtimeStartTransport + ] ): model_config = ConfigDict( populate_by_name=True, ) root: Annotated[ - WebsocketThreadRealtimeStartTransport | WebrtcThreadRealtimeStartTransport, + WebsocketThreadRealtimeStartTransport + | WebrtcThreadRealtimeStartTransport + | ExistingCallThreadRealtimeStartTransport, Field(description="EXPERIMENTAL - transport used by thread realtime."), ] @@ -4971,6 +5677,11 @@ class ThreadRealtimeTranscriptDoneNotification(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] +class ThreadRealtimeTranscriptRole(Enum): + user = "user" + assistant = "assistant" + + class ThreadResumeParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4987,6 +5698,13 @@ class ThreadResumeParams(BaseModel): config: dict[str, Any] | None = None cwd: str | None = None developer_instructions: Annotated[str | None, Field(alias="developerInstructions")] = None + exclude_turns: Annotated[ + bool | None, + Field( + alias="excludeTurns", + description="When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.", + ), + ] = None model: Annotated[ str | None, Field(description="Configuration overrides for the resumed thread, if any.") ] = None @@ -4997,6 +5715,27 @@ class ThreadResumeParams(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] +class ThreadRevertParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + before_turn_id: Annotated[ + str, + Field( + alias="beforeTurnId", + description="Turn excluded from the replacement history, together with every later turn.", + ), + ] + thread_id: Annotated[str, Field(alias="threadId")] + + +class ThreadRevertedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + thread_id: Annotated[str, Field(alias="threadId")] + + class ThreadRollbackParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5018,33 +5757,22 @@ class ThreadSearchSortKey(Enum): recency_at = "recency_at" -class ThreadSection(BaseModel): +class ThreadSectionAppearance(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - id: Annotated[ - str, - Field( - description="Opaque UUIDv7 identity that remains stable when the section is renamed." - ), - ] - name: Annotated[str, Field(description="The current user-visible section name.")] + color: str | None = None + icon: str | None = None class ThreadSectionCreateParams(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + appearance: ThreadSectionAppearance | None = None name: Annotated[str, Field(description="The user-visible name of the section.")] -class ThreadSectionCreateResponse(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - section: ThreadSection - - class ThreadSectionDeleteParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5077,20 +5805,6 @@ class ThreadSectionListParams(BaseModel): ] = None -class ThreadSectionListResponse(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - data: list[ThreadSection] - next_cursor: Annotated[ - str | None, - Field( - alias="nextCursor", - description="Opaque cursor for the next page, or `null` when no sections remain.", - ), - ] = None - - class ThreadSectionMoveParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5126,6 +5840,12 @@ class ThreadSectionUpdateParams(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + appearance: Annotated[ + ThreadSectionAppearance | None, + Field( + description="Omit to preserve appearance, use `null` to clear it, or provide a replacement." + ), + ] = None name: Annotated[str, Field(description="The updated user-visible name of the section.")] section_id: Annotated[ str, @@ -5136,13 +5856,6 @@ class ThreadSectionUpdateParams(BaseModel): ] -class ThreadSectionUpdateResponse(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - section: ThreadSection - - class ThreadSetNameParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5169,6 +5882,13 @@ class ThreadShellCommandParams(BaseModel): ), ] thread_id: Annotated[str, Field(alias="threadId")] + timeout_ms: Annotated[ + int | None, + Field( + alias="timeoutMs", + description="Maximum execution time in milliseconds. Defaults to one hour when omitted or null. Must be non-negative; zero requests an immediate timeout, not unlimited execution. Does not affect the immediate RPC acknowledgement.", + ), + ] = None class ThreadShellCommandResponse(BaseModel): @@ -5266,6 +5986,16 @@ class ThreadStatusChangedNotification(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] +class TurnStartedThreadTimelineEntry(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + position: Annotated[int, Field(ge=0)] + started_at: int | None = None + turn_id: str + type: Annotated[Literal["turnStarted"], Field(title="TurnStartedThreadTimelineEntryType")] + + class ThreadUnarchiveParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5293,6 +6023,21 @@ class ThreadUnsubscribeStatus(Enum): unsubscribed = "unsubscribed" +class ThreadUsageBreakdownGroup(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + cached_input_tokens: Annotated[int | None, Field(alias="cachedInputTokens")] = None + estimated_usage_credits_micros: Annotated[int, Field(alias="estimatedUsageCreditsMicros")] + input_tokens: Annotated[int | None, Field(alias="inputTokens")] = None + model: str | None = None + net_new_input_tokens: Annotated[int | None, Field(alias="netNewInputTokens")] = None + output_tokens: Annotated[int | None, Field(alias="outputTokens")] = None + reasoning_effort: Annotated[str | None, Field(alias="reasoningEffort")] = None + speed: str | None = None + total_tokens: Annotated[int | None, Field(alias="totalTokens")] = None + + class TokenUsageBreakdown(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5696,10 +6441,21 @@ class AppConfig(BaseModel): default_tools_enabled: bool | None = None destructive_enabled: bool | None = None enabled: bool | None = True + links: Annotated[ + AppLinksConfig | None, Field(description="Per-account approval settings keyed by link ID.") + ] = None open_world_enabled: bool | None = None tools: AppToolsConfig | None = None +class AppLinkConfig(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + approvals_reviewer: ApprovalsReviewer | None = None + default_tools_approval_mode: AppToolApproval | None = None + + class AppMetadata(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5736,6 +6492,24 @@ class AppTemplateSummary(BaseModel): template_id: Annotated[str, Field(alias="templateId")] +class ApplicationNetworkRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + domains: dict[str, NetworkDomainPermission] + enabled: Annotated[ + bool, + Field(description="When enabled, only explicitly allowed exact domains may be contacted."), + ] + + +class ApplicationRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + network: ApplicationNetworkRequirements | None = None + + class AppsConfig(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5758,6 +6532,15 @@ class AppsReadResponse(BaseModel): missing_app_ids: Annotated[list[str], Field(alias="missingAppIds")] +class BrowserUseConfig(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + allow_history_access: bool | None = None + default_origin_policy: BrowserUseOriginPolicyConfig | None = None + origins: dict[str, Any] | None = None + + class CancelLoginAccountResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5911,6 +6694,15 @@ class ThreadRollbackRequest(BaseModel): params: ThreadRollbackParams +class ThreadRevertRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["thread/revert"], Field(title="Thread/revertRequestMethod")] + params: ThreadRevertParams + + class ThreadSectionListRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5971,6 +6763,15 @@ class ThreadReadRequest(BaseModel): params: ThreadReadParams +class ThreadItemsListRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["thread/items/list"], Field(title="Thread/items/listRequestMethod")] + params: ThreadItemsListParams + + class ThreadInjectItemsRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6058,6 +6859,15 @@ class PluginInstalledRequest(BaseModel): params: PluginInstalledParams +class PluginReconcileRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["plugin/reconcile"], Field(title="Plugin/reconcileRequestMethod")] + params: PluginReconcileParams + + class PluginReadRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6402,7 +7212,7 @@ class AccountRateLimitsReadRequest(BaseModel): method: Annotated[ Literal["account/rateLimits/read"], Field(title="Account/rateLimits/readRequestMethod") ] - params: None = None + params: GetAccountRateLimitsParams | None = None class AccountRateLimitResetCreditConsumeRequest(BaseModel): @@ -6423,7 +7233,7 @@ class AccountUsageReadRequest(BaseModel): ) id: RequestId method: Annotated[Literal["account/usage/read"], Field(title="Account/usage/readRequestMethod")] - params: None = None + params: GetAccountTokenUsageParams | None = None class AccountWorkspaceMessagesReadRequest(BaseModel): @@ -6756,6 +7566,27 @@ class CommandExecResizeParams(BaseModel): size: Annotated[CommandExecTerminalSize, Field(description="New PTY size in character cells.")] +class ComputerUseRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + allow_locked_computer_use: Annotated[bool | None, Field(alias="allowLockedComputerUse")] = None + allow_persistent_approval: Annotated[bool | None, Field(alias="allowPersistentApproval")] = None + default_app_access: Annotated[AllowDenyRequirement | None, Field(alias="defaultAppAccess")] = ( + None + ) + macos: ComputerUseMacosRequirements | None = None + windows: ComputerUseWindowsRequirements | None = None + + +class ComputerUseWindowsConfig(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + aumids: dict[str, Any] | None = None + exes: list[ComputerUseWindowsExeConfig] | None = None + + class ConfigEdit(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6818,6 +7649,13 @@ class ConfigWarningNotification(BaseModel): summary: Annotated[str, Field(description="Concise summary of the warning.")] +class ConfigurationReasoning(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + effort: ReasoningEffort + + class InputImageContentItem(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7088,6 +7926,19 @@ class ExecveGuardianApprovalReviewAction(BaseModel): type: Annotated[Literal["execve"], Field(title="ExecveGuardianApprovalReviewActionType")] +class WriteStdinGuardianApprovalReviewAction(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + approval_id: Annotated[str, Field(alias="approvalId")] + cwd: LegacyAppPathString + process_id: Annotated[str, Field(alias="processId")] + stdin: str + type: Annotated[ + Literal["writeStdin"], Field(title="WriteStdinGuardianApprovalReviewActionType") + ] + + class NetworkAccessGuardianApprovalReviewAction(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7101,7 +7952,7 @@ class NetworkAccessGuardianApprovalReviewAction(BaseModel): ] -class HookMetadata(BaseModel): +class HookMetadata1(BaseModel): model_config = ConfigDict( populate_by_name=True, ) @@ -7113,12 +7964,10 @@ class HookMetadata(BaseModel): ge=0, ), ] = None - command: str | None = None current_hash: Annotated[str, Field(alias="currentHash")] display_order: Annotated[int, Field(alias="displayOrder")] enabled: bool event_name: Annotated[HookEventName, Field(alias="eventName")] - handler_type: Annotated[HookHandlerType, Field(alias="handlerType")] is_managed: Annotated[bool, Field(alias="isManaged")] key: str matcher: str | None = None @@ -7128,6 +7977,104 @@ class HookMetadata(BaseModel): status_message: Annotated[str | None, Field(alias="statusMessage")] = None timeout_sec: Annotated[int, Field(alias="timeoutSec", ge=0)] trust_status: Annotated[HookTrustStatus, Field(alias="trustStatus")] + async_: Annotated[bool | None, Field(alias="async")] = False + command: str + handler_type: Annotated[Literal["command"], Field(alias="handlerType")] + + +class HookMetadata2(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + additional_context_limit: Annotated[ + int | None, + Field( + alias="additionalContextLimit", + description="Configured `additionalContext` spill threshold. `null` uses 2,500 tokens; `0` disables spilling.", + ge=0, + ), + ] = None + current_hash: Annotated[str, Field(alias="currentHash")] + display_order: Annotated[int, Field(alias="displayOrder")] + enabled: bool + event_name: Annotated[HookEventName, Field(alias="eventName")] + is_managed: Annotated[bool, Field(alias="isManaged")] + key: str + matcher: str | None = None + plugin_id: Annotated[str | None, Field(alias="pluginId")] = None + source: HookSource + source_path: Annotated[AbsolutePathBuf, Field(alias="sourcePath")] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + timeout_sec: Annotated[int, Field(alias="timeoutSec", ge=0)] + trust_status: Annotated[HookTrustStatus, Field(alias="trustStatus")] + handler_type: Annotated[Literal["mcpTool"], Field(alias="handlerType")] + server: str + tool: str + + +class PromptHookMetadata(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + additional_context_limit: Annotated[ + int | None, + Field( + alias="additionalContextLimit", + description="Configured `additionalContext` spill threshold. `null` uses 2,500 tokens; `0` disables spilling.", + ge=0, + ), + ] = None + current_hash: Annotated[str, Field(alias="currentHash")] + display_order: Annotated[int, Field(alias="displayOrder")] + enabled: bool + event_name: Annotated[HookEventName, Field(alias="eventName")] + is_managed: Annotated[bool, Field(alias="isManaged")] + key: str + matcher: str | None = None + plugin_id: Annotated[str | None, Field(alias="pluginId")] = None + source: HookSource + source_path: Annotated[AbsolutePathBuf, Field(alias="sourcePath")] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + timeout_sec: Annotated[int, Field(alias="timeoutSec", ge=0)] + trust_status: Annotated[HookTrustStatus, Field(alias="trustStatus")] + handler_type: Annotated[Literal["prompt"], Field(alias="handlerType")] + + +class AgentHookMetadata(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + additional_context_limit: Annotated[ + int | None, + Field( + alias="additionalContextLimit", + description="Configured `additionalContext` spill threshold. `null` uses 2,500 tokens; `0` disables spilling.", + ge=0, + ), + ] = None + current_hash: Annotated[str, Field(alias="currentHash")] + display_order: Annotated[int, Field(alias="displayOrder")] + enabled: bool + event_name: Annotated[HookEventName, Field(alias="eventName")] + is_managed: Annotated[bool, Field(alias="isManaged")] + key: str + matcher: str | None = None + plugin_id: Annotated[str | None, Field(alias="pluginId")] = None + source: HookSource + source_path: Annotated[AbsolutePathBuf, Field(alias="sourcePath")] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + timeout_sec: Annotated[int, Field(alias="timeoutSec", ge=0)] + trust_status: Annotated[HookTrustStatus, Field(alias="trustStatus")] + handler_type: Annotated[Literal["agent"], Field(alias="handlerType")] + + +class HookMetadata( + RootModel[HookMetadata1 | HookMetadata2 | PromptHookMetadata | AgentHookMetadata] +): + model_config = ConfigDict( + populate_by_name=True, + ) + root: HookMetadata1 | HookMetadata2 | PromptHookMetadata | AgentHookMetadata class HookOutputEntry(BaseModel): @@ -7223,6 +8170,7 @@ class LoginAccountParams( | ChatgptDeviceCodeLoginAccountParams | ChatgptAuthTokensLoginAccountParams | AmazonBedrockLoginAccountParams + | AmazonBedrockAccessKeysLoginAccountParams ] ): model_config = ConfigDict( @@ -7233,7 +8181,8 @@ class LoginAccountParams( | ChatgptLoginAccountParams | ChatgptDeviceCodeLoginAccountParams | ChatgptAuthTokensLoginAccountParams - | AmazonBedrockLoginAccountParams, + | AmazonBedrockLoginAccountParams + | AmazonBedrockAccessKeysLoginAccountParams, Field(title="LoginAccountParams"), ] @@ -7243,6 +8192,13 @@ class McpResourceReadResponse(BaseModel): populate_by_name=True, ) contents: list[ResourceContent] + origin_call_id: Annotated[ + str | None, + Field( + alias="originCallId", + description="Originating call when the server applied app-specific resource scoping.", + ), + ] = None class McpServerStatus(BaseModel): @@ -7251,10 +8207,25 @@ class McpServerStatus(BaseModel): ) auth_status: Annotated[McpAuthStatus, Field(alias="authStatus")] name: str + plugin_id: Annotated[str | None, Field(alias="pluginId")] = None resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] resources: list[Resource] + runtime_status: Annotated[ + McpServerConnectionStatus | None, + Field( + alias="runtimeStatus", + description="Current thread-runtime connection state; null when unavailable or the configuration changed.", + ), + ] = None server_info: Annotated[McpServerInfo | None, Field(alias="serverInfo")] = None tools: dict[str, Tool] + tools_error: Annotated[ + str | None, + Field( + alias="toolsError", + description="Tool discovery failed and no catalog was returned. Null when a catalog is returned, including cached or empty catalogs.", + ), + ] = None class MemoryCitation(BaseModel): @@ -7279,6 +8250,32 @@ class MigrationDetails(BaseModel): subagents: list[SubagentMigration] | None = [] +class MisalignmentErrorDetails(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + detailed_explanation: Annotated[ + str | None, + Field( + alias="detailedExplanation", + description="A substantive localized explanation is required before offering continuation.", + ), + ] = None + error_type: Annotated[ + str | None, + Field( + alias="errorType", + description="Open-ended classification; clients must accept categories added by Responses.", + ), + ] = None + steer: Annotated[ + MisalignmentSteer | None, + Field( + description="Instruction to submit as the next turn's user input if continuation is confirmed." + ), + ] = None + + class Model(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7307,6 +8304,13 @@ class Model(BaseModel): is_default: Annotated[bool, Field(alias="isDefault")] model: str model_specialty: Annotated[str | None, Field(alias="modelSpecialty")] = None + multi_agent_version: Annotated[ + MultiAgentVersion | None, + Field( + alias="multiAgentVersion", + description="Multi-agent runtime declared by this model, when available.", + ), + ] = None service_tiers: Annotated[list[ModelServiceTier] | None, Field(alias="serviceTiers")] = [] supported_reasoning_efforts: Annotated[ list[ReasoningEffortOption], Field(alias="supportedReasoningEfforts") @@ -7426,6 +8430,35 @@ class ProcessOutputDeltaNotification(BaseModel): ] +class Project(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + created_at: Annotated[int, Field(alias="createdAt")] + id: str + metadata: dict[str, str] + name: str + position: int + recency_at: Annotated[ + int | None, + Field( + alias="recencyAt", + description="Newest non-archived member thread's recency, in Unix seconds; null when none exist.", + ), + ] = None + roots: list[ProjectRoot] + updated_at: Annotated[int, Field(alias="updatedAt")] + + +class QueuedSubmission(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + client_user_message_id: Annotated[str, Field(alias="clientUserMessageId")] + id: str + input: list[UserInput] + + class RateLimitResetCredit(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7483,6 +8516,13 @@ class RateLimitSnapshot(BaseModel): ] = None limit_id: Annotated[str | None, Field(alias="limitId")] = None limit_name: Annotated[str | None, Field(alias="limitName")] = None + normal_model_slug: Annotated[ + str | None, + Field( + alias="normalModelSlug", + description="Normal model whose display name and reasoning options describe this quota alias.", + ), + ] = None plan_type: Annotated[PlanType | None, Field(alias="planType")] = None primary: RateLimitWindow | None = None rate_limit_reached_type: Annotated[ @@ -7506,6 +8546,7 @@ class RawResponseCompletedNotification(BaseModel): thread_id: Annotated[str, Field(alias="threadId")] turn_id: Annotated[str, Field(alias="turnId")] usage: TokenUsageBreakdown | None = None + usage_metadata: Annotated[ResponseUsageMetadata | None, Field(alias="usageMetadata")] = None class MessageResponseItem(BaseModel): @@ -7531,6 +8572,16 @@ class WebSearchCallResponseItem(BaseModel): type: Annotated[Literal["web_search_call"], Field(title="WebSearchCallResponseItemType")] +class ConfigurationUpdateResponseItem(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + reasoning: ConfigurationReasoning + type: Annotated[ + Literal["configuration_update"], Field(title="ConfigurationUpdateResponseItemType") + ] + + class ReviewStartParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7538,7 +8589,7 @@ class ReviewStartParams(BaseModel): delivery: Annotated[ ReviewDelivery | None, Field( - description="Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`)." + description="Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`). Detached delivery is deprecated and emits `deprecationNotice`. Use `thread/start` followed by an inline review for a separate review thread." ), ] = None target: ReviewTarget @@ -7671,6 +8722,21 @@ class ThreadClosedServerNotification(BaseModel): params: ThreadClosedNotification +class ThreadRevertedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[Literal["thread/reverted"], Field(title="Thread/revertedNotificationMethod")] + params: ThreadRevertedNotification + + class SkillsChangedServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7720,6 +8786,40 @@ class ThreadGoalClearedServerNotification(BaseModel): params: ThreadGoalClearedNotification +class ThreadQueueChangedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["thread/queue/changed"], Field(title="Thread/queue/changedNotificationMethod") + ] + params: ThreadQueueChangedNotification + + +class ThreadProjectUpdatedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["thread/project/updated"], Field(title="Thread/project/updatedNotificationMethod") + ] + params: ThreadProjectUpdatedNotification + + class HookStartedServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7752,6 +8852,24 @@ class TurnDiffUpdatedServerNotification(BaseModel): params: TurnDiffUpdatedNotification +class AutoApprovalReviewStrictReviewRequiredServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["autoApprovalReview/strictReviewRequired"], + Field(title="AutoApprovalReview/strictReviewRequiredNotificationMethod"), + ] + params: StrictReviewRequiredNotification + + class CommandExecOutputDeltaServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7919,6 +9037,24 @@ class ThreadRealtimeItemAddedServerNotification(BaseModel): params: ThreadRealtimeItemAddedNotification +class ThreadRealtimeItemTranscriptDeltaServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["thread/realtime/item/transcript/delta"], + Field(title="Thread/realtime/item/transcript/deltaNotificationMethod"), + ] + params: ThreadRealtimeItemTranscriptDeltaNotification + + class ThreadRealtimeTranscriptDeltaServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8076,6 +9212,13 @@ class SkillMetadata(BaseModel): interface: SkillInterface | None = None name: str path: AbsolutePathBuf + plugin_id: Annotated[ + str | None, + Field( + alias="pluginId", + description="Owning plugin ID, matching `PluginSummary.id`, when known.", + ), + ] = None scope: SkillScope short_description: Annotated[ str | None, @@ -8147,6 +9290,13 @@ class ThreadForkParams(BaseModel): cwd: str | None = None developer_instructions: Annotated[str | None, Field(alias="developerInstructions")] = None ephemeral: bool | None = None + exclude_turns: Annotated[ + bool | None, + Field( + alias="excludeTurns", + description="When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.", + ), + ] = None last_turn_id: Annotated[ str | None, Field( @@ -8231,9 +9381,11 @@ class AgentMessageThreadItem(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + delivery: AgentMessageDelivery | None = None id: str memory_citation: Annotated[MemoryCitation | None, Field(alias="memoryCitation")] = None phase: MessagePhase | None = None + questions: list[AsyncUserInputQuestion] | None = None text: str type: Annotated[Literal["agentMessage"], Field(title="AgentMessageThreadItemType")] @@ -8368,61 +9520,6 @@ class WebSearchThreadItem(BaseModel): type: Annotated[Literal["webSearch"], Field(title="WebSearchThreadItemType")] -class ThreadItem( - RootModel[ - UserMessageThreadItem - | HookPromptThreadItem - | AgentMessageThreadItem - | PlanThreadItem - | ReasoningThreadItem - | CommandExecutionThreadItem - | FileChangeThreadItem - | McpToolCallThreadItem - | DynamicToolCallThreadItem - | CollabAgentToolCallThreadItem - | SubAgentActivityThreadItem - | WebSearchThreadItem - | ImageViewThreadItem - | SleepThreadItem - | ImageGenerationThreadItem - | EnteredReviewModeThreadItem - | ExitedReviewModeThreadItem - | ContextCompactionThreadItem - ] -): - model_config = ConfigDict( - populate_by_name=True, - ) - root: ( - UserMessageThreadItem - | HookPromptThreadItem - | AgentMessageThreadItem - | PlanThreadItem - | ReasoningThreadItem - | CommandExecutionThreadItem - | FileChangeThreadItem - | McpToolCallThreadItem - | DynamicToolCallThreadItem - | CollabAgentToolCallThreadItem - | SubAgentActivityThreadItem - | WebSearchThreadItem - | ImageViewThreadItem - | SleepThreadItem - | ImageGenerationThreadItem - | EnteredReviewModeThreadItem - | ExitedReviewModeThreadItem - | ContextCompactionThreadItem - ) - - -class ThreadItemEntry(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - item: ThreadItem - turn_id: Annotated[str, Field(alias="turnId", description="Turn containing this item.")] - - class ThreadListParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8453,6 +9550,12 @@ class ThreadListParams(BaseModel): description="Optional provider filter; when set, only sessions recorded under these providers are returned. When present but empty, includes all providers.", ), ] = None + originators: Annotated[ + list[str] | None, + Field( + description="Optional originator allowlist, matching any supplied value exactly. Supported by hosted backends only; the local app-server rejects a nonempty list. Omitted or empty lists leave originators unrestricted." + ), + ] = None search_term: Annotated[ str | None, Field( @@ -8494,6 +9597,69 @@ class ThreadListParams(BaseModel): ] = None +class TranscriptSegmentThreadRealtimeItem(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: str + realtime_session_id: Annotated[str, Field(alias="realtimeSessionId")] + role: ThreadRealtimeTranscriptRole + text: str + type: Annotated[ + Literal["transcriptSegment"], Field(title="TranscriptSegmentThreadRealtimeItemType") + ] + + +class RealtimeSessionClosedThreadRealtimeItem(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: str + realtime_session_id: Annotated[str, Field(alias="realtimeSessionId")] + outcome: ThreadRealtimeSessionOutcome + type: Annotated[ + Literal["realtimeSessionClosed"], Field(title="RealtimeSessionClosedThreadRealtimeItemType") + ] + + +class ThreadRealtimeItem( + RootModel[ + RealtimeSessionStartedThreadRealtimeItem + | TranscriptSegmentThreadRealtimeItem + | BemItemPromotedThreadRealtimeItem + | RealtimeSessionClosedThreadRealtimeItem + ] +): + model_config = ConfigDict( + populate_by_name=True, + ) + root: Annotated[ + RealtimeSessionStartedThreadRealtimeItem + | TranscriptSegmentThreadRealtimeItem + | BemItemPromotedThreadRealtimeItem + | RealtimeSessionClosedThreadRealtimeItem, + Field( + description="EXPERIMENTAL - a thread-scoped realtime item in the canonical timeline." + ), + ] + + +class ThreadRealtimeItemCompletedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + item: ThreadRealtimeItem + thread_id: Annotated[str, Field(alias="threadId")] + + +class ThreadRealtimeItemStartedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + item: ThreadRealtimeItem + thread_id: Annotated[str, Field(alias="threadId")] + + class ThreadResumeInitialTurnsPageParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8515,6 +9681,51 @@ class ThreadResumeInitialTurnsPageParams(BaseModel): ] = None +class ThreadSection(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + appearance: Annotated[ + ThreadSectionAppearance | None, + Field(description="Optional appearance synchronized across clients."), + ] = None + id: Annotated[ + str, + Field( + description="Opaque UUIDv7 identity that remains stable when the section is renamed." + ), + ] + name: Annotated[str, Field(description="The current user-visible section name.")] + + +class ThreadSectionCreateResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + section: ThreadSection + + +class ThreadSectionListResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + data: list[ThreadSection] + next_cursor: Annotated[ + str | None, + Field( + alias="nextCursor", + description="Opaque cursor for the next page, or `null` when no sections remain.", + ), + ] = None + + +class ThreadSectionUpdateResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + section: ThreadSection + + class ThreadSettings(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8578,6 +9789,15 @@ class ThreadStartParams(BaseModel): ] = None +class RealtimeThreadTimelineEntry(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + item: ThreadRealtimeItem + position: Annotated[int, Field(ge=0)] + type: Annotated[Literal["realtime"], Field(title="RealtimeThreadTimelineEntryType")] + + class ThreadTokenUsage(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8596,6 +9816,34 @@ class ThreadTokenUsageUpdatedNotification(BaseModel): turn_id: Annotated[str, Field(alias="turnId")] +class ThreadTurnsListParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + cursor: Annotated[ + str | None, + Field( + description="Opaque cursor to pass to the next call to continue after the last turn." + ), + ] = None + items_view: Annotated[ + TurnItemsView | None, + Field( + alias="itemsView", + description="How much item detail to include for each returned turn; defaults to summary.", + ), + ] = None + limit: Annotated[int | None, Field(description="Optional turn page size.", ge=0)] = None + sort_direction: Annotated[ + SortDirection | None, + Field( + alias="sortDirection", + description="Optional turn pagination direction; defaults to descending.", + ), + ] = None + thread_id: Annotated[str, Field(alias="threadId")] + + class ThreadUnsubscribeResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8603,6 +9851,16 @@ class ThreadUnsubscribeResponse(BaseModel): status: ThreadUnsubscribeStatus +class ThreadUsage(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + estimated_usage_credits_micros: Annotated[int, Field(alias="estimatedUsageCreditsMicros")] + estimated_usage_usd_micros: Annotated[int | None, Field(alias="estimatedUsageUsdMicros")] = None + groups: list[ThreadUsageBreakdownGroup] + thread_id: Annotated[str, Field(alias="threadId")] + + class ToolsV2(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8617,6 +9875,12 @@ class TurnError(BaseModel): additional_details: Annotated[str | None, Field(alias="additionalDetails")] = None codex_error_info: Annotated[CodexErrorInfo | None, Field(alias="codexErrorInfo")] = None message: str + misalignment: Annotated[ + MisalignmentErrorDetails | None, + Field( + description="Optional public explanation and continuation instruction for a misalignment block." + ), + ] = None class TurnPlanStep(BaseModel): @@ -8637,69 +9901,6 @@ class TurnPlanUpdatedNotification(BaseModel): turn_id: Annotated[str, Field(alias="turnId")] -class TurnStartParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - approval_policy: Annotated[ - AskForApproval | None, - Field( - alias="approvalPolicy", - description="Override the approval policy for this turn and subsequent turns.", - ), - ] = None - approvals_reviewer: Annotated[ - ApprovalsReviewer | None, - Field( - alias="approvalsReviewer", - description="Override where approval requests are routed for review on this turn and subsequent turns.", - ), - ] = None - client_user_message_id: Annotated[str | None, Field(alias="clientUserMessageId")] = None - cwd: Annotated[ - str | None, - Field(description="Override the working directory for this turn and subsequent turns."), - ] = None - effort: Annotated[ - ReasoningEffort | None, - Field(description="Override the reasoning effort for this turn and subsequent turns."), - ] = None - input: list[UserInput] - model: Annotated[ - str | None, Field(description="Override the model for this turn and subsequent turns.") - ] = None - output_schema: Annotated[ - Any | None, - Field( - alias="outputSchema", - description="Optional JSON Schema used to constrain the final assistant message for this turn.", - ), - ] = None - personality: Annotated[ - Personality | None, - Field(description="Override the personality for this turn and subsequent turns."), - ] = None - sandbox_policy: Annotated[ - SandboxPolicy | None, - Field( - alias="sandboxPolicy", - description="Override the sandbox policy for this turn and subsequent turns.", - ), - ] = None - service_tier: Annotated[ - str | None, - Field( - alias="serviceTier", - description="Override the service tier for this turn and subsequent turns.", - ), - ] = None - summary: Annotated[ - ReasoningSummary | None, - Field(description="Override the reasoning summary for this turn and subsequent turns."), - ] = None - thread_id: Annotated[str, Field(alias="threadId")] - - class TurnSteerParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8839,6 +10040,15 @@ class ThreadListRequest(BaseModel): params: ThreadListParams +class ThreadTurnsListRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["thread/turns/list"], Field(title="Thread/turns/listRequestMethod")] + params: ThreadTurnsListParams + + class PluginShareUpdateTargetsRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8851,15 +10061,6 @@ class PluginShareUpdateTargetsRequest(BaseModel): params: PluginShareUpdateTargetsParams -class TurnStartRequest(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: RequestId - method: Annotated[Literal["turn/start"], Field(title="Turn/startRequestMethod")] - params: TurnStartParams - - class TurnSteerRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -8929,6 +10130,15 @@ class ConfigValueWriteRequest(BaseModel): params: ConfigValueWriteParams +class ComputerUseConfig(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + default_app_access: AllowDenyRequirement | None = None + macos: ComputerUseMacosConfig | None = None + windows: ComputerUseWindowsConfig | None = None + + class Config(BaseModel): model_config = ConfigDict( extra="allow", @@ -8942,7 +10152,9 @@ class Config(BaseModel): description="[UNSTABLE] Optional default for where approval requests are routed for review." ), ] = None + browser_use: BrowserUseConfig | None = None compact_prompt: str | None = None + computer_use: ComputerUseConfig | None = None desktop: dict[str, Any] | None = None developer_instructions: str | None = None forced_chatgpt_workspace_id: ForcedChatgptWorkspaceIds | None = None @@ -9121,9 +10333,30 @@ class GetAccountRateLimitsResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + account_id: Annotated[ + str | None, + Field( + alias="accountId", + description="Account associated with this usage snapshot, when supplied by the backend.", + ), + ] = None + ordinary_usage_allowed: Annotated[ + bool | None, + Field( + alias="ordinaryUsageAllowed", + description="Backend permission for ordinary included usage, validated against the active account. Null means unavailable; clients must not infer recovery from percentages or reset times.", + ), + ] = None rate_limit_reset_credits: Annotated[ RateLimitResetCreditsSummary | None, Field(alias="rateLimitResetCredits") ] = None + rate_limit_upsell: Annotated[ + Any | None, + Field( + alias="rateLimitUpsell", + description="Optional backend-owned banner from the same usage read. Its nested keys retain the backend's snake_case contract; an absent banner leaves the client's existing UI unchanged.", + ), + ] = None rate_limits: Annotated[ RateLimitSnapshot, Field( @@ -9140,6 +10373,23 @@ class GetAccountRateLimitsResponse(BaseModel): ] = None +class GetAccountTokenUsageResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + daily_usage_buckets: Annotated[ + list[AccountTokenUsageDailyBucket] | None, Field(alias="dailyUsageBuckets") + ] = None + summary: AccountTokenUsageSummary + thread_usage: Annotated[ + ThreadUsage | None, + Field( + alias="threadUsage", + description="Estimated usage when a thread was requested and its billing route is available.", + ), + ] = None + + class GetWorkspaceMessagesResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -9166,38 +10416,6 @@ class HookCompletedNotification(BaseModel): turn_id: Annotated[str | None, Field(alias="turnId")] = None -class ItemCompletedNotification(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - completed_at_ms: Annotated[ - int, - Field( - alias="completedAtMs", - description="Unix timestamp (in milliseconds) when this item lifecycle completed.", - ), - ] - item: ThreadItem - thread_id: Annotated[str, Field(alias="threadId")] - turn_id: Annotated[str, Field(alias="turnId")] - - -class ItemStartedNotification(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - item: ThreadItem - started_at_ms: Annotated[ - int, - Field( - alias="startedAtMs", - description="Unix timestamp (in milliseconds) when this item lifecycle started.", - ), - ] - thread_id: Annotated[str, Field(alias="threadId")] - turn_id: Annotated[str, Field(alias="turnId")] - - class ListMcpServerStatusResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -9325,9 +10543,11 @@ class FunctionCallOutputResponseItem(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - call_id: str + call_id: str | None = None id: str | None = None internal_chat_message_metadata_passthrough: InternalChatMessageMetadataPassthrough | None = None + name: str | None = None + namespace: str | None = None output: FunctionCallOutputBody type: Annotated[ Literal["function_call_output"], Field(title="FunctionCallOutputResponseItemType") @@ -9363,6 +10583,7 @@ class ResponseItem( | WebSearchCallResponseItem | ImageGenerationCallResponseItem | CompactionResponseItem + | ConfigurationUpdateResponseItem | CompactionTriggerResponseItem | ContextCompactionResponseItem | OtherResponseItem @@ -9385,6 +10606,7 @@ class ResponseItem( | WebSearchCallResponseItem | ImageGenerationCallResponseItem | CompactionResponseItem + | ConfigurationUpdateResponseItem | CompactionTriggerResponseItem | ContextCompactionResponseItem | OtherResponseItem @@ -9490,36 +10712,6 @@ class TurnPlanUpdatedServerNotification(BaseModel): params: TurnPlanUpdatedNotification -class ItemStartedServerNotification(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - emitted_at_ms: Annotated[ - int | None, - Field( - alias="emittedAtMs", - description="Unix timestamp (in milliseconds) when app-server emitted this notification.", - ), - ] = None - method: Annotated[Literal["item/started"], Field(title="Item/startedNotificationMethod")] - params: ItemStartedNotification - - -class ItemCompletedServerNotification(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - emitted_at_ms: Annotated[ - int | None, - Field( - alias="emittedAtMs", - description="Unix timestamp (in milliseconds) when app-server emitted this notification.", - ), - ] = None - method: Annotated[Literal["item/completed"], Field(title="Item/completedNotificationMethod")] - params: ItemCompletedNotification - - class ItemFileChangePatchUpdatedServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -9609,6 +10801,42 @@ class ExternalAgentConfigImportCompletedServerNotification(BaseModel): params: ExternalAgentConfigImportCompletedNotification +class ThreadRealtimeItemStartedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["thread/realtime/item/started"], + Field(title="Thread/realtime/item/startedNotificationMethod"), + ] + params: ThreadRealtimeItemStartedNotification + + +class ThreadRealtimeItemCompletedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[ + Literal["thread/realtime/item/completed"], + Field(title="Thread/realtime/item/completedNotificationMethod"), + ] + params: ThreadRealtimeItemCompletedNotification + + class WindowsSandboxSetupCompletedServerNotification(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -9642,6 +10870,139 @@ class SessionSource(RootModel[SessionSourceValue | CustomSessionSource | SubAgen root: SessionSourceValue | CustomSessionSource | SubAgentSessionSource +class FunctionCallOutputThreadItem(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: str + name: str + namespace: str | None = None + output: FunctionCallOutputBody + type: Annotated[Literal["functionCallOutput"], Field(title="FunctionCallOutputThreadItemType")] + + +class ThreadItem( + RootModel[ + UserMessageThreadItem + | HookPromptThreadItem + | AgentMessageThreadItem + | FunctionCallOutputThreadItem + | PlanThreadItem + | ReasoningThreadItem + | CommandExecutionThreadItem + | FileChangeThreadItem + | McpToolCallThreadItem + | DynamicToolCallThreadItem + | CollabAgentToolCallThreadItem + | SubAgentActivityThreadItem + | WebSearchThreadItem + | ImageViewThreadItem + | SleepThreadItem + | ImageGenerationThreadItem + | EnteredReviewModeThreadItem + | ExitedReviewModeThreadItem + | ContextCompactionThreadItem + ] +): + model_config = ConfigDict( + populate_by_name=True, + ) + root: ( + UserMessageThreadItem + | HookPromptThreadItem + | AgentMessageThreadItem + | FunctionCallOutputThreadItem + | PlanThreadItem + | ReasoningThreadItem + | CommandExecutionThreadItem + | FileChangeThreadItem + | McpToolCallThreadItem + | DynamicToolCallThreadItem + | CollabAgentToolCallThreadItem + | SubAgentActivityThreadItem + | WebSearchThreadItem + | ImageViewThreadItem + | SleepThreadItem + | ImageGenerationThreadItem + | EnteredReviewModeThreadItem + | ExitedReviewModeThreadItem + | ContextCompactionThreadItem + ) + + +class ThreadItemEntry(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + item: ThreadItem + turn_id: Annotated[str, Field(alias="turnId", description="Turn containing this item.")] + + +class ThreadItemsListResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + backwards_cursor: Annotated[ + str | None, + Field( + alias="backwardsCursor", + description="Opaque cursor to pass as `cursor` when reversing `sortDirection`. This is only populated when the page contains at least one item.", + ), + ] = None + data: list[ThreadItemEntry] + next_cursor: Annotated[ + str | None, + Field( + alias="nextCursor", + description="Opaque cursor to pass to the next call to continue after the last item. if None, there are no more items to return.", + ), + ] = None + + +class ItemThreadTimelineEntry(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + item: ThreadItem + position: Annotated[int, Field(ge=0)] + turn_id: Annotated[str, Field(alias="turnId")] + type: Annotated[Literal["item"], Field(title="ItemThreadTimelineEntryType")] + + +class TurnCompletedThreadTimelineEntry(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + completed_at: int | None = None + duration_ms: int | None = None + error: TurnError | None = None + position: Annotated[int, Field(ge=0)] + started_at: int | None = None + status: TurnStatus + turn_id: str + type: Annotated[Literal["turnCompleted"], Field(title="TurnCompletedThreadTimelineEntryType")] + + +class ThreadTimelineEntry( + RootModel[ + ItemThreadTimelineEntry + | RealtimeThreadTimelineEntry + | TurnStartedThreadTimelineEntry + | TurnCompletedThreadTimelineEntry + ] +): + model_config = ConfigDict( + populate_by_name=True, + ) + root: Annotated[ + ItemThreadTimelineEntry + | RealtimeThreadTimelineEntry + | TurnStartedThreadTimelineEntry + | TurnCompletedThreadTimelineEntry, + Field(description="EXPERIMENTAL - one item or turn boundary in canonical rollout order."), + ] + + class Turn(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -9705,6 +11066,15 @@ class TurnStartedNotification(BaseModel): turn: Turn +class TurnToolOutput(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + name: str + namespace: str | None = None + output: FunctionCallOutputBody + + class TurnsPage(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -9752,7 +11122,13 @@ class ConfigRequirements(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + additional_developer_instructions: Annotated[ + str | None, Field(alias="additionalDeveloperInstructions") + ] = None allow_appshots: Annotated[bool | None, Field(alias="allowAppshots")] = None + allow_browser_and_computer_use: Annotated[ + bool | None, Field(alias="allowBrowserAndComputerUse") + ] = None allow_login_shell: Annotated[bool | None, Field(alias="allowLoginShell")] = None allow_managed_hooks_only: Annotated[bool | None, Field(alias="allowManagedHooksOnly")] = None allow_remote_control: Annotated[bool | None, Field(alias="allowRemoteControl")] = None @@ -9771,10 +11147,15 @@ class ConfigRequirements(BaseModel): allowed_windows_sandbox_implementations: Annotated[ list[WindowsSandboxSetupMode] | None, Field(alias="allowedWindowsSandboxImplementations") ] = None + auto_review: Annotated[AutoReviewRequirements | None, Field(alias="autoReview")] = None browser_use: Annotated[BrowserUseRequirements | None, Field(alias="browserUse")] = None + chatgpt_base_url: Annotated[str | None, Field(alias="chatgptBaseUrl")] = None check_for_update_on_startup: Annotated[bool | None, Field(alias="checkForUpdateOnStartup")] = ( None ) + cli_auth_credentials_store: Annotated[ + CliAuthCredentialsStoreMode | None, Field(alias="cliAuthCredentialsStore") + ] = None computer_use: Annotated[ComputerUseRequirements | None, Field(alias="computerUse")] = None default_permissions: Annotated[str | None, Field(alias="defaultPermissions")] = None enforce_residency: Annotated[ResidencyRequirement | None, Field(alias="enforceResidency")] = ( @@ -9784,6 +11165,7 @@ class ConfigRequirements(BaseModel): None ) feedback: FeedbackRequirements | None = None + in_app_browser: Annotated[InAppBrowserRequirements | None, Field(alias="inAppBrowser")] = None log_dir: Annotated[str | None, Field(alias="logDir")] = None model_catalog_json: Annotated[str | None, Field(alias="modelCatalogJson")] = None models: ModelsRequirements | None = None @@ -9867,6 +11249,38 @@ class ExternalAgentConfigImportParams(BaseModel): ] = None +class ItemCompletedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + completed_at_ms: Annotated[ + int, + Field( + alias="completedAtMs", + description="Unix timestamp (in milliseconds) when this item lifecycle completed.", + ), + ] + item: ThreadItem + thread_id: Annotated[str, Field(alias="threadId")] + turn_id: Annotated[str, Field(alias="turnId")] + + +class ItemStartedNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + item: ThreadItem + started_at_ms: Annotated[ + int, + Field( + alias="startedAtMs", + description="Unix timestamp (in milliseconds) when this item lifecycle started.", + ), + ] + thread_id: Annotated[str, Field(alias="threadId")] + turn_id: Annotated[str, Field(alias="turnId")] + + class PluginDetail(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -9994,6 +11408,36 @@ class TurnCompletedServerNotification(BaseModel): params: TurnCompletedNotification +class ItemStartedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[Literal["item/started"], Field(title="Item/startedNotificationMethod")] + params: ItemStartedNotification + + +class ItemCompletedServerNotification(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + emitted_at_ms: Annotated[ + int | None, + Field( + alias="emittedAtMs", + description="Unix timestamp (in milliseconds) when app-server emitted this notification.", + ), + ] = None + method: Annotated[Literal["item/completed"], Field(title="Item/completedNotificationMethod")] + params: ItemCompletedNotification + + class Thread(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -10043,9 +11487,22 @@ class Thread(BaseModel): description="Optional Git metadata captured when the thread was created.", ), ] = None + history_mode: Annotated[ + ThreadHistoryMode | None, + Field( + alias="historyMode", + description="Persisted thread history contract selected when this thread was created.", + ), + ] = "legacy" id: Annotated[ str, Field(description="Identifier for this thread. Codex-generated thread IDs are UUIDv7.") ] + model: Annotated[ + str | None, + Field( + description="Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry." + ), + ] = None model_provider: Annotated[ str, Field( @@ -10054,6 +11511,12 @@ class Thread(BaseModel): ), ] name: Annotated[str | None, Field(description="Optional user-facing thread title.")] = None + originator: Annotated[ + str | None, + Field( + description="Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable." + ), + ] = None parent_thread_id: Annotated[ str | None, Field( @@ -10065,6 +11528,20 @@ class Thread(BaseModel): preview: Annotated[ str, Field(description="Usually the first user message in the thread, if available.") ] + project_id: Annotated[ + str | None, + Field( + alias="projectId", + description="Canonical project assignment owned by app-server, if any.", + ), + ] = None + reasoning_effort: Annotated[ + ReasoningEffort | None, + Field( + alias="reasoningEffort", + description="Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + ), + ] = None recency_at: Annotated[ int | None, Field( @@ -10207,6 +11684,13 @@ class ThreadResumeResponse(BaseModel): description="Environment-native paths to instruction source files currently loaded for this thread.", ), ] = [] + items_backwards_cursor: Annotated[ + str | None, + Field( + alias="itemsBackwardsCursor", + description='Opaque cursor for hydrating paginated items backwards.\n\nPass this as `cursor` to `thread/items/list` with `sortDirection: "desc"`. The first page includes the item identified by the cursor.', + ), + ] = None model: str model_provider: Annotated[str, Field(alias="modelProvider")] reasoning_effort: Annotated[ReasoningEffort | None, Field(alias="reasoningEffort")] = None @@ -10218,6 +11702,39 @@ class ThreadResumeResponse(BaseModel): ] service_tier: Annotated[str | None, Field(alias="serviceTier")] = None thread: Thread + turns_backwards_cursor: Annotated[ + str | None, + Field( + alias="turnsBackwardsCursor", + description='Opaque cursor for hydrating paginated turns backwards.\n\nPass this as `cursor` to `thread/turns/list` with `sortDirection: "desc"`. The first page includes the turn identified by the cursor.', + ), + ] = None + + +class ThreadRevertResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + items_backwards_cursor: Annotated[ + str | None, + Field( + alias="itemsBackwardsCursor", + description='Opaque cursor for hydrating paginated items backwards.\n\nPass this as `cursor` to `thread/items/list` with `sortDirection: "desc"`. The first page includes the item identified by the cursor.', + ), + ] = None + thread: Annotated[ + Thread, + Field( + description="Updated loaded thread metadata. `turns` is always empty; hydrate retained history through `thread/turns/list`." + ), + ] + turns_backwards_cursor: Annotated[ + str | None, + Field( + alias="turnsBackwardsCursor", + description='Opaque cursor for hydrating paginated turns backwards.\n\nPass this as `cursor` to `thread/turns/list` with `sortDirection: "desc"`. The first page includes the turn identified by the cursor.', + ), + ] = None class ThreadRollbackResponse(BaseModel): @@ -10280,6 +11797,27 @@ class ThreadStartedNotification(BaseModel): thread: Thread +class ThreadTurnsListResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + backwards_cursor: Annotated[ + str | None, + Field( + alias="backwardsCursor", + description="Opaque cursor to pass as `cursor` when reversing `sortDirection`. This is only populated when the page contains at least one turn. Use it with the opposite `sortDirection` to include the anchor turn again and catch updates to that turn.", + ), + ] = None + data: list[Turn] + next_cursor: Annotated[ + str | None, + Field( + alias="nextCursor", + description="Opaque cursor to pass to the next call to continue after the last turn. if None, there are no more turns to return.", + ), + ] = None + + class ThreadUnarchiveResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -10287,6 +11825,93 @@ class ThreadUnarchiveResponse(BaseModel): thread: Thread +class TurnStartParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + approval_policy: Annotated[ + AskForApproval | None, + Field( + alias="approvalPolicy", + description="Override the approval policy for this turn and subsequent turns.", + ), + ] = None + approvals_reviewer: Annotated[ + ApprovalsReviewer | None, + Field( + alias="approvalsReviewer", + description="Override where approval requests are routed for review on this turn and subsequent turns.", + ), + ] = None + client_user_message_id: Annotated[str | None, Field(alias="clientUserMessageId")] = None + cwd: Annotated[ + str | None, + Field(description="Override the working directory for this turn and subsequent turns."), + ] = None + effort: Annotated[ + ReasoningEffort | None, + Field(description="Override the reasoning effort for this turn and subsequent turns."), + ] = None + input: list[UserInput] + model: Annotated[ + str | None, Field(description="Override the model for this turn and subsequent turns.") + ] = None + output_schema: Annotated[ + Any | None, + Field( + alias="outputSchema", + description="Optional JSON Schema used to constrain the final assistant message for this turn.", + ), + ] = None + personality: Annotated[ + Personality | None, + Field(description="Override the personality for this turn and subsequent turns."), + ] = None + sandbox_policy: Annotated[ + SandboxPolicy | None, + Field( + alias="sandboxPolicy", + description="Override the sandbox policy for this turn and subsequent turns.", + ), + ] = None + service_tier: Annotated[ + str | None, + Field( + alias="serviceTier", + description="Override the service tier for this turn and subsequent turns.", + ), + ] = None + service_tier_for_turn: Annotated[ + str | None, + Field( + alias="serviceTierForTurn", + description="Override the service tier only when this request starts a new turn. Use \"default\" for standard speed. Omitted or null inherits the thread's tier. Does not change the thread's tier or a turn being steered.", + ), + ] = None + summary: Annotated[ + ReasoningSummary | None, + Field(description="Override the reasoning summary for this turn and subsequent turns."), + ] = None + thread_id: Annotated[str, Field(alias="threadId")] + tool_output: Annotated[TurnToolOutput | None, Field(alias="toolOutput")] = None + turn_trigger: Annotated[ + str | None, + Field( + alias="turnTrigger", + description="Optional source classification for the caller that starts this turn. Ignored when this request steers an already-active turn.", + ), + ] = None + + +class TurnStartRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["turn/start"], Field(title="Turn/startRequestMethod")] + params: TurnStartParams + + class ExternalAgentConfigImportRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -10331,6 +11956,7 @@ class ClientRequest( | ThreadShellCommandRequest | ThreadApproveGuardianDeniedActionRequest | ThreadRollbackRequest + | ThreadRevertRequest | ThreadListRequest | ThreadSectionListRequest | ThreadSectionCreateRequest @@ -10338,6 +11964,8 @@ class ClientRequest( | ThreadSectionDeleteRequest | ThreadLoadedListRequest | ThreadReadRequest + | ThreadTurnsListRequest + | ThreadItemsListRequest | ThreadInjectItemsRequest | SkillsListRequest | SkillsExtraRootsSetRequest @@ -10347,6 +11975,7 @@ class ClientRequest( | MarketplaceUpgradeRequest | PluginListRequest | PluginInstalledRequest + | PluginReconcileRequest | PluginReadRequest | PluginSkillReadRequest | PluginShareSaveRequest @@ -10432,6 +12061,7 @@ class ClientRequest( | ThreadShellCommandRequest | ThreadApproveGuardianDeniedActionRequest | ThreadRollbackRequest + | ThreadRevertRequest | ThreadListRequest | ThreadSectionListRequest | ThreadSectionCreateRequest @@ -10439,6 +12069,8 @@ class ClientRequest( | ThreadSectionDeleteRequest | ThreadLoadedListRequest | ThreadReadRequest + | ThreadTurnsListRequest + | ThreadItemsListRequest | ThreadInjectItemsRequest | SkillsListRequest | SkillsExtraRootsSetRequest @@ -10448,6 +12080,7 @@ class ClientRequest( | MarketplaceUpgradeRequest | PluginListRequest | PluginInstalledRequest + | PluginReconcileRequest | PluginReadRequest | PluginSkillReadRequest | PluginShareSaveRequest @@ -10529,6 +12162,7 @@ class GuardianApprovalReviewAction( RootModel[ CommandGuardianApprovalReviewAction | ExecveGuardianApprovalReviewAction + | WriteStdinGuardianApprovalReviewAction | ApplyPatchGuardianApprovalReviewAction | NetworkAccessGuardianApprovalReviewAction | McpToolCallGuardianApprovalReviewAction @@ -10541,6 +12175,7 @@ class GuardianApprovalReviewAction( root: ( CommandGuardianApprovalReviewAction | ExecveGuardianApprovalReviewAction + | WriteStdinGuardianApprovalReviewAction | ApplyPatchGuardianApprovalReviewAction | NetworkAccessGuardianApprovalReviewAction | McpToolCallGuardianApprovalReviewAction @@ -10576,7 +12211,7 @@ class ItemGuardianApprovalReviewCompletedNotification(BaseModel): str | None, Field( alias="targetItemId", - description="Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", + description="Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - stdin reviews, which refer to the existing parent command item and have a separate approval ID in the action payload - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", ), ] = None thread_id: Annotated[str, Field(alias="threadId")] @@ -10603,7 +12238,7 @@ class ItemGuardianApprovalReviewStartedNotification(BaseModel): str | None, Field( alias="targetItemId", - description="Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", + description="Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - stdin reviews, which refer to the existing parent command item and have a separate approval ID in the action payload - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", ), ] = None thread_id: Annotated[str, Field(alias="threadId")] @@ -10691,10 +12326,14 @@ class ServerNotification( | ThreadDeletedServerNotification | ThreadUnarchivedServerNotification | ThreadClosedServerNotification + | ThreadRevertedServerNotification | SkillsChangedServerNotification | ThreadNameUpdatedServerNotification | ThreadGoalUpdatedServerNotification | ThreadGoalClearedServerNotification + | ThreadQueueChangedServerNotification + | ProjectChangedServerNotification + | ThreadProjectUpdatedServerNotification | ThreadEnvironmentConnectedServerNotification | ThreadEnvironmentDisconnectedServerNotification | ThreadSettingsUpdatedServerNotification @@ -10708,6 +12347,7 @@ class ServerNotification( | ItemStartedServerNotification | ItemAutoApprovalReviewStartedServerNotification | ItemAutoApprovalReviewCompletedServerNotification + | AutoApprovalReviewStrictReviewRequiredServerNotification | ItemCompletedServerNotification | ItemAgentMessageDeltaServerNotification | ItemPlanDeltaServerNotification @@ -10722,6 +12362,7 @@ class ServerNotification( | ItemMcpToolCallProgressServerNotification | McpServerOauthLoginCompletedServerNotification | McpServerStartupStatusUpdatedServerNotification + | McpServerEventStreamNotificationServerNotification | AccountUpdatedServerNotification | AccountRateLimitsUpdatedServerNotification | AppListUpdatedServerNotification @@ -10735,6 +12376,8 @@ class ServerNotification( | ThreadCompactedServerNotification | ModelReroutedServerNotification | ModelVerificationServerNotification + | ModelProviderAuthRecoveryStartedServerNotification + | ModelProviderAuthRecoveryCompletedServerNotification | TurnModerationMetadataServerNotification | ModelSafetyBufferingUpdatedServerNotification | WarningServerNotification @@ -10745,6 +12388,9 @@ class ServerNotification( | FuzzyFileSearchSessionCompletedServerNotification | ThreadRealtimeStartedServerNotification | ThreadRealtimeItemAddedServerNotification + | ThreadRealtimeItemStartedServerNotification + | ThreadRealtimeItemTranscriptDeltaServerNotification + | ThreadRealtimeItemCompletedServerNotification | ThreadRealtimeTranscriptDeltaServerNotification | ThreadRealtimeTranscriptDoneServerNotification | ThreadRealtimeOutputAudioDeltaServerNotification @@ -10767,10 +12413,14 @@ class ServerNotification( | ThreadDeletedServerNotification | ThreadUnarchivedServerNotification | ThreadClosedServerNotification + | ThreadRevertedServerNotification | SkillsChangedServerNotification | ThreadNameUpdatedServerNotification | ThreadGoalUpdatedServerNotification | ThreadGoalClearedServerNotification + | ThreadQueueChangedServerNotification + | ProjectChangedServerNotification + | ThreadProjectUpdatedServerNotification | ThreadEnvironmentConnectedServerNotification | ThreadEnvironmentDisconnectedServerNotification | ThreadSettingsUpdatedServerNotification @@ -10784,6 +12434,7 @@ class ServerNotification( | ItemStartedServerNotification | ItemAutoApprovalReviewStartedServerNotification | ItemAutoApprovalReviewCompletedServerNotification + | AutoApprovalReviewStrictReviewRequiredServerNotification | ItemCompletedServerNotification | ItemAgentMessageDeltaServerNotification | ItemPlanDeltaServerNotification @@ -10798,6 +12449,7 @@ class ServerNotification( | ItemMcpToolCallProgressServerNotification | McpServerOauthLoginCompletedServerNotification | McpServerStartupStatusUpdatedServerNotification + | McpServerEventStreamNotificationServerNotification | AccountUpdatedServerNotification | AccountRateLimitsUpdatedServerNotification | AppListUpdatedServerNotification @@ -10811,6 +12463,8 @@ class ServerNotification( | ThreadCompactedServerNotification | ModelReroutedServerNotification | ModelVerificationServerNotification + | ModelProviderAuthRecoveryStartedServerNotification + | ModelProviderAuthRecoveryCompletedServerNotification | TurnModerationMetadataServerNotification | ModelSafetyBufferingUpdatedServerNotification | WarningServerNotification @@ -10821,6 +12475,9 @@ class ServerNotification( | FuzzyFileSearchSessionCompletedServerNotification | ThreadRealtimeStartedServerNotification | ThreadRealtimeItemAddedServerNotification + | ThreadRealtimeItemStartedServerNotification + | ThreadRealtimeItemTranscriptDeltaServerNotification + | ThreadRealtimeItemCompletedServerNotification | ThreadRealtimeTranscriptDeltaServerNotification | ThreadRealtimeTranscriptDoneServerNotification | ThreadRealtimeOutputAudioDeltaServerNotification diff --git a/sdk/python/src/openai_codex/models.py b/sdk/python/src/openai_codex/models.py index d9d15dc684..c3c8a0894d 100644 --- a/sdk/python/src/openai_codex/models.py +++ b/sdk/python/src/openai_codex/models.py @@ -5,38 +5,41 @@ from typing import TypeAlias from pydantic import BaseModel +from .generated.notification_registry import KnownNotificationPayload as _KnownNotificationPayload + +# Preserve the notification names previously importable from this module. from .generated.v2_all import ( - AccountLoginCompletedNotification, - AccountRateLimitsUpdatedNotification, - AccountUpdatedNotification, - AgentMessageDeltaNotification, - AppListUpdatedNotification, - CommandExecutionOutputDeltaNotification, - ConfigWarningNotification, - ContextCompactedNotification, - DeprecationNoticeNotification, - ErrorNotification, - FileChangeOutputDeltaNotification, - ItemCompletedNotification, - ItemStartedNotification, - McpServerOauthLoginCompletedNotification, - McpToolCallProgressNotification, - PlanDeltaNotification, - RawResponseItemCompletedNotification, - ReasoningSummaryPartAddedNotification, - ReasoningSummaryTextDeltaNotification, - ReasoningTextDeltaNotification, - TerminalInteractionNotification, - ThreadGoalClearedNotification, - ThreadGoalUpdatedNotification, - ThreadNameUpdatedNotification, - ThreadStartedNotification, - ThreadTokenUsageUpdatedNotification, - TurnCompletedNotification, - TurnDiffUpdatedNotification, - TurnPlanUpdatedNotification, - TurnStartedNotification, - WindowsWorldWritableWarningNotification, + AccountLoginCompletedNotification as AccountLoginCompletedNotification, + AccountRateLimitsUpdatedNotification as AccountRateLimitsUpdatedNotification, + AccountUpdatedNotification as AccountUpdatedNotification, + AgentMessageDeltaNotification as AgentMessageDeltaNotification, + AppListUpdatedNotification as AppListUpdatedNotification, + CommandExecutionOutputDeltaNotification as CommandExecutionOutputDeltaNotification, + ConfigWarningNotification as ConfigWarningNotification, + ContextCompactedNotification as ContextCompactedNotification, + DeprecationNoticeNotification as DeprecationNoticeNotification, + ErrorNotification as ErrorNotification, + FileChangeOutputDeltaNotification as FileChangeOutputDeltaNotification, + ItemCompletedNotification as ItemCompletedNotification, + ItemStartedNotification as ItemStartedNotification, + McpServerOauthLoginCompletedNotification as McpServerOauthLoginCompletedNotification, + McpToolCallProgressNotification as McpToolCallProgressNotification, + PlanDeltaNotification as PlanDeltaNotification, + RawResponseItemCompletedNotification as RawResponseItemCompletedNotification, + ReasoningSummaryPartAddedNotification as ReasoningSummaryPartAddedNotification, + ReasoningSummaryTextDeltaNotification as ReasoningSummaryTextDeltaNotification, + ReasoningTextDeltaNotification as ReasoningTextDeltaNotification, + TerminalInteractionNotification as TerminalInteractionNotification, + ThreadGoalClearedNotification as ThreadGoalClearedNotification, + ThreadGoalUpdatedNotification as ThreadGoalUpdatedNotification, + ThreadNameUpdatedNotification as ThreadNameUpdatedNotification, + ThreadStartedNotification as ThreadStartedNotification, + ThreadTokenUsageUpdatedNotification as ThreadTokenUsageUpdatedNotification, + TurnCompletedNotification as TurnCompletedNotification, + TurnDiffUpdatedNotification as TurnDiffUpdatedNotification, + TurnPlanUpdatedNotification as TurnPlanUpdatedNotification, + TurnStartedNotification as TurnStartedNotification, + WindowsWorldWritableWarningNotification as WindowsWorldWritableWarningNotification, ) JsonScalar: TypeAlias = str | int | float | bool | None @@ -49,39 +52,9 @@ class UnknownNotification: params: JsonObject +# Preserve the existing raw-item type, which app-server omits from its notification schema. NotificationPayload: TypeAlias = ( - AccountLoginCompletedNotification - | AccountRateLimitsUpdatedNotification - | AccountUpdatedNotification - | AgentMessageDeltaNotification - | AppListUpdatedNotification - | CommandExecutionOutputDeltaNotification - | ConfigWarningNotification - | ContextCompactedNotification - | DeprecationNoticeNotification - | ErrorNotification - | FileChangeOutputDeltaNotification - | ItemCompletedNotification - | ItemStartedNotification - | McpServerOauthLoginCompletedNotification - | McpToolCallProgressNotification - | PlanDeltaNotification - | RawResponseItemCompletedNotification - | ReasoningSummaryPartAddedNotification - | ReasoningSummaryTextDeltaNotification - | ReasoningTextDeltaNotification - | TerminalInteractionNotification - | ThreadNameUpdatedNotification - | ThreadGoalClearedNotification - | ThreadGoalUpdatedNotification - | ThreadStartedNotification - | ThreadTokenUsageUpdatedNotification - | TurnCompletedNotification - | TurnDiffUpdatedNotification - | TurnPlanUpdatedNotification - | TurnStartedNotification - | WindowsWorldWritableWarningNotification - | UnknownNotification + _KnownNotificationPayload | RawResponseItemCompletedNotification | UnknownNotification ) diff --git a/sdk/python/tests/test_artifact_workflow_and_binaries.py b/sdk/python/tests/test_artifact_workflow_and_binaries.py index 936e85d179..da567cd026 100644 --- a/sdk/python/tests/test_artifact_workflow_and_binaries.py +++ b/sdk/python/tests/test_artifact_workflow_and_binaries.py @@ -10,9 +10,13 @@ import urllib.error from pathlib import Path import pytest -import tomllib from pydantic import ValidationError +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib + ROOT = Path(__file__).resolve().parents[1] @@ -378,38 +382,76 @@ def test_generate_types_wires_all_generation_steps() -> None: ] -def _load_runtime_schema_bundle(tmp_path: Path) -> dict: - """Ask the pinned runtime package for a real schema bundle used by tests.""" +@pytest.mark.parametrize("schema_override", [None, "override-schema"]) +def test_generation_resolves_configured_schema_and_explicit_override( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, schema_override: str | None +) -> None: script = _load_update_script_module() - schema_dir = script.generate_schema_from_pinned_runtime(tmp_path / "schema") + sdk_dir = tmp_path / "sdk" / "python" + sdk_dir.mkdir(parents=True) + monkeypatch.setattr(script, "sdk_root", lambda: sdk_dir) + monkeypatch.chdir(tmp_path) + selected_schemas: list[Path] = [] + monkeypatch.setattr(script, "generate_types_from_schema_dir", selected_schemas.append) + args = ["generate-types"] + if schema_override is None: + (sdk_dir / "pyproject.toml").write_text( + '[tool.codex.codegen]\nschema-dir = "../../configured-schema"\n' + ) + expected_schema = tmp_path / "configured-schema" + else: + args.extend(["--schema-dir", schema_override]) + expected_schema = tmp_path / schema_override + + script.main(args) + + assert selected_schemas == [expected_schema] + + +def _load_repository_schema_bundle() -> dict: + """Read the repository app-server schema bundle used by generation.""" + script = _load_update_script_module() + pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text()) + schema_dir = ROOT / pyproject["tool"]["codex"]["codegen"]["schema-dir"] return json.loads(script.schema_bundle_path(schema_dir).read_text()) -def test_schema_normalization_only_flattens_string_literal_oneofs( - tmp_path: Path, -) -> None: - """Schema normalization should only flatten the enum-shaped oneOf variants.""" +def test_schema_normalization_flattens_string_literal_oneofs() -> None: script = _load_update_script_module() - schema = _load_runtime_schema_bundle(tmp_path) - definitions = schema["definitions"] - flattened = [ - name - for name, definition in definitions.items() - if isinstance(definition, dict) and script._flatten_string_enum_one_of(definition.copy()) - ] + definition = { + "title": "Mode", + "description": "Allowed modes.", + "oneOf": [ + {"type": "string", "enum": ["first"]}, + {"type": "string", "enum": ["second"]}, + ], + } - assert sorted(flattened) == [ - "AuthMode", - "AutoCompactTokenLimitScope", - "CommandExecOutputStream", - "ConsumeAccountRateLimitResetCreditOutcome", - "ExperimentalFeatureStage", - "InputModality", - "MessagePhase", - "PluginAvailability", - "ProcessOutputStream", - "TurnItemsView", - ] + assert script._flatten_string_enum_one_of(definition) + assert definition == { + "title": "Mode", + "description": "Allowed modes.", + "type": "string", + "enum": ["first", "second"], + } + + +@pytest.mark.parametrize( + "branch", + [ + {"type": "object", "properties": {"value": {"type": "string"}}}, + {"type": "string", "enum": ["first", "second"]}, + {"type": "string", "enum": [1]}, + {"type": "string", "enum": ["second"], "minLength": 2}, + ], +) +def test_schema_normalization_preserves_nonliteral_unions(branch: dict) -> None: + script = _load_update_script_module() + definition = {"oneOf": [{"type": "string", "enum": ["first"]}, branch]} + original = json.loads(json.dumps(definition)) + + assert not script._flatten_string_enum_one_of(definition) + assert definition == original def test_schema_normalization_makes_chatgpt_account_email_nullable() -> None: @@ -438,12 +480,10 @@ def test_schema_normalization_makes_chatgpt_account_email_nullable() -> None: assert "email" in chatgpt_account["required"] -def test_python_codegen_schema_annotation_adds_stable_variant_titles( - tmp_path: Path, -) -> None: +def test_python_codegen_schema_annotation_adds_stable_variant_titles() -> None: """Schema annotations should give generated protocol classes stable names.""" script = _load_update_script_module() - schema = _load_runtime_schema_bundle(tmp_path) + schema = _load_repository_schema_bundle() script._annotate_schema(schema) definitions = schema["definitions"] @@ -525,25 +565,6 @@ def test_runtime_distribution_name_is_consistent() -> None: ) -def test_source_sdk_template_pins_published_runtime() -> None: - """The source template should carry a development version and reviewed runtime pin.""" - script = _load_update_script_module() - pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text()) - - assert { - "sdk_template_version": pyproject["project"]["version"], - "runtime_pin": script.pinned_runtime_version(), - "dependencies": pyproject["project"]["dependencies"], - } == { - "sdk_template_version": "0.0.0-dev", - "runtime_pin": "0.147.0", - "dependencies": [ - "pydantic>=2.12", - "openai-codex-cli-bin==0.147.0", - ], - } - - def test_source_sdk_package_declares_stable_documentation() -> None: """Public package metadata should link stable docs.""" pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text()) @@ -858,11 +879,24 @@ def test_runtime_package_layout_is_included_by_wheel_config( ] -def test_stage_sdk_release_preserves_reviewed_runtime_pin(tmp_path: Path) -> None: +def test_stage_sdk_release_packages_reviewed_artifacts(tmp_path: Path) -> None: script = _load_update_script_module() - staged = script.stage_python_sdk_package( - tmp_path / "sdk-stage", - "0.147.0", + staged = tmp_path / "sdk-stage" + source_project = tomllib.loads((ROOT / "pyproject.toml").read_text()) + generated_paths = [ + "src/openai_codex/generated/v2_all.py", + "src/openai_codex/generated/notification_registry.py", + "src/openai_codex/api.py", + ] + reviewed_artifacts = {path: (ROOT / path).read_bytes() for path in generated_paths} + + script.main( + [ + "stage-sdk", + str(staged), + "--sdk-version", + "0.153.0", + ] ) pyproject = tomllib.loads((staged / "pyproject.toml").read_text()) @@ -872,12 +906,10 @@ def test_stage_sdk_release_preserves_reviewed_runtime_pin(tmp_path: Path) -> Non "dependencies": pyproject["project"]["dependencies"], } == { "name": "openai-codex", - "version": "0.147.0", - "dependencies": [ - "pydantic>=2.12", - "openai-codex-cli-bin==0.147.0", - ], + "version": "0.153.0", + "dependencies": source_project["project"]["dependencies"], } + assert {path: (staged / path).read_bytes() for path in generated_paths} == reviewed_artifacts assert ( '__version__ = "0.147.0"' not in (staged / "src" / "openai_codex" / "__init__.py").read_text() @@ -933,48 +965,6 @@ def test_sdk_release_matches_stable_runtime(tmp_path: Path) -> None: } -def test_stage_sdk_runs_type_generation_before_staging(tmp_path: Path) -> None: - script = _load_update_script_module() - calls: list[str] = [] - args = script.parse_args( - [ - "stage-sdk", - str(tmp_path / "sdk-stage"), - "--sdk-version", - "0.147.0", - ] - ) - - def fake_generate_types() -> None: - calls.append("generate_types") - - def fake_stage_sdk_package(_staging_dir: Path, sdk_version: str) -> Path: - calls.append(f"stage_sdk:{sdk_version}") - return tmp_path / "sdk-stage" - - def fake_stage_runtime_package( - _staging_dir: Path, - _runtime_version: str, - _package_dir: Path, - _platform_tag: str | None, - ) -> Path: - raise AssertionError("runtime staging should not run for stage-sdk") - - def fake_current_sdk_version() -> str: - return "0.116.0a1" - - ops = script.CliOps( - generate_types=fake_generate_types, - stage_python_sdk_package=fake_stage_sdk_package, - stage_python_runtime_package=fake_stage_runtime_package, - current_sdk_version=fake_current_sdk_version, - ) - - script.run_command(args, ops) - - assert calls == ["generate_types", "stage_sdk:0.147.0"] - - def test_stage_runtime_stages_package_without_type_generation(tmp_path: Path) -> None: script = _load_update_script_module() package_archive = _write_fake_codex_package_archive(tmp_path, script) @@ -991,7 +981,7 @@ def test_stage_runtime_stages_package_without_type_generation(tmp_path: Path) -> ] ) - def fake_generate_types() -> None: + def fake_generate_types(_schema_dir: Path) -> None: calls.append("generate_types") def fake_stage_sdk_package(_staging_dir: Path, _codex_version: str) -> Path: @@ -1006,14 +996,10 @@ def test_stage_runtime_stages_package_without_type_generation(tmp_path: Path) -> calls.append(f"stage_runtime:{codex_version}:{platform_tag}:{package_archive.name}") return tmp_path / "runtime-stage" - def fake_current_sdk_version() -> str: - return "0.116.0a1" - ops = script.CliOps( generate_types=fake_generate_types, stage_python_sdk_package=fake_stage_sdk_package, stage_python_runtime_package=fake_stage_runtime_package, - current_sdk_version=fake_current_sdk_version, ) script.run_command(args, ops) diff --git a/sdk/python/tests/test_client_rpc_methods.py b/sdk/python/tests/test_client_rpc_methods.py index a6ec750cd3..e055e551c4 100644 --- a/sdk/python/tests/test_client_rpc_methods.py +++ b/sdk/python/tests/test_client_rpc_methods.py @@ -1,22 +1,28 @@ from __future__ import annotations from pathlib import Path +from typing import get_type_hints import pytest from openai_codex.client import CodexClient, _params_dict from openai_codex.generated.notification_registry import notification_turn_id from openai_codex.generated.v2_all import ( + AbsolutePathBuf, AccountRateLimitsUpdatedNotification, AccountUpdatedNotification, AgentMessageDeltaNotification, + ApplyPatchGuardianApprovalReviewAction, ApprovalsReviewer, + AuthRecoveryNotification, + CommandGuardianApprovalReviewAction, GetAccountResponse, PlanType, ReasoningEffort, ReasoningEffortOption, ThreadForkParams, ThreadListParams, + ThreadQueueChangedNotification, ThreadResumeResponse, ThreadStartParams, ThreadTokenUsageUpdatedNotification, @@ -30,6 +36,31 @@ from openai_codex.types import ThreadSource ROOT = Path(__file__).resolve().parents[1] +@pytest.mark.parametrize( + ("model", "fields"), + [ + ( + CommandGuardianApprovalReviewAction, + {"type": "command", "command": "pwd", "source": "shell"}, + ), + ( + ApplyPatchGuardianApprovalReviewAction, + {"type": "applyPatch", "files": [AbsolutePathBuf("/workspace/file")]}, + ), + ], +) +def test_approval_review_paths_preserve_existing_wrappers(model, fields) -> None: + action = model(cwd=AbsolutePathBuf("/workspace"), **fields) + expected = { + **fields, + "cwd": "/workspace", + } + if "files" in expected: + expected["files"] = ["/workspace/file"] + assert action.model_dump(mode="json") == expected + assert isinstance(action.cwd, AbsolutePathBuf) + + def test_generated_params_models_are_snake_case_and_dump_by_alias() -> None: params = ThreadListParams(search_term="needle", limit=5) @@ -209,6 +240,44 @@ def test_unknown_notifications_fall_back_to_unknown_payloads() -> None: assert event.payload.params["msg"] == {"type": "turn_aborted"} +@pytest.mark.parametrize( + ("method", "params", "expected"), + [ + ( + "modelProvider/authRecoveryCompleted", + { + "provider": "openai", + "message": "Authentication recovered", + "threadId": "thread-1", + "turnId": "turn-1", + }, + AuthRecoveryNotification( + provider="openai", + message="Authentication recovered", + thread_id="thread-1", + turn_id="turn-1", + ), + ), + ( + "thread/queue/changed", + {"threadId": "thread-1"}, + ThreadQueueChangedNotification(thread_id="thread-1"), + ), + ("warning", {"message": "heads up"}, WarningNotification(message="heads up")), + ( + "future/notification", + {"newField": "value"}, + UnknownNotification(params={"newField": "value"}), + ), + ], +) +def test_decoded_notifications_match_the_declared_payload_type(method, params, expected) -> None: + event = CodexClient()._coerce_notification(method, params) + + assert event == Notification(method=method, payload=expected) + assert isinstance(event.payload, get_type_hints(Notification)["payload"]) + + def test_invalid_notification_payload_falls_back_to_unknown() -> None: client = CodexClient() event = client._coerce_notification("thread/tokenUsage/updated", {"threadId": "missing"}) diff --git a/sdk/python/tests/test_contract_generation.py b/sdk/python/tests/test_contract_generation.py index e7c9c91c1a..2b610c46bd 100644 --- a/sdk/python/tests/test_contract_generation.py +++ b/sdk/python/tests/test_contract_generation.py @@ -1,11 +1,13 @@ from __future__ import annotations -import importlib.metadata import os +import runpy import subprocess import sys from pathlib import Path +import pytest + ROOT = Path(__file__).resolve().parents[1] GENERATED_TARGETS = [ Path("src/openai_codex/generated/notification_registry.py"), @@ -35,14 +37,10 @@ def _snapshot_targets(root: Path) -> dict[str, dict[str, bytes] | bytes | None]: def test_generated_files_are_up_to_date(): - """Regenerating from the pinned runtime package should leave artifacts unchanged.""" + """Regenerating from repository schemas should leave reviewed artifacts unchanged.""" before = _snapshot_targets(ROOT) - # Regenerate contract artifacts via the pinned runtime package, not a local - # app-server binary from the checkout or CI environment. - assert importlib.metadata.version("openai-codex-cli-bin") == "0.147.0" env = os.environ.copy() - env.pop("CODEX_EXEC_PATH", None) python_bin = str(Path(sys.executable).parent) env["PATH"] = f"{python_bin}{os.pathsep}{env.get('PATH', '')}" @@ -55,3 +53,44 @@ def test_generated_files_are_up_to_date(): after = _snapshot_targets(ROOT) assert before == after, "Generated files drifted after regeneration" + + +@pytest.mark.parametrize("mode", ["repository", "scratch", "experimental"]) +def test_schema_refresh_only_updates_python_for_repository_schemas(monkeypatch, tmp_path, mode): + script = ROOT.parents[1] / "codex-rs/app-server-protocol/scripts/write_schema_fixtures.py" + arguments = { + "repository": [], + "scratch": ["--schema-root", str(tmp_path / "schema")], + "experimental": ["--experimental"], + }[mode] + calls = [] + monkeypatch.setattr(sys, "argv", [str(script), *arguments]) + monkeypatch.setattr(subprocess, "run", lambda args, **kwargs: calls.append((args, kwargs))) + + runpy.run_path(str(script), run_name="__main__") + + assert [args[0] for args, _kwargs in calls] == ( + ["cargo", "uv"] if mode == "repository" else ["cargo"] + ) + assert all(kwargs["check"] for _args, kwargs in calls) + if mode == "repository": + assert calls[1][0][-3:] == [ + "generate-types", + "--schema-dir", + str(ROOT.parents[1] / "codex-rs/app-server-protocol/schema/json"), + ] + + +def test_schema_generation_failure_does_not_update_python(monkeypatch): + script = ROOT.parents[1] / "codex-rs/app-server-protocol/scripts/write_schema_fixtures.py" + calls = [] + + def fail(args, **_kwargs): + calls.append(args[0]) + raise subprocess.CalledProcessError(1, args) + + monkeypatch.setattr(sys, "argv", [str(script)]) + monkeypatch.setattr(subprocess, "run", fail) + with pytest.raises(subprocess.CalledProcessError): + runpy.run_path(str(script), run_name="__main__") + assert calls == ["cargo"]