Skip to content

Commit 30376ac

Browse files
committed
Reject unsupported opencode Zen models
1 parent c839d41 commit 30376ac

2 files changed

Lines changed: 64 additions & 10 deletions

File tree

web/src/app/api/v1/chat/completions/__tests__/completions.test.ts

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -880,10 +880,6 @@ describe('/api/v1/chat/completions POST endpoint', () => {
880880
codebuffModel: openCodeZenModels.opencode_minimax_m2_7,
881881
upstreamModel: 'minimax-m2.7',
882882
},
883-
{
884-
codebuffModel: 'opencode/qwen3-coder',
885-
upstreamModel: 'qwen3-coder',
886-
},
887883
{
888884
codebuffModel: 'moonshotai/kimi-k2.6',
889885
upstreamModel: 'kimi-k2.6',
@@ -991,6 +987,59 @@ describe('/api/v1/chat/completions POST endpoint', () => {
991987
FETCH_PATH_TEST_TIMEOUT_MS,
992988
)
993989

990+
it(
991+
'rejects unsupported OpenCode Zen-prefixed models without calling the provider',
992+
async () => {
993+
const fetchViaOpenCodeZen = mock(
994+
async (url: string | URL | Request) => {
995+
if (String(url).startsWith('https://api.ipinfo.io/lookup/')) {
996+
return Response.json({})
997+
}
998+
999+
throw new Error('OpenCode Zen provider should not be called')
1000+
},
1001+
) as unknown as typeof globalThis.fetch
1002+
1003+
const req = new NextRequest(
1004+
'http://localhost:3000/api/v1/chat/completions',
1005+
{
1006+
method: 'POST',
1007+
headers: {
1008+
Authorization: 'Bearer test-api-key-123',
1009+
},
1010+
body: JSON.stringify({
1011+
model: 'opencode/qwen3-coder',
1012+
messages: [{ role: 'user', content: 'hello' }],
1013+
stream: false,
1014+
codebuff_metadata: {
1015+
run_id: 'run-123',
1016+
client_id: 'test-client-id-123',
1017+
},
1018+
}),
1019+
},
1020+
)
1021+
1022+
const response = await postChatCompletions({
1023+
req,
1024+
getUserInfoFromApiKey: mockGetUserInfoFromApiKey,
1025+
logger: mockLogger,
1026+
trackEvent: mockTrackEvent,
1027+
getUserUsageData: mockGetUserUsageData,
1028+
getAgentRunFromId: mockGetAgentRunFromId,
1029+
fetch: fetchViaOpenCodeZen,
1030+
insertMessageBigquery: mockInsertMessageBigquery,
1031+
loggerWithContext: mockLoggerWithContext,
1032+
})
1033+
1034+
const body = await response.json()
1035+
expect(response.status).toBe(400)
1036+
expect(body.error.code).toBe('unsupported_model')
1037+
expect(body.error.message).toContain('opencode/qwen3-coder')
1038+
expect(fetchViaOpenCodeZen).toHaveBeenCalledTimes(0)
1039+
},
1040+
FETCH_PATH_TEST_TIMEOUT_MS,
1041+
)
1042+
9941043
it('rejects the DeepSeek V4 free agent when it requests another free model', async () => {
9951044
const req = new NextRequest(
9961045
'http://localhost:3000/api/v1/chat/completions',

web/src/llm-api/opencode-zen.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const OPENCODE_ZEN_MODEL_ALIASES: Record<string, string> = {
4444
[openCodeZenModels.opencode_minimax_m2_7]: MINIMAX_M2_7_ZEN_MODEL,
4545
[MOONSHOT_KIMI_MODEL]: KIMI_ZEN_MODEL,
4646
}
47+
const SUPPORTED_OPENCODE_ZEN_MODELS = Object.keys(OPENCODE_ZEN_MODEL_ALIASES)
4748

4849
const KIMI_ZEN_PRICING: OpenCodeZenPricing = {
4950
inputCostPerToken: 0.95 / 1_000_000,
@@ -69,12 +70,16 @@ export function isOpenCodeZenModel(model: unknown): model is string {
6970
}
7071

7172
function getOpenCodeZenModelId(model: string): string {
72-
return (
73-
OPENCODE_ZEN_MODEL_ALIASES[model] ??
74-
(model.startsWith(OPENCODE_MODEL_PREFIX)
75-
? model.slice(OPENCODE_MODEL_PREFIX.length)
76-
: model)
77-
)
73+
const opencodeId = OPENCODE_ZEN_MODEL_ALIASES[model]
74+
if (opencodeId) return opencodeId
75+
76+
throw new OpenCodeZenError(400, 'Bad Request', {
77+
error: {
78+
message: `Unsupported OpenCode Zen model: ${model}. Supported models: ${SUPPORTED_OPENCODE_ZEN_MODELS.join(', ')}`,
79+
code: 'unsupported_model',
80+
type: 'invalid_request_error',
81+
},
82+
})
7883
}
7984

8085
function getOpenCodeZenPricing(model: string): OpenCodeZenPricing {

0 commit comments

Comments
 (0)