open-webui / open-webui/computer
bug: opencode agent profiles never work — adapter requests OpenAPI operationIds as URL paths
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 569
- Forks
- 79
- PR merge metrics
- No merged PRs in 30d
Description
Summary
Every prompt sent to an opencode agent profile fails immediately with:
Expecting value: line 1 column 1 (char 0)
The root cause is in cptr/utils/agents/opencode.py: the adapter requests opencode's OpenAPI operationId values as if they were URL paths. Any route whose real path contains the {sessionID} parameter is therefore unreachable.
The affected route lists are byte-identical in 0.9.3, 0.9.7, 0.9.12, 0.9.16, 0.9.19 and 0.9.20, so this is not a regression — opencode profiles appear never to have worked in a released version.
Environment
- cptr 0.9.19 and 0.9.20 (both reproduce)
- opencode 1.18.5
- macOS, Python 3.11, host install via
uvx "cptr[agents]@latest"
Symptom
The assistant message contains only the error above. It appears ~90 ms after send — no model call is ever made. Server log:
File "cptr/utils/chat_task.py", line 1726, in _run_agent_target
raise RuntimeError(event.message)
AgentError(message='Expecting value: line 1 column 1 (char 0)')
RuntimeError: Expecting value: line 1 column 1 (char 0)
Root cause
_request() tries a list of candidate paths and returns the first that parses. Those candidates are opencode's operationIds, not routes. From opencode 1.18.5's own /doc:
| adapter requests | what the string actually is | real route |
|---|---|---|
session.create |
operationId | POST /session |
event.subscribe |
operationId | GET /event |
session.promptAsync |
operationId | POST /session/{sessionID}/prompt_async |
session.abort |
operationId | POST /session/{sessionID}/abort |
session.prompt |
operationId | POST /session/{sessionID}/message |
Why it half-works. For session.create and event.subscribe the real paths are the bare collection nouns /session and /event, and the adapter's third fallback happens to be exactly that — so session creation and the event stream succeed by coincidence. _start_opencode_prompt and the abort call map to routes containing {sessionID}, which no static candidate string can produce, so they always fail.
Why the error is a JSON error and not a 404. opencode serves its web UI for unmatched routes with HTTP 200, so response.raise_for_status() passes and response.json() then fails on <!doctype html> — which is exactly Expecting value: line 1 column 1 (char 0).
Reproduction
Configure any opencode agent profile with any model and send a message. Or directly against a running opencode serve:
# adapter's three prompt candidates -- all return the web UI with HTTP 200
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' -X POST \
"http://127.0.0.1:$PORT/session/prompt" \
-H 'Content-Type: application/json' -d '{"sessionID":"...","parts":[]}'
# 200 2884 <- HTML, not JSON
# the real route
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' -X POST \
"http://127.0.0.1:$PORT/session/$SID/prompt_async" \
-H 'Content-Type: application/json' \
-d '{"sessionID":"'"$SID"'","model":{"providerID":"...","modelID":"..."},"parts":[{"type":"text","text":"hi"}]}'
# 204 0 <- accepted
Second defect, only observable once the routes are fixed
With the prompt route corrected, the reply is garbled two ways:
- Assistant text is emitted twice. opencode streams incremental
message.part.deltaevents and then a full-textmessage.part.updatedsnapshot. The delta branch of_text_from_eventnever records intoemitted, so the snapshot seesprevious == ""and re-emits the whole text. - The user's own prompt is echoed into the reply. opencode publishes the user's message as an ordinary text part, and
message.part.updatedis emitted for it without any role check.
Combined result for a one-line prompt: the prompt text followed by the answer repeated.
Suggested fix
In cptr/utils/agents/opencode.py:
- Prompt — try
f"session/{session_id}/prompt_async"first, keeping the existing names as fallbacks. - Abort — likewise
f"session/{session_id}/abort". _request— return{}whenresponse.contentis empty. The corrected prompt route answers204 No Content, which otherwise trips the identical JSON error. Empty body only: an HTML body must keep raising, or thesession.createprobe stops falling through to/session._text_from_event— build a role ledger frommessage.updated(it carriesproperties.sessionID, so it survives the session filter, plusproperties.info.{id,role}), skip parts whosemessageIDbelongs to a user message, and let the delta branch advanceemittedso the trailing snapshot is a no-op.
I've been running these four changes against opencode 1.18.5 for a day: clean single-copy replies, no prompt echo, and working tool calls.
Possibly related
#170 reports being unable to connect Computer to opencode. Different surface (Docker networking) and no error text quoted, so I filed separately, but it may share this root cause.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in cptr/utils/agents/opencode.py, reading _request, _start_opencode_prompt, the abort call, and _text_from_event. Reproduce the route and 204 response against opencode serve, then verify that prompts complete with one assistant copy, user text is excluded, abort works, and HTML responses still raise errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- openapi, python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100