Add model provider configuration writes to the app-server (#58876)

## What changed

Add `modelProvider/config/write` to create custom gateways and patch existing custom gateway, Bedrock, and GCP settings. Return the saved provider using the same safe summary as `modelProvider/list`.

Omitted fields retain their values; explicit `null` clears nullable user overrides. Apply patches through the shared configuration queue with last-write-wins semantics, enforce field editability and validation, and preserve unexposed authentication settings.

Saving leaves provider selection and authentication unchanged. Saving the provider used by `model/list` invalidates its catalog for the next access. Include documentation and generated JSON, TypeScript, and Python protocol types.

## Testing

Add coverage for gateway creation and ID allocation, patch and null semantics, configuration policy enforcement, authentication preservation, and catalog refresh after active-provider edits. Extend Python protocol tests to verify wire round trips and reject mismatched provider IDs or fields.

GitOrigin-RevId: 850fe2c48ef5274944400efb5a859d720179c923
This commit is contained in:
alexsong-oai
2026-11-02 12:22:22 +00:00
committed by copyberry
parent 3026a8e54e
commit 053e5b1ca9
33 changed files with 2357 additions and 92 deletions
+35 -1
View File
@@ -5,6 +5,7 @@ from pathlib import Path
from typing import get_type_hints
import pytest
from pydantic import ValidationError
from openai_codex._runtime_requirements import CheckoutCapabilities
from openai_codex.client import CodexClient, _params_dict
@@ -20,6 +21,8 @@ from openai_codex.generated.v2_all import (
AuthRecoveryNotification,
CommandGuardianApprovalReviewAction,
GetAccountResponse,
ModelProviderConfigWriteParams,
ModelProviderConfigWriteResponse,
ModelProviderListResponse,
PlanType,
ReasoningEffort,
@@ -42,7 +45,7 @@ from openai_codex.types import ThreadSource
ROOT = Path(__file__).resolve().parents[1]
def test_model_provider_list_preserves_wire_fields_for_each_auth_mode() -> None:
def test_model_provider_responses_preserve_wire_fields_for_each_auth_mode() -> None:
wire = {
"data": [
{
@@ -91,6 +94,37 @@ def test_model_provider_list_preserves_wire_fields_for_each_auth_mode() -> None:
}
response = ModelProviderListResponse.model_validate(wire)
assert response.model_dump(mode="json", by_alias=True) == wire
for provider in wire["data"]:
saved = ModelProviderConfigWriteResponse.model_validate({"provider": provider})
assert saved.model_dump(mode="json", by_alias=True) == {"provider": provider}
@pytest.mark.parametrize(
"settings",
[
{"type": "customGateway", "name": "Gateway", "baseUrl": "https://example.test"},
{"type": "customGateway", "providerId": "team", "envKey": None},
{"type": "bedrock", "providerId": "amazon-bedrock", "region": None},
{"type": "bedrock", "providerId": "amazon-bedrock-runtime", "profile": "work"},
{"type": "gcp", "providerId": "gcp", "credentialFile": None, "location": "global"},
],
)
def test_provider_patch_preserves_explicit_null_and_omitted_fields(settings: dict) -> None:
wire = {"settings": settings}
params = ModelProviderConfigWriteParams.model_validate(wire)
assert params.model_dump(mode="json", by_alias=True, exclude_unset=True) == wire
@pytest.mark.parametrize(
"settings",
[
{"type": "bedrock", "providerId": "gcp", "region": "us-east-1"},
{"type": "gcp", "providerId": "gcp", "region": "us-east-1"},
],
)
def test_provider_patch_rejects_mismatched_id_or_fields(settings: dict) -> None:
with pytest.raises(ValidationError):
ModelProviderConfigWriteParams.model_validate({"settings": settings})
def test_inline_review_delivery_round_trips_as_an_enum() -> None: