返回 DeepSeek-Reasonix
opencode_go_test.go
根目录 / internal / provider / opencode_go_test.go
1 package provider
2
3 import "testing"
4
5 func TestOpenCodeGoChatModelsMatchPinnedLimits(t *testing.T) {
6 want := map[string]OpenCodeGoModelLimits{
7 "glm-5.3": {Context: 1_000_000, MaxOutput: 131_072},
8 "glm-5.2": {Context: 1_000_000, MaxOutput: 131_072},
9 "glm-5.1": {Context: 202_752, MaxOutput: 32_768},
10 "kimi-k3": {Context: 1_048_576, MaxOutput: 131_072},
11 "kimi-k2.7-code": {Context: 262_144, MaxOutput: 262_144},
12 "kimi-k2.6": {Context: 262_144, MaxOutput: 65_536},
13 "deepseek-v4-pro": {Context: 1_000_000, MaxOutput: 384_000},
14 "deepseek-v4-flash": {Context: 1_000_000, MaxOutput: 384_000},
15 "deepseek-v4-flash-vision-exp": {Context: 1_000_000, MaxOutput: 384_000},
16 "mimo-v2.5-pro": {Context: 1_048_576, MaxOutput: 128_000},
17 "mimo-v2.5": {Context: 1_000_000, MaxOutput: 128_000},
18 "hy3": {Context: 256_000, MaxOutput: 64_000},
19 }
20 got := OpenCodeGoChatModels()
21 for id, lim := range want {
22 if got[id] != lim {
23 t.Fatalf("%s = %+v, want %+v", id, got[id], lim)
24 }
25 }
26 }
27
28 func TestOpenCodeGoAnthropicModelsMatchPinnedLimits(t *testing.T) {
29 want := map[string]OpenCodeGoModelLimits{
30 "qwen3.8-max": {Context: 1_000_000, MaxOutput: 131_072},
31 "qwen3.7-max": {Context: 1_000_000, MaxOutput: 65_536},
32 "qwen3.7-plus": {Context: 1_000_000, MaxOutput: 65_536},
33 "qwen3.6-plus": {Context: 1_000_000, MaxOutput: 65_536},
34 "minimax-m3": {Context: 1_000_000, MaxOutput: 131_072},
35 "minimax-m2.7": {Context: 204_800, MaxOutput: 131_072},
36 "minimax-m2.5": {Context: 204_800, MaxOutput: 65_536},
37 }
38 got := OpenCodeGoAnthropicModels()
39 for id, lim := range want {
40 if got[id] != lim {
41 t.Fatalf("%s = %+v, want %+v", id, got[id], lim)
42 }
43 }
44 }
45
46 func TestOpenCodeGoResponsesModelsMatchPinnedLimits(t *testing.T) {
47 want := map[string]OpenCodeGoModelLimits{
48 "grok-4.5": {Context: 500_000, MaxOutput: 500_000},
49 "gpt-5.6-luna": {Context: 1_050_000, MaxOutput: 128_000},
50 "muse-spark-1.2-contributor": {Context: 1_048_576, MaxOutput: 131_072},
51 }
52 got := OpenCodeGoResponsesModels()
53 for id, lim := range want {
54 if got[id] != lim {
55 t.Fatalf("%s = %+v, want %+v", id, got[id], lim)
56 }
57 }
58 }
59
60 func TestFilterOfficialOpenCodeGoModelsKeepsOnlyRouteCompatibleModels(t *testing.T) {
61 all := []string{"grok-4.5", "gpt-5.6-luna", "muse-spark-1.2-contributor", "glm-5.3", "glm-5.2", "hy3", "qwen3.8-max", "qwen3.7-plus", "deepseek-flash", "deepseek-v4-flash", "unknown-future-model"}
62 tests := []struct {
63 name string
64 kind string
65 baseURL string
66 want []string
67 }{
68 {name: "chat", kind: "openai", baseURL: "https://opencode.ai/zen/go/v1", want: []string{"glm-5.3", "glm-5.2", "hy3", "qwen3.8-max", "qwen3.7-plus", "deepseek-flash", "deepseek-v4-flash"}},
69 {name: "anthropic", kind: "anthropic", baseURL: "https://opencode.ai/zen/go", want: []string{"qwen3.8-max", "qwen3.7-plus", "deepseek-flash", "deepseek-v4-flash"}},
70 {name: "responses", kind: "responses", baseURL: "https://opencode.ai/zen/go/v1", want: []string{"grok-4.5", "gpt-5.6-luna", "muse-spark-1.2-contributor", "deepseek-flash", "deepseek-v4-flash"}},
71 {name: "custom endpoint is untouched", kind: "anthropic", baseURL: "https://relay.example/zen/go", want: all},
72 }
73
74 for _, tt := range tests {
75 t.Run(tt.name, func(t *testing.T) {
76 got := FilterOfficialOpenCodeGoModels(tt.kind, tt.baseURL, all)
77 if len(got) != len(tt.want) {
78 t.Fatalf("filtered models = %v, want %v", got, tt.want)
79 }
80 for i := range got {
81 if got[i] != tt.want[i] {
82 t.Fatalf("filtered models = %v, want %v", got, tt.want)
83 }
84 }
85 })
86 }
87 }
88
89 func TestLookupOfficialOpenCodeGoRejectsLookalikes(t *testing.T) {
90 if _, ok := LookupOfficialOpenCodeGo("openai", "https://opencode.ai.attacker.example/zen/go/v1", "kimi-k3"); ok {
91 t.Fatal("lookalike host must not match")
92 }
93 if _, ok := LookupOfficialOpenCodeGo("openai", "https://opencode.ai/zen/go/v1?x=1", "kimi-k3"); ok {
94 t.Fatal("query string must not match")
95 }
96 if _, ok := LookupOfficialOpenCodeGo("openai", "http://opencode.ai/zen/go/v1", "kimi-k3"); ok {
97 t.Fatal("http must not match")
98 }
99 if _, ok := LookupOfficialOpenCodeGo("openai", "https://opencode.ai/zen/go/v1", "future-model"); ok {
100 t.Fatal("unknown model must not assume limits")
101 }
102 if _, ok := LookupOfficialOpenCodeGo("openai", "https://gateway.example/zen/go/v1", "kimi-k3"); ok {
103 t.Fatal("custom proxy must not match")
104 }
105 }
106
107 func TestLookupOfficialOpenCodeGoKnownRoutes(t *testing.T) {
108 chat, ok := LookupOfficialOpenCodeGo("openai", "https://opencode.ai/zen/go/v1", "kimi-k3")
109 if !ok || chat.Context != 1_048_576 || chat.MaxOutput != 131_072 {
110 t.Fatalf("chat kimi-k3 = %+v ok=%v", chat, ok)
111 }
112 anth, ok := LookupOfficialOpenCodeGo("anthropic", "https://opencode.ai/zen/go", "qwen3.7-plus")
113 if !ok || anth.MaxOutput != 65_536 {
114 t.Fatalf("anthropic qwen = %+v ok=%v", anth, ok)
115 }
116 resp, ok := LookupOfficialOpenCodeGo("responses", "https://opencode.ai/zen/go/v1", "deepseek-v4-flash")
117 if !ok || resp.MaxOutput != 384_000 {
118 t.Fatalf("responses flash = %+v ok=%v", resp, ok)
119 }
120 grok, ok := LookupOfficialOpenCodeGo("responses", "https://opencode.ai/zen/go/v1", "grok-4.5")
121 if !ok || grok.Context != 500_000 || grok.MaxOutput != 500_000 {
122 t.Fatalf("responses grok = %+v ok=%v", grok, ok)
123 }
124 }
125
126 func TestOpenCodeGoModelInfoUsesExactLocalCatalog(t *testing.T) {
127 vision, ok := OpenCodeGoModelInfo("openai", "https://opencode.ai/zen/go/v1", "kimi-k3")
128 if !ok || !vision.SupportsInput(ModalityImage) {
129 t.Fatalf("kimi-k3 metadata = %+v, ok=%t", vision, ok)
130 }
131 text, ok := OpenCodeGoModelInfo("openai", "https://opencode.ai/zen/go/v1", "glm-5.2")
132 if !ok || text.SupportsInput(ModalityImage) || len(text.InputModalities) != 1 || text.InputModalities[0] != ModalityText {
133 t.Fatalf("glm-5.2 metadata = %+v, ok=%t", text, ok)
134 }
135 if _, ok := OpenCodeGoModelInfo("openai", "https://opencode.ai/zen/go/v1", "omen-alpha"); ok {
136 t.Fatal("uncatalogued model must not be inferred from its endpoint")
137 }
138 kimi, ok := OpenCodeGoModelInfo("openai", "https://opencode.ai/zen/go/v1", "kimi-k2.6")
139 if !ok || !kimi.SupportsInput(ModalityImage) {
140 t.Fatalf("pi catalog kimi-k2.6 metadata = %+v, ok=%t", kimi, ok)
141 }
142 if kimi.ContextWindow == 0 || kimi.MaxOutputTokens == 0 || kimi.API == "" {
143 t.Fatalf("pi catalog should preserve model metadata, got %+v", kimi)
144 }
145 }
146
147 func TestPiCatalogModelInfosContainsMultipleProviders(t *testing.T) {
148 for _, id := range []string{"opencode-go", "deepseek", "anthropic", "openai"} {
149 if models := PiCatalogModelInfos(id); len(models) == 0 {
150 t.Fatalf("pi catalog provider %q is empty", id)
151 }
152 }
153 }
154
155 func TestPiCatalogModelInfoForProviderRequiresExactServingRoute(t *testing.T) {
156 model, ok := PiCatalogModelInfoForProvider("opencode-go", "openai", "https://opencode.ai/zen/go/v1", "qwen3.6-plus")
157 if !ok || !model.SupportsInput(ModalityImage) || model.API != "openai-completions" {
158 t.Fatalf("OpenCode catalog model = %+v, ok=%t", model, ok)
159 }
160 if _, ok := PiCatalogModelInfoForProvider("opencode-go", "openai", "https://gateway.example/v1", "qwen3.6-plus"); ok {
161 t.Fatal("custom endpoint must not inherit pi catalog metadata")
162 }
163 }
164
165 func TestPiCatalogContractKeepsKnownCapabilityFacts(t *testing.T) {
166 cases := []struct {
167 kind, baseURL, model string
168 image bool
169 }{
170 {"openai", "https://opencode.ai/zen/go/v1", "kimi-k3", true},
171 {"openai", "https://opencode.ai/zen/go/v1", "glm-5.2", false},
172 {"openai", "https://opencode.ai/zen/go/v1", "deepseek-v4-flash-vision-exp", true},
173 {"anthropic", "https://opencode.ai/zen/go", "qwen3.8-flash", true},
174 {"responses", "https://opencode.ai/zen/go/v1", "grok-4.6", true},
175 }
176 for _, tc := range cases {
177 info, ok := PiCatalogModelInfo(tc.kind, tc.baseURL, tc.model)
178 if !ok || info.SupportsInput(ModalityImage) != tc.image {
179 t.Fatalf("pi catalog contract %s/%s = %+v, ok=%t", tc.kind, tc.model, info, ok)
180 }
181 }
182 }
183
184 func TestModelScopeModelInfoUsesVerifiedLocalCatalog(t *testing.T) {
185 vision, ok := ModelScopeModelInfo("openai", "https://api-inference.modelscope.cn/v1", "Qwen/Qwen3.5-27B")
186 if !ok || !vision.SupportsInput(ModalityImage) {
187 t.Fatalf("ModelScope vision metadata = %+v, ok=%t", vision, ok)
188 }
189 text, ok := ModelScopeModelInfo("openai", "https://api-inference.modelscope.cn/v1", "ZhipuAI/GLM-5.2")
190 if !ok || text.SupportsInput(ModalityImage) {
191 t.Fatalf("ModelScope text metadata = %+v, ok=%t", text, ok)
192 }
193 if _, ok := ModelScopeModelInfo("openai", "https://gateway.example/v1", "Qwen/Qwen3.5-27B"); ok {
194 t.Fatal("custom ModelScope lookalike must not use the local catalog")
195 }
196 }
197
198 func TestBuiltinModelInfoIncludesDeepSeekVisionSKU(t *testing.T) {
199 vision, ok := BuiltinModelInfo("openai", "https://api.deepseek.com/v1", "deepseek-v4-flash-vision-exp")
200 if !ok || !vision.SupportsInput(ModalityImage) {
201 t.Fatalf("DeepSeek vision metadata = %+v, ok=%t", vision, ok)
202 }
203 text, ok := BuiltinModelInfo("openai", "https://api.deepseek.com/v1", "deepseek-v4-pro")
204 if !ok || text.SupportsInput(ModalityImage) {
205 t.Fatalf("DeepSeek text metadata = %+v, ok=%t", text, ok)
206 }
207 }
208
208 lines GO