Merge pull request #1139 from RooVetGit/cte/cline-options

Add a ClineOptions type
This commit is contained in:
Chris Estreich 2025-02-23 22:20:05 -08:00 committed by GitHub
commit 91ca35ddc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 124 additions and 156 deletions

View file

@ -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<string, boolean>
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<string, boolean>,
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<typeof Cline>): [Cline, Promise<void>] {
args[10] = false // startTask
const instance = new Cline(...args)
static create(options: ClineOptions): [Cline, Promise<void>] {
const instance = new Cline({ ...options, startTask: false })
const { images, task, historyItem } = options
let promise
let task
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

View file

@ -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}`)

View file

@ -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) {

View file

@ -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]