From 64b96cbb1fa029ca0f37269ee55af88c0e895367 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Mon, 7 Apr 2025 18:11:35 -0400 Subject: [PATCH 1/6] Progress --- src/api/index.ts | 2 +- src/api/providers/{ => pearai}/pearai.ts | 27 +- src/api/providers/pearai/pearaiGeneric.ts | 323 ++++++++++++++++++ src/services/mcp/McpHub.ts | 2 +- src/shared/api.ts | 90 ++++- src/shared/pearaiApi.ts | 142 ++++++++ webview-ui/src/components/chat/ChatView.tsx | 1 - .../src/components/settings/ApiOptions.tsx | 5 +- webview-ui/src/hooks/usePearAiModels.ts | 3 +- 9 files changed, 572 insertions(+), 23 deletions(-) rename src/api/providers/{ => pearai}/pearai.ts (86%) create mode 100644 src/api/providers/pearai/pearaiGeneric.ts create mode 100644 src/shared/pearaiApi.ts diff --git a/src/api/index.ts b/src/api/index.ts index 93284ace184..b5777439843 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -19,7 +19,7 @@ import { VsCodeLmHandler } from "./providers/vscode-lm" import { ApiStream } from "./transform/stream" import { UnboundHandler } from "./providers/unbound" import { RequestyHandler } from "./providers/requesty" -import { PearAiHandler } from "./providers/pearai" +import { PearAiHandler } from "./providers/pearai/pearai" import { HumanRelayHandler } from "./providers/human-relay" import { FakeAIHandler } from "./providers/fake-ai" diff --git a/src/api/providers/pearai.ts b/src/api/providers/pearai/pearai.ts similarity index 86% rename from src/api/providers/pearai.ts rename to src/api/providers/pearai/pearai.ts index 6c2898d165a..37d978ef78d 100644 --- a/src/api/providers/pearai.ts +++ b/src/api/providers/pearai/pearai.ts @@ -1,13 +1,15 @@ import * as vscode from "vscode" -import { ApiHandlerOptions, PEARAI_URL, ModelInfo } from "../../shared/api" -import { AnthropicHandler } from "./anthropic" -import { DeepSeekHandler } from "./deepseek" +import { ApiHandlerOptions, ModelInfo } from "../../../shared/api" +import { AnthropicHandler } from "../anthropic" +import { DeepSeekHandler } from "../deepseek" import Anthropic from "@anthropic-ai/sdk" -import { BaseProvider } from "./base-provider" -import { SingleCompletionHandler } from "../" -import { OpenRouterHandler } from "./openrouter" -import { GeminiHandler } from "./gemini" -import { OpenAiHandler } from "./openai" +import { BaseProvider } from "../base-provider" +import { SingleCompletionHandler } from "../.." +import { OpenRouterHandler } from "../openrouter" +import { GeminiHandler } from "../gemini" +import { OpenAiHandler } from "../openai" +import { PearAIGenericHandler } from "./pearaiGeneric" +import { PEARAI_URL } from "../../../shared/pearaiApi" interface PearAiModelsResponse { models: { @@ -20,7 +22,7 @@ interface PearAiModelsResponse { } export class PearAiHandler extends BaseProvider implements SingleCompletionHandler { - private handler!: AnthropicHandler | OpenAiHandler + private handler!: AnthropicHandler | PearAIGenericHandler constructor(options: ApiHandlerOptions) { super() @@ -41,7 +43,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl vscode.commands.executeCommand("pearai.checkPearAITokens", undefined) } - this.handler = new OpenAiHandler({ + this.handler = new PearAIGenericHandler({ ...options, openAiBaseUrl: PEARAI_URL, openAiApiKey: options.pearaiApiKey, @@ -74,7 +76,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl apiModelId: underlyingModel, }) } else { - this.handler = new OpenAiHandler({ + this.handler = new PearAIGenericHandler({ ...options, openAiBaseUrl: PEARAI_URL, openAiApiKey: options.pearaiApiKey, @@ -98,7 +100,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl anthropicBaseUrl: PEARAI_URL, }) } else { - this.handler = new OpenAiHandler({ + this.handler = new PearAIGenericHandler({ ...options, openAiBaseUrl: PEARAI_URL, openAiApiKey: options.pearaiApiKey, @@ -108,7 +110,6 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl } getModel(): { id: string; info: ModelInfo } { - console.dir(this.handler) const baseModel = this.handler.getModel() return { id: baseModel.id, diff --git a/src/api/providers/pearai/pearaiGeneric.ts b/src/api/providers/pearai/pearaiGeneric.ts new file mode 100644 index 00000000000..7492e1c5a98 --- /dev/null +++ b/src/api/providers/pearai/pearaiGeneric.ts @@ -0,0 +1,323 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI, { AzureOpenAI } from "openai" +import axios from "axios" + +import { + ApiHandlerOptions, + azureOpenAiDefaultApiVersion, + ModelInfo, + openAiModelInfoSaneDefaults, +} from "../../../shared/api" +import { SingleCompletionHandler } from "../../index" +import { convertToOpenAiMessages } from "../../transform/openai-format" +import { convertToR1Format } from "../../transform/r1-format" +import { convertToSimpleMessages } from "../../transform/simple-format" +import { ApiStream, ApiStreamUsageChunk } from "../../transform/stream" +import { BaseProvider } from "../base-provider" +import { XmlMatcher } from "../../../utils/xml-matcher" +import { allModels, pearAiDefaultModelId, pearAiDefaultModelInfo } from "../../../shared/pearaiApi" + +const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 + +export const defaultHeaders = { + "HTTP-Referer": "https://trypear.ai", + "X-Title": "PearAI", +} + +export interface OpenAiHandlerOptions extends ApiHandlerOptions {} + +export class PearAIGenericHandler extends BaseProvider implements SingleCompletionHandler { + protected options: OpenAiHandlerOptions + private client: OpenAI + + constructor(options: OpenAiHandlerOptions) { + super() + this.options = options + + const baseURL = this.options.openAiBaseUrl ?? "https://api.openai.com/v1" + const apiKey = this.options.openAiApiKey ?? "not-provided" + let urlHost: string + + try { + urlHost = new URL(this.options.openAiBaseUrl ?? "").host + } catch (error) { + // Likely an invalid `openAiBaseUrl`; we're still working on + // proper settings validation. + urlHost = "" + } + + if (urlHost === "azure.com" || urlHost.endsWith(".azure.com") || options.openAiUseAzure) { + // Azure API shape slightly differs from the core API shape: + // https://github.com/openai/openai-node?tab=readme-ov-file#microsoft-azure-openai + this.client = new AzureOpenAI({ + baseURL, + apiKey, + apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion, + defaultHeaders, + }) + } else { + this.client = new OpenAI({ baseURL, apiKey, defaultHeaders }) + } + } + + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const modelInfo = this.getModel().info + const modelUrl = this.options.openAiBaseUrl ?? "" + const modelId = this.options.openAiModelId ?? "" + + const deepseekReasoner = modelId.includes("deepseek-reasoner") + const ark = modelUrl.includes(".volces.com") + + if (modelId.startsWith("o3-mini")) { + yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages) + return + } + + if (this.options.openAiStreamingEnabled ?? true) { + let systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = { + role: "system", + content: systemPrompt, + } + + let convertedMessages + if (deepseekReasoner) { + convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) + } else if (ark) { + convertedMessages = [systemMessage, ...convertToSimpleMessages(messages)] + } else { + if (modelInfo.supportsPromptCache) { + systemMessage = { + role: "system", + content: [ + { + type: "text", + text: systemPrompt, + // @ts-ignore-next-line + cache_control: { type: "ephemeral" }, + }, + ], + } + } + convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)] + if (modelInfo.supportsPromptCache) { + // Note: the following logic is copied from openrouter: + // Add cache_control to the last two user messages + // (note: this works because we only ever add one user message at a time, but if we added multiple we'd need to mark the user message before the last assistant message) + const lastTwoUserMessages = convertedMessages.filter((msg) => msg.role === "user").slice(-2) + lastTwoUserMessages.forEach((msg) => { + if (typeof msg.content === "string") { + msg.content = [{ type: "text", text: msg.content }] + } + if (Array.isArray(msg.content)) { + // NOTE: this is fine since env details will always be added at the end. but if it weren't there, and the user added a image_url type message, it would pop a text part before it and then move it after to the end. + let lastTextPart = msg.content.filter((part) => part.type === "text").pop() + + if (!lastTextPart) { + lastTextPart = { type: "text", text: "..." } + msg.content.push(lastTextPart) + } + // @ts-ignore-next-line + lastTextPart["cache_control"] = { type: "ephemeral" } + } + }) + } + } + + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { + model: modelId, + temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), + messages: convertedMessages, + stream: true as const, + stream_options: { include_usage: true }, + } + if (this.options.includeMaxTokens) { + requestOptions.max_tokens = modelInfo.maxTokens + } + + const stream = await this.client.chat.completions.create(requestOptions) + + const matcher = new XmlMatcher( + "think", + (chunk) => + ({ + type: chunk.matched ? "reasoning" : "text", + text: chunk.data, + }) as const, + ) + + let lastUsage + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta ?? {} + + if (delta.content) { + for (const chunk of matcher.update(delta.content)) { + yield chunk + } + } + + if ("reasoning_content" in delta && delta.reasoning_content) { + yield { + type: "reasoning", + text: (delta.reasoning_content as string | undefined) || "", + } + } + if (chunk.usage) { + lastUsage = chunk.usage + } + } + for (const chunk of matcher.final()) { + yield chunk + } + + if (lastUsage) { + yield this.processUsageMetrics(lastUsage, modelInfo) + } + } else { + // o1 for instance doesnt support streaming, non-1 temp, or system prompt + const systemMessage: OpenAI.Chat.ChatCompletionUserMessageParam = { + role: "user", + content: systemPrompt, + } + + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { + model: modelId, + messages: deepseekReasoner + ? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) + : [systemMessage, ...convertToOpenAiMessages(messages)], + } + + const response = await this.client.chat.completions.create(requestOptions) + + yield { + type: "text", + text: response.choices[0]?.message.content || "", + } + yield this.processUsageMetrics(response.usage, modelInfo) + } + } + + protected processUsageMetrics(usage: any, modelInfo?: ModelInfo): ApiStreamUsageChunk { + console.dir("USAGE") + console.dir(usage?.prompt_tokens_details) + return { + type: "usage", + inputTokens: usage?.prompt_tokens || 0, + outputTokens: usage?.completion_tokens || 0, + cacheWriteTokens: usage?.prompt_tokens_details?.cache_miss_tokens, + cacheReadTokens: usage?.prompt_tokens_details?.cached_tokens, + } + } + + override getModel(): { id: string; info: ModelInfo } { + const modelId = this.options.pearaiModelId ?? pearAiDefaultModelId + return { + id: modelId, + info: allModels[modelId] ?? pearAiDefaultModelInfo, + } + } + + async completePrompt(prompt: string): Promise { + try { + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { + model: this.getModel().id, + messages: [{ role: "user", content: prompt }], + } + + const response = await this.client.chat.completions.create(requestOptions) + return response.choices[0]?.message.content || "" + } catch (error) { + if (error instanceof Error) { + throw new Error(`OpenAI completion error: ${error.message}`) + } + throw error + } + } + + private async *handleO3FamilyMessage( + modelId: string, + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + ): ApiStream { + if (this.options.openAiStreamingEnabled ?? true) { + const stream = await this.client.chat.completions.create({ + model: "o3-mini", + messages: [ + { + role: "developer", + content: `Formatting re-enabled\n${systemPrompt}`, + }, + ...convertToOpenAiMessages(messages), + ], + stream: true, + stream_options: { include_usage: true }, + reasoning_effort: this.getModel().info.reasoningEffort, + }) + + yield* this.handleStreamResponse(stream) + } else { + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { + model: modelId, + messages: [ + { + role: "developer", + content: `Formatting re-enabled\n${systemPrompt}`, + }, + ...convertToOpenAiMessages(messages), + ], + } + + const response = await this.client.chat.completions.create(requestOptions) + + yield { + type: "text", + text: response.choices[0]?.message.content || "", + } + yield this.processUsageMetrics(response.usage) + } + } + + private async *handleStreamResponse(stream: AsyncIterable): ApiStream { + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta + if (delta?.content) { + yield { + type: "text", + text: delta.content, + } + } + + if (chunk.usage) { + yield { + type: "usage", + inputTokens: chunk.usage.prompt_tokens || 0, + outputTokens: chunk.usage.completion_tokens || 0, + } + } + } + } +} + +export async function getOpenAiModels(baseUrl?: string, apiKey?: string) { + try { + if (!baseUrl) { + return [] + } + + if (!URL.canParse(baseUrl)) { + return [] + } + + const config: Record = {} + + if (apiKey) { + config["headers"] = { Authorization: `Bearer ${apiKey}` } + } + + const response = await axios.get(`${baseUrl}/models`, config) + const modelsArray = response.data?.data?.map((model: any) => model.id) || [] + return [...new Set(modelsArray)] + } catch (error) { + return [] + } +} diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 2919d69ff6c..ad481a16d3d 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -30,7 +30,7 @@ import { } from "../../shared/mcp" import { fileExistsAtPath } from "../../utils/fs" import { arePathsEqual } from "../../utils/path" -import { PEARAI_URL } from "../../shared/api" +import { PEARAI_URL } from "../../shared/pearaiApi" export type McpConnection = { server: McpServer diff --git a/src/shared/api.ts b/src/shared/api.ts index 362ea95a362..75dcced3a8a 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -1043,9 +1043,9 @@ export const unboundDefaultModelInfo: ModelInfo = { } // 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" // PearAI export type PearAiModelId = keyof typeof pearAiModels @@ -1077,3 +1077,89 @@ export const pearAiModels = { "PearAI Model automatically routes you to the most best / most suitable model on the market. Recommended for most users.", }, } as const satisfies Record + +export const allModels = { + // Anthropic models + ...Object.entries(anthropicModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`anthropic/${key}`]: value, + }), + {}, + ), + + // Bedrock models + ...Object.entries(bedrockModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`bedrock/${key}`]: value, + }), + {}, + ), + + // Glama models (single default model) + [`glama/${glamaDefaultModelId}`]: glamaDefaultModelInfo, + + // Requesty models (single default model) + [`requesty/${requestyDefaultModelId}`]: requestyDefaultModelInfo, + + // OpenRouter models (single default model) + [`openrouter/${openRouterDefaultModelId}`]: openRouterDefaultModelInfo, + + // Vertex models + ...Object.entries(vertexModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`vertex/${key}`]: value, + }), + {}, + ), + + // Gemini models + ...Object.entries(geminiModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`gemini/${key}`]: value, + }), + {}, + ), + + // OpenAI Native models + ...Object.entries(openAiNativeModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`openai-native/${key}`]: value, + }), + {}, + ), + + // DeepSeek models + ...Object.entries(deepSeekModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`deepseek/${key}`]: value, + }), + {}, + ), + + // Mistral models + ...Object.entries(mistralModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`mistral/${key}`]: value, + }), + {}, + ), + + // Unbound models (single default model) + [`unbound/${unboundDefaultModelId}`]: unboundDefaultModelInfo, + + // PearAI models + ...Object.entries(pearAiModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`pearai/${key}`]: value, + }), + {}, + ), +} as const satisfies Record diff --git a/src/shared/pearaiApi.ts b/src/shared/pearaiApi.ts new file mode 100644 index 00000000000..0a8d161608a --- /dev/null +++ b/src/shared/pearaiApi.ts @@ -0,0 +1,142 @@ +// CHANGE AS NEEDED FOR TESTING +// PROD: +// export const PEARAI_URL = "https://stingray-app-gb2an.ondigitalocean.app/pearai-server-api2/integrations/cline" + +import { + anthropicModels, + bedrockModels, + deepSeekModels, + geminiModels, + glamaDefaultModelId, + glamaDefaultModelInfo, + mistralModels, + ModelInfo, + openAiNativeModels, + openRouterDefaultModelId, + openRouterDefaultModelInfo, + requestyDefaultModelId, + requestyDefaultModelInfo, + unboundDefaultModelId, + unboundDefaultModelInfo, + vertexModels, +} from "./api" + +// DEV: +export const PEARAI_URL = "http://localhost:8000/integrations/cline" + +// PearAI +export type PearAiModelId = keyof typeof pearAiModels +export const pearAiDefaultModelId: PearAiModelId = "pearai-model" +export const pearAiDefaultModelInfo: ModelInfo = { + maxTokens: 8192, + contextWindow: 200_000, + supportsImages: true, + supportsPromptCache: true, + inputPrice: 3.0, + outputPrice: 15.0, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + description: + "PearAI Model automatically routes you to the most best / most suitable model on the market. Recommended for most users.", +} + +export const pearAiModels = { + "pearai-model": { + maxTokens: 8192, + contextWindow: 200_000, + supportsImages: true, + supportsPromptCache: true, + inputPrice: 3.0, + outputPrice: 15.0, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + description: + "PearAI Model automatically routes you to the most best / most suitable model on the market. Recommended for most users.", + }, +} as const satisfies Record + +export const allModels: { [key: string]: ModelInfo } = { + // Anthropic models + ...Object.entries(anthropicModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`anthropic/${key}`]: value, + }), + {}, + ), + + // Bedrock models + ...Object.entries(bedrockModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`bedrock/${key}`]: value, + }), + {}, + ), + + // Glama models (single default model) + [`glama/${glamaDefaultModelId}`]: glamaDefaultModelInfo, + + // Requesty models (single default model) + [`requesty/${requestyDefaultModelId}`]: requestyDefaultModelInfo, + + // OpenRouter models (single default model) + [`openrouter/${openRouterDefaultModelId}`]: openRouterDefaultModelInfo, + + // Vertex models + ...Object.entries(vertexModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`vertex/${key}`]: value, + }), + {}, + ), + + // Gemini models + ...Object.entries(geminiModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`gemini/${key}`]: value, + }), + {}, + ), + + // OpenAI Native models + ...Object.entries(openAiNativeModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`openai-native/${key}`]: value, + }), + {}, + ), + + // DeepSeek models + ...Object.entries(deepSeekModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`deepseek/${key}`]: value, + }), + {}, + ), + + // Mistral models + ...Object.entries(mistralModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`mistral/${key}`]: value, + }), + {}, + ), + + // Unbound models (single default model) + [`unbound/${unboundDefaultModelId}`]: unboundDefaultModelInfo, + + // PearAI models + ...Object.entries(pearAiModels).reduce( + (acc, [key, value]) => ({ + ...acc, + [`{key}`]: value, + }), + {}, + ), +} as const satisfies Record diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index aa050221a72..f20ac5c4cff 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -14,7 +14,6 @@ import { import { McpServer, McpTool } from "../../../../src/shared/mcp" import { findLast } from "../../../../src/shared/array" import { combineApiRequests } from "../../../../src/shared/combineApiRequests" -import { ModelInfo, pearAiDefaultModelId, pearAiDefaultModelInfo, PEARAI_URL } from "../../../../src/shared/api" import { combineCommandSequences } from "../../../../src/shared/combineCommandSequences" import { getApiMetrics } from "../../../../src/shared/getApiMetrics" import { useExtensionState } from "../../context/ExtensionStateContext" diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index a7d9f71c03b..cea35806262 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -37,10 +37,6 @@ import { unboundDefaultModelInfo, requestyDefaultModelId, requestyDefaultModelInfo, - pearAiModels, - pearAiDefaultModelId, - pearAiDefaultModelInfo, - PEARAI_URL, ApiProvider, } from "../../../../src/shared/api" import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" @@ -60,6 +56,7 @@ import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@ import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" import { usePearAiModels } from "../../hooks/usePearAiModels" +import { pearAiDefaultModelId, pearAiDefaultModelInfo } from "../../../../src/shared/pearaiApi" interface ApiOptionsProps { uriScheme: string | undefined diff --git a/webview-ui/src/hooks/usePearAiModels.ts b/webview-ui/src/hooks/usePearAiModels.ts index 7974f693597..f9e6fa8f665 100644 --- a/webview-ui/src/hooks/usePearAiModels.ts +++ b/webview-ui/src/hooks/usePearAiModels.ts @@ -1,5 +1,6 @@ import { useState, useEffect } from "react" -import { ModelInfo, pearAiDefaultModelId, pearAiDefaultModelInfo, PEARAI_URL } from "../../../src/shared/api" +import { ModelInfo } from "../../../src/shared/api" +import { pearAiDefaultModelId, pearAiDefaultModelInfo, PEARAI_URL } from "../../../src/shared/pearaiApi" import type { ApiConfiguration } from "../../../src/shared/api" export const usePearAiModels = (apiConfiguration?: ApiConfiguration) => { From 5522d6a2e9ea4840e7dc688daf651be8acde5b72 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Mon, 7 Apr 2025 20:15:49 -0400 Subject: [PATCH 2/6] Progress --- src/api/providers/pearai/pearaiGeneric.ts | 4 +- src/shared/api.ts | 130 ++---------------- .../src/context/ExtensionStateContext.tsx | 4 +- 3 files changed, 12 insertions(+), 126 deletions(-) diff --git a/src/api/providers/pearai/pearaiGeneric.ts b/src/api/providers/pearai/pearaiGeneric.ts index 7492e1c5a98..53f269c17f6 100644 --- a/src/api/providers/pearai/pearaiGeneric.ts +++ b/src/api/providers/pearai/pearaiGeneric.ts @@ -198,7 +198,6 @@ export class PearAIGenericHandler extends BaseProvider implements SingleCompleti } protected processUsageMetrics(usage: any, modelInfo?: ModelInfo): ApiStreamUsageChunk { - console.dir("USAGE") console.dir(usage?.prompt_tokens_details) return { type: "usage", @@ -210,7 +209,7 @@ export class PearAIGenericHandler extends BaseProvider implements SingleCompleti } override getModel(): { id: string; info: ModelInfo } { - const modelId = this.options.pearaiModelId ?? pearAiDefaultModelId + const modelId = this.options.openAiModelId ?? pearAiDefaultModelId return { id: modelId, info: allModels[modelId] ?? pearAiDefaultModelInfo, @@ -292,6 +291,7 @@ export class PearAIGenericHandler extends BaseProvider implements SingleCompleti type: "usage", inputTokens: chunk.usage.prompt_tokens || 0, outputTokens: chunk.usage.completion_tokens || 0, + cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens, } } } diff --git a/src/shared/api.ts b/src/shared/api.ts index 75dcced3a8a..9e91c635360 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -767,6 +767,14 @@ export const geminiModels = { inputPrice: 0, outputPrice: 0, }, + "gemini-2.0-flash": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + }, "gemini-2.0-flash-lite-preview-02-05": { maxTokens: 8192, contextWindow: 1_048_576, @@ -1041,125 +1049,3 @@ export const unboundDefaultModelInfo: ModelInfo = { cacheWritesPrice: 3.75, cacheReadsPrice: 0.3, } -// CHANGE AS NEEDED FOR TESTING -// PROD: -// 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" - -// PearAI -export type PearAiModelId = keyof typeof pearAiModels -export const pearAiDefaultModelId: PearAiModelId = "pearai-model" -export const pearAiDefaultModelInfo: ModelInfo = { - maxTokens: 8192, - contextWindow: 200_000, - supportsImages: true, - supportsPromptCache: true, - inputPrice: 3.0, - outputPrice: 15.0, - cacheWritesPrice: 3.75, - cacheReadsPrice: 0.3, - description: - "PearAI Model automatically routes you to the most best / most suitable model on the market. Recommended for most users.", -} - -export const pearAiModels = { - "pearai-model": { - maxTokens: 8192, - contextWindow: 200_000, - supportsImages: true, - supportsPromptCache: true, - inputPrice: 3.0, - outputPrice: 15.0, - cacheWritesPrice: 3.75, - cacheReadsPrice: 0.3, - description: - "PearAI Model automatically routes you to the most best / most suitable model on the market. Recommended for most users.", - }, -} as const satisfies Record - -export const allModels = { - // Anthropic models - ...Object.entries(anthropicModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [`anthropic/${key}`]: value, - }), - {}, - ), - - // Bedrock models - ...Object.entries(bedrockModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [`bedrock/${key}`]: value, - }), - {}, - ), - - // Glama models (single default model) - [`glama/${glamaDefaultModelId}`]: glamaDefaultModelInfo, - - // Requesty models (single default model) - [`requesty/${requestyDefaultModelId}`]: requestyDefaultModelInfo, - - // OpenRouter models (single default model) - [`openrouter/${openRouterDefaultModelId}`]: openRouterDefaultModelInfo, - - // Vertex models - ...Object.entries(vertexModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [`vertex/${key}`]: value, - }), - {}, - ), - - // Gemini models - ...Object.entries(geminiModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [`gemini/${key}`]: value, - }), - {}, - ), - - // OpenAI Native models - ...Object.entries(openAiNativeModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [`openai-native/${key}`]: value, - }), - {}, - ), - - // DeepSeek models - ...Object.entries(deepSeekModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [`deepseek/${key}`]: value, - }), - {}, - ), - - // Mistral models - ...Object.entries(mistralModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [`mistral/${key}`]: value, - }), - {}, - ), - - // Unbound models (single default model) - [`unbound/${unboundDefaultModelId}`]: unboundDefaultModelInfo, - - // PearAI models - ...Object.entries(pearAiModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [`pearai/${key}`]: value, - }), - {}, - ), -} as const satisfies Record diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 74fa58f6416..99be49b6cc2 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -12,9 +12,8 @@ import { unboundDefaultModelInfo, requestyDefaultModelId, requestyDefaultModelInfo, - PEARAI_URL, - pearAiModels, } from "../../../src/shared/api" + import { vscode } from "../utils/vscode" import { convertTextMateToHljs } from "../utils/textMateToHljs" import { findLastIndex } from "../../../src/shared/array" @@ -24,6 +23,7 @@ import { Mode, CustomModePrompts, defaultModeSlug, defaultPrompts, ModeConfig } import { CustomSupportPrompts } from "../../../src/shared/support-prompt" import { experimentDefault, ExperimentId } from "../../../src/shared/experiments" import { TelemetrySetting } from "../../../src/shared/TelemetrySetting" +import { PEARAI_URL, pearAiModels } from "../../../src/shared/pearaiApi" export interface ExtensionStateContextType extends ExtensionState { didHydrateState: boolean From 96afcfba9247b55125e08d1c0843b0f3594253ee Mon Sep 17 00:00:00 2001 From: nang-dev Date: Mon, 7 Apr 2025 21:45:10 -0400 Subject: [PATCH 3/6] Progress --- src/api/providers/pearai/pearai.ts | 19 ++--------- src/api/providers/pearai/pearaiGeneric.ts | 33 +++++++++++++++---- src/shared/api.ts | 4 +-- src/shared/pearaiApi.ts | 2 +- .../src/components/settings/ApiOptions.tsx | 9 +++-- .../src/components/settings/constants.ts | 1 - 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/api/providers/pearai/pearai.ts b/src/api/providers/pearai/pearai.ts index 37d978ef78d..8b90228dce0 100644 --- a/src/api/providers/pearai/pearai.ts +++ b/src/api/providers/pearai/pearai.ts @@ -67,7 +67,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl } const data = (await response.json()) as PearAiModelsResponse const underlyingModel = data.models[modelId]?.underlyingModelUpdated || "claude-3-5-sonnet-20241022" - if (underlyingModel.startsWith("claude")) { + if (underlyingModel.startsWith("claude") || modelId.startsWith("anthropic/")) { // Default to Claude this.handler = new AnthropicHandler({ ...options, @@ -93,7 +93,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl apiModelId: "claude-3-5-sonnet-20241022", }) } - } else if (modelId.startsWith("claude")) { + } else if (modelId.startsWith("claude") || modelId.startsWith("anthropic/")) { this.handler = new AnthropicHandler({ ...options, apiKey: options.pearaiApiKey, @@ -111,20 +111,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl getModel(): { id: string; info: ModelInfo } { const baseModel = this.handler.getModel() - return { - id: baseModel.id, - info: { - ...baseModel.info, - // Inherit all capabilities from the underlying model - supportsImages: baseModel.info.supportsImages, - supportsComputerUse: baseModel.info.supportsComputerUse, - supportsPromptCache: baseModel.info.supportsPromptCache, - inputPrice: baseModel.info.inputPrice || 0, - outputPrice: baseModel.info.outputPrice || 0, - cacheWritesPrice: baseModel.info.cacheWritesPrice ? baseModel.info.cacheWritesPrice : undefined, - cacheReadsPrice: baseModel.info.cacheReadsPrice ? baseModel.info.cacheReadsPrice : undefined, - }, - } + return baseModel } async *createMessage(systemPrompt: string, messages: any[]): AsyncGenerator { diff --git a/src/api/providers/pearai/pearaiGeneric.ts b/src/api/providers/pearai/pearaiGeneric.ts index 53f269c17f6..783127a60e3 100644 --- a/src/api/providers/pearai/pearaiGeneric.ts +++ b/src/api/providers/pearai/pearaiGeneric.ts @@ -16,6 +16,7 @@ import { ApiStream, ApiStreamUsageChunk } from "../../transform/stream" import { BaseProvider } from "../base-provider" import { XmlMatcher } from "../../../utils/xml-matcher" import { allModels, pearAiDefaultModelId, pearAiDefaultModelInfo } from "../../../shared/pearaiApi" +import { calculateApiCostOpenAI } from "../../../utils/cost" const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 @@ -65,6 +66,9 @@ export class PearAIGenericHandler extends BaseProvider implements SingleCompleti const modelUrl = this.options.openAiBaseUrl ?? "" const modelId = this.options.openAiModelId ?? "" + console.dir("MODEL INFO") + console.dir(modelInfo) + console.dir(modelId) const deepseekReasoner = modelId.includes("deepseek-reasoner") const ark = modelUrl.includes(".volces.com") @@ -198,21 +202,36 @@ export class PearAIGenericHandler extends BaseProvider implements SingleCompleti } protected processUsageMetrics(usage: any, modelInfo?: ModelInfo): ApiStreamUsageChunk { - console.dir(usage?.prompt_tokens_details) + const inputTokens = usage?.prompt_tokens || 0 + const outputTokens = usage?.completion_tokens || 0 + const cacheWriteTokens = usage?.prompt_tokens_details?.caching_tokens || 0 + const cacheReadTokens = usage?.prompt_tokens_details?.cached_tokens || 0 + const totalCost = modelInfo + ? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens) + : 0 + + console.dir("COST") + console.log(totalCost) + console.dir("MODEL") + console.dir(modelInfo) return { type: "usage", - inputTokens: usage?.prompt_tokens || 0, - outputTokens: usage?.completion_tokens || 0, - cacheWriteTokens: usage?.prompt_tokens_details?.cache_miss_tokens, - cacheReadTokens: usage?.prompt_tokens_details?.cached_tokens, + inputTokens: inputTokens, + outputTokens: outputTokens, + cacheWriteTokens: cacheWriteTokens, + cacheReadTokens: cacheReadTokens, + totalCost: totalCost, } } override getModel(): { id: string; info: ModelInfo } { - const modelId = this.options.openAiModelId ?? pearAiDefaultModelId + const modelId = this.options.openAiModelId ?? "none" + console.log("MODEL INFO IN GETMODEL", allModels[modelId]) + console.log("Available models:", Object.keys(allModels)) + console.log("Keys: ", Object.keys(allModels[modelId])) return { id: modelId, - info: allModels[modelId] ?? pearAiDefaultModelInfo, + info: allModels[modelId], } } diff --git a/src/shared/api.ts b/src/shared/api.ts index 9e91c635360..f0c78a18a50 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -772,8 +772,8 @@ export const geminiModels = { contextWindow: 1_048_576, supportsImages: true, supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, + inputPrice: 0.15, + outputPrice: 0.6, }, "gemini-2.0-flash-lite-preview-02-05": { maxTokens: 8192, diff --git a/src/shared/pearaiApi.ts b/src/shared/pearaiApi.ts index 0a8d161608a..5251b8b6e6c 100644 --- a/src/shared/pearaiApi.ts +++ b/src/shared/pearaiApi.ts @@ -135,7 +135,7 @@ export const allModels: { [key: string]: ModelInfo } = { ...Object.entries(pearAiModels).reduce( (acc, [key, value]) => ({ ...acc, - [`{key}`]: value, + [key]: value, }), {}, ), diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index cea35806262..e6541b4dfb7 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -56,7 +56,7 @@ import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@ import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" import { usePearAiModels } from "../../hooks/usePearAiModels" -import { pearAiDefaultModelId, pearAiDefaultModelInfo } from "../../../../src/shared/pearaiApi" +import { allModels, pearAiDefaultModelId, pearAiDefaultModelInfo } from "../../../../src/shared/pearaiApi" interface ApiOptionsProps { uriScheme: string | undefined @@ -1690,10 +1690,9 @@ export function normalizeApiConfiguration( } case "pearai": { // Always use the models from the hook which are fetched when provider is selected - return getProviderData( - pearAiModelsQuery || { [pearAiDefaultModelId]: pearAiDefaultModelInfo }, - pearAiDefaultModelId, - ) + let query = pearAiModelsQuery + console.log("query", query) + return getProviderData(pearAiModelsQuery || allModels, pearAiDefaultModelId) } default: return getProviderData(anthropicModels, anthropicDefaultModelId) diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index 02d8bd52455..6da2a89651e 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -7,7 +7,6 @@ import { geminiModels, mistralModels, openAiNativeModels, - pearAiModels, vertexModels, } from "../../../../src/shared/api" From 0d55bc9002ff583d4eee5361310875a51c831e25 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Mon, 7 Apr 2025 22:18:46 -0400 Subject: [PATCH 4/6] Progress --- src/api/providers/pearai/pearai.ts | 21 +++++++++++++++++++ src/shared/pearaiApi.ts | 9 -------- webview-ui/src/components/chat/TaskHeader.tsx | 7 ++++++- .../src/components/settings/ApiOptions.tsx | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/api/providers/pearai/pearai.ts b/src/api/providers/pearai/pearai.ts index 8b90228dce0..1fdba1b52d9 100644 --- a/src/api/providers/pearai/pearai.ts +++ b/src/api/providers/pearai/pearai.ts @@ -23,6 +23,8 @@ interface PearAiModelsResponse { export class PearAiHandler extends BaseProvider implements SingleCompletionHandler { private handler!: AnthropicHandler | PearAIGenericHandler + private pearAiModelsResponse: PearAiModelsResponse | null = null + private options: ApiHandlerOptions constructor(options: ApiHandlerOptions) { super() @@ -42,6 +44,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl } else { vscode.commands.executeCommand("pearai.checkPearAITokens", undefined) } + this.options = options this.handler = new PearAIGenericHandler({ ...options, @@ -66,6 +69,7 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl throw new Error(`Failed to fetch models: ${response.statusText}`) } const data = (await response.json()) as PearAiModelsResponse + this.pearAiModelsResponse = data const underlyingModel = data.models[modelId]?.underlyingModelUpdated || "claude-3-5-sonnet-20241022" if (underlyingModel.startsWith("claude") || modelId.startsWith("anthropic/")) { // Default to Claude @@ -110,6 +114,23 @@ export class PearAiHandler extends BaseProvider implements SingleCompletionHandl } getModel(): { id: string; info: ModelInfo } { + if ( + this.pearAiModelsResponse && + this.options.apiModelId === "pearai-model" && + this.pearAiModelsResponse.models + ) { + const modelInfo = this.pearAiModelsResponse.models[this.options.apiModelId] + if (modelInfo) { + return { + id: this.options.apiModelId, + info: { + contextWindow: modelInfo.contextWindow || 4096, // provide default or actual value + supportsPromptCache: modelInfo.supportsPromptCaching || false, // provide default or actual value + ...modelInfo, + }, + } + } + } const baseModel = this.handler.getModel() return baseModel } diff --git a/src/shared/pearaiApi.ts b/src/shared/pearaiApi.ts index 5251b8b6e6c..9d169810132 100644 --- a/src/shared/pearaiApi.ts +++ b/src/shared/pearaiApi.ts @@ -130,13 +130,4 @@ export const allModels: { [key: string]: ModelInfo } = { // Unbound models (single default model) [`unbound/${unboundDefaultModelId}`]: unboundDefaultModelInfo, - - // PearAI models - ...Object.entries(pearAiModels).reduce( - (acc, [key, value]) => ({ - ...acc, - [key]: value, - }), - {}, - ), } as const satisfies Record diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 1a790bbec05..6423794b640 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -18,6 +18,7 @@ import Thumbnails from "../common/Thumbnails" import { normalizeApiConfiguration } from "../settings/ApiOptions" import { DeleteTaskDialog } from "../history/DeleteTaskDialog" import { vscBadgeBackground, vscEditorBackground, vscInputBackground } from "../ui" +import { usePearAiModels } from "@/hooks/usePearAiModels" interface TaskHeaderProps { task: ClineMessage @@ -44,7 +45,11 @@ const TaskHeader: React.FC = ({ }) => { const { t } = useTranslation() const { apiConfiguration, currentTaskItem } = useExtensionState() - const { selectedModelInfo } = useMemo(() => normalizeApiConfiguration(apiConfiguration), [apiConfiguration]) + const pearAiModels = usePearAiModels(apiConfiguration) + const { selectedModelInfo } = useMemo(() => { + return normalizeApiConfiguration(apiConfiguration, pearAiModels) + }, [apiConfiguration, pearAiModels]) + const [isTaskExpanded, setIsTaskExpanded] = useState(true) const [isTextExpanded, setIsTextExpanded] = useState(false) const [showSeeMore, setShowSeeMore] = useState(false) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index e6541b4dfb7..80b46212e6f 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1692,7 +1692,7 @@ export function normalizeApiConfiguration( // Always use the models from the hook which are fetched when provider is selected let query = pearAiModelsQuery console.log("query", query) - return getProviderData(pearAiModelsQuery || allModels, pearAiDefaultModelId) + return getProviderData(pearAiModelsQuery || {}, pearAiDefaultModelId) } default: return getProviderData(anthropicModels, anthropicDefaultModelId) From 35008364b3e03f75a97270a65eff9af1128bc682 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Mon, 7 Apr 2025 22:21:18 -0400 Subject: [PATCH 5/6] Finished --- src/api/providers/pearai/pearaiGeneric.ts | 4 ++++ src/shared/pearaiApi.ts | 9 ++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/api/providers/pearai/pearaiGeneric.ts b/src/api/providers/pearai/pearaiGeneric.ts index 783127a60e3..3e4b52b3a36 100644 --- a/src/api/providers/pearai/pearaiGeneric.ts +++ b/src/api/providers/pearai/pearaiGeneric.ts @@ -1,3 +1,7 @@ +/* +pearaiGeneric.ts is a copy of openai.ts, with minor changes to support the PearAI API. It currently is used for all hosted non-Anthropic models. +*/ + import { Anthropic } from "@anthropic-ai/sdk" import OpenAI, { AzureOpenAI } from "openai" import axios from "axios" diff --git a/src/shared/pearaiApi.ts b/src/shared/pearaiApi.ts index 9d169810132..d816e88a25e 100644 --- a/src/shared/pearaiApi.ts +++ b/src/shared/pearaiApi.ts @@ -1,6 +1,8 @@ -// CHANGE AS NEEDED FOR TESTING +// CHANGE AS NEEDED FOR DEVELOPMENT // 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" import { anthropicModels, @@ -21,9 +23,6 @@ import { vertexModels, } from "./api" -// DEV: -export const PEARAI_URL = "http://localhost:8000/integrations/cline" - // PearAI export type PearAiModelId = keyof typeof pearAiModels export const pearAiDefaultModelId: PearAiModelId = "pearai-model" From 24591507f0c5f4f0f41cf4922ddfec7b30a1f3df Mon Sep 17 00:00:00 2001 From: nang-dev Date: Mon, 7 Apr 2025 22:22:46 -0400 Subject: [PATCH 6/6] Finished --- src/api/providers/pearai/pearaiGeneric.ts | 13 +------------ webview-ui/src/components/settings/ApiOptions.tsx | 1 - 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/api/providers/pearai/pearaiGeneric.ts b/src/api/providers/pearai/pearaiGeneric.ts index 3e4b52b3a36..03cbd61a883 100644 --- a/src/api/providers/pearai/pearaiGeneric.ts +++ b/src/api/providers/pearai/pearaiGeneric.ts @@ -1,5 +1,5 @@ /* -pearaiGeneric.ts is a copy of openai.ts, with minor changes to support the PearAI API. It currently is used for all hosted non-Anthropic models. +pearaiGeneric.ts is the same as openai.ts, with changes to support the PearAI API. It currently is used for all hosted non-Anthropic models. */ import { Anthropic } from "@anthropic-ai/sdk" @@ -70,9 +70,6 @@ export class PearAIGenericHandler extends BaseProvider implements SingleCompleti const modelUrl = this.options.openAiBaseUrl ?? "" const modelId = this.options.openAiModelId ?? "" - console.dir("MODEL INFO") - console.dir(modelInfo) - console.dir(modelId) const deepseekReasoner = modelId.includes("deepseek-reasoner") const ark = modelUrl.includes(".volces.com") @@ -213,11 +210,6 @@ export class PearAIGenericHandler extends BaseProvider implements SingleCompleti const totalCost = modelInfo ? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens) : 0 - - console.dir("COST") - console.log(totalCost) - console.dir("MODEL") - console.dir(modelInfo) return { type: "usage", inputTokens: inputTokens, @@ -230,9 +222,6 @@ export class PearAIGenericHandler extends BaseProvider implements SingleCompleti override getModel(): { id: string; info: ModelInfo } { const modelId = this.options.openAiModelId ?? "none" - console.log("MODEL INFO IN GETMODEL", allModels[modelId]) - console.log("Available models:", Object.keys(allModels)) - console.log("Keys: ", Object.keys(allModels[modelId])) return { id: modelId, info: allModels[modelId], diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 80b46212e6f..e534f6b66eb 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1691,7 +1691,6 @@ export function normalizeApiConfiguration( case "pearai": { // Always use the models from the hook which are fetched when provider is selected let query = pearAiModelsQuery - console.log("query", query) return getProviderData(pearAiModelsQuery || {}, pearAiDefaultModelId) } default: