Summary
_format_tool_call in cptr/routers/gateway.py renders a tool call to OpenAI-compatible clients as the tool's name and nothing else — no command, no arguments, no output. The arguments are available on the item by the time this function runs; it reads them only to extract title, then discards the rest.
This is the remaining half of #196. That issue fixed capture (input_json_delta fragments are now accumulated) and #197 fixed the name. Both landed. But the gateway still drops everything except the name on the way out, so for an OpenAI-compatible client the original symptom of #196 is unchanged: "You can see that Bash ran; you can never see what command it ran."
Verified in v0.9.21, running live against Open WebUI 0.11.1.
Current behaviour
cptr/routers/gateway.py:
def _format_tool_call(item: dict) -> str | None:
"""Render a tool call as compact markdown for OpenAI-compatible clients."""
if item.get("type") != "function_call" or item.get("status") != "in_progress":
return None
arguments = item.get("arguments")
title = arguments.get("title") if isinstance(arguments, dict) else None
name = str(title or item.get("name") or "tool").strip() or "tool"
return f"\n\n`{name}`\n\n"
arguments is in scope and populated — claude_code.py reassembles it from the accumulated input_json and attaches it alongside title — but only title is read.
Two consequences
1. Arguments are discarded. A turn that runs five commands renders as five identical `Bash` markers with no way to tell them apart, in the response or in the persisted chat.
2. Nothing renders on completion. The guard is status != "in_progress", so the only event that produces output is the tool starting. There is no terminal output, no result, and no success/failure signal — a tool that errored looks exactly like one that succeeded.
Why this matters more than it looks
I'm reporting this on behalf of a totally blind daily user (VoiceOver, macOS) who drives cptr agents through Open WebUI as the frontend — which is the arrangement gateway.py's own docstring describes.
For a sighted user a bare Bash marker is uninformative. For a screen-reader user it is worse than uninformative: the reply reads as a sequence of identical one-word announcements with no content between them, and there is no way to tell whether the agent did what was asked, did something else, or failed. The visual UI at least conveys shape; read aloud, there is nothing there at all.
Suggested direction
Not prescriptive about the form, but two things would resolve it:
- Render the arguments, at least the primary one — the command for
Bash, the path for Read/Write/Edit. A fenced block keyed on the tool would fit the existing compact-markdown style.
- Emit on completion as well as start, so output and failure are visible. The
output event already carries the item; the status != "in_progress" guard is what excludes it.
A verbosity setting would be reasonable if full output is considered too noisy for the default — though for the accessibility case the failure signal in particular should probably not be behind a flag.
Happy to open a PR if that would help; I've read the surrounding code and the change looks contained to this function and its two call sites. Given the project's first-time-contributor policy I'd rather be asked than assume.
Summary
_format_tool_callincptr/routers/gateway.pyrenders a tool call to OpenAI-compatible clients as the tool's name and nothing else — no command, no arguments, no output. The arguments are available on the item by the time this function runs; it reads them only to extracttitle, then discards the rest.This is the remaining half of #196. That issue fixed capture (
input_json_deltafragments are now accumulated) and #197 fixed the name. Both landed. But the gateway still drops everything except the name on the way out, so for an OpenAI-compatible client the original symptom of #196 is unchanged: "You can see thatBashran; you can never see what command it ran."Verified in v0.9.21, running live against Open WebUI 0.11.1.
Current behaviour
cptr/routers/gateway.py:argumentsis in scope and populated —claude_code.pyreassembles it from the accumulatedinput_jsonand attaches it alongsidetitle— but onlytitleis read.Two consequences
1. Arguments are discarded. A turn that runs five commands renders as five identical
`Bash`markers with no way to tell them apart, in the response or in the persisted chat.2. Nothing renders on completion. The guard is
status != "in_progress", so the only event that produces output is the tool starting. There is no terminal output, no result, and no success/failure signal — a tool that errored looks exactly like one that succeeded.Why this matters more than it looks
I'm reporting this on behalf of a totally blind daily user (VoiceOver, macOS) who drives cptr agents through Open WebUI as the frontend — which is the arrangement
gateway.py's own docstring describes.For a sighted user a bare
Bashmarker is uninformative. For a screen-reader user it is worse than uninformative: the reply reads as a sequence of identical one-word announcements with no content between them, and there is no way to tell whether the agent did what was asked, did something else, or failed. The visual UI at least conveys shape; read aloud, there is nothing there at all.Suggested direction
Not prescriptive about the form, but two things would resolve it:
Bash, the path forRead/Write/Edit. A fenced block keyed on the tool would fit the existing compact-markdown style.outputevent already carries the item; thestatus != "in_progress"guard is what excludes it.A verbosity setting would be reasonable if full output is considered too noisy for the default — though for the accessibility case the failure signal in particular should probably not be behind a flag.
Happy to open a PR if that would help; I've read the surrounding code and the change looks contained to this function and its two call sites. Given the project's first-time-contributor policy I'd rather be asked than assume.