mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
feat(codex): webSearch bubbles + bare hermes-tools names in app-server bridge
Two more display gaps from #26541 grafted onto the merged bridge: - webSearch: codex's built-in web search now produces a tool.started/ tool.completed bubble pair (query as preview + args). Previously the item type wasn't in _CODEX_TOOL_ITEM_TYPES, so built-in searches showed nothing. - mcp.hermes-tools.* stripping: tools codex invokes through Hermes' own hermes-tools MCP server display as their bare names (web_search, browser_navigate) instead of mcp.hermes-tools.web_search. The inner dispatch subprocess can't fire native progress events, so the codex-level event is the display event — name it the way users know the tool. Credit: both behaviors designed and first implemented by @simpolism in PR #26541 (May 15, earliest of the app-server display-bridge family).
This commit is contained in:
parent
11a91a6d17
commit
ec3d958425
2 changed files with 60 additions and 1 deletions
|
|
@ -287,10 +287,23 @@ def _record_codex_app_server_compaction(
|
|||
# therefore deserve a tool_progress bubble pair). The projector lives in
|
||||
# agent/transports/codex_event_projector.py — keep these in sync so the
|
||||
# tool name shown in the UI matches the name recorded in messages.
|
||||
# webSearch is codex's built-in web search tool — it has no projector
|
||||
# entry (codex handles it internally) but still deserves a bubble.
|
||||
_CODEX_TOOL_ITEM_TYPES = frozenset(
|
||||
{"commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall"}
|
||||
{"commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall", "webSearch"}
|
||||
)
|
||||
|
||||
# Internal MCP server that wraps Hermes' native tools for codex. When
|
||||
# codex calls back through it, the inner dispatch runs in a SEPARATE
|
||||
# hermes-tools-mcp-server subprocess that has no access to the parent
|
||||
# agent's tool_progress_callback — so the inner call can never surface
|
||||
# its own native progress event. The codex-level mcpToolCall event IS
|
||||
# the display event for those calls; we strip the mcp.hermes-tools.*
|
||||
# namespacing and emit the bare tool name (web_search, browser_navigate,
|
||||
# vision_analyze, ...) since the user thinks of these as Hermes tools,
|
||||
# not as MCP calls.
|
||||
_INTERNAL_MCP_SERVER = "hermes-tools"
|
||||
|
||||
|
||||
def _codex_item_to_tool_name(item: dict) -> str:
|
||||
"""Synthetic Hermes tool name for a codex item. Mirrors
|
||||
|
|
@ -304,9 +317,13 @@ def _codex_item_to_tool_name(item: dict) -> str:
|
|||
if item_type == "mcpToolCall":
|
||||
server = item.get("server") or "mcp"
|
||||
tool = item.get("tool") or "unknown"
|
||||
if server == _INTERNAL_MCP_SERVER:
|
||||
return tool
|
||||
return f"mcp.{server}.{tool}"
|
||||
if item_type == "dynamicToolCall":
|
||||
return item.get("tool") or "dynamic"
|
||||
if item_type == "webSearch":
|
||||
return "web_search"
|
||||
return item_type or "unknown"
|
||||
|
||||
|
||||
|
|
@ -327,6 +344,8 @@ def _codex_item_to_args(item: dict) -> dict:
|
|||
if item_type in {"mcpToolCall", "dynamicToolCall"}:
|
||||
args = item.get("arguments") or {}
|
||||
return args if isinstance(args, dict) else {"arguments": args}
|
||||
if item_type == "webSearch":
|
||||
return {"query": item.get("query") or ""}
|
||||
return {}
|
||||
|
||||
|
||||
|
|
@ -354,6 +373,9 @@ def _codex_item_to_preview(item: dict) -> Any:
|
|||
return json.dumps(args, ensure_ascii=False)[:120]
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
if item_type == "webSearch":
|
||||
query = item.get("query") or ""
|
||||
return query[:120] if query else None
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,22 @@ class TestCodexItemToToolName:
|
|||
{"type": "dynamicToolCall", "tool": "web_search"}
|
||||
) == "web_search"
|
||||
|
||||
def test_hermes_tools_mcp_server_emits_bare_tool_name(self):
|
||||
"""The hermes-tools MCP server wraps Hermes' own tools for codex;
|
||||
the inner dispatch subprocess can't fire native progress events,
|
||||
so the codex-level event IS the display event — shown without the
|
||||
mcp.hermes-tools.* namespacing (from #26541 by @simpolism)."""
|
||||
assert _codex_item_to_tool_name(
|
||||
{"type": "mcpToolCall", "server": "hermes-tools", "tool": "web_search"}
|
||||
) == "web_search"
|
||||
assert _codex_item_to_tool_name(
|
||||
{"type": "mcpToolCall", "server": "hermes-tools", "tool": "browser_navigate"}
|
||||
) == "browser_navigate"
|
||||
|
||||
def test_web_search_builtin_maps_to_web_search(self):
|
||||
"""Codex's built-in webSearch tool gets a bubble too (#26541)."""
|
||||
assert _codex_item_to_tool_name({"type": "webSearch"}) == "web_search"
|
||||
|
||||
def test_unknown_type_returns_type_string(self):
|
||||
assert _codex_item_to_tool_name(
|
||||
{"type": "plan"}
|
||||
|
|
@ -368,6 +384,27 @@ class TestToolProgressDispatch:
|
|||
assert completed.kwargs["is_error"] is False
|
||||
assert "results" in completed.kwargs["result"]
|
||||
|
||||
def test_web_search_builtin_fires_started_and_completed(self):
|
||||
"""Codex's built-in webSearch produces a start/complete bubble pair
|
||||
with the query as preview and args (#26541)."""
|
||||
agent = _make_stub_agent()
|
||||
bridge = make_codex_app_server_event_bridge(agent)
|
||||
bridge(_item_started({
|
||||
"type": "webSearch",
|
||||
"id": "ws-1",
|
||||
"query": "hermes agent docs",
|
||||
}))
|
||||
bridge(_item_completed({
|
||||
"type": "webSearch",
|
||||
"id": "ws-1",
|
||||
"query": "hermes agent docs",
|
||||
}))
|
||||
calls = agent.tool_progress_callback.call_args_list
|
||||
assert [c.args[0] for c in calls] == ["tool.started", "tool.completed"]
|
||||
assert calls[0].args[1] == "web_search"
|
||||
assert calls[0].args[2] == "hermes agent docs"
|
||||
assert calls[0].args[3] == {"query": "hermes agent docs"}
|
||||
|
||||
def test_duration_falls_back_to_wall_time_when_codex_missing_ms(self):
|
||||
agent = _make_stub_agent()
|
||||
bridge = make_codex_app_server_event_bridge(agent)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue