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{ | |||
| 308 | 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 | 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 | 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 | 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 | 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 | 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"}, | |
| @@ -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 { | |||
| 124 | 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 | 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 | 137 | // 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 case | 137 | // 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) { | |||
| 48 | 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 | + | } | |
| @@ -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) { | |||
| 516 | 516 | } | |
| 517 | 517 | } | |
| 518 | 518 | ||
| 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). | |
| 523 | 521 | func TestOpenCodeGoCatalog(t *testing.T) { | |
| 524 | 522 | cases := []struct { | |
| 525 | 523 | id string | |
| @@ -529,6 +527,7 @@ func TestOpenCodeGoCatalog(t *testing.T) { | |||
| 529 | 527 | priceOut float64 | |
| 530 | 528 | cacheRead float64 | |
| 531 | 529 | }{ | |
| 530 | + | {"gpt-5.6-luna", 1050000, 128000, 0.2, 1.2, 0.02}, | |
| 532 | 531 | {"grok-4.5", 500000, 500000, 2, 6, 0.3}, | |
| 533 | 532 | {"kimi-k3", 1048576, 131072, 3, 15, 0.3}, | |
| 534 | 533 | } | |
| @@ -547,6 +546,17 @@ func TestOpenCodeGoCatalog(t *testing.T) { | |||
| 547 | 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 | 562 | // 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 to | 519 | + | // TestOpenCodeGoCatalog pins entries added from the vendor's current Go |
| 520 | - | // the OpenCode Go provider. Both are listed in the vendor's current Go | 520 | + | // 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 string | 523 | id string | ||
| @@ -529,6 +527,7 @@ func TestOpenCodeGoCatalog(t *testing.T) { | |||||
| 529 | priceOut float64 | 527 | priceOut float64 | ||
| 530 | cacheRead float64 | 528 | 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 for | 562 | // TestOpenAIOmitsZeroMaxTokens guards against sending max_tokens: 0 for | ||