@@ -25,7 +25,7 @@ import {
2525} from '../ai/gen-ai-attributes' ;
2626import { buildMethodPath , getFinalOperationName , getSpanOperation , setTokenUsageAttributes } from '../ai/utils' ;
2727import { handleCallbackErrors } from '../handleCallbackErrors' ;
28- import { instrumentStream } from './streaming' ;
28+ import { instrumentAsyncIterableStream , instrumentMessageStream } from './streaming' ;
2929import type {
3030 AnthropicAiInstrumentedMethod ,
3131 AnthropicAiOptions ,
@@ -194,6 +194,74 @@ function addResponseAttributes(span: Span, response: AnthropicAiResponse, record
194194 addMetadataAttributes ( span , response ) ;
195195}
196196
197+ /**
198+ * Handle common error catching and reporting for streaming requests
199+ */
200+ function handleStreamingError ( error : unknown , span : Span , methodPath : string ) : never {
201+ captureException ( error , {
202+ mechanism : { handled : false , type : 'auto.ai.anthropic' , data : { function : methodPath } } ,
203+ } ) ;
204+
205+ if ( span . isRecording ( ) ) {
206+ span . setStatus ( { code : SPAN_STATUS_ERROR , message : 'internal_error' } ) ;
207+ span . end ( ) ;
208+ }
209+ throw error ;
210+ }
211+
212+ /**
213+ * Handle streaming cases with common logic
214+ */
215+ function handleStreamingRequest < T extends unknown [ ] , R > (
216+ originalMethod : ( ...args : T ) => Promise < R > ,
217+ target : ( ...args : T ) => Promise < R > ,
218+ context : unknown ,
219+ args : T ,
220+ requestAttributes : Record < string , unknown > ,
221+ operationName : string ,
222+ methodPath : string ,
223+ params : Record < string , unknown > | undefined ,
224+ options : AnthropicAiOptions ,
225+ isStreamRequested : boolean ,
226+ ) : Promise < R > {
227+ const model = requestAttributes [ GEN_AI_REQUEST_MODEL_ATTRIBUTE ] ?? 'unknown' ;
228+ const spanConfig = {
229+ name : `${ operationName } ${ model } stream-response` ,
230+ op : getSpanOperation ( methodPath ) ,
231+ attributes : requestAttributes as Record < string , SpanAttributeValue > ,
232+ } ;
233+
234+ if ( isStreamRequested ) {
235+ return startSpanManual ( spanConfig , async span => {
236+ try {
237+ if ( options . recordInputs && params ) {
238+ addPrivateRequestAttributes ( span , params ) ;
239+ }
240+ const result = await originalMethod . apply ( context , args ) ;
241+ return instrumentAsyncIterableStream (
242+ result as AsyncIterable < AnthropicAiStreamingEvent > ,
243+ span ,
244+ options . recordOutputs ?? false ,
245+ ) as unknown as R ;
246+ } catch ( error ) {
247+ return handleStreamingError ( error , span , methodPath ) ;
248+ }
249+ } ) ;
250+ } else {
251+ return startSpanManual ( spanConfig , span => {
252+ try {
253+ if ( options . recordInputs && params ) {
254+ addPrivateRequestAttributes ( span , params ) ;
255+ }
256+ const messageStream = target . apply ( context , args ) ;
257+ return instrumentMessageStream ( messageStream , span , options . recordOutputs ?? false ) ;
258+ } catch ( error ) {
259+ return handleStreamingError ( error , span , methodPath ) ;
260+ }
261+ } ) ;
262+ }
263+ }
264+
197265/**
198266 * Instrument a method with Sentry spans
199267 * Following Sentry AI Agents Manual Instrumentation conventions
@@ -205,82 +273,62 @@ function instrumentMethod<T extends unknown[], R>(
205273 context : unknown ,
206274 options : AnthropicAiOptions ,
207275) : ( ...args : T ) => Promise < R > {
208- return async function instrumentedMethod ( ...args : T ) : Promise < R > {
209- const requestAttributes = extractRequestAttributes ( args , methodPath ) ;
210- const model = requestAttributes [ GEN_AI_REQUEST_MODEL_ATTRIBUTE ] ?? 'unknown' ;
211- const operationName = getFinalOperationName ( methodPath ) ;
276+ return new Proxy ( originalMethod , {
277+ apply ( target , thisArg , args : T ) : Promise < R > {
278+ const requestAttributes = extractRequestAttributes ( args , methodPath ) ;
279+ const model = requestAttributes [ GEN_AI_REQUEST_MODEL_ATTRIBUTE ] ?? 'unknown' ;
280+ const operationName = getFinalOperationName ( methodPath ) ;
212281
213- const params = typeof args [ 0 ] === 'object' ? ( args [ 0 ] as Record < string , unknown > ) : undefined ;
214- const isStreamRequested = Boolean ( params ?. stream ) ;
215- const isStreamingMethod = methodPath === 'messages.stream' ;
282+ const params = typeof args [ 0 ] === 'object' ? ( args [ 0 ] as Record < string , unknown > ) : undefined ;
283+ const isStreamRequested = Boolean ( params ?. stream ) ;
284+ const isStreamingMethod = methodPath === 'messages.stream' ;
216285
217- if ( isStreamRequested || isStreamingMethod ) {
218- return startSpanManual (
286+ if ( isStreamRequested || isStreamingMethod ) {
287+ return handleStreamingRequest (
288+ originalMethod ,
289+ target ,
290+ context ,
291+ args ,
292+ requestAttributes ,
293+ operationName ,
294+ methodPath ,
295+ params ,
296+ options ,
297+ isStreamRequested ,
298+ ) ;
299+ }
300+
301+ return startSpan (
219302 {
220- name : `${ operationName } ${ model } stream-response ` ,
303+ name : `${ operationName } ${ model } ` ,
221304 op : getSpanOperation ( methodPath ) ,
222305 attributes : requestAttributes as Record < string , SpanAttributeValue > ,
223306 } ,
224- async span => {
225- try {
226- if ( options . recordInputs && params ) {
227- addPrivateRequestAttributes ( span , params ) ;
228- }
229-
230- const result = await originalMethod . apply ( context , args ) ;
231- return instrumentStream (
232- result as AsyncIterable < AnthropicAiStreamingEvent > ,
233- span ,
234- options . recordOutputs ?? false ,
235- ) as unknown as R ;
236- } catch ( error ) {
237- span . setStatus ( { code : SPAN_STATUS_ERROR , message : 'internal_error' } ) ;
238- captureException ( error , {
239- mechanism : {
240- handled : false ,
241- type : 'auto.ai.anthropic' ,
242- data : {
243- function : methodPath ,
244- } ,
245- } ,
246- } ) ;
247- span . end ( ) ;
248- throw error ;
307+ span => {
308+ if ( options . recordInputs && params ) {
309+ addPrivateRequestAttributes ( span , params ) ;
249310 }
250- } ,
251- ) ;
252- }
253311
254- return startSpan (
255- {
256- name : `${ operationName } ${ model } ` ,
257- op : getSpanOperation ( methodPath ) ,
258- attributes : requestAttributes as Record < string , SpanAttributeValue > ,
259- } ,
260- span => {
261- if ( options . recordInputs && params ) {
262- addPrivateRequestAttributes ( span , params ) ;
263- }
264-
265- return handleCallbackErrors (
266- ( ) => originalMethod . apply ( context , args ) ,
267- error => {
268- captureException ( error , {
269- mechanism : {
270- handled : false ,
271- type : 'auto.ai.anthropic' ,
272- data : {
273- function : methodPath ,
312+ return handleCallbackErrors (
313+ ( ) => target . apply ( context , args ) ,
314+ error => {
315+ captureException ( error , {
316+ mechanism : {
317+ handled : false ,
318+ type : 'auto.ai.anthropic' ,
319+ data : {
320+ function : methodPath ,
321+ } ,
274322 } ,
275- } ,
276- } ) ;
277- } ,
278- ( ) => { } ,
279- result => addResponseAttributes ( span , result as AnthropicAiResponse , options . recordOutputs ) ,
280- ) ;
281- } ,
282- ) ;
283- } ;
323+ } ) ;
324+ } ,
325+ ( ) => { } ,
326+ result => addResponseAttributes ( span , result as AnthropicAiResponse , options . recordOutputs ) ,
327+ ) ;
328+ } ,
329+ ) ;
330+ } ,
331+ } ) as ( ... args : T ) => Promise < R > ;
284332}
285333
286334/**
0 commit comments