diff --git a/packages/core/src/utils/openaiLogger.test.ts b/packages/core/src/utils/openaiLogger.test.ts index ddd249029c..03626acd02 100644 --- a/packages/core/src/utils/openaiLogger.test.ts +++ b/packages/core/src/utils/openaiLogger.test.ts @@ -344,6 +344,45 @@ describe('OpenAILogger', () => { expect(logContent.response).toBeNull(); }); + it('should log request id from OpenAI API errors', async () => { + const logger = new OpenAILogger(testTempDir); + await logger.initialize(); + + const error = Object.assign(new Error('Server error'), { + requestID: 'req-server-123', + }); + + const logPath = await logger.logInteraction( + { model: 'gpt-4' }, + undefined, + error, + ); + const logContent = JSON.parse(await fs.readFile(logPath, 'utf-8')); + + expect(logContent.error).toMatchObject({ + message: 'Server error', + requestId: 'req-server-123', + }); + }); + + it('should log request id from provider error payloads', async () => { + const logger = new OpenAILogger(testTempDir); + await logger.initialize(); + + const error = new Error( + 'event:error\n:HTTP_STATUS/429\ndata:{"request_id":"req-provider-456","code":"Throttling.AllocationQuota","message":"Allocated quota exceeded"}', + ); + + const logPath = await logger.logInteraction( + { model: 'qwen-plus' }, + undefined, + error, + ); + const logContent = JSON.parse(await fs.readFile(logPath, 'utf-8')); + + expect(logContent.error.requestId).toBe('req-provider-456'); + }); + it('should use custom directory when provided', async () => { const customDir = path.join(testTempDir, 'custom-logs'); const logger = new OpenAILogger(customDir); diff --git a/packages/core/src/utils/openaiLogger.ts b/packages/core/src/utils/openaiLogger.ts index 46e22c65ed..6f57f26cf8 100644 --- a/packages/core/src/utils/openaiLogger.ts +++ b/packages/core/src/utils/openaiLogger.ts @@ -10,6 +10,7 @@ import { v4 as uuidv4 } from 'uuid'; import * as os from 'os'; import { createDebugLogger } from './debugLogger.js'; import { isInternalPromptId } from './internalPromptIds.js'; +import { getRateLimitErrorDetails } from './rateLimit.js'; const debugLogger = createDebugLogger('OPENAI_LOGGER'); const MAIN_SESSION_PROMPT_ID_DELIMITER = '########'; @@ -111,6 +112,39 @@ function contextForPromptId( }; } +function getString(value: unknown): string | undefined { + return typeof value === 'string' && value ? value : undefined; +} + +function getErrorRequestId(error: Error): string | undefined { + const source = error as { + requestID?: unknown; + request_id?: unknown; + requestId?: unknown; + response_id?: unknown; + }; + return ( + getString(source.requestID) ?? + getString(source.request_id) ?? + getString(source.requestId) ?? + getString(source.response_id) ?? + getRateLimitErrorDetails(error).requestId + ); +} + +function serializeError(error: Error): { + message: string; + stack?: string; + requestId?: string; +} { + const requestId = getErrorRequestId(error); + return { + message: error.message, + stack: error.stack, + ...(requestId ? { requestId } : {}), + }; +} + /** * Logger specifically for OpenAI API requests and responses */ @@ -175,12 +209,7 @@ export class OpenAILogger { timestamp: new Date().toISOString(), request, response: response || null, - error: error - ? { - message: error.message, - stack: error.stack, - } - : null, + error: error ? serializeError(error) : null, context: contextForPromptId(promptId), system: { hostname: os.hostname(),