I wanted to try Pi, the unopinionated coding harness, instead of OpenCode.
The idea was simple: keep my existing model setup, put Pi in front of it, and see how a less opinionated coding harness felt.
I already had OpenCodex (OCX) running as my local model proxy, so I expected this to be mostly configuration.
Instead, Muse Spark 1.2 Contributor Free started returning HTTP 500 errors.
What followed was a surprisingly deep rabbit hole involving OpenAI-compatible APIs, mitmproxy, OCX's adapter routing, and eventually a model-specific protocol mismatch.
And the final fix was only one configuration entry.
The setup
My intended architecture was:
Pi
↓
OpenCodex (OCX)
↓
Model providers
OCX exposes an OpenAI-compatible local API, so Pi could use it as its model backend.
Muse Spark 1.2 Contributor Free was already available through the OpenCode provider:
opencode-free/muse-spark-1.2-contributor-free
It was visible in live model discovery and the provider itself was reachable.
But requests through OCX returned:
500 Internal Server Error
First suspicion: authentication
The first thing I checked was authentication.
OCX's provider logs showed requests like:
[ocx:openai-chat:request]
{
"host": "opencode.ai",
"model": "muse-spark-1.2-contributor-free",
"stream": false,
"messageCount": 1,
"toolCount": 0,
"hasCredential": false
}
The hasCredential: false looked suspicious.
I tested the provider directly and OCX reported:
Connected — 64 models available.
So the provider itself was reachable.
I also tested with and without an authorization header.
The result was still a 500 for the free Muse model.
Authentication wasn't the real problem.
OpenCode was the clue
There was one useful fact:
OpenCode could use the exact same model successfully.
That gave me something much better than guessing.
Instead of asking what should happen, I could see what a working client was actually doing.
So I put OpenCode's traffic through mitmproxy.
That's where I found the important difference.
OpenCode was sending Muse Spark through:
POST /zen/v1/responses
not Chat Completions.
The request looked roughly like:
{
"model": "muse-spark-1.2-contributor-free",
"input": [...],
"max_output_tokens": 32000,
"reasoning": {
"effort": "xhigh",
"summary": "auto"
},
"stream": true
}
And the upstream response was:
HTTP/1.1 200 OK
Content-Type: text/event-stream
The stream contained the Responses API events, including reasoning and output text events, and completed successfully.
So the upstream model was working.
Now I had a much more interesting question:
Why was OCX getting a 500 when OpenCode wasn't?
Looking at the adapter
I started digging through the OCX source.
OCX has separate adapters for different OpenAI-compatible protocols, including:
openai-chat
openai-responses
My opencode-free provider was configured with:
{
"adapter": "openai-chat",
"baseUrl": "https://opencode.ai/zen/v1"
}
And the logs confirmed that Muse was being routed through the openai-chat adapter.
That gave me the mismatch:
OpenCode
↓
Muse Spark
↓
/responses
↓
200 OK
while OCX was effectively doing:
OCX
↓
Muse Spark
↓
openai-chat
↓
Chat Completions
↓
500
The provider was OpenAI-compatible.
But that didn't mean every model necessarily wanted the same OpenAI-compatible wire.
The nice part: per-model routing
I didn't want to change the entire provider to openai-responses.
Other models were already working with the existing configuration.
While looking through OCX's configuration and adapter handling, I found support for per-model adapter selection.
That meant I could keep the provider itself on openai-chat and tell OCX that Muse Spark should use the Responses adapter.
I added:
{
"modelAdapters": {
"muse-spark-1.2-contributor-free": "openai-responses"
}
}
After applying the configuration, OCX reported the model-specific adapter:
{
"muse-spark-1.2-contributor-free": "openai-responses"
}
And then it worked
I sent the same request again.
Before:
500 Internal Server Error
After:
200 OK
The response was:
OCX Muse works
The final routing became:
Pi
↓
OCX
↓
opencode-free
↓
Muse Spark
↓
openai-responses
↓
/zen/v1/responses
↓
200 OK
The funny part
The entire investigation started because I wanted to try Pi.
I wasn't trying to reverse-engineer Muse Spark.
I wasn't trying to investigate OpenCode's HTTP implementation.
I wasn't even trying to debug OCX.
I just wanted to see what happened if I switched from OpenCode to a more unopinionated coding harness while keeping my existing model infrastructure.
That turned into:
Pi
↓
500 error
↓
Check credentials
↓
Check provider
↓
Compare with OpenCode
↓
mitmproxy
↓
Observe /responses
↓
Inspect OCX source
↓
Find model-specific adapter routing
↓
openai-responses
↓
200 OK
And honestly, this was one of those debugging sessions where the final configuration change is tiny, but getting to it is the interesting part.
The takeaway
The biggest lesson for me was that "OpenAI-compatible" is not necessarily one single wire protocol.
A provider can expose multiple models while different models—or different clients—may expect different API surfaces.
In this case, the model worked through the Responses API, while my proxy was initially sending it through the Chat Completions adapter.
The most useful debugging step wasn't staring at the 500.
It was capturing the request from the client that already worked and asking:
"What is this client actually sending?"
That turned a vague 500 into a concrete protocol mismatch.
And all of this because I wanted to try another coding harness. 😄
Top comments (0)