diff --git a/extensions/vscode/src/extension/VsCodeExtension.ts b/extensions/vscode/src/extension/VsCodeExtension.ts index 962c4ec01b5..ef669494693 100644 --- a/extensions/vscode/src/extension/VsCodeExtension.ts +++ b/extensions/vscode/src/extension/VsCodeExtension.ts @@ -42,6 +42,7 @@ import { VsCodeIdeUtils } from "../util/ideUtils"; import { VsCodeIde } from "../VsCodeIde"; import { ConfigYamlDocumentLinkProvider } from "./ConfigYamlDocumentLinkProvider"; +import { setupPromptLogging } from "./setupPromptLogging"; import { VsCodeMessenger } from "./VsCodeMessenger"; import { modelSupportsNextEdit } from "core/llm/autodetect"; @@ -292,6 +293,7 @@ export class VsCodeExtension { ); this.core = new Core(inProcessMessenger, this.ide); + context.subscriptions.push(setupPromptLogging(this.core.llmLogger)); this.configHandler = this.core.configHandler; resolveConfigHandler?.(this.configHandler); diff --git a/extensions/vscode/src/extension/setupPromptLogging.ts b/extensions/vscode/src/extension/setupPromptLogging.ts new file mode 100644 index 00000000000..c5be4752326 --- /dev/null +++ b/extensions/vscode/src/extension/setupPromptLogging.ts @@ -0,0 +1,18 @@ +import fs from "node:fs"; + +import { LLMLogger } from "core/llm/logger"; +import { LLMLogFormatter } from "core/llm/logFormatter"; +import { getPromptLogsPath } from "core/util/paths"; + +export function setupPromptLogging(llmLogger: LLMLogger) { + const promptLogsPath = getPromptLogsPath(); + const output = fs.createWriteStream(promptLogsPath); + + new LLMLogFormatter(llmLogger, output); + + return { + dispose() { + output.end(); + }, + }; +} diff --git a/extensions/vscode/src/extension/setupPromptLogging.vitest.ts b/extensions/vscode/src/extension/setupPromptLogging.vitest.ts new file mode 100644 index 00000000000..01706fac7fc --- /dev/null +++ b/extensions/vscode/src/extension/setupPromptLogging.vitest.ts @@ -0,0 +1,62 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +const { + mockCreateWriteStream, + mockEnd, + mockGetPromptLogsPath, + mockLLMLogFormatter, +} = vi.hoisted(() => ({ + mockEnd: vi.fn(), + mockCreateWriteStream: vi.fn(() => ({ + end: vi.fn(), + })), + mockGetPromptLogsPath: vi.fn(() => "/tmp/prompt.log"), + mockLLMLogFormatter: vi.fn(), +})); + +mockCreateWriteStream.mockImplementation(() => ({ + end: mockEnd, +})); + +vi.mock("node:fs", () => ({ + __esModule: true, + default: { + createWriteStream: mockCreateWriteStream, + }, +})); + +vi.mock("core/util/paths", () => ({ + getPromptLogsPath: mockGetPromptLogsPath, +})); + +vi.mock("core/llm/logFormatter", () => ({ + LLMLogFormatter: mockLLMLogFormatter, +})); + +import { setupPromptLogging } from "./setupPromptLogging"; + +describe("setupPromptLogging", () => { + afterEach(() => { + vi.clearAllMocks(); + mockCreateWriteStream.mockImplementation(() => ({ + end: mockEnd, + })); + }); + + it("creates a prompt log stream and closes it on dispose", () => { + const llmLogger = {} as any; + + const disposable = setupPromptLogging(llmLogger); + + expect(mockGetPromptLogsPath).toHaveBeenCalledTimes(1); + expect(mockCreateWriteStream).toHaveBeenCalledWith("/tmp/prompt.log"); + expect(mockLLMLogFormatter).toHaveBeenCalledWith( + llmLogger, + expect.objectContaining({ end: mockEnd }), + ); + + disposable.dispose(); + + expect(mockEnd).toHaveBeenCalledTimes(1); + }); +});