ACP has no OpenAI `tools`/`tool_calls` channel: a prompt is text and a
response is text plus the agent's own tool notifications. Hermes' agentic
surface — memory, todo, skill_manage — is dispatched from OpenAI-shaped
tool_calls, so on an ACP provider it only works if the schemas travel into
the prompt as text and the calls are parsed back out of the response text.
copilot-acp already carried that bridge as private module-level helpers.
Lift it verbatim into `agent/acp_openai_bridge.py` so every ACP client
shares one implementation of the wire contract instead of re-deriving it —
`agent/claude_code_acp_client.py` (#81375) is currently a third copy of the
same four functions, and each copy is a place the `<tool_call>` contract can
drift.
copilot-acp is migrated onto it as the in-tree consumer and loses 176 lines
of duplication; its prompt shape is unchanged, which the new tests pin.
Two things the shared version adds over the copy:
- `render_tool_bridge_sections(..., allowlist=)`. A CLI with no tools of its
own forwards Hermes' whole toolset (copilot, unchanged: no allowlist). A
CLI that *is* an autonomous agent must forward only Hermes' agent-level
tools — re-offering the overlapping read/edit/execute ones makes Hermes
re-run work the agent already finished.
- `StreamChunks`, a list subclass that keeps response-level attributes.
Hermes reads provider extras off the object returned by
`chat.completions.create`; the old plain-list return silently dropped them
whenever a caller asked for `stream=True`.