Make history and notes tools Bridge-compatible (#40775)

## What changed

- Express nullable history and notes arguments with `anyOf` and remove unsupported schema constraints.
- Forward valid JSON object arguments to the backend without enforcing client-side limits.
- Clarify history ID, ordering, cross-agent path, and consistency semantics in the tool descriptions.

## Testing

- Cover forwarding previously restricted argument values for every history and notes tool.
- Verify app-server requests expose the Bridge-compatible schemas and still reject non-object arguments locally.

GitOrigin-RevId: 28809fe9da5099944fd04962b5e71ffda4163eea
This commit is contained in:
Brandon Zhang authored and copyberry committed 2026-08-26 03:46:23 +00:00
1 parent e24190caa9
commit 25a6e316c8
4 files changed
+123 -127

No files matched your search

@@ -187,6 +187,79 @@ async fn installed_extension_exposes_and_invokes_history_notes_tools() -> TestRe
})
);
for (namespace, name, mut arguments) in [
("history", "list_windows", json!({"limit": 101})),
("history", "list_items", json!({})),
(
"history",
"list_items",
json!({"limit": 21, "max_chars_per_item": 4_000}),
),
(
"history",
"read_item",
json!({"window_id": "window", "item_id": "item", "limit_chars": 20_001}),
),
(
"history",
"search_contents",
json!({"query": "x".repeat(1_001), "limit": 21}),
),
("history", "search_contents", json!({"query": ""})),
("notes", "list_files_by_prefix", json!({"max_results": 101})),
(
"notes",
"search_contents",
json!({"query": "x".repeat(1_001), "max_files": 21, "max_matches_per_file": 11}),
),
("notes", "search_contents", json!({"query": ""})),
(
"notes",
"read_file",
json!({"path": "notes.md", "start_line": -2}),
),
(
"notes",
"append_to_file",
json!({"path": "notes.md", "text": "append"}),
),
(
"notes",
"write_file",
json!({"path": "notes.md", "text": "replace"}),
),
] {
server.reset().await;
Mock::given(method("POST"))
.and(path(format!(
"/backend-api/codex/alpha/{namespace}/v2/{name}"
)))
.and(header("x-openai-actor-authorization", "actor-biscuit"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"encrypted_output": "enc_history"
})))
.mount(&server)
.await;
let tool_name = ToolName::namespaced(namespace, name);
let tool = tools
.iter()
.find(|tool| tool.tool_name() == tool_name)
.expect("exposed tool");
tool.handle(tool_call(tool_name, arguments.clone())).await?;
arguments["context"] = json!({
"session_id": "session-123",
"current_agent_name": "/root/worker",
});
let requests = server.received_requests().await.expect("recorded requests");
assert_eq!(
requests
.iter()
.map(|request| serde_json::from_slice::<serde_json::Value>(&request.body))
.collect::<Result<Vec<_>, _>>()?,
vec![arguments]
);
}
for result in [
json!({"text": ""}),
json!({"text": "x".repeat(4_001)}),