@@ -12,6 +12,61 @@ import {
1212
1313const logger = createLogger ( 'Tools' )
1414
15+ // Extract a concise, meaningful error message from diverse API error shapes
16+ function getDeepApiErrorMessage ( errorInfo ?: {
17+ status ?: number
18+ statusText ?: string
19+ data ?: any
20+ } ) : string {
21+ return (
22+ // GraphQL errors (Linear API)
23+ errorInfo ?. data ?. errors ?. [ 0 ] ?. message ||
24+ // X/Twitter API specific pattern
25+ errorInfo ?. data ?. errors ?. [ 0 ] ?. detail ||
26+ // Generic details array
27+ errorInfo ?. data ?. details ?. [ 0 ] ?. message ||
28+ // Hunter API pattern
29+ errorInfo ?. data ?. errors ?. [ 0 ] ?. details ||
30+ // Direct errors array (when errors[0] is a string or simple object)
31+ ( Array . isArray ( errorInfo ?. data ?. errors )
32+ ? typeof errorInfo . data . errors [ 0 ] === 'string'
33+ ? errorInfo . data . errors [ 0 ]
34+ : errorInfo . data . errors [ 0 ] ?. message
35+ : undefined ) ||
36+ // Notion/Discord/GitHub/Twilio pattern
37+ errorInfo ?. data ?. message ||
38+ // SOAP/XML fault patterns
39+ errorInfo ?. data ?. fault ?. faultstring ||
40+ errorInfo ?. data ?. faultstring ||
41+ // Microsoft/OAuth error descriptions
42+ errorInfo ?. data ?. error_description ||
43+ // Airtable/Google fallback pattern
44+ ( typeof errorInfo ?. data ?. error === 'object'
45+ ? errorInfo ?. data ?. error ?. message || JSON . stringify ( errorInfo ?. data ?. error )
46+ : errorInfo ?. data ?. error ) ||
47+ // HTTP status text fallback
48+ errorInfo ?. statusText ||
49+ // Final fallback
50+ `Request failed with status ${ errorInfo ?. status || 'unknown' } `
51+ )
52+ }
53+
54+ // Create an Error instance from errorInfo and attach useful context
55+ function createTransformedErrorFromErrorInfo ( errorInfo ?: {
56+ status ?: number
57+ statusText ?: string
58+ data ?: any
59+ } ) : Error {
60+ const message = getDeepApiErrorMessage ( errorInfo )
61+ const transformed = new Error ( message )
62+ Object . assign ( transformed , {
63+ status : errorInfo ?. status ,
64+ statusText : errorInfo ?. statusText ,
65+ data : errorInfo ?. data ,
66+ } )
67+ return transformed
68+ }
69+
1570/**
1671 * Process file outputs for a tool result if execution context is available
1772 * Uses dynamic imports to avoid client-side bundling issues
@@ -410,60 +465,54 @@ async function handleInternalRequest(
410465
411466 const response = await fetch ( fullUrl , requestOptions )
412467
413- // Parse response data once
468+ // For non-OK responses, attempt JSON first; if parsing fails, preserve legacy error expected by tests
469+ if ( ! response . ok ) {
470+ let errorData : any
471+ try {
472+ errorData = await response . json ( )
473+ } catch ( jsonError ) {
474+ logger . error ( `[${ requestId } ] JSON parse error for ${ toolId } :` , {
475+ error : jsonError instanceof Error ? jsonError . message : String ( jsonError ) ,
476+ } )
477+ throw new Error ( `Failed to parse response from ${ toolId } : ${ jsonError } ` )
478+ }
479+
480+ const { isError, errorInfo } = isErrorResponse ( response , errorData )
481+ if ( isError ) {
482+ const errorToTransform = createTransformedErrorFromErrorInfo ( errorInfo )
483+
484+ logger . error ( `[${ requestId } ] Internal API error for ${ toolId } :` , {
485+ status : errorInfo ?. status ,
486+ errorData : errorInfo ?. data ,
487+ } )
488+
489+ throw errorToTransform
490+ }
491+ }
492+
493+ // Parse response data once with guard for empty 202 bodies
414494 let responseData
415- try {
416- responseData = await response . json ( )
417- } catch ( jsonError ) {
418- logger . error ( `[${ requestId } ] JSON parse error for ${ toolId } :` , {
419- error : jsonError instanceof Error ? jsonError . message : String ( jsonError ) ,
420- } )
421- throw new Error ( `Failed to parse response from ${ toolId } : ${ jsonError } ` )
495+ const status = response . status
496+ if ( status === 202 ) {
497+ // Many APIs (e.g., Microsoft Graph) return 202 with empty body
498+ responseData = { status }
499+ } else {
500+ try {
501+ responseData = await response . json ( )
502+ } catch ( jsonError ) {
503+ logger . error ( `[${ requestId } ] JSON parse error for ${ toolId } :` , {
504+ error : jsonError instanceof Error ? jsonError . message : String ( jsonError ) ,
505+ } )
506+ throw new Error ( `Failed to parse response from ${ toolId } : ${ jsonError } ` )
507+ }
422508 }
423509
424510 // Check for error conditions
425511 const { isError, errorInfo } = isErrorResponse ( response , responseData )
426512
427513 if ( isError ) {
428514 // Handle error case
429- const errorToTransform = new Error (
430- // GraphQL errors (Linear API)
431- errorInfo ?. data ?. errors ?. [ 0 ] ?. message ||
432- // X/Twitter API specific pattern
433- errorInfo ?. data ?. errors ?. [ 0 ] ?. detail ||
434- // Generic details array
435- errorInfo ?. data ?. details ?. [ 0 ] ?. message ||
436- // Hunter API pattern
437- errorInfo ?. data ?. errors ?. [ 0 ] ?. details ||
438- // Direct errors array (when errors[0] is a string or simple object)
439- ( Array . isArray ( errorInfo ?. data ?. errors )
440- ? typeof errorInfo . data . errors [ 0 ] === 'string'
441- ? errorInfo . data . errors [ 0 ]
442- : errorInfo . data . errors [ 0 ] ?. message
443- : undefined ) ||
444- // Notion/Discord/GitHub/Twilio pattern
445- errorInfo ?. data ?. message ||
446- // SOAP/XML fault patterns
447- errorInfo ?. data ?. fault ?. faultstring ||
448- errorInfo ?. data ?. faultstring ||
449- // Microsoft/OAuth error descriptions
450- errorInfo ?. data ?. error_description ||
451- // Airtable/Google fallback pattern
452- ( typeof errorInfo ?. data ?. error === 'object'
453- ? errorInfo ?. data ?. error ?. message || JSON . stringify ( errorInfo ?. data ?. error )
454- : errorInfo ?. data ?. error ) ||
455- // HTTP status text fallback
456- errorInfo ?. statusText ||
457- // Final fallback
458- `Request failed with status ${ errorInfo ?. status || 'unknown' } `
459- )
460-
461- // Add error context
462- Object . assign ( errorToTransform , {
463- status : errorInfo ?. status ,
464- statusText : errorInfo ?. statusText ,
465- data : errorInfo ?. data ,
466- } )
515+ const errorToTransform = createTransformedErrorFromErrorInfo ( errorInfo )
467516
468517 logger . error ( `[${ requestId } ] Internal API error for ${ toolId } :` , {
469518 status : errorInfo ?. status ,
0 commit comments