mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Support self-serve Business ProLite accounts (#35785)
## What changed - Recognize `self_serve_business_prolite` across authentication, account and rate-limit APIs, generated schemas, workspace classification, status display, cloud configuration gating, and usage-limit messaging. - Keep the Python SDK's `PlanType` enum compatible with non-empty string values introduced by newer Codex runtimes while preserving its known constants. ## Testing - Cover token parsing, account reads and notifications, backend rate-limit mapping, workspace behavior, error formatting, and Python SDK response coercion for the new plan value. GitOrigin-RevId: 70bc17a7c4ba4028cb10e4333cc1f2ac64da361f
This commit is contained in:
@@ -608,6 +608,7 @@ def generate_v2_all(schema_dir: Path) -> None:
|
||||
_require_nullable_chatgpt_account_email(out_path)
|
||||
_preserve_reasoning_effort_enum(out_path)
|
||||
_preserve_thread_source_enum(out_path)
|
||||
_preserve_plan_type_enum(out_path)
|
||||
_normalize_generated_timestamps(out_path)
|
||||
|
||||
|
||||
@@ -695,6 +696,36 @@ 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."""
|
||||
source = out_path.read_text()
|
||||
class_start = source.find("class PlanType(Enum):")
|
||||
if class_start == -1:
|
||||
raise RuntimeError("Generated SDK is missing PlanType")
|
||||
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):",
|
||||
1,
|
||||
).rstrip()
|
||||
class_source += """
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> PlanType | None:
|
||||
if not isinstance(value, str) or not value:
|
||||
return None
|
||||
member = str.__new__(cls, value)
|
||||
member._name_ = value
|
||||
member._value_ = value
|
||||
return member
|
||||
"""
|
||||
out_path.write_text(source[:class_start] + class_source + source[class_end:])
|
||||
|
||||
|
||||
def _notification_specs(schema_dir: Path) -> list[tuple[str, str]]:
|
||||
"""Map each server notification method to its generated payload model class."""
|
||||
server_notifications = json.loads((schema_dir / "ServerNotification.json").read_text())
|
||||
|
||||
@@ -2505,7 +2505,7 @@ class PlanDeltaNotification(BaseModel):
|
||||
turn_id: Annotated[str, Field(alias="turnId")]
|
||||
|
||||
|
||||
class PlanType(Enum):
|
||||
class PlanType(str, Enum):
|
||||
free = "free"
|
||||
go = "go"
|
||||
plus = "plus"
|
||||
@@ -2519,6 +2519,15 @@ class PlanType(Enum):
|
||||
edu = "edu"
|
||||
unknown = "unknown"
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> PlanType | None:
|
||||
if not isinstance(value, str) or not value:
|
||||
return None
|
||||
member = str.__new__(cls, value)
|
||||
member._name_ = value
|
||||
member._value_ = value
|
||||
return member
|
||||
|
||||
|
||||
class PluginAuthPolicy(Enum):
|
||||
on_install = "ON_INSTALL"
|
||||
|
||||
@@ -5,8 +5,12 @@ from pathlib import Path
|
||||
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 (
|
||||
AccountRateLimitsUpdatedNotification,
|
||||
AccountUpdatedNotification,
|
||||
AgentMessageDeltaNotification,
|
||||
ApprovalsReviewer,
|
||||
GetAccountResponse,
|
||||
PlanType,
|
||||
ReasoningEffort,
|
||||
ReasoningEffortOption,
|
||||
ThreadForkParams,
|
||||
@@ -37,6 +41,38 @@ def test_generated_v2_bundle_has_single_shared_plan_type_definition() -> None:
|
||||
assert source.count("class PlanType(") == 1
|
||||
|
||||
|
||||
def test_plan_type_accepts_business_prolite_from_newer_runtime() -> None:
|
||||
"""New runtime plan values should remain typed when using a codex_bin override."""
|
||||
plan_type = "self_serve_business_prolite"
|
||||
response = GetAccountResponse.model_validate(
|
||||
{
|
||||
"account": {
|
||||
"type": "chatgpt",
|
||||
"email": "user@example.com",
|
||||
"planType": plan_type,
|
||||
},
|
||||
"requiresOpenaiAuth": True,
|
||||
}
|
||||
)
|
||||
assert response.account is not None
|
||||
assert response.account.root.plan_type.value == plan_type
|
||||
|
||||
client = CodexClient()
|
||||
account_updated = client._coerce_notification(
|
||||
"account/updated",
|
||||
{"authMode": "chatgpt", "planType": plan_type},
|
||||
)
|
||||
assert isinstance(account_updated.payload, AccountUpdatedNotification)
|
||||
assert account_updated.payload.plan_type == PlanType(plan_type)
|
||||
|
||||
rate_limits_updated = client._coerce_notification(
|
||||
"account/rateLimits/updated",
|
||||
{"rateLimits": {"planType": plan_type}},
|
||||
)
|
||||
assert isinstance(rate_limits_updated.payload, AccountRateLimitsUpdatedNotification)
|
||||
assert rate_limits_updated.payload.rate_limits.plan_type == PlanType(plan_type)
|
||||
|
||||
|
||||
def test_reasoning_effort_preserves_enum_constants_and_accepts_future_values() -> None:
|
||||
"""Known effort members and new runtime values should share the enum-style API."""
|
||||
known_option = ReasoningEffortOption.model_validate(
|
||||
|
||||
Reference in New Issue
Block a user