|
|
@@ -8,138 +8,51 @@ import * as path from "path"
|
|
|
import * as vscode from "vscode"
|
|
|
import simpleGit from "simple-git"
|
|
|
|
|
|
-import { buildApiHandler } from "../../api"
|
|
|
+import { ApiConfiguration, ApiProvider, ModelInfo } from "../../shared/api"
|
|
|
+import { findLast } from "../../shared/array"
|
|
|
+import { CustomSupportPrompts, supportPrompt } from "../../shared/support-prompt"
|
|
|
+import { GlobalFileNames } from "../../shared/globalFileNames"
|
|
|
+import type { SecretKey, GlobalStateKey } from "../../shared/globalState"
|
|
|
+import { HistoryItem } from "../../shared/HistoryItem"
|
|
|
+import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
|
|
|
+import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
|
|
|
+import { Mode, CustomModePrompts, PromptComponent, defaultModeSlug } from "../../shared/modes"
|
|
|
+import { checkExistKey } from "../../shared/checkExistApiConfig"
|
|
|
+import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
|
|
|
import { downloadTask } from "../../integrations/misc/export-markdown"
|
|
|
import { openFile, openImage } from "../../integrations/misc/open-file"
|
|
|
import { selectImages } from "../../integrations/misc/process-images"
|
|
|
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 { findLast } from "../../shared/array"
|
|
|
-import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
|
|
|
-import { HistoryItem } from "../../shared/HistoryItem"
|
|
|
-import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
|
|
|
-import { Mode, CustomModePrompts, PromptComponent, defaultModeSlug } from "../../shared/modes"
|
|
|
-import { SYSTEM_PROMPT } from "../prompts/system"
|
|
|
+import { McpServerManager } from "../../services/mcp/McpServerManager"
|
|
|
import { fileExistsAtPath } from "../../utils/fs"
|
|
|
-import { Cline } from "../Cline"
|
|
|
-import { openMention } from "../mentions"
|
|
|
-import { getNonce } from "./getNonce"
|
|
|
-import { getUri } from "./getUri"
|
|
|
import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
|
|
|
-import { checkExistKey } from "../../shared/checkExistApiConfig"
|
|
|
import { singleCompletionHandler } from "../../utils/single-completion-handler"
|
|
|
import { searchCommits } from "../../utils/git"
|
|
|
+import { getDiffStrategy } from "../diff/DiffStrategy"
|
|
|
+import { SYSTEM_PROMPT } from "../prompts/system"
|
|
|
import { ConfigManager } from "../config/ConfigManager"
|
|
|
import { CustomModesManager } from "../config/CustomModesManager"
|
|
|
-import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
|
|
|
-import { CustomSupportPrompts, supportPrompt } from "../../shared/support-prompt"
|
|
|
-
|
|
|
+import { buildApiHandler } from "../../api"
|
|
|
+import { getOpenRouterModels } from "../../api/providers/openrouter"
|
|
|
+import { getGlamaModels } from "../../api/providers/glama"
|
|
|
+import { getUnboundModels } from "../../api/providers/unbound"
|
|
|
+import { getRequestyModels } from "../../api/providers/requesty"
|
|
|
+import { getOpenAiModels } from "../../api/providers/openai"
|
|
|
+import { getOllamaModels } from "../../api/providers/ollama"
|
|
|
+import { getVsCodeLmModels } from "../../api/providers/vscode-lm"
|
|
|
+import { getLmStudioModels } from "../../api/providers/lmstudio"
|
|
|
import { ACTION_NAMES } from "../CodeActionProvider"
|
|
|
-import { McpServerManager } from "../../services/mcp/McpServerManager"
|
|
|
+import { Cline } from "../Cline"
|
|
|
+import { openMention } from "../mentions"
|
|
|
+import { getNonce } from "./getNonce"
|
|
|
+import { getUri } from "./getUri"
|
|
|
|
|
|
-/*
|
|
|
-https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
|
|
-
|
|
|
-https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts
|
|
|
-*/
|
|
|
-
|
|
|
-type SecretKey =
|
|
|
- | "apiKey"
|
|
|
- | "glamaApiKey"
|
|
|
- | "openRouterApiKey"
|
|
|
- | "awsAccessKey"
|
|
|
- | "awsSecretKey"
|
|
|
- | "awsSessionToken"
|
|
|
- | "openAiApiKey"
|
|
|
- | "geminiApiKey"
|
|
|
- | "openAiNativeApiKey"
|
|
|
- | "deepSeekApiKey"
|
|
|
- | "mistralApiKey"
|
|
|
- | "unboundApiKey"
|
|
|
- | "requestyApiKey"
|
|
|
-type GlobalStateKey =
|
|
|
- | "apiProvider"
|
|
|
- | "apiModelId"
|
|
|
- | "glamaModelId"
|
|
|
- | "glamaModelInfo"
|
|
|
- | "awsRegion"
|
|
|
- | "awsUseCrossRegionInference"
|
|
|
- | "awsProfile"
|
|
|
- | "awsUseProfile"
|
|
|
- | "vertexProjectId"
|
|
|
- | "vertexRegion"
|
|
|
- | "lastShownAnnouncementId"
|
|
|
- | "customInstructions"
|
|
|
- | "alwaysAllowReadOnly"
|
|
|
- | "alwaysAllowWrite"
|
|
|
- | "alwaysAllowExecute"
|
|
|
- | "alwaysAllowBrowser"
|
|
|
- | "alwaysAllowMcp"
|
|
|
- | "alwaysAllowModeSwitch"
|
|
|
- | "taskHistory"
|
|
|
- | "openAiBaseUrl"
|
|
|
- | "openAiModelId"
|
|
|
- | "openAiCustomModelInfo"
|
|
|
- | "openAiUseAzure"
|
|
|
- | "ollamaModelId"
|
|
|
- | "ollamaBaseUrl"
|
|
|
- | "lmStudioModelId"
|
|
|
- | "lmStudioBaseUrl"
|
|
|
- | "anthropicBaseUrl"
|
|
|
- | "anthropicThinking"
|
|
|
- | "azureApiVersion"
|
|
|
- | "openAiStreamingEnabled"
|
|
|
- | "openRouterModelId"
|
|
|
- | "openRouterModelInfo"
|
|
|
- | "openRouterBaseUrl"
|
|
|
- | "openRouterUseMiddleOutTransform"
|
|
|
- | "allowedCommands"
|
|
|
- | "soundEnabled"
|
|
|
- | "soundVolume"
|
|
|
- | "diffEnabled"
|
|
|
- | "checkpointsEnabled"
|
|
|
- | "browserViewportSize"
|
|
|
- | "screenshotQuality"
|
|
|
- | "fuzzyMatchThreshold"
|
|
|
- | "preferredLanguage" // Language setting for Cline's communication
|
|
|
- | "writeDelayMs"
|
|
|
- | "terminalOutputLineLimit"
|
|
|
- | "mcpEnabled"
|
|
|
- | "enableMcpServerCreation"
|
|
|
- | "alwaysApproveResubmit"
|
|
|
- | "requestDelaySeconds"
|
|
|
- | "rateLimitSeconds"
|
|
|
- | "currentApiConfigName"
|
|
|
- | "listApiConfigMeta"
|
|
|
- | "vsCodeLmModelSelector"
|
|
|
- | "mode"
|
|
|
- | "modeApiConfigs"
|
|
|
- | "customModePrompts"
|
|
|
- | "customSupportPrompts"
|
|
|
- | "enhancementApiConfigId"
|
|
|
- | "experiments" // Map of experiment IDs to their enabled state
|
|
|
- | "autoApprovalEnabled"
|
|
|
- | "customModes" // Array of custom modes
|
|
|
- | "unboundModelId"
|
|
|
- | "requestyModelId"
|
|
|
- | "requestyModelInfo"
|
|
|
- | "unboundModelInfo"
|
|
|
- | "modelTemperature"
|
|
|
- | "mistralCodestralUrl"
|
|
|
- | "maxOpenTabsContext"
|
|
|
-
|
|
|
-export const GlobalFileNames = {
|
|
|
- apiConversationHistory: "api_conversation_history.json",
|
|
|
- uiMessages: "ui_messages.json",
|
|
|
- glamaModels: "glama_models.json",
|
|
|
- openRouterModels: "openrouter_models.json",
|
|
|
- requestyModels: "requesty_models.json",
|
|
|
- mcpSettings: "cline_mcp_settings.json",
|
|
|
- unboundModels: "unbound_models.json",
|
|
|
-}
|
|
|
+/**
|
|
|
+ * https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
|
|
+ * https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts
|
|
|
+ */
|
|
|
|
|
|
export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
public static readonly sideBarId = "roo-cline.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
|
|
|
@@ -619,15 +532,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
|
|
|
this.postStateToWebview()
|
|
|
this.workspaceTracker?.initializeFilePaths() // don't await
|
|
|
+
|
|
|
getTheme().then((theme) =>
|
|
|
this.postMessageToWebview({ type: "theme", text: JSON.stringify(theme) }),
|
|
|
)
|
|
|
- // post last cached models in case the call to endpoint fails
|
|
|
- this.readOpenRouterModels().then((openRouterModels) => {
|
|
|
- if (openRouterModels) {
|
|
|
- this.postMessageToWebview({ type: "openRouterModels", openRouterModels })
|
|
|
- }
|
|
|
- })
|
|
|
|
|
|
// If MCP Hub is already initialized, update the webview with current server list
|
|
|
if (this.mcpHub) {
|
|
|
@@ -637,13 +545,37 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- // gui relies on model info to be up-to-date to provide the most accurate pricing, so we need to fetch the latest details on launch.
|
|
|
- // we do this for all users since many users switch between api providers and if they were to switch back to openrouter it would be showing outdated model info if we hadn't retrieved the latest at this point
|
|
|
- // (see normalizeApiConfiguration > openrouter)
|
|
|
- this.refreshOpenRouterModels().then(async (openRouterModels) => {
|
|
|
+ const cacheDir = await this.ensureCacheDirectoryExists()
|
|
|
+
|
|
|
+ // Post last cached models in case the call to endpoint fails.
|
|
|
+ this.readModelsFromCache(GlobalFileNames.openRouterModels).then((openRouterModels) => {
|
|
|
if (openRouterModels) {
|
|
|
- // update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
|
|
+ this.postMessageToWebview({ type: "openRouterModels", openRouterModels })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // GUI relies on model info to be up-to-date to provide
|
|
|
+ // the most accurate pricing, so we need to fetch the
|
|
|
+ // latest details on launch.
|
|
|
+ // We do this for all users since many users switch
|
|
|
+ // between api providers and if they were to switch back
|
|
|
+ // to OpenRouter it would be showing outdated model info
|
|
|
+ // if we hadn't retrieved the latest at this point
|
|
|
+ // (see normalizeApiConfiguration > openrouter).
|
|
|
+ getOpenRouterModels().then(async (openRouterModels) => {
|
|
|
+ if (Object.keys(openRouterModels).length > 0) {
|
|
|
+ await fs.writeFile(
|
|
|
+ path.join(cacheDir, GlobalFileNames.openRouterModels),
|
|
|
+ JSON.stringify(openRouterModels),
|
|
|
+ )
|
|
|
+ await this.postMessageToWebview({ type: "openRouterModels", openRouterModels })
|
|
|
+
|
|
|
+ // Update model info in state (this needs to be
|
|
|
+ // done here since we don't want to update state
|
|
|
+ // while settings is open, and we may refresh
|
|
|
+ // models there).
|
|
|
const { apiConfiguration } = await this.getState()
|
|
|
+
|
|
|
if (apiConfiguration.openRouterModelId) {
|
|
|
await this.updateGlobalState(
|
|
|
"openRouterModelInfo",
|
|
|
@@ -653,15 +585,23 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
- this.readGlamaModels().then((glamaModels) => {
|
|
|
+
|
|
|
+ this.readModelsFromCache(GlobalFileNames.glamaModels).then((glamaModels) => {
|
|
|
if (glamaModels) {
|
|
|
this.postMessageToWebview({ type: "glamaModels", glamaModels })
|
|
|
}
|
|
|
})
|
|
|
- this.refreshGlamaModels().then(async (glamaModels) => {
|
|
|
- if (glamaModels) {
|
|
|
- // update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
|
|
+
|
|
|
+ getGlamaModels().then(async (glamaModels) => {
|
|
|
+ if (Object.keys(glamaModels).length > 0) {
|
|
|
+ await fs.writeFile(
|
|
|
+ path.join(cacheDir, GlobalFileNames.glamaModels),
|
|
|
+ JSON.stringify(glamaModels),
|
|
|
+ )
|
|
|
+ await this.postMessageToWebview({ type: "glamaModels", glamaModels })
|
|
|
+
|
|
|
const { apiConfiguration } = await this.getState()
|
|
|
+
|
|
|
if (apiConfiguration.glamaModelId) {
|
|
|
await this.updateGlobalState(
|
|
|
"glamaModelInfo",
|
|
|
@@ -672,14 +612,22 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- this.readUnboundModels().then((unboundModels) => {
|
|
|
+ this.readModelsFromCache(GlobalFileNames.unboundModels).then((unboundModels) => {
|
|
|
if (unboundModels) {
|
|
|
this.postMessageToWebview({ type: "unboundModels", unboundModels })
|
|
|
}
|
|
|
})
|
|
|
- this.refreshUnboundModels().then(async (unboundModels) => {
|
|
|
- if (unboundModels) {
|
|
|
+
|
|
|
+ getUnboundModels().then(async (unboundModels) => {
|
|
|
+ if (Object.keys(unboundModels).length > 0) {
|
|
|
+ await fs.writeFile(
|
|
|
+ path.join(cacheDir, GlobalFileNames.unboundModels),
|
|
|
+ JSON.stringify(unboundModels),
|
|
|
+ )
|
|
|
+ await this.postMessageToWebview({ type: "unboundModels", unboundModels })
|
|
|
+
|
|
|
const { apiConfiguration } = await this.getState()
|
|
|
+
|
|
|
if (apiConfiguration?.unboundModelId) {
|
|
|
await this.updateGlobalState(
|
|
|
"unboundModelInfo",
|
|
|
@@ -690,15 +638,24 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- this.readRequestyModels().then((requestyModels) => {
|
|
|
+ this.readModelsFromCache(GlobalFileNames.requestyModels).then((requestyModels) => {
|
|
|
if (requestyModels) {
|
|
|
this.postMessageToWebview({ type: "requestyModels", requestyModels })
|
|
|
}
|
|
|
})
|
|
|
- this.refreshRequestyModels().then(async (requestyModels) => {
|
|
|
- if (requestyModels) {
|
|
|
- // update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
|
|
+
|
|
|
+ const requestyApiKey = await this.getSecret("requestyApiKey")
|
|
|
+
|
|
|
+ getRequestyModels({ apiKey: requestyApiKey }).then(async (requestyModels) => {
|
|
|
+ if (Object.keys(requestyModels).length > 0) {
|
|
|
+ await fs.writeFile(
|
|
|
+ path.join(cacheDir, GlobalFileNames.requestyModels),
|
|
|
+ JSON.stringify(requestyModels),
|
|
|
+ )
|
|
|
+ await this.postMessageToWebview({ type: "requestyModels", requestyModels })
|
|
|
+
|
|
|
const { apiConfiguration } = await this.getState()
|
|
|
+
|
|
|
if (apiConfiguration.requestyModelId) {
|
|
|
await this.updateGlobalState(
|
|
|
"requestyModelInfo",
|
|
|
@@ -841,41 +798,84 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
case "resetState":
|
|
|
await this.resetState()
|
|
|
break
|
|
|
- case "requestOllamaModels":
|
|
|
- const ollamaModels = await this.getOllamaModels(message.text)
|
|
|
- this.postMessageToWebview({ type: "ollamaModels", ollamaModels })
|
|
|
- break
|
|
|
- case "requestLmStudioModels":
|
|
|
- const lmStudioModels = await this.getLmStudioModels(message.text)
|
|
|
- this.postMessageToWebview({ type: "lmStudioModels", lmStudioModels })
|
|
|
- break
|
|
|
- case "requestVsCodeLmModels":
|
|
|
- const vsCodeLmModels = await this.getVsCodeLmModels()
|
|
|
- this.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
|
|
|
+ case "refreshOpenRouterModels":
|
|
|
+ const openRouterModels = await getOpenRouterModels()
|
|
|
+
|
|
|
+ if (Object.keys(openRouterModels).length > 0) {
|
|
|
+ const cacheDir = await this.ensureCacheDirectoryExists()
|
|
|
+ await fs.writeFile(
|
|
|
+ path.join(cacheDir, GlobalFileNames.openRouterModels),
|
|
|
+ JSON.stringify(openRouterModels),
|
|
|
+ )
|
|
|
+ await this.postMessageToWebview({ type: "openRouterModels", openRouterModels })
|
|
|
+ }
|
|
|
+
|
|
|
break
|
|
|
case "refreshGlamaModels":
|
|
|
- await this.refreshGlamaModels()
|
|
|
+ const glamaModels = await getGlamaModels()
|
|
|
+
|
|
|
+ if (Object.keys(glamaModels).length > 0) {
|
|
|
+ const cacheDir = await this.ensureCacheDirectoryExists()
|
|
|
+ await fs.writeFile(
|
|
|
+ path.join(cacheDir, GlobalFileNames.glamaModels),
|
|
|
+ JSON.stringify(glamaModels),
|
|
|
+ )
|
|
|
+ await this.postMessageToWebview({ type: "glamaModels", glamaModels })
|
|
|
+ }
|
|
|
+
|
|
|
break
|
|
|
- case "refreshOpenRouterModels":
|
|
|
- await this.refreshOpenRouterModels()
|
|
|
+ case "refreshUnboundModels":
|
|
|
+ const unboundModels = await getUnboundModels()
|
|
|
+
|
|
|
+ if (Object.keys(unboundModels).length > 0) {
|
|
|
+ const cacheDir = await this.ensureCacheDirectoryExists()
|
|
|
+ await fs.writeFile(
|
|
|
+ path.join(cacheDir, GlobalFileNames.unboundModels),
|
|
|
+ JSON.stringify(unboundModels),
|
|
|
+ )
|
|
|
+ await this.postMessageToWebview({ type: "unboundModels", unboundModels })
|
|
|
+ }
|
|
|
+
|
|
|
+ break
|
|
|
+ case "refreshRequestyModels":
|
|
|
+ if (message?.values?.apiKey) {
|
|
|
+ const requestyModels = await getRequestyModels({ apiKey: message.values.apiKey })
|
|
|
+
|
|
|
+ if (Object.keys(requestyModels).length > 0) {
|
|
|
+ const cacheDir = await this.ensureCacheDirectoryExists()
|
|
|
+ await fs.writeFile(
|
|
|
+ path.join(cacheDir, GlobalFileNames.requestyModels),
|
|
|
+ JSON.stringify(requestyModels),
|
|
|
+ )
|
|
|
+ await this.postMessageToWebview({ type: "requestyModels", requestyModels })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
break
|
|
|
case "refreshOpenAiModels":
|
|
|
if (message?.values?.baseUrl && message?.values?.apiKey) {
|
|
|
- const openAiModels = await this.getOpenAiModels(
|
|
|
+ const openAiModels = await getOpenAiModels(
|
|
|
message?.values?.baseUrl,
|
|
|
message?.values?.apiKey,
|
|
|
)
|
|
|
this.postMessageToWebview({ type: "openAiModels", openAiModels })
|
|
|
}
|
|
|
+
|
|
|
break
|
|
|
- case "refreshUnboundModels":
|
|
|
- await this.refreshUnboundModels()
|
|
|
+ case "requestOllamaModels":
|
|
|
+ const ollamaModels = await getOllamaModels(message.text)
|
|
|
+ // TODO: Cache like we do for OpenRouter, etc?
|
|
|
+ this.postMessageToWebview({ type: "ollamaModels", ollamaModels })
|
|
|
break
|
|
|
- case "refreshRequestyModels":
|
|
|
- if (message?.values?.apiKey) {
|
|
|
- const requestyModels = await this.refreshRequestyModels(message?.values?.apiKey)
|
|
|
- this.postMessageToWebview({ type: "requestyModels", requestyModels: requestyModels })
|
|
|
- }
|
|
|
+ case "requestLmStudioModels":
|
|
|
+ const lmStudioModels = await getLmStudioModels(message.text)
|
|
|
+ // TODO: Cache like we do for OpenRouter, etc?
|
|
|
+ this.postMessageToWebview({ type: "lmStudioModels", lmStudioModels })
|
|
|
+ break
|
|
|
+ case "requestVsCodeLmModels":
|
|
|
+ const vsCodeLmModels = await getVsCodeLmModels()
|
|
|
+ // TODO: Cache like we do for OpenRouter, etc?
|
|
|
+ this.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
|
|
|
break
|
|
|
case "openImage":
|
|
|
openImage(message.text!)
|
|
|
@@ -1789,157 +1789,22 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
return settingsDir
|
|
|
}
|
|
|
|
|
|
- // Ollama
|
|
|
-
|
|
|
- async getOllamaModels(baseUrl?: string) {
|
|
|
- try {
|
|
|
- if (!baseUrl) {
|
|
|
- baseUrl = "http://localhost:11434"
|
|
|
- }
|
|
|
- if (!URL.canParse(baseUrl)) {
|
|
|
- return []
|
|
|
- }
|
|
|
- const response = await axios.get(`${baseUrl}/api/tags`)
|
|
|
- const modelsArray = response.data?.models?.map((model: any) => model.name) || []
|
|
|
- const models = [...new Set<string>(modelsArray)]
|
|
|
- return models
|
|
|
- } catch (error) {
|
|
|
- return []
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // LM Studio
|
|
|
-
|
|
|
- async getLmStudioModels(baseUrl?: string) {
|
|
|
- try {
|
|
|
- if (!baseUrl) {
|
|
|
- baseUrl = "http://localhost:1234"
|
|
|
- }
|
|
|
- if (!URL.canParse(baseUrl)) {
|
|
|
- return []
|
|
|
- }
|
|
|
- const response = await axios.get(`${baseUrl}/v1/models`)
|
|
|
- const modelsArray = response.data?.data?.map((model: any) => model.id) || []
|
|
|
- const models = [...new Set<string>(modelsArray)]
|
|
|
- return models
|
|
|
- } catch (error) {
|
|
|
- return []
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // VSCode LM API
|
|
|
- private async getVsCodeLmModels() {
|
|
|
- try {
|
|
|
- const models = await vscode.lm.selectChatModels({})
|
|
|
- return models || []
|
|
|
- } catch (error) {
|
|
|
- this.outputChannel.appendLine(
|
|
|
- `Error fetching VS Code LM models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
|
|
|
- )
|
|
|
- return []
|
|
|
- }
|
|
|
+ private async ensureCacheDirectoryExists() {
|
|
|
+ const cacheDir = path.join(this.context.globalStorageUri.fsPath, "cache")
|
|
|
+ await fs.mkdir(cacheDir, { recursive: true })
|
|
|
+ return cacheDir
|
|
|
}
|
|
|
|
|
|
- // OpenAi
|
|
|
-
|
|
|
- async getOpenAiModels(baseUrl?: string, apiKey?: string) {
|
|
|
- try {
|
|
|
- if (!baseUrl) {
|
|
|
- return []
|
|
|
- }
|
|
|
-
|
|
|
- if (!URL.canParse(baseUrl)) {
|
|
|
- return []
|
|
|
- }
|
|
|
-
|
|
|
- const config: Record<string, any> = {}
|
|
|
- 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) || []
|
|
|
- const models = [...new Set<string>(modelsArray)]
|
|
|
- return models
|
|
|
- } catch (error) {
|
|
|
- return []
|
|
|
- }
|
|
|
- }
|
|
|
+ private async readModelsFromCache(filename: string): Promise<Record<string, ModelInfo> | undefined> {
|
|
|
+ const filePath = path.join(await this.ensureCacheDirectoryExists(), filename)
|
|
|
+ const fileExists = await fileExistsAtPath(filePath)
|
|
|
|
|
|
- // Requesty
|
|
|
- async readRequestyModels(): Promise<Record<string, ModelInfo> | undefined> {
|
|
|
- const requestyModelsFilePath = path.join(
|
|
|
- await this.ensureCacheDirectoryExists(),
|
|
|
- GlobalFileNames.requestyModels,
|
|
|
- )
|
|
|
- const fileExists = await fileExistsAtPath(requestyModelsFilePath)
|
|
|
if (fileExists) {
|
|
|
- const fileContents = await fs.readFile(requestyModelsFilePath, "utf8")
|
|
|
+ const fileContents = await fs.readFile(filePath, "utf8")
|
|
|
return JSON.parse(fileContents)
|
|
|
}
|
|
|
- return undefined
|
|
|
- }
|
|
|
-
|
|
|
- async refreshRequestyModels(apiKey?: string) {
|
|
|
- const requestyModelsFilePath = path.join(
|
|
|
- await this.ensureCacheDirectoryExists(),
|
|
|
- GlobalFileNames.requestyModels,
|
|
|
- )
|
|
|
-
|
|
|
- const models: Record<string, ModelInfo> = {}
|
|
|
- try {
|
|
|
- const config: Record<string, any> = {}
|
|
|
- if (!apiKey) {
|
|
|
- apiKey = (await this.getSecret("requestyApiKey")) as string
|
|
|
- }
|
|
|
-
|
|
|
- if (!apiKey) {
|
|
|
- this.outputChannel.appendLine("No Requesty API key found")
|
|
|
- return models
|
|
|
- }
|
|
|
-
|
|
|
- if (apiKey) {
|
|
|
- config["headers"] = { Authorization: `Bearer ${apiKey}` }
|
|
|
- }
|
|
|
|
|
|
- const response = await axios.get("https://router.requesty.ai/v1/models", config)
|
|
|
-
|
|
|
- if (response.data) {
|
|
|
- const rawModels = response.data.data
|
|
|
- const parsePrice = (price: any) => {
|
|
|
- if (price) {
|
|
|
- return parseFloat(price) * 1_000_000
|
|
|
- }
|
|
|
- return undefined
|
|
|
- }
|
|
|
- for (const rawModel of rawModels) {
|
|
|
- const modelInfo: ModelInfo = {
|
|
|
- maxTokens: rawModel.max_output_tokens,
|
|
|
- contextWindow: rawModel.context_window,
|
|
|
- supportsImages: rawModel.support_image,
|
|
|
- supportsComputerUse: rawModel.support_computer_use,
|
|
|
- supportsPromptCache: rawModel.supports_caching,
|
|
|
- inputPrice: parsePrice(rawModel.input_price),
|
|
|
- outputPrice: parsePrice(rawModel.output_price),
|
|
|
- description: rawModel.description,
|
|
|
- cacheWritesPrice: parsePrice(rawModel.caching_price),
|
|
|
- cacheReadsPrice: parsePrice(rawModel.cached_price),
|
|
|
- }
|
|
|
-
|
|
|
- models[rawModel.id] = modelInfo
|
|
|
- }
|
|
|
- } else {
|
|
|
- this.outputChannel.appendLine("Invalid response from Requesty API")
|
|
|
- }
|
|
|
- await fs.writeFile(requestyModelsFilePath, JSON.stringify(models))
|
|
|
- } catch (error) {
|
|
|
- this.outputChannel.appendLine(
|
|
|
- `Error fetching Requesty models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- await this.postMessageToWebview({ type: "requestyModels", requestyModels: models })
|
|
|
- return models
|
|
|
+ return undefined
|
|
|
}
|
|
|
|
|
|
// OpenRouter
|
|
|
@@ -1970,11 +1835,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
// await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome
|
|
|
}
|
|
|
|
|
|
- private async ensureCacheDirectoryExists(): Promise<string> {
|
|
|
- const cacheDir = path.join(this.context.globalStorageUri.fsPath, "cache")
|
|
|
- await fs.mkdir(cacheDir, { recursive: true })
|
|
|
- return cacheDir
|
|
|
- }
|
|
|
+ // Glama
|
|
|
|
|
|
async handleGlamaCallback(code: string) {
|
|
|
let apiKey: string
|
|
|
@@ -2005,225 +1866,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
// await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome
|
|
|
}
|
|
|
|
|
|
- private async readModelsFromCache(filename: string): Promise<Record<string, ModelInfo> | undefined> {
|
|
|
- const filePath = path.join(await this.ensureCacheDirectoryExists(), filename)
|
|
|
- const fileExists = await fileExistsAtPath(filePath)
|
|
|
- if (fileExists) {
|
|
|
- const fileContents = await fs.readFile(filePath, "utf8")
|
|
|
- return JSON.parse(fileContents)
|
|
|
- }
|
|
|
- return undefined
|
|
|
- }
|
|
|
-
|
|
|
- async readGlamaModels(): Promise<Record<string, ModelInfo> | undefined> {
|
|
|
- return this.readModelsFromCache(GlobalFileNames.glamaModels)
|
|
|
- }
|
|
|
-
|
|
|
- async refreshGlamaModels() {
|
|
|
- const glamaModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.glamaModels)
|
|
|
-
|
|
|
- const models: Record<string, ModelInfo> = {}
|
|
|
- try {
|
|
|
- const response = await axios.get("https://glama.ai/api/gateway/v1/models")
|
|
|
- /*
|
|
|
- {
|
|
|
- "added": "2024-12-24T15:12:49.324Z",
|
|
|
- "capabilities": [
|
|
|
- "adjustable_safety_settings",
|
|
|
- "caching",
|
|
|
- "code_execution",
|
|
|
- "function_calling",
|
|
|
- "json_mode",
|
|
|
- "json_schema",
|
|
|
- "system_instructions",
|
|
|
- "tuning",
|
|
|
- "input:audio",
|
|
|
- "input:image",
|
|
|
- "input:text",
|
|
|
- "input:video",
|
|
|
- "output:text"
|
|
|
- ],
|
|
|
- "id": "google-vertex/gemini-1.5-flash-002",
|
|
|
- "maxTokensInput": 1048576,
|
|
|
- "maxTokensOutput": 8192,
|
|
|
- "pricePerToken": {
|
|
|
- "cacheRead": null,
|
|
|
- "cacheWrite": null,
|
|
|
- "input": "0.000000075",
|
|
|
- "output": "0.0000003"
|
|
|
- }
|
|
|
- }
|
|
|
- */
|
|
|
- if (response.data) {
|
|
|
- const rawModels = response.data
|
|
|
- const parsePrice = (price: any) => {
|
|
|
- if (price) {
|
|
|
- return parseFloat(price) * 1_000_000
|
|
|
- }
|
|
|
- return undefined
|
|
|
- }
|
|
|
- for (const rawModel of rawModels) {
|
|
|
- const modelInfo: ModelInfo = {
|
|
|
- maxTokens: rawModel.maxTokensOutput,
|
|
|
- contextWindow: rawModel.maxTokensInput,
|
|
|
- supportsImages: rawModel.capabilities?.includes("input:image"),
|
|
|
- supportsComputerUse: rawModel.capabilities?.includes("computer_use"),
|
|
|
- supportsPromptCache: rawModel.capabilities?.includes("caching"),
|
|
|
- inputPrice: parsePrice(rawModel.pricePerToken?.input),
|
|
|
- outputPrice: parsePrice(rawModel.pricePerToken?.output),
|
|
|
- description: undefined,
|
|
|
- cacheWritesPrice: parsePrice(rawModel.pricePerToken?.cacheWrite),
|
|
|
- cacheReadsPrice: parsePrice(rawModel.pricePerToken?.cacheRead),
|
|
|
- }
|
|
|
-
|
|
|
- models[rawModel.id] = modelInfo
|
|
|
- }
|
|
|
- } else {
|
|
|
- this.outputChannel.appendLine("Invalid response from Glama API")
|
|
|
- }
|
|
|
- await fs.writeFile(glamaModelsFilePath, JSON.stringify(models))
|
|
|
- } catch (error) {
|
|
|
- this.outputChannel.appendLine(
|
|
|
- `Error fetching Glama models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- await this.postMessageToWebview({ type: "glamaModels", glamaModels: models })
|
|
|
- return models
|
|
|
- }
|
|
|
-
|
|
|
- async readOpenRouterModels(): Promise<Record<string, ModelInfo> | undefined> {
|
|
|
- return this.readModelsFromCache(GlobalFileNames.openRouterModels)
|
|
|
- }
|
|
|
-
|
|
|
- async refreshOpenRouterModels() {
|
|
|
- const openRouterModelsFilePath = path.join(
|
|
|
- await this.ensureCacheDirectoryExists(),
|
|
|
- GlobalFileNames.openRouterModels,
|
|
|
- )
|
|
|
-
|
|
|
- const models: Record<string, ModelInfo> = {}
|
|
|
-
|
|
|
- try {
|
|
|
- const response = await axios.get("https://openrouter.ai/api/v1/models")
|
|
|
-
|
|
|
- if (response.data?.data) {
|
|
|
- const rawModels = response.data.data
|
|
|
- const parsePrice = (price: any) => {
|
|
|
- if (price) {
|
|
|
- return parseFloat(price) * 1_000_000
|
|
|
- }
|
|
|
- return undefined
|
|
|
- }
|
|
|
-
|
|
|
- for (const rawModel of rawModels) {
|
|
|
- const modelInfo: ModelInfo = {
|
|
|
- maxTokens: rawModel.top_provider?.max_completion_tokens,
|
|
|
- contextWindow: rawModel.context_length,
|
|
|
- supportsImages: rawModel.architecture?.modality?.includes("image"),
|
|
|
- supportsPromptCache: false,
|
|
|
- inputPrice: parsePrice(rawModel.pricing?.prompt),
|
|
|
- outputPrice: parsePrice(rawModel.pricing?.completion),
|
|
|
- description: rawModel.description,
|
|
|
- }
|
|
|
-
|
|
|
- switch (rawModel.id) {
|
|
|
- case "anthropic/claude-3.7-sonnet":
|
|
|
- case "anthropic/claude-3.7-sonnet:beta":
|
|
|
- case "anthropic/claude-3.5-sonnet":
|
|
|
- case "anthropic/claude-3.5-sonnet:beta":
|
|
|
- // NOTE: this needs to be synced with api.ts/openrouter default model info.
|
|
|
- modelInfo.supportsComputerUse = true
|
|
|
- modelInfo.supportsPromptCache = true
|
|
|
- modelInfo.cacheWritesPrice = 3.75
|
|
|
- modelInfo.cacheReadsPrice = 0.3
|
|
|
- break
|
|
|
- case "anthropic/claude-3.5-sonnet-20240620":
|
|
|
- case "anthropic/claude-3.5-sonnet-20240620:beta":
|
|
|
- modelInfo.supportsPromptCache = true
|
|
|
- modelInfo.cacheWritesPrice = 3.75
|
|
|
- modelInfo.cacheReadsPrice = 0.3
|
|
|
- break
|
|
|
- case "anthropic/claude-3-5-haiku":
|
|
|
- case "anthropic/claude-3-5-haiku:beta":
|
|
|
- case "anthropic/claude-3-5-haiku-20241022":
|
|
|
- case "anthropic/claude-3-5-haiku-20241022:beta":
|
|
|
- case "anthropic/claude-3.5-haiku":
|
|
|
- case "anthropic/claude-3.5-haiku:beta":
|
|
|
- case "anthropic/claude-3.5-haiku-20241022":
|
|
|
- case "anthropic/claude-3.5-haiku-20241022:beta":
|
|
|
- modelInfo.supportsPromptCache = true
|
|
|
- modelInfo.cacheWritesPrice = 1.25
|
|
|
- modelInfo.cacheReadsPrice = 0.1
|
|
|
- break
|
|
|
- case "anthropic/claude-3-opus":
|
|
|
- case "anthropic/claude-3-opus:beta":
|
|
|
- modelInfo.supportsPromptCache = true
|
|
|
- modelInfo.cacheWritesPrice = 18.75
|
|
|
- modelInfo.cacheReadsPrice = 1.5
|
|
|
- break
|
|
|
- case "anthropic/claude-3-haiku":
|
|
|
- case "anthropic/claude-3-haiku:beta":
|
|
|
- modelInfo.supportsPromptCache = true
|
|
|
- modelInfo.cacheWritesPrice = 0.3
|
|
|
- modelInfo.cacheReadsPrice = 0.03
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- models[rawModel.id] = modelInfo
|
|
|
- }
|
|
|
- } else {
|
|
|
- this.outputChannel.appendLine("Invalid response from OpenRouter API")
|
|
|
- }
|
|
|
- await fs.writeFile(openRouterModelsFilePath, JSON.stringify(models))
|
|
|
- } catch (error) {
|
|
|
- this.outputChannel.appendLine(
|
|
|
- `Error fetching OpenRouter models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- await this.postMessageToWebview({ type: "openRouterModels", openRouterModels: models })
|
|
|
- return models
|
|
|
- }
|
|
|
-
|
|
|
- async readUnboundModels(): Promise<Record<string, ModelInfo> | undefined> {
|
|
|
- return this.readModelsFromCache(GlobalFileNames.unboundModels)
|
|
|
- }
|
|
|
-
|
|
|
- async refreshUnboundModels() {
|
|
|
- const unboundModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.unboundModels)
|
|
|
-
|
|
|
- const models: Record<string, ModelInfo> = {}
|
|
|
- try {
|
|
|
- const response = await axios.get("https://api.getunbound.ai/models")
|
|
|
-
|
|
|
- if (response.data) {
|
|
|
- const rawModels: Record<string, any> = response.data
|
|
|
- for (const [modelId, model] of Object.entries(rawModels)) {
|
|
|
- models[modelId] = {
|
|
|
- maxTokens: model?.maxTokens ? parseInt(model.maxTokens) : undefined,
|
|
|
- contextWindow: model?.contextWindow ? parseInt(model.contextWindow) : 0,
|
|
|
- supportsImages: model?.supportsImages ?? false,
|
|
|
- supportsPromptCache: model?.supportsPromptCaching ?? false,
|
|
|
- supportsComputerUse: model?.supportsComputerUse ?? false,
|
|
|
- inputPrice: model?.inputTokenPrice ? parseFloat(model.inputTokenPrice) : undefined,
|
|
|
- outputPrice: model?.outputTokenPrice ? parseFloat(model.outputTokenPrice) : undefined,
|
|
|
- cacheWritesPrice: model?.cacheWritePrice ? parseFloat(model.cacheWritePrice) : undefined,
|
|
|
- cacheReadsPrice: model?.cacheReadPrice ? parseFloat(model.cacheReadPrice) : undefined,
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- await fs.writeFile(unboundModelsFilePath, JSON.stringify(models))
|
|
|
- } catch (error) {
|
|
|
- this.outputChannel.appendLine(
|
|
|
- `Error fetching Unbound models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- await this.postMessageToWebview({ type: "unboundModels", unboundModels: models })
|
|
|
- return models
|
|
|
- }
|
|
|
-
|
|
|
// Task history
|
|
|
|
|
|
async getTaskWithId(id: string): Promise<{
|
|
|
@@ -2810,26 +2452,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|
|
return await this.context.globalState.get(key)
|
|
|
}
|
|
|
|
|
|
- // workspace
|
|
|
-
|
|
|
- private async updateWorkspaceState(key: string, value: any) {
|
|
|
- await this.context.workspaceState.update(key, value)
|
|
|
- }
|
|
|
-
|
|
|
- private async getWorkspaceState(key: string) {
|
|
|
- return await this.context.workspaceState.get(key)
|
|
|
- }
|
|
|
-
|
|
|
- // private async clearState() {
|
|
|
- // this.context.workspaceState.keys().forEach((key) => {
|
|
|
- // this.context.workspaceState.update(key, undefined)
|
|
|
- // })
|
|
|
- // this.context.globalState.keys().forEach((key) => {
|
|
|
- // this.context.globalState.update(key, undefined)
|
|
|
- // })
|
|
|
- // this.context.secrets.delete("apiKey")
|
|
|
- // }
|
|
|
-
|
|
|
// secrets
|
|
|
|
|
|
public async storeSecret(key: SecretKey, value?: string) {
|