Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion codex-rs/analytics/src/reducer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2534,7 +2534,7 @@ fn web_search_action_kind(action: &WebSearchAction) -> WebSearchActionKind {

fn web_search_query_count(query: &str, action: Option<&WebSearchAction>) -> Option<u64> {
match action {
Some(WebSearchAction::Search { query, queries }) => queries
Some(WebSearchAction::Search { query, queries, .. }) => queries
.as_ref()
.map(|queries| usize_to_u64(queries.len()))
.or_else(|| query.as_ref().map(|_| 1)),
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/thread_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2449,6 +2449,7 @@ mod tests {
action: CoreWebSearchAction::Search {
query: Some("codex".into()),
queries: None,
sources: None,
},
results: Some(vec![CoreWebSearchResult {
url: "https://example.com/codex".into(),
Expand Down Expand Up @@ -3933,6 +3934,7 @@ mod tests {
action: CoreWebSearchAction::Search {
query: Some("codex".into()),
queries: None,
sources: None,
},
results: None,
}),
Expand Down Expand Up @@ -4070,6 +4072,7 @@ mod tests {
action: CoreWebSearchAction::Search {
query: Some("codex".into()),
queries: None,
sources: None,
},
results: None,
})),
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/app-server-protocol/src/protocol/v2/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ pub enum WebSearchAction {
impl From<codex_protocol::models::WebSearchAction> for WebSearchAction {
fn from(value: codex_protocol::models::WebSearchAction) -> Self {
match value {
codex_protocol::models::WebSearchAction::Search { query, queries } => {
codex_protocol::models::WebSearchAction::Search { query, queries, .. } => {
WebSearchAction::Search { query, queries }
}
codex_protocol::models::WebSearchAction::OpenPage { url } => {
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server-protocol/src/protocol/v2/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2777,6 +2777,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
action: CoreWebSearchAction::Search {
query: Some("docs".to_string()),
queries: None,
sources: None,
},
results: Some(vec![CoreWebSearchResult {
url: "https://openai.com/docs".to_string(),
Expand Down
32 changes: 28 additions & 4 deletions codex-rs/core/src/event_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ use codex_protocol::items::ReasoningItem;
use codex_protocol::items::TurnItem;
use codex_protocol::items::UserMessageItem;
use codex_protocol::items::WebSearchItem;
use codex_protocol::items::WebSearchResult;
use codex_protocol::models::ContentItem;
use codex_protocol::models::MessagePhase;
use codex_protocol::models::ReasoningItemContent;
use codex_protocol::models::ReasoningItemReasoningSummary;
use codex_protocol::models::ResponseItem;
use codex_protocol::models::WebSearchAction;
use codex_protocol::models::WebSearchSource;
use codex_protocol::models::is_image_close_tag_text;
use codex_protocol::models::is_image_open_tag_text;
use codex_protocol::models::is_local_image_close_tag_text;
Expand Down Expand Up @@ -196,15 +198,37 @@ pub fn parse_turn_item(item: &ResponseItem) -> Option<TurnItem> {
}))
}
ResponseItem::WebSearchCall { id, action, .. } => {
let (action, query) = match action {
Some(action) => (action.clone(), web_search_action_detail(action)),
None => (WebSearchAction::Other, String::new()),
let (action, query, results) = match action {
Some(action) => {
let query = web_search_action_detail(action);
let mut action = action.clone();
let results = match &mut action {
WebSearchAction::Search { sources, .. } => sources
.take()
.map(|sources| {
sources
.into_iter()
.map(|source| match source {
WebSearchSource::Url { url } => WebSearchResult {
url,
title: None,
snippet: None,
},
})
.collect::<Vec<_>>()
})
.filter(|results| !results.is_empty()),
_ => None,
};
(action, query, results)
}
None => (WebSearchAction::Other, String::new(), None),
};
Some(TurnItem::WebSearch(WebSearchItem {
id: id.clone().unwrap_or_default(),
query,
action,
results: None,
results,
}))
}
ResponseItem::ImageGenerationCall {
Expand Down
12 changes: 11 additions & 1 deletion codex-rs/core/src/event_mapping_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ use codex_protocol::items::AgentMessageContent;
use codex_protocol::items::HookPromptFragment;
use codex_protocol::items::TurnItem;
use codex_protocol::items::WebSearchItem;
use codex_protocol::items::WebSearchResult;
use codex_protocol::items::build_hook_prompt_message;
use codex_protocol::models::ContentItem;
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
use codex_protocol::models::ReasoningItemContent;
use codex_protocol::models::ReasoningItemReasoningSummary;
use codex_protocol::models::ResponseItem;
use codex_protocol::models::WebSearchAction;
use codex_protocol::models::WebSearchSource;
use codex_protocol::protocol::CONTEXT_WINDOW_CLOSE_TAG;
use codex_protocol::protocol::CONTEXT_WINDOW_GUIDANCE_CLOSE_TAG;
use codex_protocol::protocol::CONTEXT_WINDOW_GUIDANCE_OPEN_TAG;
Expand Down Expand Up @@ -492,6 +494,9 @@ fn parses_web_search_call() {
action: Some(WebSearchAction::Search {
query: Some("weather".to_string()),
queries: None,
sources: Some(vec![WebSearchSource::Url {
url: "https://example.com/weather".to_string(),
}]),
}),
internal_chat_message_metadata_passthrough: None,
};
Expand All @@ -507,8 +512,13 @@ fn parses_web_search_call() {
action: WebSearchAction::Search {
query: Some("weather".to_string()),
queries: None,
sources: None,
},
results: None,
results: Some(vec![WebSearchResult {
url: "https://example.com/weather".to_string(),
title: None,
snippet: None,
}]),
}
),
other => panic!("expected TurnItem::WebSearch, got {other:?}"),
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/tools/handlers/extension_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ mod tests {
action: WebSearchAction::Search {
query: Some("rust trait object".to_string()),
queries: None,
sources: None,
},
results: None,
});
Expand Down Expand Up @@ -438,6 +439,7 @@ mod tests {
action: WebSearchAction::Search {
query: Some("rust trait object".to_string()),
queries: None,
sources: None,
},
results: None,
};
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/web_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn search_action_detail(query: &Option<String>, queries: &Option<Vec<String>>) -

pub fn web_search_action_detail(action: &WebSearchAction) -> String {
match action {
WebSearchAction::Search { query, queries } => search_action_detail(query, queries),
WebSearchAction::Search { query, queries, .. } => search_action_detail(query, queries),
WebSearchAction::OpenPage { url } => url.clone().unwrap_or_default(),
WebSearchAction::FindInPage { url, pattern } => match (pattern, url) {
(Some(pattern), Some(url)) => format!("'{pattern}' in {url}"),
Expand Down
21 changes: 21 additions & 0 deletions codex-rs/core/tests/common/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,27 @@ pub fn ev_web_search_call_done(id: &str, status: &str, query: &str) -> Value {
})
}

pub fn ev_web_search_call_done_with_sources(
id: &str,
status: &str,
query: &str,
sources: &[&str],
) -> Value {
let sources = sources
.iter()
.map(|url| serde_json::json!({"type": "url", "url": url}))
.collect::<Vec<_>>();
serde_json::json!({
"type": "response.output_item.done",
"item": {
"type": "web_search_call",
"id": id,
"status": status,
"action": {"type": "search", "query": query, "sources": sources}
}
})
}

pub fn ev_image_generation_call(
id: &str,
status: &str,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/tests/suite/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3040,6 +3040,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
action: Some(WebSearchAction::Search {
query: Some("weather".into()),
queries: None,
sources: None,
}),
internal_chat_message_metadata_passthrough: None,
});
Expand Down
59 changes: 52 additions & 7 deletions codex-rs/core/tests/suite/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Settings;
use codex_protocol::items::AgentMessageContent;
use codex_protocol::items::TurnItem;
use codex_protocol::items::WebSearchItem;
use codex_protocol::items::WebSearchResult;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::WebSearchAction;
use codex_protocol::protocol::AskForApproval;
Expand All @@ -30,7 +32,7 @@ use core_test_support::responses::ev_reasoning_summary_text_delta;
use core_test_support::responses::ev_reasoning_text_delta;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::ev_web_search_call_added_partial;
use core_test_support::responses::ev_web_search_call_done;
use core_test_support::responses::ev_web_search_call_done_with_sources;
use core_test_support::responses::mount_sse_once;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
Expand Down Expand Up @@ -285,7 +287,12 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> {
let TestCodex { codex, .. } = test_codex().build(&server).await?;

let web_search_added = ev_web_search_call_added_partial("web-search-1", "in_progress");
let web_search_done = ev_web_search_call_done("web-search-1", "completed", "weather seattle");
let web_search_done = ev_web_search_call_done_with_sources(
"web-search-1",
"completed",
"weather seattle",
&["https://weather.example/seattle"],
);

let first_response = sse(vec![
ev_response_created("resp-1"),
Expand Down Expand Up @@ -335,16 +342,54 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> {
assert_eq!(begin.call_id, "web-search-1");
assert_eq!(started.0.id, begin.call_id);
assert!(started.1 > 0);
assert_eq!(completed.0.id, begin.call_id);
assert!(completed.1 > 0);
assert_eq!(
completed.0.action,
WebSearchAction::Search {
query: Some("weather seattle".to_string()),
queries: None,
completed.0,
WebSearchItem {
id: begin.call_id,
query: "weather seattle".to_string(),
action: WebSearchAction::Search {
query: Some("weather seattle".to_string()),
queries: None,
sources: None,
},
results: Some(vec![WebSearchResult {
url: "https://weather.example/seattle".to_string(),
title: None,
snippet: None,
}]),
}
);

wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let second_mock = mount_sse_once(
&server,
sse(vec![
ev_response_created("resp-2"),
ev_assistant_message("msg-2", "done"),
ev_completed("resp-2"),
]),
)
.await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "summarize the search".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
additional_context: Default::default(),
thread_settings: Default::default(),
})
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

let request = second_mock.single_request();
let web_search_items = request.inputs_of_type("web_search_call");
assert_eq!(web_search_items.len(), 1);
assert!(web_search_items[0].pointer("/action/sources").is_none());

Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions codex-rs/exec/tests/event_processor_with_json_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ fn web_search_completion_preserves_query_and_action() {
action: WebSearchAction::Search {
query: Some("rust async await".to_string()),
queries: None,
sources: None,
},
results: Some(vec![WebSearchResult {
url: "https://example.com/rust-async".to_string(),
Expand Down Expand Up @@ -472,6 +473,7 @@ fn web_search_start_and_completion_reuse_item_id() {
action: WebSearchAction::Search {
query: Some("rust async await".to_string()),
queries: None,
sources: None,
},
results: None,
}),
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/ext/web-search/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,12 @@ fn query_action(queries: &[SearchQuery]) -> Option<WebSearchAction> {
[query] => Some(WebSearchAction::Search {
query: Some(query.q.clone()),
queries: None,
sources: None,
}),
queries => Some(WebSearchAction::Search {
query: None,
queries: Some(queries.iter().map(|query| query.q.clone()).collect()),
sources: None,
}),
}
}
Expand Down Expand Up @@ -205,6 +207,7 @@ mod tests {
WebSearchAction::Search {
query: None,
queries: Some(vec!["waterfalls".to_string(), "mountains".to_string()]),
sources: None,
},
),
(
Expand Down
Loading
Loading