mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
## Why User-verification cancellations previously discarded failure reasons, leaving MCP servers unable to distinguish user cancellation from unavailable approval, interruption, or invalid proof. ## What changed - Carry a bounded `openai/userVerificationReason` value in response `_meta` for verification failures, declines, and cancellations across the TUI, app server, and MCP client. - Preserve recognized reasons and classify invalid responses, missing or invalid proofs, response errors, closed response channels, interruptions, and unavailable approval. - Discard unknown reasons and unrelated metadata. Keep proof material out of decline and cancellation responses, and keep display messages and raw error diagnostics out of response metadata. ## Testing Add and update tests for typed failure propagation, metadata filtering, invalid proofs, response-channel failures, interrupted requests, and unavailable approval, including TUI RPC and native MCP response coverage. GitOrigin-RevId: 6b7f855ce8eb13e759b5b86d0dc2b1afdfcff7b3
101 lines
3.1 KiB
Rust
101 lines
3.1 KiB
Rust
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn accepted_device_proof_uses_existing_content_and_discards_metadata() {
|
|
assert_eq!(
|
|
from_client_result(Ok(Ok(json!({
|
|
"action": "accept", "content": {"credentialId": "AQID", "signature": "BAUG"},
|
|
"_meta": {"ignored": "untrusted"},
|
|
})))),
|
|
McpServerElicitationRequestResponse {
|
|
action: McpServerElicitationAction::Accept,
|
|
content: Some(json!({"credentialId": "AQID", "signature": "BAUG"})),
|
|
meta: None,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_response_or_accept_without_proof_cancels() {
|
|
for (response, reason) in [
|
|
(json!({"invalid": "response"}), "invalidResponse"),
|
|
(
|
|
json!({"action": "accept", "content": null, "_meta": null}),
|
|
"invalidProof",
|
|
),
|
|
(
|
|
json!({"action": "accept", "content": {}, "_meta": null}),
|
|
"invalidProof",
|
|
),
|
|
] {
|
|
assert_eq!(
|
|
from_client_result(Ok(Ok(response))),
|
|
McpServerElicitationRequestResponse {
|
|
action: McpServerElicitationAction::Cancel,
|
|
content: None,
|
|
meta: Some(json!({"openai/userVerificationReason": reason})),
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_response_keeps_only_a_known_reason() {
|
|
for (input, expected) in [
|
|
(
|
|
json!({"openai/userVerificationReason": "timeout", "secret": "discard"}),
|
|
Some(json!({"openai/userVerificationReason": "timeout"})),
|
|
),
|
|
(
|
|
json!({"openai/userVerificationReason": "arbitrary secret"}),
|
|
None,
|
|
),
|
|
(json!(null), None),
|
|
] {
|
|
assert_eq!(
|
|
from_client_result(Ok(Ok(
|
|
json!({"action": "cancel", "content": {"signature": "discard"}, "_meta": input})
|
|
))),
|
|
McpServerElicitationRequestResponse {
|
|
action: McpServerElicitationAction::Cancel,
|
|
content: None,
|
|
meta: expected
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn runtime_failures_keep_their_reason_without_error_text() {
|
|
for (data, reason) in [
|
|
(Some(json!({"reason": "turnTransition"})), "interrupted"),
|
|
(None, "responseError"),
|
|
] {
|
|
let response = from_client_result(Ok(Err(codex_app_server_protocol::JSONRPCErrorError {
|
|
code: -1,
|
|
message: "private error details".into(),
|
|
data,
|
|
})));
|
|
assert_eq!(
|
|
response,
|
|
McpServerElicitationRequestResponse {
|
|
action: McpServerElicitationAction::Cancel,
|
|
content: None,
|
|
meta: Some(json!({"openai/userVerificationReason": reason}))
|
|
}
|
|
);
|
|
}
|
|
let (sender, receiver) = oneshot::channel();
|
|
drop(sender);
|
|
assert_eq!(
|
|
from_client_result(receiver.await),
|
|
McpServerElicitationRequestResponse {
|
|
action: McpServerElicitationAction::Cancel,
|
|
content: None,
|
|
meta: Some(json!({"openai/userVerificationReason": "responseChannelClosed"}))
|
|
}
|
|
);
|
|
}
|