@@ -1028,13 +1028,13 @@ describe("openai transport stream", () => {
10281028 }
10291029 } ) ;
10301030
1031- it ( "refuses OpenAI-compatible chat streams with no user or assistant payload turns" , async ( ) => {
1031+ it ( "refuses ModelStudio chat streams with no user or assistant payload turns" , async ( ) => {
10321032 const model = {
1033- id : "mlx-community/Qwen3-30B-A3B-6bit " ,
1034- name : "Qwen3 MLX " ,
1033+ id : "qwen-coder-plus " ,
1034+ name : "qwen-coder-plus " ,
10351035 api : "openai-completions" ,
1036- provider : "mlx " ,
1037- baseUrl : "http ://127.0.0.1:9 /v1" ,
1036+ provider : "qwen " ,
1037+ baseUrl : "https ://dashscope-intl.aliyuncs.com/compatible-mode /v1" ,
10381038 reasoning : false ,
10391039 input : [ "text" ] ,
10401040 cost : { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } ,
@@ -1068,6 +1068,96 @@ describe("openai transport stream", () => {
10681068 expect ( String ( errorPayload ?. errorMessage ) ) . toContain ( "system/tool-only request" ) ;
10691069 } ) ;
10701070
1071+ it ( "allows generic OpenAI-compatible chat streams without the ModelStudio turn guard" , async ( ) => {
1072+ let capturedRoles : string [ ] | undefined ;
1073+ const server = createServer ( ( req , res ) => {
1074+ let body = "" ;
1075+ req . setEncoding ( "utf8" ) ;
1076+ req . on ( "data" , ( chunk ) => {
1077+ body += chunk ;
1078+ } ) ;
1079+ req . on ( "end" , ( ) => {
1080+ const parsed = JSON . parse ( body ) as { messages ?: Array < { role ?: string } > } ;
1081+ capturedRoles = parsed . messages ?. map ( ( message ) => message . role ?? "" ) ;
1082+ res . writeHead ( 200 , {
1083+ "content-type" : "text/event-stream; charset=utf-8" ,
1084+ "cache-control" : "no-cache" ,
1085+ connection : "keep-alive" ,
1086+ } ) ;
1087+ const created = Math . floor ( Date . now ( ) / 1000 ) ;
1088+ res . write (
1089+ `data: ${ JSON . stringify ( {
1090+ id : "chatcmpl-system-only" ,
1091+ object : "chat.completion.chunk" ,
1092+ created,
1093+ model : "generic-openai-compatible" ,
1094+ choices : [
1095+ {
1096+ index : 0 ,
1097+ delta : { role : "assistant" , content : "OK" } ,
1098+ finish_reason : null ,
1099+ } ,
1100+ ] ,
1101+ } ) } \n\n`,
1102+ ) ;
1103+ res . write (
1104+ `data: ${ JSON . stringify ( {
1105+ id : "chatcmpl-system-only" ,
1106+ object : "chat.completion.chunk" ,
1107+ created,
1108+ model : "generic-openai-compatible" ,
1109+ choices : [ { index : 0 , delta : { } , finish_reason : "stop" } ] ,
1110+ } ) } \n\n`,
1111+ ) ;
1112+ res . write ( "data: [DONE]\n\n" ) ;
1113+ res . end ( ) ;
1114+ } ) ;
1115+ } ) ;
1116+
1117+ await new Promise < void > ( ( resolve ) => server . listen ( 0 , "127.0.0.1" , resolve ) ) ;
1118+ try {
1119+ const address = server . address ( ) ;
1120+ if ( ! address || typeof address === "string" ) {
1121+ throw new Error ( "Missing loopback server address" ) ;
1122+ }
1123+ const model = {
1124+ id : "generic-openai-compatible" ,
1125+ name : "Generic OpenAI Compatible" ,
1126+ api : "openai-completions" ,
1127+ provider : "custom-openai-compatible" ,
1128+ baseUrl : `http://127.0.0.1:${ address . port } /v1` ,
1129+ reasoning : false ,
1130+ input : [ "text" ] ,
1131+ cost : { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } ,
1132+ contextWindow : 4096 ,
1133+ maxTokens : 256 ,
1134+ } satisfies Model < "openai-completions" > ;
1135+ const stream = createOpenAICompletionsTransportStreamFn ( ) (
1136+ model ,
1137+ {
1138+ systemPrompt : "runtime-only system prompt" ,
1139+ messages : [ ] ,
1140+ tools : [ ] ,
1141+ } as never ,
1142+ { apiKey : "test-key" } as never ,
1143+ ) ;
1144+
1145+ let doneReason : string | undefined ;
1146+ for await ( const event of stream as AsyncIterable < { type : string ; reason ?: string } > ) {
1147+ if ( event . type === "done" ) {
1148+ doneReason = event . reason ;
1149+ }
1150+ }
1151+
1152+ expect ( capturedRoles ) . toEqual ( [ "system" ] ) ;
1153+ expect ( doneReason ) . toBe ( "stop" ) ;
1154+ } finally {
1155+ await new Promise < void > ( ( resolve , reject ) => {
1156+ server . close ( ( error ) => ( error ? reject ( error ) : resolve ( ) ) ) ;
1157+ } ) ;
1158+ }
1159+ } ) ;
1160+
10711161 it ( "parses JSON chat completions returned to streaming requests" , async ( ) => {
10721162 let capturedStreamFlag : unknown ;
10731163 const server = createServer ( ( req , res ) => {
0 commit comments