diff --git a/internal/lua/adapters/opencode.lua b/internal/lua/adapters/opencode.lua index 72e2e06..0d21eda 100644 --- a/internal/lua/adapters/opencode.lua +++ b/internal/lua/adapters/opencode.lua @@ -54,8 +54,14 @@ function adapter.transform_request(raw_body) if not ok then return raw_body end req.disable_thinking = nil req.extra_body = nil - if type(req.stream_options) ~= "table" then req.stream_options = {} end - req.stream_options.include_usage = true + -- stream_options is only valid alongside stream:true. Sending it on a + -- non-streaming request is rejected by stricter upstreams (OpenCode Go: + -- "stream_options should be set along with stream"), which failed every + -- non-stream call. Only attach it when the request actually streams. + if req.stream then + if type(req.stream_options) ~= "table" then req.stream_options = {} end + req.stream_options.include_usage = true + end if req.messages then local kept = {} for _, msg in ipairs(req.messages) do diff --git a/internal/lua/toolcall_preservation_test.go b/internal/lua/toolcall_preservation_test.go index f4f7c9f..5aed735 100644 --- a/internal/lua/toolcall_preservation_test.go +++ b/internal/lua/toolcall_preservation_test.go @@ -118,3 +118,36 @@ func TestAdaptersPreserveToolCalls(t *testing.T) { } } } + +// TestOpenCodeStreamOptionsOnlyWhenStreaming pins the fix for +// "stream_options should be set along with stream": OpenCode Go (and other +// strict OpenAI-compatible upstreams) reject a non-streaming request that +// carries stream_options, which broke every non-stream call through the +// opencode adapter. +func TestOpenCodeStreamOptionsOnlyWhenStreaming(t *testing.T) { + vm := NewVM(freshAdapterDir(t)) + if err := vm.Start(); err != nil { + t.Fatal(err) + } + defer vm.Stop() + + // non-streaming: stream_options must be absent entirely + out, err := vm.Transform("opencode", "transform_request", + `{"model":"m","messages":[{"role":"user","content":"hi"}]}`) + if err != nil { + t.Fatal(err) + } + if strings.Contains(out, "stream_options") { + t.Fatalf("non-streaming request must not carry stream_options: %s", out) + } + + // streaming: stream_options.include_usage must be set + sout, err := vm.Transform("opencode", "transform_request", + `{"model":"m","stream":true,"messages":[{"role":"user","content":"hi"}]}`) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(sout, "stream_options") || !strings.Contains(sout, "include_usage") { + t.Fatalf("streaming request must carry stream_options.include_usage: %s", sout) + } +}