@@ -13,6 +13,12 @@ function sse(name: string, data: Record<string, unknown>): string {
1313 return `event: ${ name } \ndata: ${ JSON . stringify ( data ) } \n\n` ;
1414}
1515
16+ function dataOnlySse ( data : Record < string , unknown > ) : string {
17+ return `data: ${ JSON . stringify ( data ) } \n\n` ;
18+ }
19+
20+ const DONE_SSE = "data: [DONE]\n\n" ;
21+
1622function streamFrom ( text : string ) : ReadableStream < Uint8Array > {
1723 const encoder = new TextEncoder ( ) ;
1824 return new ReadableStream ( {
@@ -99,6 +105,113 @@ describe("claude outbound SSE", () => {
99105 expect ( startIndexes ) . toEqual ( [ 0 , 1 , 2 ] ) ;
100106 } ) ;
101107
108+ test ( "data-only Responses frames infer event names from payload types" , async ( ) => {
109+ const upstream = [
110+ dataOnlySse ( { type : "response.created" , response : { id : "resp_data_only" , status : "in_progress" } } ) ,
111+ dataOnlySse ( { type : "response.output_text.delta" , delta : "data-only stream" } ) ,
112+ dataOnlySse ( {
113+ type : "response.completed" ,
114+ response : { status : "completed" , usage : { input_tokens : 8 , output_tokens : 2 } } ,
115+ } ) ,
116+ DONE_SSE ,
117+ ] . join ( "" ) ;
118+
119+ const events = await collectEvents ( responsesSseToAnthropicSse ( streamFrom ( upstream ) , "m" ) ) ;
120+ expect ( events . map ( e => e . name ) ) . toEqual ( [
121+ "message_start" , "ping" ,
122+ "content_block_start" , "content_block_delta" , "content_block_stop" ,
123+ "message_delta" , "message_stop" ,
124+ ] ) ;
125+ expect ( events . find ( e => e . name === "content_block_delta" ) ! . data . delta ) . toEqual ( {
126+ type : "text_delta" ,
127+ text : "data-only stream" ,
128+ } ) ;
129+ expect ( events . find ( e => e . name === "message_delta" ) ! . data ) . toMatchObject ( {
130+ delta : { stop_reason : "end_turn" } ,
131+ usage : { input_tokens : 8 , output_tokens : 2 } ,
132+ } ) ;
133+ } ) ;
134+
135+ test ( "explicit and data-only Responses frames can interleave" , async ( ) => {
136+ const upstream = [
137+ sse ( "response.created" , { response : { id : "resp_mixed" , status : "in_progress" } } ) ,
138+ dataOnlySse ( { type : "response.output_text.delta" , delta : "mixed stream" } ) ,
139+ sse ( "response.completed" , { response : { status : "completed" } } ) ,
140+ DONE_SSE ,
141+ ] . join ( "" ) ;
142+
143+ const events = await collectEvents ( responsesSseToAnthropicSse ( streamFrom ( upstream ) , "m" ) ) ;
144+ expect ( events . find ( e => e . name === "content_block_delta" ) ! . data . delta ) . toEqual ( {
145+ type : "text_delta" ,
146+ text : "mixed stream" ,
147+ } ) ;
148+ expect ( events . at ( - 1 ) ! . name ) . toBe ( "message_stop" ) ;
149+ } ) ;
150+
151+ test ( "data-only Responses frames survive non-streaming aggregation" , async ( ) => {
152+ const upstream = [
153+ dataOnlySse ( { type : "response.output_text.delta" , delta : "data-only message" } ) ,
154+ dataOnlySse ( {
155+ type : "response.completed" ,
156+ response : { status : "completed" , usage : { input_tokens : 5 , output_tokens : 3 } } ,
157+ } ) ,
158+ DONE_SSE ,
159+ ] . join ( "" ) ;
160+
161+ const anthropicSse = responsesSseToAnthropicSse ( streamFrom ( upstream ) , "m" ) ;
162+ const message = await collectAnthropicMessage ( anthropicSse , "m" ) ;
163+ expect ( message ) . toMatchObject ( {
164+ type : "message" ,
165+ role : "assistant" ,
166+ content : [ { type : "text" , text : "data-only message" } ] ,
167+ stop_reason : "end_turn" ,
168+ usage : { input_tokens : 5 , output_tokens : 3 } ,
169+ } ) ;
170+ } ) ;
171+
172+ test ( "explicit event names override payload types and untyped data-only frames stay ignored" , async ( ) => {
173+ const upstream = [
174+ dataOnlySse ( { delta : "must stay ignored" } ) ,
175+ sse ( "response.output_text.delta" , { type : "response.ignored" , delta : "visible" } ) ,
176+ sse ( "response.completed" , {
177+ type : "response.output_text.delta" ,
178+ delta : "must not override the explicit terminal event" ,
179+ response : { status : "completed" } ,
180+ } ) ,
181+ ] . join ( "" ) ;
182+
183+ const events = await collectEvents ( responsesSseToAnthropicSse ( streamFrom ( upstream ) , "m" ) ) ;
184+ const textDeltas = events
185+ . filter ( e => e . name === "content_block_delta" && e . data . delta ?. type === "text_delta" )
186+ . map ( e => e . data . delta . text ) ;
187+ expect ( textDeltas ) . toEqual ( [ "visible" ] ) ;
188+ expect ( events . at ( - 1 ) ! . name ) . toBe ( "message_stop" ) ;
189+ } ) ;
190+
191+ test ( "data-only [DONE] without a Responses terminal frame still fails closed" , async ( ) => {
192+ const upstream = [
193+ dataOnlySse ( { type : "response.output_text.delta" , delta : "partial" } ) ,
194+ DONE_SSE ,
195+ ] . join ( "" ) ;
196+
197+ const events = await collectEvents ( responsesSseToAnthropicSse ( streamFrom ( upstream ) , "m" ) ) ;
198+ expect ( events . some ( e => e . name === "message_stop" ) ) . toBe ( false ) ;
199+ expect ( events . find ( e => e . name === "content_block_delta" ) ! . data . delta ) . toEqual ( {
200+ type : "text_delta" ,
201+ text : "partial" ,
202+ } ) ;
203+ expect ( events . at ( - 1 ) ) . toMatchObject ( {
204+ name : "error" ,
205+ data : {
206+ type : "error" ,
207+ error : {
208+ type : "overloaded_error" ,
209+ message : "upstream stream ended before a terminal frame (truncated response)" ,
210+ } ,
211+ } ,
212+ } ) ;
213+ } ) ;
214+
102215 test ( "failed -> error event with taxonomy type" , async ( ) => {
103216 const upstream = [
104217 sse ( "response.created" , { response : { } } ) ,
0 commit comments