diff --git a/.changeset/comprehensive-ai-sdk-error-messages.md b/.changeset/comprehensive-ai-sdk-error-messages.md new file mode 100644 index 00000000..d2523a13 --- /dev/null +++ b/.changeset/comprehensive-ai-sdk-error-messages.md @@ -0,0 +1,5 @@ +--- +'@openai/agents-extensions': patch +--- + +Improve AI SDK error messages in tracing to include comprehensive error details like responseBody, statusCode, and responseHeaders when tracing is enabled. diff --git a/packages/agents-extensions/src/aiSdk.ts b/packages/agents-extensions/src/aiSdk.ts index fce58c8d..52df3114 100644 --- a/packages/agents-extensions/src/aiSdk.ts +++ b/packages/agents-extensions/src/aiSdk.ts @@ -784,22 +784,38 @@ export class AiSdkModel implements Model { data: { error: request.tracing === true - ? String(error) - : error instanceof Error - ? error.name - : undefined, + ? { + name: error.name, + message: error.message, + // Include AI SDK specific error fields if they exist. + ...(typeof error === 'object' && error !== null + ? { + ...('responseBody' in error + ? { responseBody: (error as any).responseBody } + : {}), + ...('responseHeaders' in error + ? { + responseHeaders: (error as any) + .responseHeaders, + } + : {}), + ...('statusCode' in error + ? { statusCode: (error as any).statusCode } + : {}), + ...('cause' in error + ? { cause: (error as any).cause } + : {}), + } + : {}), + } + : error.name, }, }); } else { span.setError({ message: 'Unknown error', data: { - error: - request.tracing === true - ? String(error) - : error instanceof Error - ? error.name - : undefined, + error: request.tracing === true ? String(error) : undefined, }, }); } @@ -999,11 +1015,37 @@ export class AiSdkModel implements Model { } catch (error) { if (span) { span.setError({ - message: 'Error streaming response', + message: + error instanceof Error ? error.message : 'Error streaming response', data: { error: request.tracing === true - ? String(error) + ? error instanceof Error + ? { + name: error.name, + message: error.message, + // Include AI SDK specific error fields if they exist. + ...(typeof error === 'object' && error !== null + ? { + ...('responseBody' in error + ? { responseBody: (error as any).responseBody } + : {}), + ...('responseHeaders' in error + ? { + responseHeaders: (error as any) + .responseHeaders, + } + : {}), + ...('statusCode' in error + ? { statusCode: (error as any).statusCode } + : {}), + ...('cause' in error + ? { cause: (error as any).cause } + : {}), + } + : {}), + } + : String(error) : error instanceof Error ? error.name : undefined, diff --git a/packages/agents-extensions/test/aiSdk.test.ts b/packages/agents-extensions/test/aiSdk.test.ts index b3193dad..3c0132ea 100644 --- a/packages/agents-extensions/test/aiSdk.test.ts +++ b/packages/agents-extensions/test/aiSdk.test.ts @@ -1376,4 +1376,92 @@ describe('AiSdkModel', () => { expect(parseArguments('{"a":1,"b":"c"}')).toEqual({ a: 1, b: 'c' }); }); }); + + describe('Error handling with tracing', () => { + test('captures comprehensive AI SDK error details when tracing enabled', async () => { + // Simulate an AI SDK error with responseBody and other fields. + const aiSdkError = new Error('API call failed'); + aiSdkError.name = 'AI_APICallError'; + (aiSdkError as any).responseBody = { + error: { + message: 'Rate limit exceeded', + code: 'rate_limit_exceeded', + type: 'insufficient_quota', + }, + }; + (aiSdkError as any).responseHeaders = { + 'x-request-id': 'req_abc123', + 'retry-after': '60', + }; + (aiSdkError as any).statusCode = 429; + + const model = new AiSdkModel( + stubModel({ + async doGenerate() { + throw aiSdkError; + }, + }), + ); + + try { + await withTrace('test-trace', () => + model.getResponse({ + input: 'test input', + tools: [], + handoffs: [], + modelSettings: {}, + outputType: 'text', + tracing: true, + } as any), + ); + expect.fail('Should have thrown error'); + } catch (error: any) { + // Error should be re-thrown. + expect(error.message).toBe('API call failed'); + // Verify error has the AI SDK fields. + expect((error as any).responseBody).toBeDefined(); + expect((error as any).statusCode).toBe(429); + } + }); + + test('propagates error with AI SDK fields in streaming mode', async () => { + const aiSdkError = new Error('Stream failed'); + aiSdkError.name = 'AI_StreamError'; + (aiSdkError as any).responseBody = { + error: { message: 'Connection timeout', code: 'timeout' }, + }; + (aiSdkError as any).statusCode = 504; + + const model = new AiSdkModel( + stubModel({ + async doStream() { + throw aiSdkError; + }, + }), + ); + + try { + await withTrace('test-stream', async () => { + const iter = model.getStreamedResponse({ + input: 'test', + tools: [], + handoffs: [], + modelSettings: {}, + outputType: 'text', + tracing: true, + } as any); + + for await (const _ of iter) { + // Should not get here. + } + }); + expect.fail('Should have thrown error'); + } catch (error: any) { + expect(error.message).toBe('Stream failed'); + // Verify error has the AI SDK fields. + expect((error as any).responseBody).toBeDefined(); + expect((error as any).statusCode).toBe(504); + } + }); + }); });