@@ -25,7 +25,7 @@ import {
25
25
} from '../ai/gen-ai-attributes' ;
26
26
import { buildMethodPath , getFinalOperationName , getSpanOperation , setTokenUsageAttributes } from '../ai/utils' ;
27
27
import { handleCallbackErrors } from '../handleCallbackErrors' ;
28
- import { instrumentStream } from './streaming' ;
28
+ import { instrumentAsyncIterableStream , instrumentMessageStream } from './streaming' ;
29
29
import type {
30
30
AnthropicAiInstrumentedMethod ,
31
31
AnthropicAiOptions ,
@@ -194,6 +194,74 @@ function addResponseAttributes(span: Span, response: AnthropicAiResponse, record
194
194
addMetadataAttributes ( span , response ) ;
195
195
}
196
196
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
+
197
265
/**
198
266
* Instrument a method with Sentry spans
199
267
* Following Sentry AI Agents Manual Instrumentation conventions
@@ -205,82 +273,62 @@ function instrumentMethod<T extends unknown[], R>(
205
273
context : unknown ,
206
274
options : AnthropicAiOptions ,
207
275
) : ( ...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 ) ;
212
281
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' ;
216
285
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 (
219
302
{
220
- name : `${ operationName } ${ model } stream-response ` ,
303
+ name : `${ operationName } ${ model } ` ,
221
304
op : getSpanOperation ( methodPath ) ,
222
305
attributes : requestAttributes as Record < string , SpanAttributeValue > ,
223
306
} ,
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 ) ;
249
310
}
250
- } ,
251
- ) ;
252
- }
253
311
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
+ } ,
274
322
} ,
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 > ;
284
332
}
285
333
286
334
/**
0 commit comments