Files
codex/codex-rs/ext/web-search/src/output.rs
T
sgarg-oai 184c9c555e Return standalone web search failures to the model (#58147)
## Why

Standalone web search errors currently abort the turn as fatal failures, preventing the model from responding to them.

## What changed

Return client setup errors to the model and represent failed search requests as unsuccessful tool outputs. Failed requests still emit search completion events and retain external-context handling. Limit error messages to 1,024 bytes, truncating at a UTF-8 character boundary with a truncation marker.

## Testing

Add coverage for failure status reporting and UTF-8-safe error truncation. Extend workspace routing tests to verify that a rejected redirect returns a bounded diagnostic to the model, allows the turn to complete, emits search lifecycle events, and marks thread memory as polluted.

GitOrigin-RevId: 9fb308990afa2ce0db5d9beed9bc66d9c1500418
2026-10-29 18:41:17 +00:00

66 lines
1.8 KiB
Rust

use codex_extension_api::ToolOutput;
use codex_extension_api::ToolPayload;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ResponseInputItem;
pub(crate) struct SearchOutput {
payload: FunctionCallOutputPayload,
}
impl SearchOutput {
pub(crate) fn new(result: Result<String, String>) -> Self {
let payload = match result {
Ok(text) => FunctionCallOutputPayload::from_content_items(vec![
FunctionCallOutputContentItem::InputText { text },
]),
Err(error) => FunctionCallOutputPayload {
success: Some(false),
..FunctionCallOutputPayload::from_text(error)
},
};
Self { payload }
}
}
impl ToolOutput for SearchOutput {
fn log_output(&self) -> String {
"[standalone web search output]".to_string()
}
fn success_for_logging(&self) -> bool {
self.payload.success.unwrap_or(true)
}
fn contains_external_context(&self) -> bool {
true
}
fn to_response_item(&self, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem {
ResponseInputItem::FunctionCallOutput {
call_id: call_id.to_string(),
output: self.payload.clone(),
}
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::SearchOutput;
use super::ToolOutput;
#[test]
fn reports_search_status_for_logging_and_downstream_handling() {
for (result, success) in [
(Ok("found".into()), None),
(Err("failed".into()), Some(false)),
] {
let output = SearchOutput::new(result);
assert_eq!(output.payload.success, success);
assert_eq!(output.success_for_logging(), success.unwrap_or(true));
}
}
}