From 0528941e24a1e500b60cb181842d0fe9e6c8e009 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 11 Sep 2026 13:13:05 +0800 Subject: [PATCH] fix(opencode): only send stream_options with stream:true OpenCode Go (and other strict OpenAI-compatible upstreams) reject a non-streaming request that carries stream_options with "stream_options should be set along with stream". The adapter attached it unconditionally, so every non-stream call through the opencode adapter failed on those upstreams. Verified against OpenCode Go: 25/25 configured models now pass a real completion through the gateway (they previously 400'd). Test: TestOpenCodeStreamOptionsOnlyWhenStreaming (absent when non-streaming, include_usage present when streaming). --- internal/lua/adapters/opencode.lua | 10 +++++-- internal/lua/toolcall_preservation_test.go | 33 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) 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) + } +}