@@ -869,13 +869,24 @@ describe('/api/v1/chat/completions POST endpoint', () => {
869869 )
870870
871871 it (
872- 'routes OpenCode Zen models to the direct OpenCode Zen provider' ,
872+ 'routes OpenCode Zen-prefixed and Kimi models to the direct OpenCode Zen provider' ,
873873 async ( ) => {
874- const expectedUpstreamModel : Record < string , string > = {
875- 'opencode/kimi-k2.6' : 'kimi-k2.6' ,
876- }
874+ const testCases = [
875+ {
876+ codebuffModel : openCodeZenModels . opencode_kimi_k2_6 ,
877+ upstreamModel : 'kimi-k2.6' ,
878+ } ,
879+ {
880+ codebuffModel : openCodeZenModels . opencode_minimax_m2_7 ,
881+ upstreamModel : 'minimax-m2.7' ,
882+ } ,
883+ {
884+ codebuffModel : 'moonshotai/kimi-k2.6' ,
885+ upstreamModel : 'kimi-k2.6' ,
886+ } ,
887+ ]
877888
878- for ( const codebuffModel of Object . values ( openCodeZenModels ) ) {
889+ for ( const { codebuffModel, upstreamModel } of testCases ) {
879890 const fetchedBodies : Record < string , unknown > [ ] = [ ]
880891 const fetchedUrls : string [ ] = [ ]
881892 const fetchViaOpenCodeZen = mock (
@@ -889,7 +900,7 @@ describe('/api/v1/chat/completions POST endpoint', () => {
889900 return new Response (
890901 JSON . stringify ( {
891902 id : 'test-id' ,
892- model : expectedUpstreamModel [ codebuffModel ] ,
903+ model : upstreamModel ,
893904 choices : [ { message : { content : 'test response' } } ] ,
894905 usage : {
895906 prompt_tokens : 10 ,
@@ -968,16 +979,67 @@ describe('/api/v1/chat/completions POST endpoint', () => {
968979 expect ( fetchedUrls [ 0 ] ) . toBe (
969980 'https://opencode.ai/zen/v1/chat/completions' ,
970981 )
971- expect ( fetchedBodies [ 0 ] . model ) . toBe (
972- expectedUpstreamModel [ codebuffModel ] ,
973- )
982+ expect ( fetchedBodies [ 0 ] . model ) . toBe ( upstreamModel )
974983 expect ( body . model ) . toBe ( codebuffModel )
975984 expect ( body . provider ) . toBe ( 'OpenCode Zen' )
976985 }
977986 } ,
978987 FETCH_PATH_TEST_TIMEOUT_MS ,
979988 )
980989
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+
9811043 it ( 'rejects the DeepSeek V4 free agent when it requests another free model' , async ( ) => {
9821044 const req = new NextRequest (
9831045 'http://localhost:3000/api/v1/chat/completions' ,
0 commit comments