mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
8d97b5c246
Propagate client JSON-RPC errors for app-server request callbacks.
Previously a number of possible errors were collapsed to `channel
closed`. Now we should be able to see the underlying client error.
### Summary
This change stops masking client JSON-RPC error responses as generic
callback cancellation in app-server server->client request flows.
Previously, when the client responded with a JSON-RPC error, we removed
the callback entry but did not send anything to the waiting oneshot
receiver. Waiters then observed channel closure (for example, auth
refresh request canceled: channel closed), which hid the actual client
error.
Now, client JSON-RPC errors are forwarded through the callback channel
and handled explicitly by request consumers.
### User-visible behavior
- External auth refresh now surfaces real client JSON-RPC errors when
provided.
- True transport/callback-drop cases still report
canceled/channel-closed semantics.
### Example: client JSON-RPC error is now propagated (not masked as
"canceled")
When app-server asks the client to refresh ChatGPT auth tokens, it sends
a server->client JSON-RPC request like:
```json
{
"id": 42,
"method": "account/chatgptAuthTokens/refresh",
"params": {
"reason": "unauthorized",
"previousAccountId": "org-abc"
}
}
```
If the client cannot refresh and responds with a JSON-RPC error:
```
{
"id": 42,
"error": {
"code": -32000,
"message": "refresh failed",
"data": null
}
}
```
app-server now forwards that error through the callback path and
surfaces:
`auth refresh request failed: code=-32000 message=refresh failed`
Previously, this same case could be reported as:
`auth refresh request canceled: channel closed`
93 lines
3.0 KiB
Rust
93 lines
3.0 KiB
Rust
use codex_app_server_protocol::DynamicToolCallResponse;
|
|
use codex_core::CodexThread;
|
|
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
|
|
use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;
|
|
use codex_protocol::protocol::Op;
|
|
use std::sync::Arc;
|
|
use tokio::sync::oneshot;
|
|
use tracing::error;
|
|
|
|
use crate::outgoing_message::ClientRequestResult;
|
|
|
|
pub(crate) async fn on_call_response(
|
|
call_id: String,
|
|
receiver: oneshot::Receiver<ClientRequestResult>,
|
|
conversation: Arc<CodexThread>,
|
|
) {
|
|
let response = receiver.await;
|
|
let value = match response {
|
|
Ok(Ok(value)) => value,
|
|
Ok(Err(err)) => {
|
|
error!("request failed with client error: {err:?}");
|
|
let fallback = CoreDynamicToolResponse {
|
|
content_items: vec![CoreDynamicToolCallOutputContentItem::InputText {
|
|
text: "dynamic tool request failed".to_string(),
|
|
}],
|
|
success: false,
|
|
};
|
|
if let Err(err) = conversation
|
|
.submit(Op::DynamicToolResponse {
|
|
id: call_id.clone(),
|
|
response: fallback,
|
|
})
|
|
.await
|
|
{
|
|
error!("failed to submit DynamicToolResponse: {err}");
|
|
}
|
|
return;
|
|
}
|
|
Err(err) => {
|
|
error!("request failed: {err:?}");
|
|
let fallback = CoreDynamicToolResponse {
|
|
content_items: vec![CoreDynamicToolCallOutputContentItem::InputText {
|
|
text: "dynamic tool request failed".to_string(),
|
|
}],
|
|
success: false,
|
|
};
|
|
if let Err(err) = conversation
|
|
.submit(Op::DynamicToolResponse {
|
|
id: call_id.clone(),
|
|
response: fallback,
|
|
})
|
|
.await
|
|
{
|
|
error!("failed to submit DynamicToolResponse: {err}");
|
|
}
|
|
return;
|
|
}
|
|
};
|
|
|
|
let response = serde_json::from_value::<DynamicToolCallResponse>(value).unwrap_or_else(|err| {
|
|
error!("failed to deserialize DynamicToolCallResponse: {err}");
|
|
DynamicToolCallResponse {
|
|
content_items: vec![
|
|
codex_app_server_protocol::DynamicToolCallOutputContentItem::InputText {
|
|
text: "dynamic tool response was invalid".to_string(),
|
|
},
|
|
],
|
|
success: false,
|
|
}
|
|
});
|
|
|
|
let DynamicToolCallResponse {
|
|
content_items,
|
|
success,
|
|
} = response;
|
|
let response = CoreDynamicToolResponse {
|
|
content_items: content_items
|
|
.into_iter()
|
|
.map(CoreDynamicToolCallOutputContentItem::from)
|
|
.collect(),
|
|
success,
|
|
};
|
|
if let Err(err) = conversation
|
|
.submit(Op::DynamicToolResponse {
|
|
id: call_id,
|
|
response,
|
|
})
|
|
.await
|
|
{
|
|
error!("failed to submit DynamicToolResponse: {err}");
|
|
}
|
|
}
|