diff --git a/packages/core/src/core/loggingContentGenerator/loggingContentGenerator.test.ts b/packages/core/src/core/loggingContentGenerator/loggingContentGenerator.test.ts index 06be16ea51..b39e653572 100644 --- a/packages/core/src/core/loggingContentGenerator/loggingContentGenerator.test.ts +++ b/packages/core/src/core/loggingContentGenerator/loggingContentGenerator.test.ts @@ -66,6 +66,7 @@ const createConfig = (overrides: Record = {}): Config => { return { getContentGeneratorConfig: () => configContent, getAuthType: () => configContent.authType as AuthType | undefined, + getWorkingDir: () => process.cwd(), } as Config; }; diff --git a/packages/core/src/core/loggingContentGenerator/loggingContentGenerator.ts b/packages/core/src/core/loggingContentGenerator/loggingContentGenerator.ts index 61fc885e95..4f4b00138c 100644 --- a/packages/core/src/core/loggingContentGenerator/loggingContentGenerator.ts +++ b/packages/core/src/core/loggingContentGenerator/loggingContentGenerator.ts @@ -62,7 +62,10 @@ export class LoggingContentGenerator implements ContentGenerator { // Extract fields needed for initialization from passed config // (config.getContentGeneratorConfig() may not be available yet during refreshAuth) if (generatorConfig.enableOpenAILogging) { - this.openaiLogger = new OpenAILogger(generatorConfig.openAILoggingDir); + this.openaiLogger = new OpenAILogger( + generatorConfig.openAILoggingDir, + config.getWorkingDir(), + ); this.schemaCompliance = generatorConfig.schemaCompliance; } } diff --git a/packages/core/src/utils/openaiLogger.test.ts b/packages/core/src/utils/openaiLogger.test.ts index 9d3387e4b4..4aa545e104 100644 --- a/packages/core/src/utils/openaiLogger.test.ts +++ b/packages/core/src/utils/openaiLogger.test.ts @@ -387,4 +387,86 @@ describe('OpenAILogger', () => { expect(logPath).toContain(specialPath); }); }); + + describe('cwd parameter', () => { + it('should use provided cwd for default log directory instead of process.cwd()', async () => { + const customCwd = path.join(testTempDir, 'project-root'); + await fs.mkdir(customCwd, { recursive: true }); + const logger = new OpenAILogger(undefined, customCwd); + await logger.initialize(); + + const request = { test: 'request' }; + const response = { test: 'response' }; + + const logPath = await logger.logInteraction(request, response); + const expectedDir = path.join(customCwd, 'logs', 'openai'); + createdDirs.push(expectedDir); + + expect(logPath).toContain(expectedDir); + }); + + it('should resolve relative customLogDir against provided cwd', async () => { + const customCwd = path.join(testTempDir, 'project-root-2'); + await fs.mkdir(customCwd, { recursive: true }); + const relativeDir = 'my-logs'; + const logger = new OpenAILogger(relativeDir, customCwd); + await logger.initialize(); + + const request = { test: 'request' }; + const response = { test: 'response' }; + + const logPath = await logger.logInteraction(request, response); + const expectedDir = path.resolve(customCwd, relativeDir); + createdDirs.push(expectedDir); + + expect(logPath).toContain(expectedDir); + }); + + it('should not use cwd when customLogDir is an absolute path', async () => { + const customCwd = path.join(testTempDir, 'project-root-3'); + const absoluteLogDir = path.join(testTempDir, 'absolute-logs'); + const logger = new OpenAILogger(absoluteLogDir, customCwd); + await logger.initialize(); + + const request = { test: 'request' }; + const response = { test: 'response' }; + + const logPath = await logger.logInteraction(request, response); + createdDirs.push(absoluteLogDir); + + expect(logPath).toContain(absoluteLogDir); + expect(logPath).not.toContain(customCwd); + }); + + it('should not use cwd when customLogDir starts with ~', async () => { + const customCwd = path.join(testTempDir, 'project-root-4'); + const logger = new OpenAILogger('~/test-openai-logs', customCwd); + await logger.initialize(); + + const request = { test: 'request' }; + const response = { test: 'response' }; + + const logPath = await logger.logInteraction(request, response); + const expectedDir = path.join(os.homedir(), 'test-openai-logs'); + createdDirs.push(expectedDir); + + expect(logPath).toContain(expectedDir); + expect(logPath).not.toContain(customCwd); + }); + + it('should fall back to process.cwd() when cwd is not provided', async () => { + const relativeDir = 'test-relative-logs'; + const logger = new OpenAILogger(relativeDir); + await logger.initialize(); + + const request = { test: 'request' }; + const response = { test: 'response' }; + + const logPath = await logger.logInteraction(request, response); + const expectedDir = path.resolve(process.cwd(), relativeDir); + createdDirs.push(expectedDir); + + expect(logPath).toContain(expectedDir); + }); + }); }); diff --git a/packages/core/src/utils/openaiLogger.ts b/packages/core/src/utils/openaiLogger.ts index c6a56ee0aa..43028de2c7 100644 --- a/packages/core/src/utils/openaiLogger.ts +++ b/packages/core/src/utils/openaiLogger.ts @@ -22,8 +22,12 @@ export class OpenAILogger { /** * Creates a new OpenAI logger * @param customLogDir Optional custom log directory path (supports relative paths, absolute paths, and ~ expansion) + * @param cwd Optional working directory for resolving relative paths. Defaults to process.cwd(). + * In ACP mode, process.cwd() may be '/' (filesystem root), so callers should + * pass the project working directory from Config.getWorkingDir(). */ - constructor(customLogDir?: string) { + constructor(customLogDir?: string, cwd?: string) { + const baseCwd = cwd || process.cwd(); if (customLogDir) { // Resolve relative paths to absolute paths // Handle ~ expansion @@ -31,12 +35,12 @@ export class OpenAILogger { if (customLogDir === '~' || customLogDir.startsWith('~/')) { resolvedPath = path.join(os.homedir(), customLogDir.slice(1)); } else if (!path.isAbsolute(customLogDir)) { - // If it's a relative path, resolve it relative to current working directory - resolvedPath = path.resolve(process.cwd(), customLogDir); + // If it's a relative path, resolve it relative to provided working directory + resolvedPath = path.resolve(baseCwd, customLogDir); } this.logDir = path.normalize(resolvedPath); } else { - this.logDir = path.join(process.cwd(), 'logs', 'openai'); + this.logDir = path.join(baseCwd, 'logs', 'openai'); } }