detached 4548953

Add GPT-5.6 Luna to OpenCode Go

commit
45489539966c1daa2e69af974f464ecff4b065b2
author
patriceckhart · · 2026-08-04 11:03 UTC
parent
e455c59
signature
unsigned
refs
tag: v0.3.34
4 files · +53 -6
M packages/provider/catalog_builtin.go +1 -0
@@ -308,6 +308,7 @@ var builtinCatalog = []Model{
308308 {Provider: "opencode-go", ID: "glm-5", DisplayName: "GLM-5", ContextWindow: 202752, MaxOutput: 32768, Reasoning: true, PriceInput: 1, PriceOutput: 3.2, PriceCacheRead: 0.2, BaseURL: "https://opencode.ai/zen/go/v1"},
309309 {Provider: "opencode-go", ID: "glm-5.1", DisplayName: "GLM-5.1", ContextWindow: 202752, MaxOutput: 32768, Reasoning: true, PriceInput: 1.4, PriceOutput: 4.4, PriceCacheRead: 0.26, BaseURL: "https://opencode.ai/zen/go/v1"},
310310 {Provider: "opencode-go", ID: "glm-5.2", DisplayName: "GLM-5.2", ContextWindow: 1024000, MaxOutput: 128000, Reasoning: true, PriceInput: 0.91, PriceOutput: 2.86, PriceCacheRead: 0.169, BaseURL: "https://opencode.ai/zen/go/v1"},
311+ {Provider: "opencode-go", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", API: APIResponses, ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 0.2, PriceOutput: 1.2, PriceCacheRead: 0.02, PriceCacheWrite: 0.25, PriceTierInputTokens: 272000, PriceInputAbove: 0.4, PriceOutputAbove: 1.8, PriceCacheReadAbove: 0.04, PriceCacheWriteAbove: 0.5, BaseURL: "https://opencode.ai/zen/go/v1"},
311312 {Provider: "opencode-go", ID: "grok-4.5", DisplayName: "Grok 4.5", ContextWindow: 500000, MaxOutput: 500000, Reasoning: true, PriceInput: 2, PriceOutput: 6, PriceCacheRead: 0.3, BaseURL: "https://opencode.ai/zen/go/v1"},
312313 {Provider: "opencode-go", ID: "kimi-k2.5", DisplayName: "Kimi K2.5", ContextWindow: 262144, MaxOutput: 65536, Reasoning: true, PriceInput: 0.6, PriceOutput: 3, PriceCacheRead: 0.1, BaseURL: "https://opencode.ai/zen/go/v1"},
313314 {Provider: "opencode-go", ID: "kimi-k2.6", DisplayName: "Kimi K2.6", ContextWindow: 262144, MaxOutput: 65536, Reasoning: true, PriceInput: 0.95, PriceOutput: 4, PriceCacheRead: 0.16, BaseURL: "https://opencode.ai/zen/go/v1"},
@@ -308,6 +308,7 @@ var builtinCatalog = []Model{
308 {Provider: "opencode-go", ID: "glm-5", DisplayName: "GLM-5", ContextWindow: 202752, MaxOutput: 32768, Reasoning: true, PriceInput: 1, PriceOutput: 3.2, PriceCacheRead: 0.2, BaseURL: "https://opencode.ai/zen/go/v1"},308 {Provider: "opencode-go", ID: "glm-5", DisplayName: "GLM-5", ContextWindow: 202752, MaxOutput: 32768, Reasoning: true, PriceInput: 1, PriceOutput: 3.2, PriceCacheRead: 0.2, BaseURL: "https://opencode.ai/zen/go/v1"},
309 {Provider: "opencode-go", ID: "glm-5.1", DisplayName: "GLM-5.1", ContextWindow: 202752, MaxOutput: 32768, Reasoning: true, PriceInput: 1.4, PriceOutput: 4.4, PriceCacheRead: 0.26, BaseURL: "https://opencode.ai/zen/go/v1"},309 {Provider: "opencode-go", ID: "glm-5.1", DisplayName: "GLM-5.1", ContextWindow: 202752, MaxOutput: 32768, Reasoning: true, PriceInput: 1.4, PriceOutput: 4.4, PriceCacheRead: 0.26, BaseURL: "https://opencode.ai/zen/go/v1"},
310 {Provider: "opencode-go", ID: "glm-5.2", DisplayName: "GLM-5.2", ContextWindow: 1024000, MaxOutput: 128000, Reasoning: true, PriceInput: 0.91, PriceOutput: 2.86, PriceCacheRead: 0.169, BaseURL: "https://opencode.ai/zen/go/v1"},310 {Provider: "opencode-go", ID: "glm-5.2", DisplayName: "GLM-5.2", ContextWindow: 1024000, MaxOutput: 128000, Reasoning: true, PriceInput: 0.91, PriceOutput: 2.86, PriceCacheRead: 0.169, BaseURL: "https://opencode.ai/zen/go/v1"},
311+ {Provider: "opencode-go", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", API: APIResponses, ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 0.2, PriceOutput: 1.2, PriceCacheRead: 0.02, PriceCacheWrite: 0.25, PriceTierInputTokens: 272000, PriceInputAbove: 0.4, PriceOutputAbove: 1.8, PriceCacheReadAbove: 0.04, PriceCacheWriteAbove: 0.5, BaseURL: "https://opencode.ai/zen/go/v1"},
311 {Provider: "opencode-go", ID: "grok-4.5", DisplayName: "Grok 4.5", ContextWindow: 500000, MaxOutput: 500000, Reasoning: true, PriceInput: 2, PriceOutput: 6, PriceCacheRead: 0.3, BaseURL: "https://opencode.ai/zen/go/v1"},312 {Provider: "opencode-go", ID: "grok-4.5", DisplayName: "Grok 4.5", ContextWindow: 500000, MaxOutput: 500000, Reasoning: true, PriceInput: 2, PriceOutput: 6, PriceCacheRead: 0.3, BaseURL: "https://opencode.ai/zen/go/v1"},
312 {Provider: "opencode-go", ID: "kimi-k2.5", DisplayName: "Kimi K2.5", ContextWindow: 262144, MaxOutput: 65536, Reasoning: true, PriceInput: 0.6, PriceOutput: 3, PriceCacheRead: 0.1, BaseURL: "https://opencode.ai/zen/go/v1"},313 {Provider: "opencode-go", ID: "kimi-k2.5", DisplayName: "Kimi K2.5", ContextWindow: 262144, MaxOutput: 65536, Reasoning: true, PriceInput: 0.6, PriceOutput: 3, PriceCacheRead: 0.1, BaseURL: "https://opencode.ai/zen/go/v1"},
313 {Provider: "opencode-go", ID: "kimi-k2.6", DisplayName: "Kimi K2.6", ContextWindow: 262144, MaxOutput: 65536, Reasoning: true, PriceInput: 0.95, PriceOutput: 4, PriceCacheRead: 0.16, BaseURL: "https://opencode.ai/zen/go/v1"},314 {Provider: "opencode-go", ID: "kimi-k2.6", DisplayName: "Kimi K2.6", ContextWindow: 262144, MaxOutput: 65536, Reasoning: true, PriceInput: 0.95, PriceOutput: 4, PriceCacheRead: 0.16, BaseURL: "https://opencode.ai/zen/go/v1"},
M packages/provider/extra_providers.go +7 -2
@@ -124,9 +124,14 @@ func NewOpenCode(apiKey, baseURL string) Client {
124124 return NewOpenAICompat("opencode", apiKey, baseURL, "https://opencode.ai/zen/v1")
125125 }
126126
127-// NewOpenCodeGo is the opencode-go variant.
127+// NewOpenCodeGo routes each model through the wire API exposed by OpenCode Go.
128128 func NewOpenCodeGo(apiKey, baseURL string) Client {
129- return NewOpenAICompat("opencode-go", apiKey, baseURL, "https://opencode.ai/zen/go/v1")
129+ const defaultBaseURL = "https://opencode.ai/zen/go/v1"
130+ return NewModelRouter("opencode-go",
131+ NewOpenAICompat("opencode-go", apiKey, baseURL, defaultBaseURL),
132+ map[string]Client{
133+ APIResponses: NewOpenAIResponsesNamed(apiKey, firstNonEmptyString(baseURL, defaultBaseURL), "opencode-go"),
134+ })
130135 }
131136
132137 // NewMinimaxOpenAI is the OpenAI-completions flavor of MiniMax, in case
@@ -124,9 +124,14 @@ func NewOpenCode(apiKey, baseURL string) Client {
124 return NewOpenAICompat("opencode", apiKey, baseURL, "https://opencode.ai/zen/v1")124 return NewOpenAICompat("opencode", apiKey, baseURL, "https://opencode.ai/zen/v1")
125 }125 }
126 126
127-// NewOpenCodeGo is the opencode-go variant.127+// NewOpenCodeGo routes each model through the wire API exposed by OpenCode Go.
128 func NewOpenCodeGo(apiKey, baseURL string) Client {128 func NewOpenCodeGo(apiKey, baseURL string) Client {
129- return NewOpenAICompat("opencode-go", apiKey, baseURL, "https://opencode.ai/zen/go/v1")129+ const defaultBaseURL = "https://opencode.ai/zen/go/v1"
130+ return NewModelRouter("opencode-go",
131+ NewOpenAICompat("opencode-go", apiKey, baseURL, defaultBaseURL),
132+ map[string]Client{
133+ APIResponses: NewOpenAIResponsesNamed(apiKey, firstNonEmptyString(baseURL, defaultBaseURL), "opencode-go"),
134+ })
130 }135 }
131 136
132 // NewMinimaxOpenAI is the OpenAI-completions flavor of MiniMax, in case137 // NewMinimaxOpenAI is the OpenAI-completions flavor of MiniMax, in case
M packages/provider/model_router_test.go +31 -0
@@ -48,3 +48,34 @@ func TestModelRouterRejectsMissingAPIClient(t *testing.T) {
4848 t.Fatal("expected missing API client error")
4949 }
5050 }
51+
52+func TestOpenCodeGoRoutesLunaToResponses(t *testing.T) {
53+ router := NewOpenCodeGo("token", "https://example.com/go/v1").(*modelRouter)
54+ if got := router.fallback.(*openaiClient).baseURL; got != "https://example.com/go/v1" {
55+ t.Fatalf("Completions base URL = %q", got)
56+ }
57+ responses := router.byAPI[APIResponses].(*renamedClient).inner.(*codexClient)
58+ if got := responses.baseURL; got != "https://example.com/go/v1/responses" {
59+ t.Fatalf("Responses base URL = %q", got)
60+ }
61+
62+ completionsCapture := &routeCaptureClient{name: "opencode-go"}
63+ responsesCapture := &routeCaptureClient{name: "opencode-go"}
64+ router.fallback = completionsCapture
65+ router.byAPI[APIResponses] = responsesCapture
66+ for _, model := range []string{"gpt-5.6-luna", "kimi-k3"} {
67+ stream, err := router.Stream(context.Background(), Request{Model: model})
68+ if err != nil {
69+ t.Fatal(err)
70+ }
71+ for range stream {
72+ }
73+ }
74+
75+ if len(responsesCapture.models) != 1 || responsesCapture.models[0] != "gpt-5.6-luna" {
76+ t.Fatalf("Responses models = %v", responsesCapture.models)
77+ }
78+ if len(completionsCapture.models) != 1 || completionsCapture.models[0] != "kimi-k3" {
79+ t.Fatalf("Completions models = %v", completionsCapture.models)
80+ }
81+}
@@ -48,3 +48,34 @@ func TestModelRouterRejectsMissingAPIClient(t *testing.T) {
48 t.Fatal("expected missing API client error")48 t.Fatal("expected missing API client error")
49 }49 }
50 }50 }
51+
52+func TestOpenCodeGoRoutesLunaToResponses(t *testing.T) {
53+ router := NewOpenCodeGo("token", "https://example.com/go/v1").(*modelRouter)
54+ if got := router.fallback.(*openaiClient).baseURL; got != "https://example.com/go/v1" {
55+ t.Fatalf("Completions base URL = %q", got)
56+ }
57+ responses := router.byAPI[APIResponses].(*renamedClient).inner.(*codexClient)
58+ if got := responses.baseURL; got != "https://example.com/go/v1/responses" {
59+ t.Fatalf("Responses base URL = %q", got)
60+ }
61+
62+ completionsCapture := &routeCaptureClient{name: "opencode-go"}
63+ responsesCapture := &routeCaptureClient{name: "opencode-go"}
64+ router.fallback = completionsCapture
65+ router.byAPI[APIResponses] = responsesCapture
66+ for _, model := range []string{"gpt-5.6-luna", "kimi-k3"} {
67+ stream, err := router.Stream(context.Background(), Request{Model: model})
68+ if err != nil {
69+ t.Fatal(err)
70+ }
71+ for range stream {
72+ }
73+ }
74+
75+ if len(responsesCapture.models) != 1 || responsesCapture.models[0] != "gpt-5.6-luna" {
76+ t.Fatalf("Responses models = %v", responsesCapture.models)
77+ }
78+ if len(completionsCapture.models) != 1 || completionsCapture.models[0] != "kimi-k3" {
79+ t.Fatalf("Completions models = %v", completionsCapture.models)
80+ }
81+}
M packages/provider/provider_test.go +14 -4
@@ -516,10 +516,8 @@ func TestGPT56CatalogEntries(t *testing.T) {
516516 }
517517 }
518518
519-// TestOpenCodeGoCatalog pins the Grok 4.5 and Kimi K3 entries added to
520-// the OpenCode Go provider. Both are listed in the vendor's current Go
521-// model lineup (https://opencode.ai/docs/go) and were missing from the
522-// baked-in catalog.
519+// TestOpenCodeGoCatalog pins entries added from the vendor's current Go
520+// model lineup (https://opencode.ai/docs/go).
523521 func TestOpenCodeGoCatalog(t *testing.T) {
524522 cases := []struct {
525523 id string
@@ -529,6 +527,7 @@ func TestOpenCodeGoCatalog(t *testing.T) {
529527 priceOut float64
530528 cacheRead float64
531529 }{
530+ {"gpt-5.6-luna", 1050000, 128000, 0.2, 1.2, 0.02},
532531 {"grok-4.5", 500000, 500000, 2, 6, 0.3},
533532 {"kimi-k3", 1048576, 131072, 3, 15, 0.3},
534533 }
@@ -547,6 +546,17 @@ func TestOpenCodeGoCatalog(t *testing.T) {
547546 t.Fatalf("opencode-go/%s baseURL: %s", tc.id, m.BaseURL)
548547 }
549548 }
549+
550+ luna, err := FindModel("opencode-go", "gpt-5.6-luna")
551+ if err != nil {
552+ t.Fatal(err)
553+ }
554+ if luna.API != APIResponses || luna.PriceCacheWrite != 0.25 {
555+ t.Fatalf("opencode-go/gpt-5.6-luna API or cache-write price: %+v", luna)
556+ }
557+ if luna.PriceTierInputTokens != 272000 || luna.PriceInputAbove != 0.4 || luna.PriceOutputAbove != 1.8 || luna.PriceCacheReadAbove != 0.04 || luna.PriceCacheWriteAbove != 0.5 {
558+ t.Fatalf("opencode-go/gpt-5.6-luna long-context prices: %+v", luna)
559+ }
550560 }
551561
552562 // TestOpenAIOmitsZeroMaxTokens guards against sending max_tokens: 0 for
@@ -516,10 +516,8 @@ func TestGPT56CatalogEntries(t *testing.T) {
516 }516 }
517 }517 }
518 518
519-// TestOpenCodeGoCatalog pins the Grok 4.5 and Kimi K3 entries added to519+// TestOpenCodeGoCatalog pins entries added from the vendor's current Go
520-// the OpenCode Go provider. Both are listed in the vendor's current Go520+// model lineup (https://opencode.ai/docs/go).
521-// model lineup (https://opencode.ai/docs/go) and were missing from the
522-// baked-in catalog.
523 func TestOpenCodeGoCatalog(t *testing.T) {521 func TestOpenCodeGoCatalog(t *testing.T) {
524 cases := []struct {522 cases := []struct {
525 id string523 id string
@@ -529,6 +527,7 @@ func TestOpenCodeGoCatalog(t *testing.T) {
529 priceOut float64527 priceOut float64
530 cacheRead float64528 cacheRead float64
531 }{529 }{
530+ {"gpt-5.6-luna", 1050000, 128000, 0.2, 1.2, 0.02},
532 {"grok-4.5", 500000, 500000, 2, 6, 0.3},531 {"grok-4.5", 500000, 500000, 2, 6, 0.3},
533 {"kimi-k3", 1048576, 131072, 3, 15, 0.3},532 {"kimi-k3", 1048576, 131072, 3, 15, 0.3},
534 }533 }
@@ -547,6 +546,17 @@ func TestOpenCodeGoCatalog(t *testing.T) {
547 t.Fatalf("opencode-go/%s baseURL: %s", tc.id, m.BaseURL)546 t.Fatalf("opencode-go/%s baseURL: %s", tc.id, m.BaseURL)
548 }547 }
549 }548 }
549+
550+ luna, err := FindModel("opencode-go", "gpt-5.6-luna")
551+ if err != nil {
552+ t.Fatal(err)
553+ }
554+ if luna.API != APIResponses || luna.PriceCacheWrite != 0.25 {
555+ t.Fatalf("opencode-go/gpt-5.6-luna API or cache-write price: %+v", luna)
556+ }
557+ if luna.PriceTierInputTokens != 272000 || luna.PriceInputAbove != 0.4 || luna.PriceOutputAbove != 1.8 || luna.PriceCacheReadAbove != 0.04 || luna.PriceCacheWriteAbove != 0.5 {
558+ t.Fatalf("opencode-go/gpt-5.6-luna long-context prices: %+v", luna)
559+ }
550 }560 }
551 561
552 // TestOpenAIOmitsZeroMaxTokens guards against sending max_tokens: 0 for562 // TestOpenAIOmitsZeroMaxTokens guards against sending max_tokens: 0 for

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close