diff --git a/package-lock.json b/package-lock.json index 93d82af9191..5dc9c8bfddb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "roo-cline", + "name": "pearai-roo-cline", "version": "3.3.6", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "roo-cline", + "name": "pearai-roo-cline", "version": "3.3.6", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", diff --git a/package.json b/package.json index 638c1f6223a..5c4097cd6ef 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "roo-cline", - "displayName": "Roo Code (prev. Roo Cline)", - "description": "A VS Code plugin that enhances coding with AI-powered automation, multi-model support, and experimental features.", - "publisher": "RooVeterinaryInc", + "name": "pearai-roo-cline", + "displayName": "PearAI Roo Code / Cline", + "description": "PearAI's integration of Roo Code / Cline, a coding agent.", + "publisher": "PearAI", "version": "3.3.6", "icon": "assets/icons/rocket.png", "galleryBanner": { @@ -13,7 +13,7 @@ "vscode": "^1.84.0" }, "author": { - "name": "Roo Vet" + "name": "PearAI" }, "repository": { "type": "git", diff --git a/src/api/index.ts b/src/api/index.ts index b3927b4c13d..55f88e7ff32 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -14,6 +14,7 @@ import { DeepSeekHandler } from "./providers/deepseek" import { MistralHandler } from "./providers/mistral" import { VsCodeLmHandler } from "./providers/vscode-lm" import { ApiStream } from "./transform/stream" +import { PearAiHandler } from "./providers/pearai" import { UnboundHandler } from "./providers/unbound" export interface SingleCompletionHandler { @@ -54,6 +55,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { return new VsCodeLmHandler(options) case "mistral": return new MistralHandler(options) + case "pearai": + return new PearAiHandler(options) case "unbound": return new UnboundHandler(options) default: diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index e65b82ddef5..ac976cad956 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -83,7 +83,10 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { case "claude-3-opus-20240229": case "claude-3-haiku-20240307": return { - headers: { "anthropic-beta": "prompt-caching-2024-07-31" }, + headers: { + "anthropic-beta": "prompt-caching-2024-07-31", + "authorization": `Bearer ${this.options.apiKey}`, + }, } default: return undefined diff --git a/src/api/providers/pearai.ts b/src/api/providers/pearai.ts new file mode 100644 index 00000000000..ba79fb2149e --- /dev/null +++ b/src/api/providers/pearai.ts @@ -0,0 +1,31 @@ +import { OpenAiHandler } from "./openai" +import * as vscode from "vscode" +import { ApiHandlerOptions, PEARAI_URL } from "../../shared/api" +import { AnthropicHandler } from "./anthropic" + +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 + ), + ), + ) + } + }) + throw new Error("PearAI API key not found. Please login to PearAI.") + } + super({ + ...options, + apiKey: options.pearaiApiKey, + anthropicBaseUrl: PEARAI_URL, + }) + } +} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 139ed901563..b0d70bd0164 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -14,7 +14,7 @@ import { getTheme } from "../../integrations/theme/getTheme" import { getDiffStrategy } from "../diff/DiffStrategy" import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker" import { McpHub } from "../../services/mcp/McpHub" -import { ApiConfiguration, ApiProvider, ModelInfo } from "../../shared/api" +import { ApiConfiguration, ApiProvider, ModelInfo, PEARAI_URL } from "../../shared/api" import { findLast } from "../../shared/array" import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage" import { HistoryItem } from "../../shared/HistoryItem" @@ -63,6 +63,8 @@ type SecretKey = | "openAiNativeApiKey" | "deepSeekApiKey" | "mistralApiKey" + | "pearai-token" + | "pearai-refresh" // Array of custom modes | "unboundApiKey" type GlobalStateKey = | "apiProvider" @@ -98,6 +100,10 @@ type GlobalStateKey = | "openRouterModelId" | "openRouterModelInfo" | "openRouterBaseUrl" + | "pearaiModelId" + | "pearaiModelInfo" + | "pearaiBaseUrl" + | "pearaiApiKey" | "openRouterUseMiddleOutTransform" | "allowedCommands" | "soundEnabled" @@ -1373,6 +1379,19 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("mode", defaultModeSlug) await this.postStateToWebview() } + break + case "openPearAiAuth": + const extensionUrl = `${vscode.env.uriScheme}://pearai.pearai/auth` + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(extensionUrl)) + + await vscode.env.openExternal( + await vscode.env.asExternalUri( + vscode.Uri.parse( + `https://trypear.ai/signin?callback=${callbackUri.toString()}`, // Change to localhost if running locally + ), + ), + ) + break } }, null, @@ -1422,7 +1441,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { // Update mode's default config const { mode } = await this.getState() if (mode) { - const currentApiConfigName = await this.getGlobalState("currentApiConfigName") + const currentApiConfigName = (await this.getGlobalState("currentApiConfigName")) ?? "default" const listApiConfig = await this.configManager.listConfig() const config = listApiConfig?.find((c) => c.name === currentApiConfigName) if (config?.id) { @@ -1468,6 +1487,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { openRouterUseMiddleOutTransform, vsCodeLmModelSelector, mistralApiKey, + pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, unboundApiKey, unboundModelId, } = apiConfiguration @@ -1508,6 +1530,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("openRouterUseMiddleOutTransform", openRouterUseMiddleOutTransform) await this.updateGlobalState("vsCodeLmModelSelector", vsCodeLmModelSelector) await this.storeSecret("mistralApiKey", mistralApiKey) + await this.updateGlobalState("pearaiBaseUrl", PEARAI_URL) + await this.updateGlobalState("pearaiModelId", pearaiModelId) + await this.updateGlobalState("pearaiModelInfo", pearaiModelInfo) await this.storeSecret("unboundApiKey", unboundApiKey) await this.updateGlobalState("unboundModelId", unboundModelId) if (this.cline) { @@ -2140,6 +2165,11 @@ export class ClineProvider implements vscode.WebviewViewProvider { openAiNativeApiKey, deepSeekApiKey, mistralApiKey, + pearaiApiKey, + pearaiRefreshKey, + pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, azureApiVersion, openAiStreamingEnabled, openRouterModelId, @@ -2213,6 +2243,11 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getSecret("openAiNativeApiKey") as Promise, this.getSecret("deepSeekApiKey") as Promise, this.getSecret("mistralApiKey") as Promise, + 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("azureApiVersion") as Promise, this.getGlobalState("openAiStreamingEnabled") as Promise, this.getGlobalState("openRouterModelId") as Promise, @@ -2303,6 +2338,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { openAiNativeApiKey, deepSeekApiKey, mistralApiKey, + pearaiApiKey, + pearaiBaseUrl, + pearaiModelId, + pearaiModelInfo, azureApiVersion, openAiStreamingEnabled, openRouterModelId, diff --git a/src/extension.ts b/src/extension.ts index 719f38d5e8c..e06a54db386 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -53,6 +53,24 @@ export function activate(context: vscode.ExtensionContext) { }), ) + context.subscriptions.push( + vscode.commands.registerCommand("pearai-roo-cline.pearaiLogin", async (data) => { + console.dir("Logged in to PearAI:") + console.dir(data) + context.secrets.store("pearai-token", data.accessToken) + context.secrets.store("pearai-refresh", data.refreshToken) + vscode.commands.executeCommand("roo-cline.plusButtonClicked") + }), + ) + + context.subscriptions.push( + vscode.commands.registerCommand("pearai-roo-cline.pearaiLogout", async () => { + console.dir("Logged out of PearAI:") + context.secrets.delete("pearai-token") + context.secrets.delete("pearai-refresh") + }), + ) + context.subscriptions.push( vscode.commands.registerCommand("roo-cline.mcpButtonClicked", () => { sidebarProvider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" }) diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 719d25aa3e6..f17e2aa79d9 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -82,6 +82,7 @@ export interface WebviewMessage { | "deleteCustomMode" | "setopenAiCustomModelInfo" | "openCustomModesSettings" + | "openPearAiAuth" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/src/shared/api.ts b/src/shared/api.ts index 7da67fd3eb6..fc7cc3f792f 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -14,6 +14,7 @@ export type ApiProvider = | "deepseek" | "vscode-lm" | "mistral" + | "pearai" | "unbound" export interface ApiHandlerOptions { @@ -58,6 +59,10 @@ export interface ApiHandlerOptions { deepSeekBaseUrl?: string deepSeekApiKey?: string includeMaxTokens?: boolean + pearaiApiKey?: string + pearaiBaseUrl?: string + pearaiModelId?: string + pearaiModelInfo?: ModelInfo unboundApiKey?: string unboundModelId?: string } @@ -615,3 +620,9 @@ export const unboundModels = { "deepseek/deepseek-reasoner": deepSeekModels["deepseek-reasoner"], "mistral/codestral-latest": mistralModels["codestral-latest"], } as const satisfies Record + +// 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" diff --git a/src/shared/checkExistApiConfig.ts b/src/shared/checkExistApiConfig.ts index 8cb8055cc84..f225eb22d8e 100644 --- a/src/shared/checkExistApiConfig.ts +++ b/src/shared/checkExistApiConfig.ts @@ -16,6 +16,7 @@ export function checkExistKey(config: ApiConfiguration | undefined) { config.deepSeekApiKey, config.mistralApiKey, config.vsCodeLmModelSelector, + config.pearaiBaseUrl, ].some((key) => key !== undefined) : false } diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 519b7e5efb6..a05a5cda607 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -994,13 +994,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie }}> {showAnnouncement && }
-

What can I do for you?

+

PearAI Coding Agent (Powered by Roo Code / Cline)

- Thanks to the latest breakthroughs in agentic coding capabilities, I can handle complex - software development tasks step-by-step. With tools that let me create & edit files, explore - complex projects, use the browser, and execute terminal commands (after you grant - permission), I can assist you in ways that go beyond code completion or tech support. I can - even use MCP to create new tools and extend my own capabilities. + Ask me to create a new feature, fix a bug, anything else. I can create & edit files, explore + complex projects, use the browser, and execute terminal commands!

{taskHistory.length > 0 && } diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 4bdff0b061e..da078530326 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1,6 +1,12 @@ import { Checkbox, Dropdown, Pane } from "vscrui" import type { DropdownOption } from "vscrui" -import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" +import { + VSCodeButton, + VSCodeLink, + VSCodeRadio, + VSCodeRadioGroup, + VSCodeTextField, +} from "@vscode/webview-ui-toolkit/react" import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react" import { useEvent, useInterval } from "react-use" import { @@ -136,6 +142,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = }} style={{ minWidth: 130, position: "relative", zIndex: OPENROUTER_MODEL_PICKER_Z_INDEX + 1 }} options={[ + { value: "pearai", label: "PearAI" }, { value: "openrouter", label: "OpenRouter" }, { value: "anthropic", label: "Anthropic" }, { value: "gemini", label: "Google Gemini" }, @@ -154,6 +161,40 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) = /> + {selectedProvider === "pearai" && ( +
+ {!apiConfiguration?.pearaiApiKey ? ( + <> + { + vscode.postMessage({ + type: "openPearAiAuth", + }) + }}> + Login to PearAI + +

+ Connect your PearAI account to use servers. +

+ + ) : ( +

+ User already logged in to PearAI. Click 'Done' to proceed! +

+ )} +
+ )} + {selectedProvider === "anthropic" && (