Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ export interface SingleCompletionHandler {
}

export interface ApiHandler {
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
getModel(): { id: string; info: ModelInfo }
// completePrompt(prompt: string): Promise<string>
createMessage(systemPrompt: string, messages: any[]): AsyncGenerator<any>
getModel(): { id: string; info: ModelInfo } | Promise<{ id: string; info: ModelInfo }>

/**
* Counts tokens for content blocks
Expand All @@ -42,7 +43,7 @@ export interface ApiHandler {
countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number>
}

export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler | Promise<ApiHandler> {
const { apiProvider, ...options } = configuration
switch (apiProvider) {
case "anthropic":
Expand Down Expand Up @@ -76,7 +77,7 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
case "requesty":
return new RequestyHandler(options)
case "pearai":
return new PearAiHandler(options)
return PearAiHandler.create(options)
case "human-relay":
return new HumanRelayHandler(options)
case "fake-ai":
Expand Down
2 changes: 1 addition & 1 deletion src/api/providers/base-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export abstract class BaseProvider implements ApiHandler {
// Cache the Tiktoken encoder instance since it's stateless
private encoder: Tiktoken | null = null
abstract createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
abstract getModel(): { id: string; info: ModelInfo }
abstract getModel(): { id: string; info: ModelInfo } | Promise<{ id: string; info: ModelInfo }>

/**
* Default token counting implementation using tiktoken
Expand Down
36 changes: 23 additions & 13 deletions src/api/providers/pearai/pearai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl
private handler!: AnthropicHandler | PearAIGenericHandler
private pearAiModelsResponse: PearAiModelsResponse | null = null
private options: ApiHandlerOptions
private initializationPromise: Promise<void> | null = null

constructor(options: ApiHandlerOptions) {
// Private constructor - use the static create method instead
private constructor(options: ApiHandlerOptions) {
super()
if (!options.pearaiApiKey) {
vscode.window.showErrorMessage("PearAI API key not found.", "Login to PearAI").then(async (selection) => {
Expand All @@ -45,18 +47,16 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl
vscode.commands.executeCommand("pearai.checkPearAITokens", undefined)
}
this.options = options
// Start initialization immediately
this.initializationPromise = this.initializeHandler(options)
}

this.handler = new PearAIGenericHandler({
...options,
openAiBaseUrl: PEARAI_URL,
openAiApiKey: options.pearaiApiKey,
openAiModelId: "deepseek/deepseek-chat",
})

// Then try to initialize the correct handler asynchronously
this.initializeHandler(options).catch((error) => {
console.error("Failed to initialize PearAI handler:", error)
})
// Static factory method that properly awaits initialization
public static async create(options: ApiHandlerOptions): Promise<PearAiHandler> {
const instance = new PearAiHandler(options)
// Wait for initialization to complete
await instance.initializationPromise
return instance
}

private async initializeHandler(options: ApiHandlerOptions): Promise<void> {
Expand Down Expand Up @@ -116,7 +116,15 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl
}
}

getModel(): { id: string; info: ModelInfo } {
private async ensureInitialized(): Promise<void> {
if (this.initializationPromise) {
await this.initializationPromise
}
}

async getModel(): Promise<{ id: string; info: ModelInfo }> {
await this.ensureInitialized()

if (this.options.apiModelId) {
let modelInfo = null
if (this.options.apiModelId.startsWith("pearai")) {
Expand All @@ -142,6 +150,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl
}

async *createMessage(systemPrompt: string, messages: any[]): AsyncGenerator<any> {
await this.ensureInitialized()
const generator = this.handler.createMessage(systemPrompt, messages)
let warningMsg = ""

Expand All @@ -168,6 +177,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl
}

async completePrompt(prompt: string): Promise<string> {
await this.ensureInitialized()
return this.handler.completePrompt(prompt)
}
}
20 changes: 19 additions & 1 deletion src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import { readLines } from "../integrations/misc/read-lines"
import { getWorkspacePath } from "../utils/path"
import { isBinaryFile } from "isbinaryfile"
import { AnthropicHandler } from "../api/providers/anthropic"

type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
type UserContent = Array<Anthropic.Messages.ContentBlockParam>
Expand Down Expand Up @@ -208,7 +209,15 @@
this.instanceId = crypto.randomUUID().slice(0, 8)
this.taskNumber = -1
this.apiConfiguration = apiConfiguration
this.api = buildApiHandler(apiConfiguration)

// Initialize with a temporary handler that will be replaced
this.api = new AnthropicHandler(apiConfiguration)

// Then asynchronously initialize the correct handler
this.initializeApiHandler(apiConfiguration).catch(error => {
console.error("Failed to initialize API handler:", error)
})

this.urlContentFetcher = new UrlContentFetcher(provider.context)
this.browserSession = new BrowserSession(provider.context)
this.customInstructions = customInstructions
Expand Down Expand Up @@ -276,7 +285,7 @@
}

this.diffStrategy = getDiffStrategy(
this.api.getModel().id,

Check failure on line 288 in src/core/Cline.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'id' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
this.fuzzyMatchThreshold,
experimentalDiffStrategy,
multiSearchReplaceDiffStrategy,
Expand Down Expand Up @@ -1140,7 +1149,7 @@
return SYSTEM_PROMPT(
provider.context,
this.cwd,
(this.api.getModel().info.supportsComputerUse ?? false) && (browserToolEnabled ?? true),

Check failure on line 1152 in src/core/Cline.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'info' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
mcpHub,
this.diffStrategy,
browserViewportSize,
Expand Down Expand Up @@ -1173,7 +1182,7 @@
// Default max tokens value for thinking models when no specific value is set
const DEFAULT_THINKING_MODEL_MAX_TOKENS = 16_384

const modelInfo = this.api.getModel().info

Check failure on line 1185 in src/core/Cline.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'info' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
const maxTokens = modelInfo.thinking
? this.apiConfiguration.modelMaxTokens || DEFAULT_THINKING_MODEL_MAX_TOKENS
: modelInfo.maxTokens
Expand All @@ -1197,7 +1206,7 @@
const cleanConversationHistory = this.apiConversationHistory.map(({ role, content }) => {
// Handle array content (could contain image blocks)
if (Array.isArray(content)) {
if (!this.api.getModel().info.supportsImages) {

Check failure on line 1209 in src/core/Cline.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'info' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
// Convert image blocks to text descriptions
content = content.map((block) => {
if (block.type === "image") {
Expand Down Expand Up @@ -1578,7 +1587,7 @@
newContent = newContent.split("\n").slice(0, -1).join("\n").trim()
}

if (!this.api.getModel().id.includes("claude")) {

Check failure on line 1590 in src/core/Cline.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'id' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
// it seems not just llama models are doing this, but also gemini and potentially others
if (
newContent.includes("&gt;") ||
Expand Down Expand Up @@ -3292,7 +3301,7 @@
if (this.consecutiveMistakeCount >= 3) {
const { response, text, images } = await this.ask(
"mistake_limit_reached",
this.api.getModel().id.includes("claude")

Check failure on line 3304 in src/core/Cline.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'id' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
? `This may indicate a failure in his thought process or inability to use a tool properly, which can be mitigated with some user guidance (e.g. "Try breaking down the task into smaller steps").`
: "Agent uses complex prompts and iterative task execution that may be challenging for less capable models. For best results, it's recommended to use Claude 3.7 Sonnet for its advanced agentic coding capabilities.",
)
Expand Down Expand Up @@ -3385,7 +3394,7 @@
cost:
totalCost ??
calculateApiCostAnthropic(
this.api.getModel().info,

Check failure on line 3397 in src/core/Cline.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'info' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
inputTokens,
outputTokens,
cacheWriteTokens,
Expand Down Expand Up @@ -3831,7 +3840,7 @@

// Add context tokens information
const { contextTokens, totalCost } = getApiMetrics(this.clineMessages)
const modelInfo = this.api.getModel().info

Check failure on line 3843 in src/core/Cline.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'info' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
const contextWindow = modelInfo.contextWindow
const contextPercentage =
contextTokens && contextWindow ? Math.round((contextTokens / contextWindow) * 100) : undefined
Expand Down Expand Up @@ -4173,6 +4182,15 @@
this.enableCheckpoints = false
}
}

private async initializeApiHandler(apiConfiguration: ApiConfiguration): Promise<void> {
const apiHandler = buildApiHandler(apiConfiguration)
if (apiHandler instanceof Promise) {
this.api = await apiHandler
} else {
this.api = apiHandler
}
}
}

function escapeRegExp(string: string): string {
Expand Down
7 changes: 6 additions & 1 deletion src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2074,7 +2074,7 @@
const rooIgnoreInstructions = this.getCurrentCline()?.rooIgnoreController?.getInstructions()

// Determine if browser tools can be used based on model support and user settings
const modelSupportsComputerUse = this.getCurrentCline()?.api.getModel().info.supportsComputerUse ?? false

Check failure on line 2077 in src/core/webview/ClineProvider.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'info' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
const canUseBrowserTool = modelSupportsComputerUse && (browserToolEnabled ?? true)

const systemPrompt = await SYSTEM_PROMPT(
Expand Down Expand Up @@ -2159,7 +2159,12 @@
await this.contextProxy.setApiConfiguration(apiConfiguration)

if (this.getCurrentCline()) {
this.getCurrentCline()!.api = buildApiHandler(apiConfiguration)
const apiHandler = buildApiHandler(apiConfiguration)
if (apiHandler instanceof Promise) {
this.getCurrentCline()!.api = await apiHandler
} else {
this.getCurrentCline()!.api = apiHandler
}
}
}

Expand Down Expand Up @@ -2874,7 +2879,7 @@
// Add model ID if available
const currentCline = this.getCurrentCline()
if (currentCline?.api) {
const { id: modelId } = currentCline.api.getModel()

Check failure on line 2882 in src/core/webview/ClineProvider.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'id' does not exist on type '{ id: string; info: ModelInfo; } | Promise<{ id: string; info: ModelInfo; }>'.
if (modelId) {
properties.modelId = modelId
}
Expand Down
6 changes: 4 additions & 2 deletions src/utils/__tests__/enhance-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ describe("enhancePrompt", () => {
const result = await singleCompletionHandler(mockApiConfig, "Test prompt")

expect(result).toBe("Enhanced prompt")
const handler = buildApiHandler(mockApiConfig)
const handlerPromise = buildApiHandler(mockApiConfig)
const handler = await (handlerPromise instanceof Promise ? handlerPromise : Promise.resolve(handlerPromise))
expect((handler as any).completePrompt).toHaveBeenCalledWith(`Test prompt`)
})

Expand All @@ -59,7 +60,8 @@ describe("enhancePrompt", () => {
)

expect(result).toBe("Enhanced prompt")
const handler = buildApiHandler(mockApiConfig)
const handlerPromise = buildApiHandler(mockApiConfig)
const handler = await (handlerPromise instanceof Promise ? handlerPromise : Promise.resolve(handlerPromise))
expect((handler as any).completePrompt).toHaveBeenCalledWith(`${customEnhancePrompt}\n\nTest prompt`)
})

Expand Down
3 changes: 2 additions & 1 deletion src/utils/single-completion-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export async function singleCompletionHandler(apiConfiguration: ApiConfiguration
throw new Error("No valid API configuration provided")
}

const handler = buildApiHandler(apiConfiguration)
const handlerPromise = buildApiHandler(apiConfiguration)
const handler = await (handlerPromise instanceof Promise ? handlerPromise : Promise.resolve(handlerPromise))

// Check if handler supports single completions
if (!("completePrompt" in handler)) {
Expand Down
Loading