mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
Propagate turn metadata to standalone web search
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -44,6 +45,10 @@ const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
#[tokio::test]
|
||||
async fn standalone_web_search_round_trips_output() -> Result<()> {
|
||||
let call_id = "web-run-1";
|
||||
let client_metadata = HashMap::from([(
|
||||
"client-metadata-key".to_string(),
|
||||
"client-metadata-value".to_string(),
|
||||
)]);
|
||||
let server = responses::start_mock_server().await;
|
||||
mount_search_response(&server).await;
|
||||
|
||||
@@ -105,6 +110,7 @@ async fn standalone_web_search_round_trips_output() -> Result<()> {
|
||||
text: "Search the web".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
responsesapi_client_metadata: Some(client_metadata.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
@@ -144,7 +150,10 @@ async fn standalone_web_search_round_trips_output() -> Result<()> {
|
||||
"standalone web search should replace hosted web search"
|
||||
);
|
||||
|
||||
let search_body = search_request_body(&server).await?;
|
||||
let search_request = search_request(&server).await?;
|
||||
let search_body = search_request
|
||||
.body_json::<Value>()
|
||||
.context("search request body should be JSON")?;
|
||||
assert_eq!(search_body["model"], json!("mock-model"));
|
||||
assert_eq!(
|
||||
search_body["commands"],
|
||||
@@ -169,6 +178,18 @@ async fn standalone_web_search_round_trips_output() -> Result<()> {
|
||||
"content": [{"type": "input_text", "text": "Search the web"}],
|
||||
}))
|
||||
);
|
||||
let turn_metadata_header = search_request
|
||||
.headers
|
||||
.get("x-codex-turn-metadata")
|
||||
.context("standalone search should include x-codex-turn-metadata")?
|
||||
.to_str()
|
||||
.context("x-codex-turn-metadata should be valid ASCII")?;
|
||||
let turn_metadata: Value = serde_json::from_str(turn_metadata_header)
|
||||
.context("x-codex-turn-metadata should be valid JSON")?;
|
||||
assert_eq!(
|
||||
turn_metadata["client-metadata-key"],
|
||||
json!("client-metadata-value")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
responses::strip_metadata_from_json(requests[1].function_call_output(call_id)),
|
||||
@@ -285,16 +306,15 @@ fn has_hosted_web_search(body: &Value) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
async fn search_request_body(server: &MockServer) -> Result<Value> {
|
||||
server
|
||||
async fn search_request(server: &MockServer) -> Result<wiremock::Request> {
|
||||
let requests = server
|
||||
.received_requests()
|
||||
.await
|
||||
.context("failed to fetch received requests")?
|
||||
.context("failed to fetch received requests")?;
|
||||
requests
|
||||
.into_iter()
|
||||
.find(|request| request.url.path() == "/api/codex/alpha/search")
|
||||
.context("expected standalone search request")?
|
||||
.body_json()
|
||||
.context("search request body should be JSON")
|
||||
.context("expected standalone search request")
|
||||
}
|
||||
|
||||
fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> {
|
||||
|
||||
@@ -13,6 +13,7 @@ use codex_tools::ToolSearchInfo;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::TurnItemEmissionFuture;
|
||||
use codex_tools::TurnItemEmitter;
|
||||
use codex_utils_string::to_ascii_json_string;
|
||||
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::session::session::Session;
|
||||
@@ -22,6 +23,7 @@ use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::apply_granted_turn_permissions;
|
||||
use crate::tools::registry::CoreToolRuntime;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::turn_metadata::McpTurnMetadataContext;
|
||||
|
||||
pub(crate) struct ExtensionToolAdapter(Arc<dyn codex_tools::ToolExecutor<ExtensionToolCall>>);
|
||||
|
||||
@@ -114,6 +116,14 @@ impl TurnItemEmitter for CoreTurnItemEmitter {
|
||||
async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall {
|
||||
let conversation_history =
|
||||
ConversationHistory::new(invocation.session.clone_history().await.into_raw_items());
|
||||
let codex_turn_metadata = invocation
|
||||
.turn
|
||||
.turn_metadata_state
|
||||
.current_meta_value_for_mcp_request(McpTurnMetadataContext {
|
||||
model: invocation.turn.model_info.slug.as_str(),
|
||||
reasoning_effort: invocation.turn.effective_reasoning_effort(),
|
||||
})
|
||||
.and_then(|metadata| to_ascii_json_string(&metadata).ok());
|
||||
let mut environments =
|
||||
Vec::with_capacity(invocation.step_context.environments.turn_environments.len());
|
||||
for environment in &invocation.step_context.environments.turn_environments {
|
||||
@@ -146,6 +156,7 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall {
|
||||
call_id: invocation.call_id.clone(),
|
||||
tool_name: invocation.tool_name.clone(),
|
||||
model: invocation.turn.model_info.slug.clone(),
|
||||
codex_turn_metadata,
|
||||
truncation_policy: invocation.turn.model_info.truncation_policy.into(),
|
||||
conversation_history,
|
||||
turn_item_emitter: Arc::new(CoreTurnItemEmitter {
|
||||
|
||||
@@ -4,6 +4,7 @@ use codex_api::SearchCommands;
|
||||
use codex_api::SearchQuery;
|
||||
use codex_api::SearchRequest;
|
||||
use codex_api::SearchSettings;
|
||||
use codex_core::X_CODEX_TURN_METADATA_HEADER;
|
||||
use codex_core::web_search_action_detail;
|
||||
use codex_extension_api::ExtensionTurnItem;
|
||||
use codex_extension_api::FunctionCallError;
|
||||
@@ -28,6 +29,7 @@ use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ToolExposure;
|
||||
use codex_tools::default_namespace_description;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
use url::Url;
|
||||
|
||||
use crate::history::recent_input;
|
||||
@@ -113,6 +115,12 @@ impl WebSearchTool {
|
||||
u64::try_from(call.truncation_policy.token_budget()).unwrap_or(u64::MAX),
|
||||
),
|
||||
};
|
||||
let mut extra_headers = HeaderMap::new();
|
||||
if let Some(turn_metadata) = call.codex_turn_metadata.as_deref()
|
||||
&& let Ok(header_value) = HeaderValue::from_str(turn_metadata)
|
||||
{
|
||||
extra_headers.insert(X_CODEX_TURN_METADATA_HEADER, header_value);
|
||||
}
|
||||
call.turn_item_emitter
|
||||
.emit_started(extension_turn_item(
|
||||
WebSearchItem {
|
||||
@@ -126,7 +134,7 @@ impl WebSearchTool {
|
||||
))
|
||||
.await;
|
||||
let response = client
|
||||
.search(&request, HeaderMap::new())
|
||||
.search(&request, extra_headers)
|
||||
.await
|
||||
.map_err(|err| FunctionCallError::Fatal(err.to_string()))?;
|
||||
let legacy_action = match &command_action {
|
||||
|
||||
@@ -92,6 +92,7 @@ pub struct ToolCall {
|
||||
pub call_id: String,
|
||||
pub tool_name: ToolName,
|
||||
pub model: String,
|
||||
pub codex_turn_metadata: Option<String>,
|
||||
pub truncation_policy: TruncationPolicy,
|
||||
pub conversation_history: ConversationHistory,
|
||||
pub turn_item_emitter: Arc<dyn TurnItemEmitter>,
|
||||
@@ -106,6 +107,10 @@ impl std::fmt::Debug for ToolCall {
|
||||
.field("call_id", &self.call_id)
|
||||
.field("tool_name", &self.tool_name)
|
||||
.field("model", &self.model)
|
||||
.field(
|
||||
"has_codex_turn_metadata",
|
||||
&self.codex_turn_metadata.is_some(),
|
||||
)
|
||||
.field("truncation_policy", &self.truncation_policy)
|
||||
.field("conversation_history", &self.conversation_history)
|
||||
.field("turn_item_emitter", &"<host turn item emitter>")
|
||||
|
||||
Reference in New Issue
Block a user