Skip to content

Commit 026e43f

Browse files
committed
fix(opencode-go): add GPT-6 Luna support
1 parent ecab519 commit 026e43f

5 files changed

Lines changed: 134 additions & 9 deletions

File tree

‎packages/types/src/__tests__/opencode-go.test.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe("opencode-go registry", () => {
3939
]
4040
const responsesFormatModels = [
4141
"gpt-5.6-luna",
42+
"gpt-6-luna",
4243
"grok-4.6",
4344
"muse-spark-1.3-contributor",
4445
"muse-spark-1.2-contributor",
@@ -189,6 +190,30 @@ describe("opencode-go registry", () => {
189190
})
190191
})
191192

193+
it("curates gpt-6-luna with its documented Responses capabilities and pricing", () => {
194+
expect(getOpencodeGoModelInfo("gpt-6-luna")).toMatchObject({
195+
maxTokens: 128_000,
196+
supportsMaxTokens: true,
197+
supportsTemperature: false,
198+
contextWindow: 1_050_000,
199+
supportsImages: true,
200+
supportsPromptCache: true,
201+
supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"],
202+
reasoningEffort: "medium",
203+
inputPrice: 0.1,
204+
outputPrice: 0.5,
205+
cacheWritesPrice: 0.125,
206+
cacheReadsPrice: 0.01,
207+
longContextPricing: {
208+
thresholdTokens: 272_000,
209+
inputPriceMultiplier: 2,
210+
outputPriceMultiplier: 1.5,
211+
cacheWritesPriceMultiplier: 2,
212+
cacheReadsPriceMultiplier: 2,
213+
},
214+
})
215+
})
216+
192217
it("is disjoint from the Anthropic-format set", () => {
193218
for (const id of OPENCODE_GO_RESPONSES_FORMAT_MODELS) {
194219
expect(OPENCODE_GO_ANTHROPIC_FORMAT_MODELS.has(id)).toBe(false)

‎packages/types/src/providers/opencode-go.ts‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,8 +565,8 @@ export const opencodeGoModels: Record<string, ModelInfo> = {
565565
},
566566

567567
// --- OpenAI Responses ---
568-
// Luna is curated here because the Go gateway's model catalogue does not
569-
// currently provide its capability metadata. These values intentionally
568+
// Luna models are curated here because the Go gateway's model catalogue does
569+
// not currently provide their capability metadata. These values intentionally
570570
// describe the Go Responses route, not the OpenAI-native or Codex routes.
571571
"gpt-5.6-luna": {
572572
maxTokens: 128_000,
@@ -590,6 +590,30 @@ export const opencodeGoModels: Record<string, ModelInfo> = {
590590
description:
591591
"GPT-5.6 Luna is OpenAI's fast reasoning model with a 1M context window. Available via the Opencode Go plan.",
592592
},
593+
"gpt-6-luna": {
594+
maxTokens: 128_000,
595+
contextWindow: 1_050_000,
596+
supportsImages: true,
597+
supportsPromptCache: true,
598+
supportsMaxTokens: true,
599+
// The OpenCode Go Responses model silently drops `temperature`, so the
600+
// UI temperature control would otherwise render as a dead control.
601+
supportsTemperature: false,
602+
supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"],
603+
reasoningEffort: "medium",
604+
inputPrice: 0.1,
605+
outputPrice: 0.5,
606+
cacheWritesPrice: 0.125,
607+
cacheReadsPrice: 0.01,
608+
longContextPricing: {
609+
thresholdTokens: 272_000,
610+
inputPriceMultiplier: 2,
611+
outputPriceMultiplier: 1.5,
612+
cacheWritesPriceMultiplier: 2,
613+
cacheReadsPriceMultiplier: 2,
614+
},
615+
description: "GPT-6 Luna via the OpenCode Go Responses API.",
616+
},
593617
"grok-4.5": {
594618
maxTokens: 500_000,
595619
contextWindow: 500_000,
@@ -708,6 +732,7 @@ export const OPENCODE_GO_ANTHROPIC_FORMAT_MODELS = new Set<string>([
708732
export const OPENCODE_GO_RESPONSES_FORMAT_MODELS = new Set<string>([
709733
// --- OpenAI ---
710734
"gpt-5.6-luna",
735+
"gpt-6-luna",
711736
// --- xAI ---
712737
"grok-4.6",
713738
// --- Meta ---

‎src/api/providers/__tests__/opencode-go.spec.ts‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,17 @@ vitest.mock("../fetchers/modelCache", () => ({
4040
"glm-5.1": { ...opencodeGoModels["glm-5.1"] },
4141
// Anthropic-format model used to exercise the /v1/messages path.
4242
"qwen3.7-max": { ...opencodeGoModels["qwen3.7-max"] },
43-
// Responses-format model (Zoo-Code-Org/Zoo-Code#1431).
43+
// Responses-format models (Zoo-Code-Org/Zoo-Code#1431 and issue #1979).
4444
"gpt-5.6-luna": { ...opencodeGoModels["gpt-5.6-luna"] },
45+
"gpt-6-luna": { ...opencodeGoModels["gpt-6-luna"] },
4546
})
4647
}),
4748
refreshModels: vitest.fn().mockImplementation(function () {
4849
return Promise.resolve({
4950
"glm-5.1": { ...opencodeGoModels["glm-5.1"] },
5051
"qwen3.7-max": { ...opencodeGoModels["qwen3.7-max"] },
5152
"gpt-5.6-luna": { ...opencodeGoModels["gpt-5.6-luna"] },
53+
"gpt-6-luna": { ...opencodeGoModels["gpt-6-luna"] },
5254
})
5355
}),
5456
getModelsFromCache: vitest.fn().mockReturnValue(undefined),
@@ -833,7 +835,7 @@ describe("OpencodeGoHandler", () => {
833835
})
834836
})
835837

836-
describe("Responses-format models (gpt-5.6-luna)", () => {
838+
describe("Responses-format models (gpt-5.6-luna, gpt-6-luna)", () => {
837839
// gpt-5.6-luna is Responses-only on the Go gateway: its chat-completions
838840
// adapter fails with an opaque HTTP 500 (Zoo-Code-Org/Zoo-Code#1431),
839841
// so the handler must route it through /v1/responses and never fall
@@ -842,6 +844,10 @@ describe("OpencodeGoHandler", () => {
842844
opencodeGoApiKey: "test-key",
843845
opencodeGoModelId: "gpt-5.6-luna",
844846
}
847+
const gpt6LunaOptions: ApiHandlerOptions = {
848+
opencodeGoApiKey: "test-key",
849+
opencodeGoModelId: "gpt-6-luna",
850+
}
845851

846852
beforeEach(() => {
847853
mockResponsesCreate.mockImplementation(async () =>
@@ -982,6 +988,21 @@ describe("OpencodeGoHandler", () => {
982988
expect(mockAnthropicCreate).not.toHaveBeenCalled()
983989
})
984990

991+
it("routes gpt-6-luna streaming through responses.create only", async () => {
992+
const handler = new OpencodeGoHandler(gpt6LunaOptions)
993+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
994+
995+
const chunks = await collectStream(handler.createMessage("sys", messages))
996+
997+
expect(chunks).toContainEqual({ type: "text", text: "Hello" })
998+
expect(mockResponsesCreate).toHaveBeenCalledWith(
999+
expect.objectContaining({ model: "gpt-6-luna", stream: true }),
1000+
expect.anything(),
1001+
)
1002+
expect(mockCreate).not.toHaveBeenCalled()
1003+
expect(mockAnthropicCreate).not.toHaveBeenCalled()
1004+
})
1005+
9851006
it("streams text and reasoning chunks from the Responses event stream", async () => {
9861007
const handler = new OpencodeGoHandler(lunaOptions)
9871008
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
@@ -1315,6 +1336,20 @@ describe("OpencodeGoHandler", () => {
13151336
expect(callArgs.temperature).toBeUndefined()
13161337
})
13171338

1339+
it("completePrompt routes gpt-6-luna through responses.create only", async () => {
1340+
mockResponsesCreate.mockResolvedValue({ output_text: "Luna 6 response" })
1341+
const handler = new OpencodeGoHandler(gpt6LunaOptions)
1342+
1343+
expect(await handler.completePrompt("ping")).toBe("Luna 6 response")
1344+
1345+
expect(mockResponsesCreate).toHaveBeenCalledWith(
1346+
expect.objectContaining({ model: "gpt-6-luna", store: false }),
1347+
expect.anything(),
1348+
)
1349+
expect(mockCreate).not.toHaveBeenCalled()
1350+
expect(mockAnthropicCreate).not.toHaveBeenCalled()
1351+
})
1352+
13181353
it("forwards Responses-specific max_output_tokens and reasoning in completePrompt", async () => {
13191354
mockResponsesCreate.mockResolvedValue({ output_text: "Hello!" })
13201355
const handler = new OpencodeGoHandler({ ...lunaOptions, includeMaxTokens: true, modelMaxTokens: 7_500 })
@@ -1364,6 +1399,7 @@ describe("OpencodeGoHandler", () => {
13641399

13651400
it("classifies documented Responses models as Responses-format and other models as not", () => {
13661401
expect(isOpencodeGoResponsesFormatModel("gpt-5.6-luna")).toBe(true)
1402+
expect(isOpencodeGoResponsesFormatModel("gpt-6-luna")).toBe(true)
13671403
expect(isOpencodeGoResponsesFormatModel("grok-4.5")).toBe(false)
13681404
expect(isOpencodeGoResponsesFormatModel("grok-4.6")).toBe(true)
13691405
expect(isOpencodeGoResponsesFormatModel("muse-spark-1.3-contributor")).toBe(true)

‎src/api/providers/fetchers/__tests__/opencode-go.spec.ts‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,30 @@ describe("Opencode Go Fetchers", () => {
7575
})
7676
})
7777

78+
it("uses curated gpt-6-luna metadata when the live model omits metadata", async () => {
79+
mockedAxios.get.mockResolvedValue({ data: { data: [{ id: "gpt-6-luna" }] } })
80+
81+
const models = await getOpencodeGoModels("k")
82+
83+
expect(models["gpt-6-luna"]).toMatchObject({
84+
contextWindow: 1_050_000,
85+
maxTokens: 128_000,
86+
supportsImages: true,
87+
supportsPromptCache: true,
88+
supportsMaxTokens: true,
89+
// The Opencode Go Responses gateway rejects `temperature` for
90+
// `gpt-6-luna`, so the curated registry must opt out so the UI
91+
// hides the dead control rather than rendering it.
92+
supportsTemperature: false,
93+
supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"],
94+
reasoningEffort: "medium",
95+
inputPrice: 0.1,
96+
outputPrice: 0.5,
97+
cacheWritesPrice: 0.125,
98+
cacheReadsPrice: 0.01,
99+
})
100+
})
101+
78102
it("falls back to default context/max tokens for an unknown model when metadata is absent", async () => {
79103
mockedAxios.get.mockResolvedValue({ data: { data: [{ id: "some-unknown-model" }] } })
80104

@@ -213,6 +237,7 @@ describe("Opencode Go Fetchers", () => {
213237
"hy3",
214238
"hy3-preview",
215239
"gpt-5.6-luna",
240+
"gpt-6-luna",
216241
"grok-4.5",
217242
"grok-4.6",
218243
"muse-spark-1.3-contributor",
@@ -248,6 +273,20 @@ describe("Opencode Go Fetchers", () => {
248273
expect(info.inputPrice).toBe(1.4)
249274
})
250275

276+
it("keeps gpt-6-luna live limits authoritative over its curated metadata", () => {
277+
const info = parseOpencodeGoModel({
278+
id: "gpt-6-luna",
279+
context_window: 1_000_000,
280+
max_output_tokens: 64_000,
281+
})
282+
283+
expect(info.contextWindow).toBe(1_000_000)
284+
expect(info.maxTokens).toBe(64_000)
285+
expect(info.supportsImages).toBe(true)
286+
expect(info.inputPrice).toBe(0.1)
287+
expect(info.cacheWritesPrice).toBe(0.125)
288+
})
289+
251290
it("uses native registry defaults when the live payload omits volatile fields", () => {
252291
const info = parseOpencodeGoModel({ id: "deepseek-v4-flash" })
253292
const native = getOpencodeGoModelInfo("deepseek-v4-flash")!

‎src/api/providers/opencode-go.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ type OpencodeGoFormat = "anthropic" | "openai" | "responses"
7171
* - Anthropic Messages (`/v1/messages`) — used by Qwen (qwen3.8-max,
7272
* qwen3.7-max, qwen3.7-plus, qwen3.6-plus) and MiniMax (minimax-m3,
7373
* minimax-m2.7, minimax-m2.5) models.
74-
* - OpenAI Responses (`/v1/responses`) — used by gpt-5.6-luna, whose
75-
* chat-completions adapter fails with an opaque HTTP 500
76-
* (Zoo-Code-Org/Zoo-Code#1431).
74+
* - OpenAI Responses (`/v1/responses`) — used by `gpt-5.6-luna` and
75+
* `gpt-6-luna`, whose chat-completions adapters are not supported by the
76+
* Go gateway.
7777
*
7878
* Sending an Anthropic-format model to the chat completions endpoint is
7979
* rejected with `401 Model <id> is not supported for format oa-compat`, so this
@@ -183,7 +183,7 @@ export class OpencodeGoHandler extends RouterProvider implements SingleCompletio
183183
*
184184
* Anthropic-format models (Qwen/MiniMax) are streamed via
185185
* {@link streamAnthropicMessage} against `/v1/messages`; Responses-format
186-
* models (gpt-5.6-luna) are streamed via {@link streamResponsesMessage}
186+
* models (`gpt-5.6-luna` and `gpt-6-luna`) are streamed via {@link streamResponsesMessage}
187187
* against `/v1/responses`; all other models use the OpenAI-compatible chat
188188
* completions endpoint.
189189
*
@@ -292,7 +292,7 @@ export class OpencodeGoHandler extends RouterProvider implements SingleCompletio
292292

293293
/**
294294
* Streams an OpenAI Responses-format completion for Go models that only
295-
* accept the `/v1/responses` endpoint (currently gpt-5.6-luna).
295+
* accept the `/v1/responses` endpoint (`gpt-5.6-luna` and `gpt-6-luna`).
296296
*
297297
* Follows the focused xAI handler pattern: the conversation is converted
298298
* with the shared {@link convertToResponsesApiInput} transform, the system

0 commit comments

Comments
 (0)