[apps] Improve search tool fallback. (#14732)

- [x] Bypass tool search and stuff tool specs directly into model
context when either a. Tool search is not available for the model or b.
There are not that many tools to search for.
This commit is contained in:
Matthew Zeng
2026-03-15 21:41:55 -07:00
committed by GitHub
parent 49edf311ac
commit d4af6053e2
3 changed files with 67 additions and 8 deletions

View File

@@ -18,6 +18,7 @@ const CONNECTOR_DESCRIPTION: &str = "Plan events and manage your calendar.";
const PROTOCOL_VERSION: &str = "2025-11-25";
const SERVER_NAME: &str = "codex-apps-test";
const SERVER_VERSION: &str = "1.0.0";
const SEARCHABLE_TOOL_COUNT: usize = 100;
pub const CALENDAR_CREATE_EVENT_RESOURCE_URI: &str =
"connector://calendar/tools/calendar_create_event";
const CALENDAR_LIST_EVENTS_RESOURCE_URI: &str = "connector://calendar/tools/calendar_list_events";
@@ -32,6 +33,21 @@ impl AppsTestServer {
Self::mount_with_connector_name(server, CONNECTOR_NAME).await
}
pub async fn mount_searchable(server: &MockServer) -> Result<Self> {
mount_oauth_metadata(server).await;
mount_connectors_directory(server).await;
mount_streamable_http_json_rpc(
server,
CONNECTOR_NAME.to_string(),
CONNECTOR_DESCRIPTION.to_string(),
/*searchable*/ true,
)
.await;
Ok(Self {
chatgpt_base_url: server.uri(),
})
}
pub async fn mount_with_connector_name(
server: &MockServer,
connector_name: &str,
@@ -42,6 +58,7 @@ impl AppsTestServer {
server,
connector_name.to_string(),
CONNECTOR_DESCRIPTION.to_string(),
/*searchable*/ false,
)
.await;
Ok(Self {
@@ -97,12 +114,14 @@ async fn mount_streamable_http_json_rpc(
server: &MockServer,
connector_name: String,
connector_description: String,
searchable: bool,
) {
Mock::given(method("POST"))
.and(path_regex("^/api/codex/apps/?$"))
.respond_with(CodexAppsJsonRpcResponder {
connector_name,
connector_description,
searchable,
})
.mount(server)
.await;
@@ -111,6 +130,7 @@ async fn mount_streamable_http_json_rpc(
struct CodexAppsJsonRpcResponder {
connector_name: String,
connector_description: String,
searchable: bool,
}
impl Respond for CodexAppsJsonRpcResponder {
@@ -157,7 +177,7 @@ impl Respond for CodexAppsJsonRpcResponder {
"notifications/initialized" => ResponseTemplate::new(202),
"tools/list" => {
let id = body.get("id").cloned().unwrap_or(Value::Null);
ResponseTemplate::new(200).set_body_json(json!({
let mut response = json!({
"jsonrpc": "2.0",
"id": id,
"result": {
@@ -211,7 +231,32 @@ impl Respond for CodexAppsJsonRpcResponder {
],
"nextCursor": null
}
}))
});
if self.searchable
&& let Some(tools) = response
.pointer_mut("/result/tools")
.and_then(Value::as_array_mut)
{
for index in 2..SEARCHABLE_TOOL_COUNT {
tools.push(json!({
"name": format!("calendar_timezone_option_{index}"),
"description": format!("Read timezone option {index}."),
"inputSchema": {
"type": "object",
"properties": {
"timezone": { "type": "string" }
},
"additionalProperties": false
},
"_meta": {
"connector_id": CONNECTOR_ID,
"connector_name": self.connector_name.clone(),
"connector_description": self.connector_description.clone()
}
}));
}
}
ResponseTemplate::new(200).set_body_json(response)
}
"tools/call" => {
let id = body.get("id").cloned().unwrap_or(Value::Null);