Files
codex/codex-rs/app-server/src/gateway_oauth_notifications.rs
T
Abhinav aece559654 Allow provider selection for gateway OAuth reads and logins (#59819)
## Why

Gateway OAuth reads and logins previously used only the current provider, preventing clients from authorizing another configured provider without changing the default.

## What changed

- Add optional `modelProvider` params to `account/gatewayOAuth/read` and `account/gatewayOAuth/login`, preserving current-provider behavior for omitted or null params and selectors.
- Validate explicit providers against current managed policy without changing the default or any thread's provider.
- Retain readiness state across providers and send `account/gatewayOAuth/changed` notifications for every configured provider sharing the changed credential.
- Update protocol schemas, TypeScript types, Python models, and documentation.

## Testing

Add coverage for non-default provider login, retained readiness when alternating providers, notifications for shared credentials without starting login, and legacy TypeScript request compatibility.

GitOrigin-RevId: cfd472c322b821fe36b8c133a8de774f4ba48534
2026-11-06 04:02:11 +00:00

69 lines
2.7 KiB
Rust

//! Forward OAuth progress for every configured provider sharing the changed grant.
use crate::config_manager::ConfigManager;
use crate::outgoing_message::OutgoingMessageSender;
use codex_app_server_protocol::GatewayOAuthChangedNotification;
use codex_app_server_protocol::GatewayOAuthStatus;
use codex_app_server_protocol::ServerNotification;
use codex_login::AuthManager;
use codex_login::GatewayAuthStatus;
use std::sync::Arc;
use tokio::sync::broadcast::error::RecvError;
use tokio_util::task::AbortOnDropHandle;
pub(crate) fn status_fields(status: GatewayAuthStatus) -> (GatewayOAuthStatus, Option<String>) {
match status {
GatewayAuthStatus::NotReady => (GatewayOAuthStatus::NotReady, None),
GatewayAuthStatus::Started => (GatewayOAuthStatus::Started, None),
GatewayAuthStatus::Succeeded => (GatewayOAuthStatus::Succeeded, None),
GatewayAuthStatus::Failed { message } => (GatewayOAuthStatus::Failed, Some(message)),
}
}
pub(crate) fn spawn(
auth_manager: Arc<AuthManager>,
config_manager: ConfigManager,
outgoing: Arc<OutgoingMessageSender>,
) -> AbortOnDropHandle<()> {
let mut events = codex_login::subscribe_gateway_auth_status(&auth_manager.runtime_config());
AbortOnDropHandle::new(tokio::spawn(async move {
loop {
let change = match events.recv().await {
Ok(change) => change,
Err(RecvError::Lagged(_)) => continue,
Err(RecvError::Closed) => break,
};
let Ok(config) = config_manager
.load_latest_config(/*fallback_cwd*/ None)
.await
else {
continue;
};
for (provider_id, provider) in &config.model_providers {
let Ok(Some(manager)) = codex_model_provider::create_model_provider(
provider.clone(),
Some(Arc::clone(&auth_manager)),
)
.gateway_auth_manager() else {
continue;
};
if !matches!(manager.oauth_session(), Some(oauth) if oauth.config() == &change.config)
{
continue;
}
let (status, error) = status_fields(change.status.clone());
outgoing
.send_server_notification(ServerNotification::GatewayOAuthChanged(
GatewayOAuthChangedNotification {
auth_url: None,
provider_id: provider_id.clone(),
status,
error,
},
))
.await;
}
}
}))
}