mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Show activity for standalone web search calls (#24693)
## Why Standalone `web.run` calls run in the extension, so they need normal web-search progress activity while a request is in flight and durable completed activity after a thread is reloaded. Follow-up to #23823; uses the extension turn-item emission path added in #24813. ## What changed - Emit standalone `web.run` start/completion items through the host turn-item emitter, preserving standard client delivery and rollout persistence. - Include useful completion detail for queries, image queries, and literal-URL `open`/`find` commands. - Render completed searches as `Searched the web` or `Searched the web for <detail>`, with snapshot coverage for the detail-free case. - Extend the app-server round-trip test to verify completed search activity is reconstructed by `thread/read` after a fresh-process reload. ## Testing - `just test -p codex-web-search-extension` - `just test -p codex-app-server -E "test(standalone_web_search_round_trips_encrypted_output)"`
This commit is contained in:
committed by
GitHub
Unverified
parent
5577a9e148
commit
96f1347fa3
@@ -7,13 +7,19 @@ use app_test_support::ChatGptAuthFixture;
|
||||
use app_test_support::McpProcess;
|
||||
use app_test_support::to_response;
|
||||
use app_test_support::write_chatgpt_auth;
|
||||
use codex_app_server_protocol::ItemCompletedNotification;
|
||||
use codex_app_server_protocol::ItemStartedNotification;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ThreadItem;
|
||||
use codex_app_server_protocol::ThreadReadParams;
|
||||
use codex_app_server_protocol::ThreadReadResponse;
|
||||
use codex_app_server_protocol::ThreadStartParams;
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_app_server_protocol::WebSearchAction;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use core_test_support::responses;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -84,10 +90,11 @@ async fn standalone_web_search_round_trips_encrypted_output() -> Result<()> {
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
|
||||
let thread_id = thread.id.clone();
|
||||
|
||||
let turn_req = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: thread.id,
|
||||
thread_id: thread_id.clone(),
|
||||
client_user_message_id: None,
|
||||
input: vec![V2UserInput::Text {
|
||||
text: "Search the web".to_string(),
|
||||
@@ -103,6 +110,13 @@ async fn standalone_web_search_round_trips_encrypted_output() -> Result<()> {
|
||||
.await??;
|
||||
let _turn: TurnStartResponse = to_response::<TurnStartResponse>(turn_resp)?;
|
||||
|
||||
let started = timeout(DEFAULT_READ_TIMEOUT, wait_for_web_search_started(&mut mcp)).await??;
|
||||
let completed = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
wait_for_web_search_completed(&mut mcp),
|
||||
)
|
||||
.await??;
|
||||
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
@@ -159,10 +173,83 @@ async fn standalone_web_search_round_trips_encrypted_output() -> Result<()> {
|
||||
}],
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
started.item,
|
||||
ThreadItem::WebSearch {
|
||||
id: call_id.to_string(),
|
||||
query: String::new(),
|
||||
action: Some(WebSearchAction::Other),
|
||||
}
|
||||
);
|
||||
let expected_completed_item = ThreadItem::WebSearch {
|
||||
id: call_id.to_string(),
|
||||
query: "standalone web search".to_string(),
|
||||
action: Some(WebSearchAction::Search {
|
||||
query: Some("standalone web search".to_string()),
|
||||
queries: None,
|
||||
}),
|
||||
};
|
||||
assert_eq!(completed.item, expected_completed_item);
|
||||
|
||||
drop(mcp);
|
||||
let mut reloaded_mcp =
|
||||
McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, reloaded_mcp.initialize()).await??;
|
||||
let read_req = reloaded_mcp
|
||||
.send_thread_read_request(ThreadReadParams {
|
||||
thread_id,
|
||||
include_turns: true,
|
||||
})
|
||||
.await?;
|
||||
let read_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
reloaded_mcp.read_stream_until_response_message(RequestId::Integer(read_req)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadReadResponse { thread, .. } = to_response::<ThreadReadResponse>(read_resp)?;
|
||||
let persisted_web_searches: Vec<&ThreadItem> = thread
|
||||
.turns
|
||||
.iter()
|
||||
.flat_map(|turn| &turn.items)
|
||||
.filter(|item| matches!(item, ThreadItem::WebSearch { .. }))
|
||||
.collect();
|
||||
assert_eq!(persisted_web_searches, vec![&expected_completed_item]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_for_web_search_started(mcp: &mut McpProcess) -> Result<ItemStartedNotification> {
|
||||
loop {
|
||||
let notification = mcp
|
||||
.read_stream_until_notification_message("item/started")
|
||||
.await?;
|
||||
let started: ItemStartedNotification = serde_json::from_value(
|
||||
notification
|
||||
.params
|
||||
.context("item/started notification should include params")?,
|
||||
)?;
|
||||
if matches!(&started.item, ThreadItem::WebSearch { .. }) {
|
||||
return Ok(started);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_web_search_completed(mcp: &mut McpProcess) -> Result<ItemCompletedNotification> {
|
||||
loop {
|
||||
let notification = mcp
|
||||
.read_stream_until_notification_message("item/completed")
|
||||
.await?;
|
||||
let completed: ItemCompletedNotification = serde_json::from_value(
|
||||
notification
|
||||
.params
|
||||
.context("item/completed notification should include params")?,
|
||||
)?;
|
||||
if matches!(&completed.item, ThreadItem::WebSearch { .. }) {
|
||||
return Ok(completed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn mount_search_response(server: &MockServer) {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/codex/alpha/search"))
|
||||
|
||||
Reference in New Issue
Block a user