diff --git a/packages/core/src/utils/openaiLogger.test.ts b/packages/core/src/utils/openaiLogger.test.ts index b93f5c73028..283ac44b7cb 100644 --- a/packages/core/src/utils/openaiLogger.test.ts +++ b/packages/core/src/utils/openaiLogger.test.ts @@ -195,6 +195,52 @@ describe('OpenAILogger', () => { ); }); + it('should include a subagent suffix without the session id', async () => { + const logger = new OpenAILogger(testTempDir); + await logger.initialize(); + + const request = { + model: 'claude-opus-4-7', + messages: [{ role: 'user', content: 'test' }], + }; + const response = { id: 'test-id', choices: [] }; + + const logPath = await logger.logInteraction( + request, + response, + undefined, + 'e097d32b-82d6-422a-afa6-f6184565a8ab#Explore-g2tss0#7', + ); + + const basename = path.basename(logPath); + expect(basename).toMatch( + /openai-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z-[a-f0-9]{8}-subagent-Explore-g2tss0\.json/, + ); + expect(basename).not.toContain('e097d32b'); + }); + + it('should not include a suffix for main-session prompt ids', async () => { + const logger = new OpenAILogger(testTempDir); + await logger.initialize(); + + const request = { + model: 'claude-opus-4-7', + messages: [{ role: 'user', content: 'test' }], + }; + const response = { id: 'test-id', choices: [] }; + + const logPath = await logger.logInteraction( + request, + response, + undefined, + 'e097d32b-82d6-422a-afa6-f6184565a8ab########0', + ); + + expect(path.basename(logPath)).toMatch( + /openai-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z-[a-f0-9]{8}\.json/, + ); + }); + it('should write correct log data structure', async () => { const logger = new OpenAILogger(testTempDir); await logger.initialize(); diff --git a/packages/core/src/utils/openaiLogger.ts b/packages/core/src/utils/openaiLogger.ts index 374a2d343a4..0807292427d 100644 --- a/packages/core/src/utils/openaiLogger.ts +++ b/packages/core/src/utils/openaiLogger.ts @@ -13,16 +13,38 @@ import { isInternalPromptId } from './internalPromptIds.js'; const debugLogger = createDebugLogger('OPENAI_LOGGER'); -function sanitizePromptIdForFilename( - promptId: string | undefined, +function sanitizeDiagnosticSuffix( + suffix: string | undefined, ): string | undefined { - if (!promptId || !isInternalPromptId(promptId)) return undefined; - const sanitized = promptId + if (!suffix) return undefined; + const sanitized = suffix .replace(/[^a-zA-Z0-9._-]+/g, '-') .replace(/^-+|-+$/g, ''); return sanitized || undefined; } +function extractSubagentSuffix(promptId: string): string | undefined { + const parts = promptId.split('#'); + if (parts.length !== 3) return undefined; + + const [, subagentId, turn] = parts; + if (!subagentId || !turn || !/^\d+$/.test(turn)) { + return undefined; + } + + return `subagent-${subagentId}`; +} + +function promptIdSuffixForFilename( + promptId: string | undefined, +): string | undefined { + if (!promptId) return undefined; + if (isInternalPromptId(promptId)) { + return sanitizeDiagnosticSuffix(promptId); + } + return sanitizeDiagnosticSuffix(extractSubagentSuffix(promptId)); +} + /** * Logger specifically for OpenAI API requests and responses */ @@ -75,8 +97,8 @@ export class OpenAILogger { * @param request The request sent to OpenAI * @param response The response received from OpenAI * @param error Optional error if the request failed - * @param promptId Optional prompt id; internal prompt ids are appended to - * the filename after timestamp and id. + * @param promptId Optional prompt id; internal and subagent prompt ids are + * appended to the filename after timestamp and id. * @returns The file path where the log was written */ async logInteraction( @@ -91,7 +113,7 @@ export class OpenAILogger { const timestamp = new Date().toISOString().replace(/:/g, '-'); const id = uuidv4().slice(0, 8); - const promptIdSuffix = sanitizePromptIdForFilename(promptId); + const promptIdSuffix = promptIdSuffixForFilename(promptId); const filename = promptIdSuffix ? `openai-${timestamp}-${id}-${promptIdSuffix}.json` : `openai-${timestamp}-${id}.json`;