mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Accept unknown Codex error variants in the app-server protocol (#49806)
## Why New error variants should not prevent older clients from handling app-server errors. ## What changed - Deserialize unknown `CodexErrorInfo` strings and objects as `Other` in Rust, while preserving known variants and the `"other"` serialization. - Allow arbitrary strings and objects in the exported JSON schemas and TypeScript type. - Update Python SDK generation and generated models to accept unknown error strings and object payloads. ## Testing Add Rust tests for `"other"` round trips, unknown string and object fallbacks, known structured variants, and rejection of null, boolean, numeric, and array values. GitOrigin-RevId: 40c17896019924b2ef7970ee2205ea0154d0d1b3
This commit is contained in:
committed by
copyberry
parent
e7798c9944
commit
a5d56d8120
@@ -286,6 +286,18 @@ def _flatten_string_enum_one_of(definition: dict[str, Any]) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _normalize_open_ended_enum(definition: dict[str, Any]) -> None:
|
||||
variants = definition.pop("oneOf", definition.get("anyOf", []))
|
||||
definition["anyOf"] = [
|
||||
variant
|
||||
for variant in variants
|
||||
if "enum" in variant or variant.get("type") not in ("string", ["string", "object"])
|
||||
]
|
||||
object_fallback = {"type": "object", "additionalProperties": {}}
|
||||
if object_fallback not in definition["anyOf"]:
|
||||
definition["anyOf"].append(object_fallback)
|
||||
|
||||
|
||||
DISCRIMINATOR_KEYS = ("type", "method", "mode", "state", "status", "role", "reason")
|
||||
|
||||
|
||||
@@ -522,8 +534,10 @@ def _normalized_schema_bundle_text(schema_dir: Path) -> str:
|
||||
_preserve_guardian_approval_path_wrappers(schema)
|
||||
definitions = schema.get("definitions", {})
|
||||
if isinstance(definitions, dict):
|
||||
for definition in definitions.values():
|
||||
for name, definition in definitions.items():
|
||||
if isinstance(definition, dict):
|
||||
if name == "CodexErrorInfo":
|
||||
_normalize_open_ended_enum(definition)
|
||||
_flatten_string_enum_one_of(definition)
|
||||
# Normalize the schema into something datamodel-code-generator can map to
|
||||
# stable class names instead of anonymous numbered helpers.
|
||||
@@ -580,9 +594,10 @@ def generate_v2_all(schema_dir: Path) -> None:
|
||||
_require_nullable_field(
|
||||
out_path, "McpResourceReadTarget", r"link_id: Annotated\[\n(?: .*\n)+ \]"
|
||||
)
|
||||
_preserve_open_enum(out_path, "CodexErrorInfoValue", allow_empty=True)
|
||||
_preserve_reasoning_effort_enum(out_path)
|
||||
_preserve_thread_source_enum(out_path)
|
||||
_preserve_plan_type_enum(out_path)
|
||||
_preserve_open_enum(out_path, "PlanType")
|
||||
_normalize_generated_timestamps(out_path)
|
||||
|
||||
|
||||
@@ -687,27 +702,30 @@ def _preserve_thread_source_enum(out_path: Path) -> None:
|
||||
out_path.write_text(source[:class_start] + open_enum + source[class_end:])
|
||||
|
||||
|
||||
def _preserve_plan_type_enum(out_path: Path) -> None:
|
||||
"""Keep the public plan constants while accepting values from newer runtimes."""
|
||||
def _preserve_open_enum(out_path: Path, name: str, *, allow_empty: bool = False) -> None:
|
||||
"""Keep generated enum constants while accepting values from newer runtimes."""
|
||||
source = out_path.read_text()
|
||||
class_start = source.find("class PlanType(Enum):")
|
||||
class_start = source.find(f"class {name}(Enum):")
|
||||
if class_start == -1:
|
||||
raise RuntimeError("Generated SDK is missing PlanType")
|
||||
raise RuntimeError(f"Generated SDK is missing {name}")
|
||||
class_end = source.find("\n\nclass ", class_start)
|
||||
if class_end == -1:
|
||||
class_end = len(source)
|
||||
|
||||
class_source = source[class_start:class_end]
|
||||
class_source = class_source.replace(
|
||||
"class PlanType(Enum):",
|
||||
"class PlanType(str, Enum):",
|
||||
f"class {name}(Enum):",
|
||||
f"class {name}(str, Enum):",
|
||||
1,
|
||||
).rstrip()
|
||||
class_source += """
|
||||
invalid_value = "not isinstance(value, str)"
|
||||
if not allow_empty:
|
||||
invalid_value += " or not value"
|
||||
class_source += f"""
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> PlanType | None:
|
||||
if not isinstance(value, str) or not value:
|
||||
def _missing_(cls, value: object) -> {name} | None:
|
||||
if {invalid_value}:
|
||||
return None
|
||||
member = str.__new__(cls, value)
|
||||
member._name_ = value
|
||||
|
||||
@@ -538,7 +538,7 @@ class ClientInfo(BaseModel):
|
||||
version: str
|
||||
|
||||
|
||||
class CodexErrorInfoValue(Enum):
|
||||
class CodexErrorInfoValue(str, Enum):
|
||||
context_window_exceeded = "contextWindowExceeded"
|
||||
session_budget_exceeded = "sessionBudgetExceeded"
|
||||
usage_limit_exceeded = "usageLimitExceeded"
|
||||
@@ -555,6 +555,15 @@ class CodexErrorInfoValue(Enum):
|
||||
sandbox_error = "sandboxError"
|
||||
other = "other"
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> CodexErrorInfoValue | None:
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
member = str.__new__(cls, value)
|
||||
member._name_ = value
|
||||
member._value_ = value
|
||||
return member
|
||||
|
||||
|
||||
class HttpConnectionFailed(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
@@ -7669,6 +7678,7 @@ class CodexErrorInfo(
|
||||
| ResponseStreamDisconnectedCodexErrorInfo
|
||||
| ResponseTooManyFailedAttemptsCodexErrorInfo
|
||||
| ActiveTurnNotSteerableCodexErrorInfo
|
||||
| dict[str, Any]
|
||||
]
|
||||
):
|
||||
model_config = ConfigDict(
|
||||
@@ -7680,7 +7690,8 @@ class CodexErrorInfo(
|
||||
| ResponseStreamConnectionFailedCodexErrorInfo
|
||||
| ResponseStreamDisconnectedCodexErrorInfo
|
||||
| ResponseTooManyFailedAttemptsCodexErrorInfo
|
||||
| ActiveTurnNotSteerableCodexErrorInfo,
|
||||
| ActiveTurnNotSteerableCodexErrorInfo
|
||||
| dict[str, Any],
|
||||
Field(
|
||||
description="This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant."
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user