mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
## Summary This PR moves Codex backend request authentication from direct bearer-token handling to `AuthProvider`. The new `codex-auth-provider` crate defines the shared request-auth trait. `CodexAuth::provider()` returns a provider that can apply all headers needed for the selected auth mode. This lets ChatGPT token auth and AgentIdentity auth share the same callsite path: - ChatGPT token auth applies bearer auth plus account/FedRAMP headers where needed. - AgentIdentity auth applies AgentAssertion plus account/FedRAMP headers where needed. Reference old stack: https://github.com/openai/codex/pull/17387/changes ## Callsite Migration | Area | Change | | --- | --- | | backend-client | accepts an `AuthProvider` instead of a raw token/header | | chatgpt client/connectors | applies auth through `CodexAuth::provider()` | | cloud tasks | keeps Codex-backend gating, applies auth through provider | | cloud requirements | uses Codex-backend auth checks and provider headers | | app-server remote control | applies provider headers for backend calls | | MCP Apps/connectors | gates on `uses_codex_backend()` and keys caches from generic account getters | | model refresh | treats AgentIdentity as Codex-backend auth | | OpenAI file upload path | rejects non-Codex-backend auth before applying headers | | core client setup | keeps model-provider auth flow and allows AgentIdentity through provider-backed OpenAI auth | ## Stack 1. https://github.com/openai/codex/pull/18757: full revert 2. https://github.com/openai/codex/pull/18871: isolated Agent Identity crate 3. https://github.com/openai/codex/pull/18785: explicit AgentIdentity auth mode and startup task allocation 4. This PR: migrate Codex backend auth callsites through AuthProvider 5. https://github.com/openai/codex/pull/18904: accept AgentIdentity JWTs and load `CODEX_AGENT_IDENTITY` ## Testing Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI.
322 lines
10 KiB
Rust
322 lines
10 KiB
Rust
use super::*;
|
|
|
|
impl Session {
|
|
#[expect(
|
|
clippy::await_holding_invalid_type,
|
|
reason = "active turn checks and turn state updates must remain atomic"
|
|
)]
|
|
pub async fn request_mcp_server_elicitation(
|
|
&self,
|
|
turn_context: &TurnContext,
|
|
request_id: RequestId,
|
|
params: McpServerElicitationRequestParams,
|
|
) -> Option<ElicitationResponse> {
|
|
let server_name = params.server_name.clone();
|
|
let request = match params.request {
|
|
McpServerElicitationRequest::Form {
|
|
meta,
|
|
message,
|
|
requested_schema,
|
|
} => {
|
|
let requested_schema = match serde_json::to_value(requested_schema) {
|
|
Ok(requested_schema) => requested_schema,
|
|
Err(err) => {
|
|
warn!(
|
|
"failed to serialize MCP elicitation schema for server_name: {server_name}, request_id: {request_id}: {err:#}"
|
|
);
|
|
return None;
|
|
}
|
|
};
|
|
codex_protocol::approvals::ElicitationRequest::Form {
|
|
meta,
|
|
message,
|
|
requested_schema,
|
|
}
|
|
}
|
|
McpServerElicitationRequest::Url {
|
|
meta,
|
|
message,
|
|
url,
|
|
elicitation_id,
|
|
} => codex_protocol::approvals::ElicitationRequest::Url {
|
|
meta,
|
|
message,
|
|
url,
|
|
elicitation_id,
|
|
},
|
|
};
|
|
|
|
let (tx_response, rx_response) = oneshot::channel();
|
|
let prev_entry = {
|
|
let mut active = self.active_turn.lock().await;
|
|
match active.as_mut() {
|
|
Some(at) => {
|
|
let mut ts = at.turn_state.lock().await;
|
|
ts.insert_pending_elicitation(
|
|
server_name.clone(),
|
|
request_id.clone(),
|
|
tx_response,
|
|
)
|
|
}
|
|
None => None,
|
|
}
|
|
};
|
|
if prev_entry.is_some() {
|
|
warn!(
|
|
"Overwriting existing pending elicitation for server_name: {server_name}, request_id: {request_id}"
|
|
);
|
|
}
|
|
let id = match request_id {
|
|
rmcp::model::NumberOrString::String(value) => {
|
|
codex_protocol::mcp::RequestId::String(value.to_string())
|
|
}
|
|
rmcp::model::NumberOrString::Number(value) => {
|
|
codex_protocol::mcp::RequestId::Integer(value)
|
|
}
|
|
};
|
|
let event = EventMsg::ElicitationRequest(ElicitationRequestEvent {
|
|
turn_id: params.turn_id,
|
|
server_name,
|
|
id,
|
|
request,
|
|
});
|
|
self.send_event(turn_context, event).await;
|
|
rx_response.await.ok()
|
|
}
|
|
|
|
#[expect(
|
|
clippy::await_holding_invalid_type,
|
|
reason = "active turn checks and manager fallback must stay serialized"
|
|
)]
|
|
pub async fn resolve_elicitation(
|
|
&self,
|
|
server_name: String,
|
|
id: RequestId,
|
|
response: ElicitationResponse,
|
|
) -> anyhow::Result<()> {
|
|
let entry = {
|
|
let mut active = self.active_turn.lock().await;
|
|
match active.as_mut() {
|
|
Some(at) => {
|
|
let mut ts = at.turn_state.lock().await;
|
|
ts.remove_pending_elicitation(&server_name, &id)
|
|
}
|
|
None => None,
|
|
}
|
|
};
|
|
if let Some(tx_response) = entry {
|
|
tx_response
|
|
.send(response)
|
|
.map_err(|e| anyhow::anyhow!("failed to send elicitation response: {e:?}"))?;
|
|
return Ok(());
|
|
}
|
|
|
|
self.services
|
|
.mcp_connection_manager
|
|
.read()
|
|
.await
|
|
.resolve_elicitation(server_name, id, response)
|
|
.await
|
|
}
|
|
|
|
#[expect(
|
|
clippy::await_holding_invalid_type,
|
|
reason = "MCP resource calls are serialized through the session-owned manager guard"
|
|
)]
|
|
pub async fn list_resources(
|
|
&self,
|
|
server: &str,
|
|
params: Option<PaginatedRequestParams>,
|
|
) -> anyhow::Result<ListResourcesResult> {
|
|
self.services
|
|
.mcp_connection_manager
|
|
.read()
|
|
.await
|
|
.list_resources(server, params)
|
|
.await
|
|
}
|
|
|
|
#[expect(
|
|
clippy::await_holding_invalid_type,
|
|
reason = "MCP resource calls are serialized through the session-owned manager guard"
|
|
)]
|
|
pub async fn list_resource_templates(
|
|
&self,
|
|
server: &str,
|
|
params: Option<PaginatedRequestParams>,
|
|
) -> anyhow::Result<ListResourceTemplatesResult> {
|
|
self.services
|
|
.mcp_connection_manager
|
|
.read()
|
|
.await
|
|
.list_resource_templates(server, params)
|
|
.await
|
|
}
|
|
|
|
#[expect(
|
|
clippy::await_holding_invalid_type,
|
|
reason = "MCP resource calls are serialized through the session-owned manager guard"
|
|
)]
|
|
pub async fn read_resource(
|
|
&self,
|
|
server: &str,
|
|
params: ReadResourceRequestParams,
|
|
) -> anyhow::Result<ReadResourceResult> {
|
|
self.services
|
|
.mcp_connection_manager
|
|
.read()
|
|
.await
|
|
.read_resource(server, params)
|
|
.await
|
|
}
|
|
|
|
#[expect(
|
|
clippy::await_holding_invalid_type,
|
|
reason = "MCP tool calls are serialized through the session-owned manager guard"
|
|
)]
|
|
pub async fn call_tool(
|
|
&self,
|
|
server: &str,
|
|
tool: &str,
|
|
arguments: Option<serde_json::Value>,
|
|
meta: Option<serde_json::Value>,
|
|
) -> anyhow::Result<CallToolResult> {
|
|
self.services
|
|
.mcp_connection_manager
|
|
.read()
|
|
.await
|
|
.call_tool(server, tool, arguments, meta)
|
|
.await
|
|
}
|
|
|
|
#[expect(
|
|
clippy::await_holding_invalid_type,
|
|
reason = "MCP tool metadata reads through the session-owned manager guard"
|
|
)]
|
|
pub(crate) async fn resolve_mcp_tool_info(&self, tool_name: &ToolName) -> Option<ToolInfo> {
|
|
self.services
|
|
.mcp_connection_manager
|
|
.read()
|
|
.await
|
|
.resolve_tool_info(tool_name)
|
|
.await
|
|
}
|
|
|
|
async fn refresh_mcp_servers_inner(
|
|
&self,
|
|
turn_context: &TurnContext,
|
|
mcp_servers: HashMap<String, McpServerConfig>,
|
|
store_mode: OAuthCredentialsStoreMode,
|
|
) {
|
|
let auth = self.services.auth_manager.auth().await;
|
|
let config = self.get_config().await;
|
|
let mcp_config = config
|
|
.to_mcp_config(self.services.plugins_manager.as_ref())
|
|
.await;
|
|
let tool_plugin_provenance = self
|
|
.services
|
|
.mcp_manager
|
|
.tool_plugin_provenance(config.as_ref())
|
|
.await;
|
|
let mcp_servers = with_codex_apps_mcp(mcp_servers, auth.as_ref(), &mcp_config);
|
|
let auth_statuses =
|
|
compute_auth_statuses(mcp_servers.iter(), store_mode, auth.as_ref()).await;
|
|
{
|
|
let mut guard = self.services.mcp_startup_cancellation_token.lock().await;
|
|
guard.cancel();
|
|
*guard = CancellationToken::new();
|
|
}
|
|
let (refreshed_manager, cancel_token) = McpConnectionManager::new(
|
|
&mcp_servers,
|
|
store_mode,
|
|
auth_statuses,
|
|
&turn_context.approval_policy,
|
|
turn_context.sub_id.clone(),
|
|
self.get_tx_event(),
|
|
turn_context.sandbox_policy.get().clone(),
|
|
McpRuntimeEnvironment::new(
|
|
turn_context
|
|
.environment
|
|
.clone()
|
|
.unwrap_or_else(|| self.services.environment_manager.local_environment()),
|
|
turn_context.cwd.to_path_buf(),
|
|
),
|
|
config.codex_home.to_path_buf(),
|
|
codex_apps_tools_cache_key(auth.as_ref()),
|
|
tool_plugin_provenance,
|
|
auth.as_ref(),
|
|
)
|
|
.await;
|
|
{
|
|
let mut guard = self.services.mcp_startup_cancellation_token.lock().await;
|
|
if guard.is_cancelled() {
|
|
cancel_token.cancel();
|
|
}
|
|
*guard = cancel_token;
|
|
}
|
|
|
|
let mut manager = self.services.mcp_connection_manager.write().await;
|
|
*manager = refreshed_manager;
|
|
}
|
|
|
|
pub(crate) async fn refresh_mcp_servers_if_requested(&self, turn_context: &TurnContext) {
|
|
let refresh_config = { self.pending_mcp_server_refresh_config.lock().await.take() };
|
|
let Some(refresh_config) = refresh_config else {
|
|
return;
|
|
};
|
|
|
|
let McpServerRefreshConfig {
|
|
mcp_servers,
|
|
mcp_oauth_credentials_store_mode,
|
|
} = refresh_config;
|
|
|
|
let mcp_servers =
|
|
match serde_json::from_value::<HashMap<String, McpServerConfig>>(mcp_servers) {
|
|
Ok(servers) => servers,
|
|
Err(err) => {
|
|
warn!("failed to parse MCP server refresh config: {err}");
|
|
return;
|
|
}
|
|
};
|
|
let store_mode = match serde_json::from_value::<OAuthCredentialsStoreMode>(
|
|
mcp_oauth_credentials_store_mode,
|
|
) {
|
|
Ok(mode) => mode,
|
|
Err(err) => {
|
|
warn!("failed to parse MCP OAuth refresh config: {err}");
|
|
return;
|
|
}
|
|
};
|
|
|
|
self.refresh_mcp_servers_inner(turn_context, mcp_servers, store_mode)
|
|
.await;
|
|
}
|
|
|
|
pub(crate) async fn refresh_mcp_servers_now(
|
|
&self,
|
|
turn_context: &TurnContext,
|
|
mcp_servers: HashMap<String, McpServerConfig>,
|
|
store_mode: OAuthCredentialsStoreMode,
|
|
) {
|
|
self.refresh_mcp_servers_inner(turn_context, mcp_servers, store_mode)
|
|
.await;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) async fn mcp_startup_cancellation_token(&self) -> CancellationToken {
|
|
self.services
|
|
.mcp_startup_cancellation_token
|
|
.lock()
|
|
.await
|
|
.clone()
|
|
}
|
|
|
|
pub(crate) async fn cancel_mcp_startup(&self) {
|
|
self.services
|
|
.mcp_startup_cancellation_token
|
|
.lock()
|
|
.await
|
|
.cancel();
|
|
}
|
|
}
|