From ed4fefd708d64e6d86c408d0edefcff91ac83ee8 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 5 Mar 2025 15:01:13 -0500 Subject: [PATCH 1/5] Progress --- src/api/providers/deepseek.ts | 162 ++++++++++++++++-- src/api/providers/pearai.ts | 99 +++++++++-- src/core/webview/ClineProvider.ts | 2 +- src/shared/api.ts | 50 +++++- .../src/components/settings/ApiOptions.tsx | 9 + 5 files changed, 290 insertions(+), 32 deletions(-) diff --git a/src/api/providers/deepseek.ts b/src/api/providers/deepseek.ts index 267a41bfffc..148bf31310e 100644 --- a/src/api/providers/deepseek.ts +++ b/src/api/providers/deepseek.ts @@ -1,24 +1,158 @@ -import { OpenAiHandler, OpenAiHandlerOptions } from "./openai" -import { ModelInfo } from "../../shared/api" -import { deepSeekModels, deepSeekDefaultModelId } from "../../shared/api" - -export class DeepSeekHandler extends OpenAiHandler { - constructor(options: OpenAiHandlerOptions) { - super({ - ...options, - openAiApiKey: options.deepSeekApiKey ?? "not-provided", - openAiModelId: options.apiModelId ?? deepSeekDefaultModelId, - openAiBaseUrl: options.deepSeekBaseUrl ?? "https://api.deepseek.com/v1", - openAiStreamingEnabled: true, - includeMaxTokens: true, +import { Anthropic } from "@anthropic-ai/sdk" +import { ApiHandlerOptions, ModelInfo, deepSeekModels, deepSeekDefaultModelId } from "../../shared/api" +import { ApiHandler, SingleCompletionHandler } from "../index" +import { convertToR1Format } from "../transform/r1-format" +import { convertToOpenAiMessages } from "../transform/openai-format" +import { ApiStream } from "../transform/stream" + +interface DeepSeekUsage { + prompt_tokens: number + completion_tokens: number + prompt_cache_miss_tokens?: number + prompt_cache_hit_tokens?: number +} + +export class DeepSeekHandler implements ApiHandler, SingleCompletionHandler { + private options: ApiHandlerOptions + + constructor(options: ApiHandlerOptions) { + if (!options.deepSeekApiKey) { + throw new Error("DeepSeek API key is required. Please provide it in the settings.") + } + this.options = options + } + + private get baseUrl(): string { + return this.options.deepSeekBaseUrl ?? "https://api.deepseek.com/v1" + } + + async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const modelInfo = this.getModel().info + const modelId = this.options.apiModelId ?? deepSeekDefaultModelId + const isReasoner = modelId.includes("deepseek-reasoner") + + const systemMessage = { role: "system", content: systemPrompt } + const formattedMessages = isReasoner + ? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) + : [systemMessage, ...convertToOpenAiMessages(messages)] + + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.options.deepSeekApiKey}`, + }, + body: JSON.stringify({ + model: modelId, + messages: formattedMessages, + temperature: 0, + stream: true, + max_tokens: modelInfo.maxTokens, + }), }) + + if (!response.ok) { + throw new Error(`DeepSeek API error: ${response.statusText}`) + } + + if (!response.body) { + throw new Error("No response body received from DeepSeek API") + } + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let buffer = "" + + try { + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split("\n") + buffer = lines.pop() || "" + + for (const line of lines) { + if (line.trim() === "") continue + if (!line.startsWith("data: ")) continue + + const data = line.slice(6) + if (data === "[DONE]") continue + + try { + const chunk = JSON.parse(data) + const delta = chunk.choices[0]?.delta ?? {} + + if (delta.content) { + yield { + type: "text", + text: delta.content, + } + } + + if ("reasoning_content" in delta && delta.reasoning_content) { + yield { + type: "reasoning", + text: delta.reasoning_content, + } + } + + if (chunk.usage) { + const usage = chunk.usage as DeepSeekUsage + let inputTokens = (usage.prompt_tokens || 0) - (usage.prompt_cache_hit_tokens || 0) + yield { + type: "usage", + inputTokens: inputTokens, + outputTokens: usage.completion_tokens || 0, + cacheReadTokens: usage.prompt_cache_hit_tokens || 0, + cacheWriteTokens: usage.prompt_cache_miss_tokens || 0, + } + } + } catch (error) { + console.error("Error parsing DeepSeek response:", error) + } + } + } + } finally { + reader.releaseLock() + } } - override getModel(): { id: string; info: ModelInfo } { + getModel(): { id: string; info: ModelInfo } { const modelId = this.options.apiModelId ?? deepSeekDefaultModelId return { id: modelId, info: deepSeekModels[modelId as keyof typeof deepSeekModels] || deepSeekModels[deepSeekDefaultModelId], } } + + async completePrompt(prompt: string): Promise { + try { + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.options.deepSeekApiKey}`, + }, + body: JSON.stringify({ + model: this.getModel().id, + messages: [{ role: "user", content: prompt }], + temperature: 0, + stream: false, + }), + }) + + if (!response.ok) { + throw new Error(`DeepSeek API error: ${response.statusText}`) + } + + const data = await response.json() + return data.choices[0]?.message?.content || "" + } catch (error) { + if (error instanceof Error) { + throw new Error(`DeepSeek completion error: ${error.message}`) + } + throw error + } + } } diff --git a/src/api/providers/pearai.ts b/src/api/providers/pearai.ts index ccd9e5fd281..61b7fffa411 100644 --- a/src/api/providers/pearai.ts +++ b/src/api/providers/pearai.ts @@ -1,36 +1,100 @@ -import { OpenAiHandler } from "./openai" import * as vscode from "vscode" -import { AnthropicModelId, ApiHandlerOptions, ModelInfo, PEARAI_URL } from "../../shared/api" +import { ApiHandlerOptions, PEARAI_URL, ModelInfo } from "../../shared/api" import { AnthropicHandler } from "./anthropic" +import { DeepSeekHandler } from "./deepseek" + +interface PearAiModelsResponse { + models: { + [key: string]: { + underlyingModel?: string + [key: string]: any + } + } + defaultModelId: string +} + +export class PearAiHandler { + private handler!: AnthropicHandler | DeepSeekHandler -export class PearAiHandler extends AnthropicHandler { constructor(options: ApiHandlerOptions) { if (!options.pearaiApiKey) { vscode.window.showErrorMessage("PearAI API key not found.", "Login to PearAI").then(async (selection) => { if (selection === "Login to PearAI") { const extensionUrl = `${vscode.env.uriScheme}://pearai.pearai/auth` const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(extensionUrl)) - vscode.env.openExternal( await vscode.env.asExternalUri( - vscode.Uri.parse( - `https://trypear.ai/signin?callback=${callbackUri.toString()}`, // Change to localhost if running locally - ), + vscode.Uri.parse(`https://trypear.ai/signin?callback=${callbackUri.toString()}`), ), ) } }) throw new Error("PearAI API key not found. Please login to PearAI.") } - super({ - ...options, - apiKey: options.pearaiApiKey, - anthropicBaseUrl: PEARAI_URL, + + this.initializeHandler(options).catch((error) => { + console.error("Failed to initialize PearAI handler:", error) + throw error }) } - override getModel(): { id: AnthropicModelId; info: ModelInfo } { - const baseModel = super.getModel() + private async initializeHandler(options: ApiHandlerOptions): Promise { + const modelId = options.apiModelId || "" + + if (modelId === "pearai-model") { + try { + const response = await fetch(`${PEARAI_URL}/getPearAIAgentModels`) + if (!response.ok) { + throw new Error(`Failed to fetch models: ${response.statusText}`) + } + const data = (await response.json()) as PearAiModelsResponse + const underlyingModel = data.models[modelId]?.underlyingModel || "claude-3-5-sonnet-20241022" + + if (underlyingModel.startsWith("deepseek")) { + this.handler = new DeepSeekHandler({ + ...options, + deepSeekApiKey: options.pearaiApiKey, + deepSeekBaseUrl: PEARAI_URL, + apiModelId: underlyingModel, + }) + } else { + // Default to Claude + this.handler = new AnthropicHandler({ + ...options, + apiKey: options.pearaiApiKey, + anthropicBaseUrl: PEARAI_URL, + apiModelId: underlyingModel, + }) + } + } catch (error) { + console.error("Error fetching PearAI models:", error) + // Default to Claude if there's an error + this.handler = new AnthropicHandler({ + ...options, + apiKey: options.pearaiApiKey, + anthropicBaseUrl: PEARAI_URL, + apiModelId: "claude-3-5-sonnet-20241022", + }) + } + } else if (modelId.startsWith("claude")) { + this.handler = new AnthropicHandler({ + ...options, + apiKey: options.pearaiApiKey, + anthropicBaseUrl: PEARAI_URL, + }) + } else if (modelId.startsWith("deepseek")) { + this.handler = new DeepSeekHandler({ + ...options, + deepSeekApiKey: options.pearaiApiKey, + deepSeekBaseUrl: PEARAI_URL, + }) + } else { + throw new Error(`Unsupported model: ${modelId}`) + } + } + + getModel(): { id: string; info: ModelInfo } { + const baseModel = this.handler.getModel() return { id: baseModel.id, info: { @@ -42,4 +106,13 @@ export class PearAiHandler extends AnthropicHandler { }, } } + + async *createMessage(systemPrompt: string, messages: any[]): AsyncGenerator { + const generator = this.handler.createMessage(systemPrompt, messages) + yield* generator + } + + async completePrompt(prompt: string): Promise { + return this.handler.completePrompt(prompt) + } } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 4fb7d28ef20..428e91e82b6 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -423,7 +423,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { `style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`, `img-src ${webview.cspSource} data:`, `script-src 'unsafe-eval' https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`, - `connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`, + `connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort} http://localhost:8000`, ] return /*html*/ ` diff --git a/src/shared/api.ts b/src/shared/api.ts index 3173d4a3a8d..516f50da457 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -698,18 +698,22 @@ export const deepSeekModels = { maxTokens: 8192, contextWindow: 64_000, supportsImages: false, - supportsPromptCache: false, + supportsPromptCache: true, inputPrice: 0.014, // $0.014 per million tokens outputPrice: 0.28, // $0.28 per million tokens + cacheWritesPrice: 0.27, // $0.27 per million tokens (cache miss) + cacheReadsPrice: 0.07, // $0.07 per million tokens (cache hit) description: `DeepSeek-V3 achieves a significant breakthrough in inference speed over previous models. It tops the leaderboard among open-source models and rivals the most advanced closed-source models globally.`, }, "deepseek-reasoner": { maxTokens: 8192, contextWindow: 64_000, supportsImages: false, - supportsPromptCache: false, + supportsPromptCache: true, inputPrice: 0.55, // $0.55 per million tokens outputPrice: 2.19, // $2.19 per million tokens + cacheWritesPrice: 0.55, // $0.55 per million tokens (cache miss) + cacheReadsPrice: 0.14, // $0.14 per million tokens (cache hit) description: `DeepSeek-R1 achieves performance comparable to OpenAI-o1 across math, code, and reasoning tasks.`, }, } as const satisfies Record @@ -786,8 +790,46 @@ export const unboundDefaultModelInfo: ModelInfo = { cacheWritesPrice: 3.75, cacheReadsPrice: 0.3, } + +// PearAI Models +export type PearAiModelId = keyof typeof pearAiModels +export let pearAiDefaultModelId: PearAiModelId = "pearai-model" +const defaultPearAiModels = { + "pearai-model": { + ...anthropicModels["claude-3-5-sonnet-20241022"], + }, +} as const satisfies Record +export let pearAiModels: Record = defaultPearAiModels + // CHANGE AS NEEDED FOR TESTING // PROD: -export const PEARAI_URL = "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline" +// export const PEARAI_URL = "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline" // DEV: -// export const PEARAI_URL = "http://localhost:8000/integrations/cline" +export const PEARAI_URL = "http://localhost:8000/integrations/cline" + +// Dynamically fetch models from PearAI server +export const getPearAiModels = async () => { + try { + const res = await fetch(`${PEARAI_URL}/getPearAIAgentModels`) + if (!res.ok) throw new Error("Failed to fetch models") + const config = await res.json() + if (config.models && Object.keys(config.models).length > 0) { + pearAiModels = config.models + pearAiDefaultModelId = config.defaultModelId || "pearai-model" + console.dir("IM HER1111") + console.dir(pearAiModels) + return pearAiModels + } else { + console.dir("IM HER2222") + pearAiModels = defaultPearAiModels + return defaultPearAiModels + } + } catch (error) { + console.error("Error fetching PearAI models:", error) + pearAiModels = defaultPearAiModels + return defaultPearAiModels + } +} + +// Initialize models when module loads +getPearAiModels() diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index e79de3440d5..0c7616cfc00 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -38,6 +38,9 @@ import { unboundDefaultModelInfo, requestyDefaultModelId, requestyDefaultModelInfo, + pearAiModels, + pearAiDefaultModelId, + PearAiModelId, } from "../../../../src/shared/api" import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" @@ -58,8 +61,14 @@ const modelsByProvider: Record> = { "openai-native": openAiNativeModels, deepseek: deepSeekModels, mistral: mistralModels, + pearai: pearAiModels, } +console.dir("IM HERE888") +console.dir(pearAiModels) +console.dir("IM HERE9999") +console.dir(anthropicModels) + interface ApiOptionsProps { uriScheme: string | undefined apiConfiguration: ApiConfiguration From 382da050490a655417d8de0c3ff303d38f0a70c9 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 5 Mar 2025 15:57:03 -0500 Subject: [PATCH 2/5] Added working --- src/core/webview/ClineProvider.ts | 12 --- src/shared/api.ts | 31 ++++-- .../src/components/settings/ApiOptions.tsx | 99 ++++++++++++------- 3 files changed, 86 insertions(+), 56 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 428e91e82b6..5fece10bc56 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1691,9 +1691,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { requestyModelInfo, modelTemperature, modelMaxTokens, - pearaiBaseUrl, - pearaiModelId, - pearaiModelInfo, } = apiConfiguration await Promise.all([ this.updateGlobalState("apiProvider", apiProvider), @@ -1743,9 +1740,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.updateGlobalState("requestyModelInfo", requestyModelInfo), this.updateGlobalState("modelTemperature", modelTemperature), this.updateGlobalState("modelMaxTokens", modelMaxTokens), - await this.updateGlobalState("pearaiBaseUrl", PEARAI_URL), - await this.updateGlobalState("pearaiModelId", pearaiModelId), - await this.updateGlobalState("pearaiModelInfo", pearaiModelInfo), ]) if (this.cline) { this.cline.api = buildApiHandler(apiConfiguration) @@ -2190,8 +2184,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { pearaiApiKey, pearaiRefreshKey, pearaiBaseUrl, - pearaiModelId, - pearaiModelInfo, mistralCodestralUrl, azureApiVersion, openAiStreamingEnabled, @@ -2279,8 +2271,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getSecret("pearai-token") as Promise, this.getSecret("pearai-refresh") as Promise, this.getGlobalState("pearaiBaseUrl") as Promise, - this.getGlobalState("pearaiModelId") as Promise, - this.getGlobalState("pearaiModelInfo") as Promise, this.getGlobalState("mistralCodestralUrl") as Promise, this.getGlobalState("azureApiVersion") as Promise, this.getGlobalState("openAiStreamingEnabled") as Promise, @@ -2384,8 +2374,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { mistralApiKey, pearaiApiKey, pearaiBaseUrl, - pearaiModelId, - pearaiModelInfo, mistralCodestralUrl, azureApiVersion, openAiStreamingEnabled, diff --git a/src/shared/api.ts b/src/shared/api.ts index 516f50da457..5455e8c64e4 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -72,8 +72,6 @@ export interface ApiHandlerOptions { modelMaxTokens?: number pearaiApiKey?: string pearaiBaseUrl?: string - pearaiModelId?: string - pearaiModelInfo?: ModelInfo } export type ApiConfiguration = ApiHandlerOptions & { @@ -807,8 +805,11 @@ export let pearAiModels: Record = defaultPearAiModels // DEV: export const PEARAI_URL = "http://localhost:8000/integrations/cline" -// Dynamically fetch models from PearAI server -export const getPearAiModels = async () => { +// Promise to track initialization status +export let modelsInitialized: Promise> + +// Immediately invoked function to initialize models +modelsInitialized = (async () => { try { const res = await fetch(`${PEARAI_URL}/getPearAIAgentModels`) if (!res.ok) throw new Error("Failed to fetch models") @@ -816,11 +817,17 @@ export const getPearAiModels = async () => { if (config.models && Object.keys(config.models).length > 0) { pearAiModels = config.models pearAiDefaultModelId = config.defaultModelId || "pearai-model" - console.dir("IM HER1111") + console.log("Models successfully loaded from server") console.dir(pearAiModels) + + window.dispatchEvent( + new CustomEvent("pearAiModelsUpdated", { + detail: { models: pearAiModels, defaultModelId: pearAiDefaultModelId }, + }), + ) return pearAiModels } else { - console.dir("IM HER2222") + console.log("Using default models (no models returned from server)") pearAiModels = defaultPearAiModels return defaultPearAiModels } @@ -829,7 +836,15 @@ export const getPearAiModels = async () => { pearAiModels = defaultPearAiModels return defaultPearAiModels } +})() + +// Helper function to ensure models are loaded before operations that depend on them +export const ensureModelsLoaded = async () => { + return await modelsInitialized } -// Initialize models when module loads -getPearAiModels() +// This will log after models are initialized +modelsInitialized.then(() => { + console.log("Models initialization complete") + console.dir(pearAiModels) +}) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 0c7616cfc00..93a9460c397 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -53,22 +53,6 @@ import { validateApiConfiguration, validateModelId } from "@/utils/validate" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" -const modelsByProvider: Record> = { - anthropic: anthropicModels, - bedrock: bedrockModels, - vertex: vertexModels, - gemini: geminiModels, - "openai-native": openAiNativeModels, - deepseek: deepSeekModels, - mistral: mistralModels, - pearai: pearAiModels, -} - -console.dir("IM HERE888") -console.dir(pearAiModels) -console.dir("IM HERE9999") -console.dir(anthropicModels) - interface ApiOptionsProps { uriScheme: string | undefined apiConfiguration: ApiConfiguration @@ -106,6 +90,8 @@ const ApiOptions = ({ [requestyDefaultModelId]: requestyDefaultModelInfo, }) + const [pearAiModelsState, setPearAiModelsState] = useState>(pearAiModels) + const [openAiModels, setOpenAiModels] = useState | null>(null) const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) @@ -130,8 +116,8 @@ const ApiOptions = ({ ) const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo( - () => normalizeApiConfiguration(apiConfiguration), - [apiConfiguration], + () => normalizeApiConfiguration(apiConfiguration, pearAiModelsState), + [apiConfiguration, pearAiModelsState], ) // Debounced refresh model updates, only executed 250ms after the user @@ -228,11 +214,63 @@ const ApiOptions = ({ setVsCodeLmModels(newModels) } break + case "state": + if (message.state?.apiConfiguration?.pearaiApiKey) { + setPearAiModelsState(pearAiModels) + } + break } }, []) + // Update PearAI models when API key changes + useEffect(() => { + if (apiConfiguration?.pearaiApiKey) { + setPearAiModelsState(pearAiModels) + } + }, [apiConfiguration?.pearaiApiKey]) + useEvent("message", onMessage) + type ApiProvider = + | "anthropic" + | "bedrock" + | "vertex" + | "gemini" + | "openai-native" + | "deepseek" + | "mistral" + | "pearai" + | "openrouter" + | "glama" + | "unbound" + | "requesty" + | "openai" + | "ollama" + | "lmstudio" + | "vscode-lm" + + const modelsByProvider = useMemo>>( + () => ({ + anthropic: anthropicModels, + bedrock: bedrockModels, + vertex: vertexModels, + gemini: geminiModels, + "openai-native": openAiNativeModels, + deepseek: deepSeekModels, + mistral: mistralModels, + pearai: pearAiModelsState, + openrouter: openRouterModels, + glama: glamaModels, + unbound: unboundModels, + requesty: requestyModels, + openai: openAiModels || {}, + ollama: {}, + lmstudio: {}, + "vscode-lm": {}, + }), + [pearAiModelsState, openRouterModels, glamaModels, unboundModels, requestyModels, openAiModels], + ) + const selectedProviderModelOptions: DropdownOption[] = useMemo( () => modelsByProvider[selectedProvider] @@ -244,7 +282,7 @@ const ApiOptions = ({ })), ] : [], - [selectedProvider], + [selectedProvider, modelsByProvider], ) return ( @@ -1465,7 +1503,10 @@ export function getOpenRouterAuthUrl(uriScheme?: string) { return `https://openrouter.ai/auth?callback_url=${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/openrouter` } -export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) { +export function normalizeApiConfiguration( + apiConfiguration?: ApiConfiguration, + pearAiModelsState?: Record, +) { const provider = apiConfiguration?.apiProvider || "anthropic" const modelId = apiConfiguration?.apiModelId @@ -1552,22 +1593,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) { supportsImages: false, // VSCode LM API currently doesn't support images. }, } - case "pearai": { - // Get the base Anthropic model info - const baseModelInfo = anthropicModels[anthropicDefaultModelId] - const pearaiModelInfo: ModelInfo = { - ...baseModelInfo, - inputPrice: baseModelInfo.inputPrice, - outputPrice: baseModelInfo.outputPrice, - cacheWritesPrice: baseModelInfo.cacheWritesPrice ? baseModelInfo.cacheWritesPrice : undefined, - cacheReadsPrice: baseModelInfo.cacheWritesPrice ? baseModelInfo.cacheReadsPrice : undefined, - } - return { - selectedProvider: provider, - selectedModelId: apiConfiguration?.pearaiModelId || "pearai_model", - selectedModelInfo: pearaiModelInfo, - } - } + case "pearai": + return getProviderData(pearAiModelsState || pearAiModels, pearAiDefaultModelId) default: return getProviderData(anthropicModels, anthropicDefaultModelId) } From 650fdb27aad2099796b1c61ae6c114aa98eac816 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 5 Mar 2025 17:00:47 -0500 Subject: [PATCH 3/5] Added polish --- src/shared/api.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/shared/api.ts b/src/shared/api.ts index 5455e8c64e4..c98bfcab6cd 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -801,9 +801,9 @@ export let pearAiModels: Record = defaultPearAiModels // CHANGE AS NEEDED FOR TESTING // PROD: -// export const PEARAI_URL = "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline" +export const PEARAI_URL = "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline" // DEV: -export const PEARAI_URL = "http://localhost:8000/integrations/cline" +// export const PEARAI_URL = "http://localhost:8000/integrations/cline" // Promise to track initialization status export let modelsInitialized: Promise> @@ -818,8 +818,6 @@ modelsInitialized = (async () => { pearAiModels = config.models pearAiDefaultModelId = config.defaultModelId || "pearai-model" console.log("Models successfully loaded from server") - console.dir(pearAiModels) - window.dispatchEvent( new CustomEvent("pearAiModelsUpdated", { detail: { models: pearAiModels, defaultModelId: pearAiDefaultModelId }, @@ -846,5 +844,4 @@ export const ensureModelsLoaded = async () => { // This will log after models are initialized modelsInitialized.then(() => { console.log("Models initialization complete") - console.dir(pearAiModels) }) From 4b28cd9b22a00609a6a1ce828ee2bb0d7b261ccc Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 5 Mar 2025 17:31:56 -0500 Subject: [PATCH 4/5] Pause --- src/shared/api.ts | 4 ++-- webview-ui/src/App.tsx | 4 +--- webview-ui/src/components/chat/ChatView.tsx | 7 +++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/shared/api.ts b/src/shared/api.ts index c98bfcab6cd..8f5b78254c6 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -801,9 +801,9 @@ export let pearAiModels: Record = defaultPearAiModels // CHANGE AS NEEDED FOR TESTING // PROD: -export const PEARAI_URL = "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline" +// export const PEARAI_URL = "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline" // DEV: -// export const PEARAI_URL = "http://localhost:8000/integrations/cline" +export const PEARAI_URL = "http://localhost:8000/integrations/cline" // Promise to track initialization status export let modelsInitialized: Promise> diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 9a0b01f4c55..740ceb11acc 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -67,9 +67,7 @@ const App = () => { // Do not conditionally load ChatView, it's expensive and there's state we // don't want to lose (user input, disableInput, askResponse promise, etc.) - return showWelcome ? ( - - ) : ( + return ( <> {tab === "settings" && setTab("chat")} />} {tab === "history" && switchTab("chat")} />} diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 79afd743191..9afb4b2524d 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1011,7 +1011,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie flexDirection: "column-reverse", paddingBottom: "10px", }}> - {showAnnouncement && } {messages.length === 0 && ( <>
@@ -1037,17 +1036,17 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
)} - {/* + {/* // Flex layout explanation: // 1. Content div above uses flex: "1 1 0" to: - // - Grow to fill available space (flex-grow: 1) + // - Grow to fill available space (flex-grow: 1) // - Shrink when AutoApproveMenu needs space (flex-shrink: 1) // - Start from zero size (flex-basis: 0) to ensure proper distribution // minHeight: 0 allows it to shrink below its content height // // 2. AutoApproveMenu uses flex: "0 1 auto" to: // - Not grow beyond its content (flex-grow: 0) - // - Shrink when viewport is small (flex-shrink: 1) + // - Shrink when viewport is small (flex-shrink: 1) // - Use its content size as basis (flex-basis: auto) // This ensures it takes its natural height when there's space // but becomes scrollable when the viewport is too small From 9cde72da94f1b0cedbecd8849ffc7401153ee5c7 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 5 Mar 2025 18:07:01 -0500 Subject: [PATCH 5/5] Progress --- src/api/providers/pearai.ts | 1 - src/core/webview/ClineProvider.ts | 14 +++++++++++++- src/shared/api.ts | 8 +++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/api/providers/pearai.ts b/src/api/providers/pearai.ts index 61b7fffa411..95581244a69 100644 --- a/src/api/providers/pearai.ts +++ b/src/api/providers/pearai.ts @@ -49,7 +49,6 @@ export class PearAiHandler { } const data = (await response.json()) as PearAiModelsResponse const underlyingModel = data.models[modelId]?.underlyingModel || "claude-3-5-sonnet-20241022" - if (underlyingModel.startsWith("deepseek")) { this.handler = new DeepSeekHandler({ ...options, diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 5fece10bc56..4fb7d28ef20 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -423,7 +423,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { `style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`, `img-src ${webview.cspSource} data:`, `script-src 'unsafe-eval' https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`, - `connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort} http://localhost:8000`, + `connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`, ] return /*html*/ ` @@ -1691,6 +1691,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { requestyModelInfo, modelTemperature, modelMaxTokens, + pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, } = apiConfiguration await Promise.all([ this.updateGlobalState("apiProvider", apiProvider), @@ -1740,6 +1743,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.updateGlobalState("requestyModelInfo", requestyModelInfo), this.updateGlobalState("modelTemperature", modelTemperature), this.updateGlobalState("modelMaxTokens", modelMaxTokens), + await this.updateGlobalState("pearaiBaseUrl", PEARAI_URL), + await this.updateGlobalState("pearaiModelId", pearaiModelId), + await this.updateGlobalState("pearaiModelInfo", pearaiModelInfo), ]) if (this.cline) { this.cline.api = buildApiHandler(apiConfiguration) @@ -2184,6 +2190,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { pearaiApiKey, pearaiRefreshKey, pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, mistralCodestralUrl, azureApiVersion, openAiStreamingEnabled, @@ -2271,6 +2279,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getSecret("pearai-token") as Promise, this.getSecret("pearai-refresh") as Promise, this.getGlobalState("pearaiBaseUrl") as Promise, + this.getGlobalState("pearaiModelId") as Promise, + this.getGlobalState("pearaiModelInfo") as Promise, this.getGlobalState("mistralCodestralUrl") as Promise, this.getGlobalState("azureApiVersion") as Promise, this.getGlobalState("openAiStreamingEnabled") as Promise, @@ -2374,6 +2384,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { mistralApiKey, pearaiApiKey, pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, mistralCodestralUrl, azureApiVersion, openAiStreamingEnabled, diff --git a/src/shared/api.ts b/src/shared/api.ts index 8f5b78254c6..5f6feea186e 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -72,6 +72,8 @@ export interface ApiHandlerOptions { modelMaxTokens?: number pearaiApiKey?: string pearaiBaseUrl?: string + pearaiModelId?: string + pearaiModelInfo?: ModelInfo } export type ApiConfiguration = ApiHandlerOptions & { @@ -818,11 +820,6 @@ modelsInitialized = (async () => { pearAiModels = config.models pearAiDefaultModelId = config.defaultModelId || "pearai-model" console.log("Models successfully loaded from server") - window.dispatchEvent( - new CustomEvent("pearAiModelsUpdated", { - detail: { models: pearAiModels, defaultModelId: pearAiDefaultModelId }, - }), - ) return pearAiModels } else { console.log("Using default models (no models returned from server)") @@ -844,4 +841,5 @@ export const ensureModelsLoaded = async () => { // This will log after models are initialized modelsInitialized.then(() => { console.log("Models initialization complete") + console.dir(pearAiModels) })