@@ -2843,6 +2843,125 @@ describe("openai transport stream", () => {
28432843 } ,
28442844 ) ;
28452845
2846+ it ( "does not authorize recovered DeepSeek DSML calls when the stream omits a terminal" , async ( ) => {
2847+ const model = createDeepSeekCompletionsModel ( ) ;
2848+ const output = createAssistantOutput ( model ) ;
2849+
2850+ await testing . processOpenAICompletionsStream (
2851+ streamChunks ( [
2852+ {
2853+ id : "chatcmpl-deepseek-dsml-no-terminal" ,
2854+ object : "chat.completion.chunk" ,
2855+ created : 1 ,
2856+ model : model . id ,
2857+ choices : [
2858+ {
2859+ index : 0 ,
2860+ delta : {
2861+ content :
2862+ '<|DSML|tool_calls><|DSML|invoke name="read">{"path":"/tmp/partial.md"}</|DSML|invoke></|DSML|tool_calls>' ,
2863+ } ,
2864+ logprobs : null ,
2865+ finish_reason : null ,
2866+ } ,
2867+ ] ,
2868+ } ,
2869+ ] ) ,
2870+ output ,
2871+ model ,
2872+ { push ( ) { } } ,
2873+ ) ;
2874+
2875+ expect ( output . stopReason ) . toBe ( "stop" ) ;
2876+ expect ( output . content ) . toEqual ( [ ] ) ;
2877+ } ) ;
2878+
2879+ it ( "emits recovered DeepSeek content-filter terminals as errors" , async ( ) => {
2880+ const server = createServer ( ( req , res ) => {
2881+ req . resume ( ) ;
2882+ req . on ( "end" , ( ) => {
2883+ res . writeHead ( 200 , {
2884+ "content-type" : "text/event-stream; charset=utf-8" ,
2885+ "cache-control" : "no-cache" ,
2886+ connection : "keep-alive" ,
2887+ } ) ;
2888+ res . write (
2889+ `data: ${ JSON . stringify ( {
2890+ id : "chatcmpl-deepseek-dsml-content-filter" ,
2891+ object : "chat.completion.chunk" ,
2892+ created : 1 ,
2893+ model : "deepseek-v4-pro" ,
2894+ choices : [
2895+ {
2896+ index : 0 ,
2897+ delta : {
2898+ content :
2899+ '<|DSML|tool_calls><|DSML|invoke name="read">{"path":"/tmp/partial.md"}</|DSML|invoke></|DSML|tool_calls>' ,
2900+ } ,
2901+ finish_reason : "content_filter" ,
2902+ } ,
2903+ ] ,
2904+ } ) } \n\n`,
2905+ ) ;
2906+ res . end ( "data: [DONE]\n\n" ) ;
2907+ } ) ;
2908+ } ) ;
2909+
2910+ await new Promise < void > ( ( resolve ) => {
2911+ server . listen ( 0 , "127.0.0.1" , resolve ) ;
2912+ } ) ;
2913+ try {
2914+ const address = server . address ( ) ;
2915+ if ( ! address || typeof address === "string" ) {
2916+ throw new Error ( "Missing loopback server address" ) ;
2917+ }
2918+ const model = {
2919+ ...createDeepSeekCompletionsModel ( ) ,
2920+ baseUrl : `http://127.0.0.1:${ address . port } /v1` ,
2921+ } satisfies Model < "openai-completions" > ;
2922+ const stream = createOpenAICompletionsTransportStreamFn ( ) (
2923+ model ,
2924+ {
2925+ systemPrompt : "system" ,
2926+ messages : [ { role : "user" , content : "Read the file" , timestamp : Date . now ( ) } ] ,
2927+ tools : [ ] ,
2928+ } as never ,
2929+ { apiKey : "test-key" } as never ,
2930+ ) ;
2931+
2932+ const terminalEvents : Array < {
2933+ type : string ;
2934+ reason ?: string ;
2935+ error ?: Record < string , unknown > ;
2936+ } > = [ ] ;
2937+ for await ( const event of stream as AsyncIterable < {
2938+ type : string ;
2939+ reason ?: string ;
2940+ error ?: Record < string , unknown > ;
2941+ } > ) {
2942+ if ( event . type === "done" || event . type === "error" ) {
2943+ terminalEvents . push ( event ) ;
2944+ }
2945+ }
2946+
2947+ expect ( terminalEvents ) . toEqual ( [
2948+ expect . objectContaining ( {
2949+ type : "error" ,
2950+ reason : "error" ,
2951+ error : expect . objectContaining ( {
2952+ stopReason : "error" ,
2953+ errorMessage : "Provider finish_reason: content_filter" ,
2954+ content : [ ] ,
2955+ } ) ,
2956+ } ) ,
2957+ ] ) ;
2958+ } finally {
2959+ await new Promise < void > ( ( resolve , reject ) => {
2960+ server . close ( ( error ) => ( error ? reject ( error ) : resolve ( ) ) ) ;
2961+ } ) ;
2962+ }
2963+ } ) ;
2964+
28462965 it ( "parses repeated DeepSeek DSML name attributes consistently" , async ( ) => {
28472966 // Guards the cached attribute matchers: repeated parses must stay identical
28482967 // (no stale RegExp lastIndex) across separate stream invocations.
0 commit comments