@@ -197,6 +197,94 @@ describe('instrumentStateGraphCompile stream instrumentation', () => {
197197 expect ( spanEnd ) . toHaveBeenCalledTimes ( 1 ) ;
198198 } ) ;
199199
200+ it ( 'records response attributes when pipeThrough is unavailable' , async ( ) => {
201+ const responseMessage = { role : 'assistant' , content : 'Clear skies' } ;
202+ const stream = new TestLangGraphStream ( [ { agent : { messages : [ responseMessage ] } } ] ) ;
203+ Object . defineProperty ( stream , 'pipeThrough' , { value : undefined } ) ;
204+ const compiledGraph = { stream : vi . fn ( ) . mockResolvedValue ( stream ) } ;
205+ const compile = instrumentStateGraphCompile ( ( ) => compiledGraph , { recordOutputs : true } ) ;
206+ const graph = compile ( ) as { stream : ( ) => Promise < TestLangGraphStream < unknown > > } ;
207+
208+ await ( await graph . stream ( ) ) . pipeTo ( new WritableStream ( ) ) ;
209+
210+ expect ( spanSetAttribute ) . toHaveBeenCalledWith (
211+ GEN_AI_RESPONSE_TEXT ,
212+ '[{"role":"assistant","content":"Clear skies"}]' ,
213+ ) ;
214+ expect ( spanEnd ) . toHaveBeenCalledTimes ( 1 ) ;
215+ } ) ;
216+
217+ it ( 'does not re-enter custom pipeThrough implementations from pipeTo' , async ( ) => {
218+ const chunks : string [ ] = [ ] ;
219+ const stream = new TestLangGraphStream ( [ 'first update' , 'final update' ] ) ;
220+ Object . defineProperty ( stream , 'pipeThrough' , {
221+ configurable : true ,
222+ value (
223+ this : TestLangGraphStream < string > ,
224+ transform : ReadableWritablePair < unknown , string > ,
225+ options ?: StreamPipeOptions ,
226+ ) : ReadableStream < unknown > {
227+ void this . pipeTo ( transform . writable , options ) . catch ( ( ) => { } ) ;
228+ return transform . readable ;
229+ } ,
230+ writable : true ,
231+ } ) ;
232+ const compiledGraph = { stream : vi . fn ( ) . mockResolvedValue ( stream ) } ;
233+ const compile = instrumentStateGraphCompile ( ( ) => compiledGraph , { } ) ;
234+ const graph = compile ( ) as { stream : ( ) => Promise < TestLangGraphStream < string > > } ;
235+
236+ await (
237+ await graph . stream ( )
238+ ) . pipeTo (
239+ new WritableStream ( {
240+ write ( chunk ) {
241+ chunks . push ( chunk ) ;
242+ } ,
243+ } ) ,
244+ ) ;
245+
246+ expect ( chunks ) . toEqual ( [ 'first update' , 'final update' ] ) ;
247+ expect ( spanEnd ) . toHaveBeenCalledTimes ( 1 ) ;
248+ } ) ;
249+
250+ it ( 'forwards pipe options to the source pipeTo operation' , async ( ) => {
251+ const stream = new TestLangGraphStream ( [ 'first update' ] ) ;
252+ const originalPipeTo = stream . pipeTo . bind ( stream ) ;
253+ const sourcePipeTo = vi . fn ( ( destination : WritableStream < string > , options ?: StreamPipeOptions ) =>
254+ originalPipeTo ( destination , options ) ,
255+ ) ;
256+ Object . defineProperty ( stream , 'pipeTo' , { configurable : true , value : sourcePipeTo , writable : true } ) ;
257+ const compiledGraph = { stream : vi . fn ( ) . mockResolvedValue ( stream ) } ;
258+ const compile = instrumentStateGraphCompile ( ( ) => compiledGraph , { } ) ;
259+ const graph = compile ( ) as { stream : ( ) => Promise < TestLangGraphStream < string > > } ;
260+ const destination = new WritableStream < string > ( ) ;
261+ const options = { preventClose : true , signal : new AbortController ( ) . signal } ;
262+
263+ await ( await graph . stream ( ) ) . pipeTo ( destination , options ) ;
264+
265+ expect ( sourcePipeTo ) . toHaveBeenCalledWith ( expect . any ( WritableStream ) , options ) ;
266+ expect ( destination . locked ) . toBe ( false ) ;
267+ expect ( spanEnd ) . toHaveBeenCalledTimes ( 1 ) ;
268+ } ) ;
269+
270+ it ( 'ends the span when pipeThrough output is consumed' , async ( ) => {
271+ const responseMessage = { role : 'assistant' , content : 'Clear skies' } ;
272+ const stream = new TestLangGraphStream ( [ { agent : { messages : [ responseMessage ] } } ] ) ;
273+ const compiledGraph = { stream : vi . fn ( ) . mockResolvedValue ( stream ) } ;
274+ const compile = instrumentStateGraphCompile ( ( ) => compiledGraph , { recordOutputs : true } ) ;
275+ const graph = compile ( ) as { stream : ( ) => Promise < TestLangGraphStream < unknown > > } ;
276+
277+ const transformed = ( await graph . stream ( ) ) . pipeThrough ( new TransformStream ( ) ) ;
278+ await transformed . pipeTo ( new WritableStream ( ) ) ;
279+ await Promise . resolve ( ) ;
280+
281+ expect ( spanSetAttribute ) . toHaveBeenCalledWith (
282+ GEN_AI_RESPONSE_TEXT ,
283+ '[{"role":"assistant","content":"Clear skies"}]' ,
284+ ) ;
285+ expect ( spanEnd ) . toHaveBeenCalledTimes ( 1 ) ;
286+ } ) ;
287+
200288 it ( 'ends the span when direct next calls consume the stream' , async ( ) => {
201289 const stream = new TestLangGraphStream ( [ 'first update' ] ) ;
202290 const compiledGraph = { stream : vi . fn ( ) . mockResolvedValue ( stream ) } ;
0 commit comments