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"]