Add model provider discovery to the app server (#58845)

## What changed

- Add `modelProvider/list` to return OpenAI and explicitly configured providers, provider-specific settings, and the selected default provider.
- Report `canEdit` and `canChangeDefault` based on configuration requirements, layer precedence, and whether user configuration is enabled.
- Describe gateway authentication without resolving credentials, and omit endpoint URLs that are unsuitable for display.
- Document the API and update generated JSON schemas, TypeScript types, and Python models.

## Testing

Add app-server tests for provider discovery, authentication modes, unsafe URL suppression, and configuration editability, plus a Python wire-format round-trip test.

GitOrigin-RevId: 8ba26637cd6bdd075e0e815e88aeeb04c53a2364
This commit is contained in:
alexsong-oai
2026-11-02 08:31:14 +00:00
committed by copyberry
parent 05c08d7655
commit 3026a8e54e
29 changed files with 1763 additions and 6 deletions
@@ -1174,6 +1174,11 @@ client_request_definitions! {
serialization: None,
response: v2::GatewayOAuthCancelResponse,
},
ModelProviderList => "modelProvider/list" {
params: v2::ModelProviderListParams,
serialization: global_shared_read("config"),
response: v2::ModelProviderListResponse,
},
ModelProviderCapabilitiesRead => "modelProvider/capabilities/read" {
params: v2::ModelProviderCapabilitiesReadParams,
serialization: None,
@@ -22,6 +22,7 @@ mod item;
mod mcp;
mod memory;
mod model;
mod model_provider;
mod model_provider_error;
mod notification;
mod permissions;
@@ -65,6 +66,7 @@ pub use item::*;
pub use mcp::*;
pub use memory::*;
pub use model::*;
pub use model_provider::*;
pub use model_provider_error::*;
pub use notification::*;
pub use permissions::*;
@@ -0,0 +1,89 @@
//! Model provider discovery and the configuration fields visible to clients.
use crate::JsonSchema;
use crate::TS;
use serde::Deserialize;
use serde::Serialize;
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelProviderListParams {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelProviderListResponse {
pub data: Vec<ModelProviderSummary>,
pub default_provider_id: String,
pub can_change_default: bool,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelProviderSummary {
pub id: String,
pub name: String,
/// Whether a supported configuration field can be edited in the active user layer.
pub can_edit: bool,
/// Null when the endpoint is absent or cannot be safely displayed.
pub base_url: Option<String>,
pub settings: ProviderSettings,
}
impl std::fmt::Debug for ModelProviderSummary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ModelProviderSummary")
.field("id", &self.id)
.field("name", &self.name)
.field("can_edit", &self.can_edit)
.field("base_url", &"[REDACTED]")
.field("settings", &self.settings)
.finish()
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type", rename_all = "camelCase", export_to = "v2/")]
pub enum ProviderSettings {
#[serde(rename = "openai")]
#[ts(rename = "openai")]
OpenAi,
CustomGateway {
auth: GatewayAuth,
},
Bedrock {
profile: Option<String>,
region: Option<String>,
},
Gcp {
#[serde(rename = "projectId")]
#[ts(rename = "projectId")]
project_id: Option<String>,
location: Option<String>,
/// Path on the app-server host, without credential-file contents.
#[serde(rename = "credentialFile")]
#[ts(rename = "credentialFile")]
credential_file: Option<String>,
},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "mode", rename_all = "camelCase")]
#[ts(tag = "mode", rename_all = "camelCase", export_to = "v2/")]
pub enum GatewayAuth {
None,
EnvironmentVariable {
#[serde(rename = "envKey")]
#[ts(rename = "envKey")]
env_key: String,
},
/// Authentication is configured outside the gateway form. Preserve it on edits.
ExternallyConfigured {
#[serde(rename = "envKey")]
#[ts(rename = "envKey")]
env_key: Option<String>,
},
}