[Bugfix][Responses] Fix browser.find action type (#55305)

Signed-off-by: nicole-lihui <nicole.li@daocloud.io> Co-authored-by: Chauncey <chaunceyjiang@gmail.com>

Nicole LiHui 🥜 committed Sep 11, 2026 at 20:07 UTC 7ef4d9bfed6311e3b78a40abb4a8bb6a2fc741b0
4 files changed +46 -2
tests/entrypoints/openai/responses/test_harmony_utils.py
+17
@@ -273,6 +273,23 @@ class TestHarmonyToResponseOutput:
273 assert output_items[0].action.type == "search"
274 assert output_items[0].action.query == "cursor:weather in San Francisco"
275
276 + def test_browser_find_recipient_uses_responses_action_type(self):
277 + """browser.find must use the Responses API find_in_page action type."""
278 + message = (
279 + Message.from_role_and_content(
280 + Role.ASSISTANT, '{"pattern": "vLLM", "cursor": 42}'
281 + )
282 + .with_channel("analysis")
283 + .with_recipient("browser.find")
284 + )
285 +
286 + output_items = harmony_to_response_output(message, frozenset())
287 +
288 + assert len(output_items) == 1
289 + assert isinstance(output_items[0], ResponseFunctionWebSearch)
290 + assert output_items[0].action.type == "find_in_page"
291 + assert output_items[0].action.pattern == "vLLM"
292 +
293 def test_commentary_with_empty_content_and_no_recipient(self):
294 """Test edge case: empty commentary with recipient=None."""
295 message = Message.from_role_and_content(Role.ASSISTANT, "")
tests/entrypoints/openai/responses/test_streaming_events.py
+27
@@ -1,6 +1,9 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 +from openai.types.responses import ResponseFunctionWebSearch
5 +from openai_harmony import Message, Role
6 +
7 from vllm.entrypoints.generate.base.protocol import (
8 DeltaFunctionCall,
9 DeltaMessage,
@@ -8,11 +11,35 @@ from vllm.entrypoints.generate.base.protocol import (
11 )
12 from vllm.entrypoints.openai.responses.streaming_events import (
13 SimpleStreamingEventProcessor,
14 + StreamingState,
15 _StateType,
16 + emit_browser_tool_events,
17 split_delta,
18 )
19
20
21 +def test_browser_find_uses_responses_action_type():
22 + """Both streaming output items must use the Responses find action type."""
23 + message = (
24 + Message.from_role_and_content(
25 + Role.ASSISTANT, '{"pattern": "vLLM", "cursor": 42}'
26 + )
27 + .with_channel("analysis")
28 + .with_recipient("browser.find")
29 + )
30 +
31 + events = emit_browser_tool_events(message, StreamingState())
32 +
33 + added, done = events[0], events[-1]
34 + assert added.type == "response.output_item.added"
35 + assert done.type == "response.output_item.done"
36 + for event in (added, done):
37 + assert isinstance(event.item, ResponseFunctionWebSearch)
38 + assert event.item.action.type == "find_in_page"
39 + assert event.item.action.pattern == "vLLM"
40 + assert event.item.action.url == "cursor:42"
41 +
42 +
43 def _make_tool_call(
44 index: int, name: str | None = None, arguments: str | None = None
45 ) -> DeltaToolCall:
vllm/entrypoints/openai/responses/harmony.py
+1 -1
@@ -303,7 +303,7 @@ def _parse_browser_tool_call(message: Message, recipient: str) -> ResponseOutput
303 action = ActionFind(
304 pattern=browser_call.get("pattern", ""),
305 url=f"cursor:{browser_call.get('url', '')}",
306 - type="find",
306 + type="find_in_page",
307 )
308 else:
309 raise ValueError(f"Unknown browser action: {recipient}")
vllm/entrypoints/openai/responses/streaming_events.py
+1 -1
@@ -667,7 +667,7 @@ def emit_browser_tool_events(
667 )
668 elif function_name == "find":
669 action = response_function_web_search.ActionFind(
670 - type="find",
670 + type="find_in_page",
671 pattern=parsed_args["pattern"],
672 # TODO: translate to url
673 url=f"cursor:{parsed_args.get('cursor', '')}",