mirror of
https://github.com/openai/codex.git
synced 2026-11-03 16:23:51 +00:00
Support Codex Apps auth elicitations (#19193)
## Summary - request URL-mode MCP elicitations when Codex Apps tool calls fail with connector auth metadata - route Codex Apps auth URL elicitations into the TUI app-link flow ## Test plan - `just fmt` - `cargo test -p codex-core mcp_tool_call::tests` - `cargo test -p codex-mcp` - `cargo test -p codex-tui bottom_pane::app_link_view::tests` - `just fix -p codex-core` - `just fix -p codex-mcp` - `just fix -p codex-tui` Also attempted broader local runs: - `cargo test -p codex-core` fails in unrelated config/request-permission/proxy-sensitive tests under the current Codex Desktop environment. - `cargo test -p codex-tui` fails in unrelated status snapshots/trust-default tests because the ambient environment renders workspace-write/network permission defaults.
This commit is contained in:
1 parent
22326e263c
commit
f9a907aebe
25 files changed
+1748
-78
No files matched your search
@@ -37,6 +37,8 @@ use codex_app_server_protocol::FileChangeRequestApprovalParams;
|
||||
use codex_app_server_protocol::FileUpdateChange;
|
||||
use codex_app_server_protocol::ItemStartedNotification;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::McpServerElicitationRequest;
|
||||
use codex_app_server_protocol::McpServerElicitationRequestParams;
|
||||
use codex_app_server_protocol::McpServerStartupState;
|
||||
use codex_app_server_protocol::McpServerStatusUpdatedNotification;
|
||||
use codex_app_server_protocol::NetworkApprovalContext as AppServerNetworkApprovalContext;
|
||||
@@ -2755,6 +2757,84 @@ async fn inactive_thread_permissions_approval_preserves_file_system_permissions(
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inactive_thread_url_elicitation_routes_to_app_link() {
|
||||
let app = make_test_app().await;
|
||||
let thread_id = ThreadId::new();
|
||||
let request = ServerRequest::McpServerElicitationRequest {
|
||||
request_id: AppServerRequestId::Integer(9),
|
||||
params: McpServerElicitationRequestParams {
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_id: Some("turn-auth".to_string()),
|
||||
server_name: "payments".to_string(),
|
||||
request: McpServerElicitationRequest::Url {
|
||||
meta: None,
|
||||
message: "Review the payment details to continue.".to_string(),
|
||||
url: "https://payments.example/checkout/123".to_string(),
|
||||
elicitation_id: "payment-123".to_string(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let Some(ThreadInteractiveRequest::AppLink(params)) = app
|
||||
.interactive_request_for_thread_request(thread_id, &request)
|
||||
.await
|
||||
else {
|
||||
panic!("expected app link request");
|
||||
};
|
||||
|
||||
assert_eq!(params.title, "Action required");
|
||||
assert_eq!(params.description, Some("Server: payments".to_string()));
|
||||
assert_eq!(params.url, "https://payments.example/checkout/123");
|
||||
assert_eq!(
|
||||
params.elicitation_target,
|
||||
Some(crate::bottom_pane::AppLinkElicitationTarget {
|
||||
thread_id,
|
||||
server_name: "payments".to_string(),
|
||||
request_id: AppServerRequestId::Integer(9),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inactive_thread_invalid_url_elicitation_is_declined() {
|
||||
let (app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
|
||||
let thread_id = ThreadId::new();
|
||||
let request = ServerRequest::McpServerElicitationRequest {
|
||||
request_id: AppServerRequestId::Integer(10),
|
||||
params: McpServerElicitationRequestParams {
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_id: Some("turn-auth".to_string()),
|
||||
server_name: "payments".to_string(),
|
||||
request: McpServerElicitationRequest::Url {
|
||||
meta: None,
|
||||
message: "Review the payment details to continue.".to_string(),
|
||||
url: "http://payments.example/checkout/123".to_string(),
|
||||
elicitation_id: "payment-123".to_string(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
assert!(
|
||||
app.interactive_request_for_thread_request(thread_id, &request)
|
||||
.await
|
||||
.is_none()
|
||||
);
|
||||
assert_matches!(
|
||||
app_event_rx.try_recv(),
|
||||
Ok(AppEvent::SubmitThreadOp {
|
||||
thread_id: op_thread_id,
|
||||
op: Op::ResolveElicitation {
|
||||
server_name,
|
||||
request_id: AppServerRequestId::Integer(10),
|
||||
decision: codex_app_server_protocol::McpServerElicitationAction::Decline,
|
||||
content: None,
|
||||
meta: None,
|
||||
},
|
||||
}) if op_thread_id == thread_id && server_name == "payments"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inactive_thread_approval_badge_clears_after_turn_completion_notification() -> Result<()> {
|
||||
let mut app = make_test_app().await;
|
||||
|
||||
@@ -262,31 +262,47 @@ impl App {
|
||||
}),
|
||||
),
|
||||
ServerRequest::McpServerElicitationRequest { request_id, params } => {
|
||||
if let Some(request) = McpServerElicitationFormRequest::from_app_server_request(
|
||||
if let Some(params) = AppLinkViewParams::from_url_app_server_request(
|
||||
thread_id,
|
||||
¶ms.server_name,
|
||||
request_id.clone(),
|
||||
params.clone(),
|
||||
¶ms.request,
|
||||
) {
|
||||
Some(ThreadInteractiveRequest::AppLink(params))
|
||||
} else if let Some(request) =
|
||||
McpServerElicitationFormRequest::from_app_server_request(
|
||||
thread_id,
|
||||
request_id.clone(),
|
||||
params.clone(),
|
||||
)
|
||||
{
|
||||
Some(ThreadInteractiveRequest::McpServerElicitation(request))
|
||||
} else {
|
||||
Some(ThreadInteractiveRequest::Approval(
|
||||
ApprovalRequest::McpElicitation {
|
||||
thread_id,
|
||||
thread_label,
|
||||
server_name: params.server_name.clone(),
|
||||
request_id: request_id.clone(),
|
||||
message: match ¶ms.request {
|
||||
codex_app_server_protocol::McpServerElicitationRequest::Form {
|
||||
message,
|
||||
..
|
||||
}
|
||||
| codex_app_server_protocol::McpServerElicitationRequest::Url {
|
||||
message,
|
||||
..
|
||||
} => message.clone(),
|
||||
match ¶ms.request {
|
||||
codex_app_server_protocol::McpServerElicitationRequest::Form {
|
||||
message,
|
||||
..
|
||||
} => Some(ThreadInteractiveRequest::Approval(
|
||||
ApprovalRequest::McpElicitation {
|
||||
thread_id,
|
||||
thread_label,
|
||||
server_name: params.server_name.clone(),
|
||||
request_id: request_id.clone(),
|
||||
message: message.clone(),
|
||||
},
|
||||
},
|
||||
))
|
||||
)),
|
||||
codex_app_server_protocol::McpServerElicitationRequest::Url { .. } => {
|
||||
self.app_event_tx.resolve_elicitation(
|
||||
thread_id,
|
||||
params.server_name.clone(),
|
||||
request_id.clone(),
|
||||
codex_app_server_protocol::McpServerElicitationAction::Decline,
|
||||
/*content*/ None,
|
||||
/*meta*/ None,
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerRequest::PermissionsRequestApproval { params, .. } => Some(
|
||||
@@ -304,6 +320,9 @@ impl App {
|
||||
|
||||
pub(super) fn push_thread_interactive_request(&mut self, request: ThreadInteractiveRequest) {
|
||||
match request {
|
||||
ThreadInteractiveRequest::AppLink(params) => {
|
||||
self.chat_widget.open_app_link_view(params);
|
||||
}
|
||||
ThreadInteractiveRequest::Approval(request) => {
|
||||
self.render_inactive_patch_preview(&request);
|
||||
self.chat_widget.push_approval_request(request);
|
||||
|
||||
Reference in new issue
Block a user