diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 720b1b0d625..522af873274 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -73,6 +73,20 @@ type UserContent = Array< Anthropic.TextBlockParam | Anthropic.ImageBlockParam | Anthropic.ToolUseBlockParam | Anthropic.ToolResultBlockParam > +export type ClineOptions = { + provider: ClineProvider + apiConfiguration: ApiConfiguration + customInstructions?: string + enableDiff?: boolean + enableCheckpoints?: boolean + fuzzyMatchThreshold?: number + task?: string + images?: string[] + historyItem?: HistoryItem + experiments?: Record + startTask?: boolean +} + export class Cline { readonly taskId: string api: ApiHandler @@ -118,19 +132,19 @@ export class Cline { private didAlreadyUseTool = false private didCompleteReadingStream = false - constructor( - provider: ClineProvider, - apiConfiguration: ApiConfiguration, - customInstructions?: string, - enableDiff?: boolean, - enableCheckpoints?: boolean, - fuzzyMatchThreshold?: number, - task?: string | undefined, - images?: string[] | undefined, - historyItem?: HistoryItem | undefined, - experiments?: Record, + constructor({ + provider, + apiConfiguration, + customInstructions, + enableDiff, + enableCheckpoints, + fuzzyMatchThreshold, + task, + images, + historyItem, + experiments, startTask = true, - ) { + }: ClineOptions) { if (startTask && !task && !images && !historyItem) { throw new Error("Either historyItem or task/images must be provided") } @@ -165,21 +179,20 @@ export class Cline { } } - static create(...args: ConstructorParameters): [Cline, Promise] { - args[10] = false // startTask - const instance = new Cline(...args) - - let task + static create(options: ClineOptions): [Cline, Promise] { + const instance = new Cline({ ...options, startTask: false }) + const { images, task, historyItem } = options + let promise - if (args[6] || args[7]) { - task = instance.startTask(args[6], args[7]) - } else if (args[8]) { - task = instance.resumeTaskFromHistory() + if (images || task) { + promise = instance.startTask(task, images) + } else if (historyItem) { + promise = instance.resumeTaskFromHistory() } else { throw new Error("Either historyItem or task/images must be provided") } - return [instance, task] + return [instance, promise] } // Add method to update diffStrategy diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 9f72c4931a7..9910896ebb9 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -327,15 +327,13 @@ describe("Cline", () => { describe("constructor", () => { it("should respect provided settings", async () => { - const [cline, task] = Cline.create( - mockProvider, - mockApiConfig, - "custom instructions", - false, - false, - 0.95, // 95% threshold - "test task", - ) + const [cline, task] = Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + customInstructions: "custom instructions", + fuzzyMatchThreshold: 0.95, + task: "test task", + }) expect(cline.customInstructions).toBe("custom instructions") expect(cline.diffEnabled).toBe(false) @@ -345,15 +343,14 @@ describe("Cline", () => { }) it("should use default fuzzy match threshold when not provided", async () => { - const [cline, task] = await Cline.create( - mockProvider, - mockApiConfig, - "custom instructions", - true, - false, - undefined, - "test task", - ) + const [cline, task] = await Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + customInstructions: "custom instructions", + enableDiff: true, + fuzzyMatchThreshold: 0.95, + task: "test task", + }) expect(cline.diffEnabled).toBe(true) // The diff strategy should be created with default threshold (1.0) @@ -366,15 +363,14 @@ describe("Cline", () => { it("should use provided fuzzy match threshold", async () => { const getDiffStrategySpy = jest.spyOn(require("../diff/DiffStrategy"), "getDiffStrategy") - const [cline, task] = Cline.create( - mockProvider, - mockApiConfig, - "custom instructions", - true, - false, - 0.9, // 90% threshold - "test task", - ) + const [cline, task] = await Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + customInstructions: "custom instructions", + enableDiff: true, + fuzzyMatchThreshold: 0.9, + task: "test task", + }) expect(cline.diffEnabled).toBe(true) expect(cline.diffStrategy).toBeDefined() @@ -389,15 +385,13 @@ describe("Cline", () => { it("should pass default threshold to diff strategy when not provided", async () => { const getDiffStrategySpy = jest.spyOn(require("../diff/DiffStrategy"), "getDiffStrategy") - const [cline, task] = Cline.create( - mockProvider, - mockApiConfig, - "custom instructions", - true, - false, - undefined, - "test task", - ) + const [cline, task] = Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + customInstructions: "custom instructions", + enableDiff: true, + task: "test task", + }) expect(cline.diffEnabled).toBe(true) expect(cline.diffStrategy).toBeDefined() @@ -411,15 +405,7 @@ describe("Cline", () => { it("should require either task or historyItem", () => { expect(() => { - new Cline( - mockProvider, - mockApiConfig, - undefined, // customInstructions - false, // diffEnabled - false, // checkpointsEnabled - undefined, // fuzzyMatchThreshold - undefined, // task - ) + new Cline({ provider: mockProvider, apiConfiguration: mockApiConfig }) }).toThrow("Either historyItem or task/images must be provided") }) }) @@ -469,15 +455,11 @@ describe("Cline", () => { }) it("should include timezone information in environment details", async () => { - const [cline, task] = Cline.create( - mockProvider, - mockApiConfig, - undefined, - false, - false, - undefined, - "test task", - ) + const [cline, task] = Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + }) const details = await cline["getEnvironmentDetails"](false) @@ -493,15 +475,12 @@ describe("Cline", () => { describe("API conversation handling", () => { it("should clean conversation history before sending to API", async () => { - const [cline, task] = Cline.create( - mockProvider, - mockApiConfig, - undefined, - false, - false, - undefined, - "test task", - ) + const [cline, task] = Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + }) + cline.abandoned = true await task @@ -611,15 +590,11 @@ describe("Cline", () => { ] // Test with model that supports images - const [clineWithImages, taskWithImages] = Cline.create( - mockProvider, - configWithImages, - undefined, - false, - false, - undefined, - "test task", - ) + const [clineWithImages, taskWithImages] = Cline.create({ + provider: mockProvider, + apiConfiguration: configWithImages, + task: "test task", + }) // Mock the model info to indicate image support jest.spyOn(clineWithImages.api, "getModel").mockReturnValue({ @@ -638,15 +613,11 @@ describe("Cline", () => { clineWithImages.apiConversationHistory = conversationHistory // Test with model that doesn't support images - const [clineWithoutImages, taskWithoutImages] = Cline.create( - mockProvider, - configWithoutImages, - undefined, - false, - false, - undefined, - "test task", - ) + const [clineWithoutImages, taskWithoutImages] = Cline.create({ + provider: mockProvider, + apiConfiguration: configWithoutImages, + task: "test task", + }) // Mock the model info to indicate no image support jest.spyOn(clineWithoutImages.api, "getModel").mockReturnValue({ @@ -742,15 +713,11 @@ describe("Cline", () => { }) it.skip("should handle API retry with countdown", async () => { - const [cline, task] = Cline.create( - mockProvider, - mockApiConfig, - undefined, - false, - false, - undefined, - "test task", - ) + const [cline, task] = Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + }) // Mock delay to track countdown timing const mockDelay = jest.fn().mockResolvedValue(undefined) @@ -870,15 +837,11 @@ describe("Cline", () => { }) it.skip("should not apply retry delay twice", async () => { - const [cline, task] = Cline.create( - mockProvider, - mockApiConfig, - undefined, - false, - false, - undefined, - "test task", - ) + const [cline, task] = Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + }) // Mock delay to track countdown timing const mockDelay = jest.fn().mockResolvedValue(undefined) @@ -998,15 +961,11 @@ describe("Cline", () => { describe("loadContext", () => { it("should process mentions in task and feedback tags", async () => { - const [cline, task] = Cline.create( - mockProvider, - mockApiConfig, - undefined, - false, - false, - undefined, - "test task", - ) + const [cline, task] = Cline.create({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + }) // Mock parseMentions to track calls const mockParseMentions = jest.fn().mockImplementation((text) => `processed: ${text}`) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 05faa138342..6790224ecae 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -413,18 +413,17 @@ export class ClineProvider implements vscode.WebviewViewProvider { const modePrompt = customModePrompts?.[mode] as PromptComponent const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n") - this.cline = new Cline( - this, + this.cline = new Cline({ + provider: this, apiConfiguration, - effectiveInstructions, - diffEnabled, - checkpointsEnabled, + customInstructions: effectiveInstructions, + enableDiff: diffEnabled, + enableCheckpoints: checkpointsEnabled, fuzzyMatchThreshold, task, images, - undefined, experiments, - ) + }) } public async initClineWithHistoryItem(historyItem: HistoryItem) { @@ -444,18 +443,16 @@ export class ClineProvider implements vscode.WebviewViewProvider { const modePrompt = customModePrompts?.[mode] as PromptComponent const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n") - this.cline = new Cline( - this, + this.cline = new Cline({ + provider: this, apiConfiguration, - effectiveInstructions, - diffEnabled, - checkpointsEnabled, + customInstructions: effectiveInstructions, + enableDiff: diffEnabled, + enableCheckpoints: checkpointsEnabled, fuzzyMatchThreshold, - undefined, - undefined, historyItem, experiments, - ) + }) } public async postMessageToWebview(message: ExtensionMessage) { diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 902e536d9c8..f8df84721da 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -690,19 +690,18 @@ describe("ClineProvider", () => { await provider.initClineWithTask("Test task") // Verify Cline was initialized with mode-specific instructions - expect(Cline).toHaveBeenCalledWith( + expect(Cline).toHaveBeenCalledWith({ provider, - mockApiConfig, - modeCustomInstructions, - true, - false, - 1.0, - "Test task", - undefined, - undefined, - experimentDefault, - ) + apiConfiguration: mockApiConfig, + customInstructions: modeCustomInstructions, + enableDiff: true, + enableCheckpoints: false, + fuzzyMatchThreshold: 1.0, + task: "Test task", + experiments: experimentDefault, + }) }) + test("handles mode-specific custom instructions updates", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]