mirror of
https://github.com/cline/cline.git
synced 2026-09-05 05:02:27 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4532f3f750 | |||
| dcf4b9ad1e | |||
| 76d4ea9964 | |||
| f7d2890d5a | |||
| 1cbfc7197e | |||
| 4d8f9ea1a3 | |||
| 40b0b0c236 | |||
| 8c5e1289bf | |||
| 4cddde58ed | |||
| 9d45accc3b | |||
| 57760c1e20 | |||
| fc33e8dbd9 | |||
| b1dcaf576a | |||
| 419f2cea73 | |||
| 54d8c695a3 | |||
| 5a62ab8564 | |||
| f81afb51b5 |
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## [4.0.4]
|
||||
|
||||
### Changed
|
||||
|
||||
- Fully remove the ClinePass feature flag so ClinePass is available everywhere in the UI — onboarding, settings, the welcome promo banner, and the credit-limit "Switch to ClinePass" action.
|
||||
|
||||
## [4.0.3]
|
||||
|
||||
### Changed
|
||||
|
||||
- Enable the ClinePass provider for all users by removing the feature-flag gate that previously fell back to the standard Cline provider.
|
||||
|
||||
## [4.0.2]
|
||||
|
||||
### Added
|
||||
|
||||
- Add reasoning effort support (including `xhigh`) for DeepSeek thinking models.
|
||||
- Improve the ClinePass provider experience with clearer reasoning controls and model selection.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Show reasoning effort controls for ClinePass models and align ClinePass model resolution with the rest of the provider.
|
||||
- Prefer canonical Cline Z.ai model ids and polish ClinePass and Z.ai model metadata.
|
||||
- Fix environment variable replacement in the webview.
|
||||
- Default focus chain settings in webview state so the toggle reflects the correct value on load.
|
||||
|
||||
## [4.0.1]
|
||||
|
||||
### Changed
|
||||
|
||||
- Roll the stable VS Code extension back to the pre-SDK-migration codebase to resolve regressions reported in 4.0.0. This release ships the 3.89.2 extension code under a higher version number so existing 4.0.0 users receive the update. SDK-migration work continues separately on `main`.
|
||||
|
||||
## [3.89.2]
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.89.2",
|
||||
"version": "4.0.4",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -387,6 +387,7 @@
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.mjs --production",
|
||||
"protos": "node scripts/build-proto.mjs",
|
||||
"models": "node scripts/generate-models-dev-catalog.mjs",
|
||||
"protos-python": "node scripts/build-python-proto.mjs",
|
||||
"download-ripgrep": "node scripts/download-ripgrep.mjs",
|
||||
"postprotos": "biome format --config-path ./biome.jsonc src/shared/proto src/generated webview-ui/src/services/grpc-client.ts --write --no-errors-on-unmatched",
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const require = createRequire(import.meta.url);
|
||||
const execFileAsync = promisify(execFile);
|
||||
const repoRoot = path.resolve(__dirname, "../../..");
|
||||
const sdkCatalogPath = path.join(
|
||||
repoRoot,
|
||||
"sdk/packages/llms/src/catalog/catalog.generated.ts",
|
||||
);
|
||||
const outputPath = path.join(
|
||||
__dirname,
|
||||
"../src/shared/models/models-dev-catalog.generated.ts",
|
||||
);
|
||||
|
||||
const { GENERATED_PROVIDER_MODELS } = await import(
|
||||
pathToFileURL(sdkCatalogPath).href
|
||||
);
|
||||
|
||||
const providerLabels = Object.fromEntries([
|
||||
["anthropic", "Anthropic"],
|
||||
["bedrock", "Amazon Bedrock"],
|
||||
["vertex", "GCP Vertex AI"],
|
||||
["gemini", "Google Gemini"],
|
||||
["openai-native", "OpenAI"],
|
||||
["openai-codex", "ChatGPT Subscription"],
|
||||
["deepseek", "DeepSeek"],
|
||||
["xai", "xAI"],
|
||||
["together", "Together"],
|
||||
["sapaicore", "SAP AI Core"],
|
||||
["fireworks", "Fireworks AI"],
|
||||
["groq", "Groq"],
|
||||
["cerebras", "Cerebras"],
|
||||
["sambanova", "SambaNova"],
|
||||
["nebius", "Nebius AI Studio"],
|
||||
["huggingface", "Hugging Face"],
|
||||
["openrouter", "OpenRouter"],
|
||||
["vercel-ai-gateway", "Vercel AI Gateway"],
|
||||
["aihubmix", "AIhubmix"],
|
||||
["baseten", "Baseten"],
|
||||
["zai", "Z AI"],
|
||||
["lmstudio", "LM Studio"],
|
||||
["requesty", "Requesty"],
|
||||
["moonshot", "Moonshot"],
|
||||
["minimax", "MiniMax"],
|
||||
["wandb", "W&B Inference by CoreWeave"],
|
||||
["mistral", "Mistral"],
|
||||
["doubao", "Bytedance Doubao"],
|
||||
["qwen", "Alibaba Qwen"],
|
||||
["huawei-cloud-maas", "Huawei Cloud MaaS"],
|
||||
["hicap", "Hicap"],
|
||||
["nousResearch", "NousResearch"],
|
||||
["openai", "OpenAI Compatible"],
|
||||
["ollama", "Ollama"],
|
||||
["litellm", "LiteLLM"],
|
||||
["claude-code", "Claude Code"],
|
||||
["qwen-code", "Qwen Code"],
|
||||
["dify", "Dify.ai"],
|
||||
["oca", "Oracle Code Assist"],
|
||||
["vscode-lm", "GitHub Copilot"],
|
||||
["cline", "Cline"],
|
||||
["cline-pass", "ClinePass"],
|
||||
["asksage", "AskSage"],
|
||||
]);
|
||||
|
||||
const providerOrder = [
|
||||
"cline",
|
||||
"cline-pass",
|
||||
"openai-codex",
|
||||
"gemini",
|
||||
"openai",
|
||||
"anthropic",
|
||||
"bedrock",
|
||||
"vscode-lm",
|
||||
"deepseek",
|
||||
"openai-native",
|
||||
"openrouter",
|
||||
"ollama",
|
||||
"vertex",
|
||||
"litellm",
|
||||
"claude-code",
|
||||
"sapaicore",
|
||||
"mistral",
|
||||
"zai",
|
||||
"groq",
|
||||
"cerebras",
|
||||
"vercel-ai-gateway",
|
||||
"baseten",
|
||||
"requesty",
|
||||
"fireworks",
|
||||
"together",
|
||||
"qwen",
|
||||
"qwen-code",
|
||||
"doubao",
|
||||
"lmstudio",
|
||||
"moonshot",
|
||||
"huggingface",
|
||||
"nebius",
|
||||
"asksage",
|
||||
"xai",
|
||||
"sambanova",
|
||||
"huawei-cloud-maas",
|
||||
"dify",
|
||||
"oca",
|
||||
"minimax",
|
||||
"hicap",
|
||||
"aihubmix",
|
||||
"nousResearch",
|
||||
"wandb",
|
||||
];
|
||||
|
||||
function toLegacyModelInfo(model) {
|
||||
const capabilities = new Set(model.capabilities ?? []);
|
||||
const output = {
|
||||
name: model.name,
|
||||
maxTokens: model.maxTokens,
|
||||
contextWindow: model.contextWindow ?? model.maxInputTokens,
|
||||
supportsImages: capabilities.has("images"),
|
||||
supportsPromptCache: capabilities.has("prompt-cache"),
|
||||
supportsReasoning: capabilities.has("reasoning"),
|
||||
inputPrice: model.pricing?.input ?? 0,
|
||||
outputPrice: model.pricing?.output ?? 0,
|
||||
cacheWritesPrice: model.pricing?.cacheWrite ?? 0,
|
||||
cacheReadsPrice: model.pricing?.cacheRead ?? 0,
|
||||
supportsTools: capabilities.has("tools"),
|
||||
};
|
||||
|
||||
for (const key of Object.keys(output)) {
|
||||
if (output[key] === undefined) {
|
||||
delete output[key];
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const providerModels = Object.fromEntries(
|
||||
Object.entries(GENERATED_PROVIDER_MODELS.providers).map(
|
||||
([providerId, models]) => [
|
||||
providerId,
|
||||
Object.fromEntries(
|
||||
Object.entries(models).map(([modelId, model]) => [
|
||||
modelId,
|
||||
toLegacyModelInfo(model),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const providerOptions = providerOrder
|
||||
.filter((value) => providerLabels[value])
|
||||
.map((value) => ({ value, label: providerLabels[value] }));
|
||||
|
||||
const file = `/**
|
||||
* Auto-generated from @cline/llms models.dev catalog.
|
||||
*
|
||||
* Source: sdk/packages/llms/src/catalog/catalog.generated.ts
|
||||
* Do not edit by hand; run apps/vscode/scripts/generate-models-dev-catalog.mjs after updating the SDK model catalog.
|
||||
*/
|
||||
|
||||
import type { ApiProvider, ModelInfo, OpenAiCompatibleModelInfo } from "../api"
|
||||
|
||||
export const modelsDevProviderModels = ${JSON.stringify(providerModels, null, "\t")} as const satisfies Record<string, Record<string, ModelInfo | OpenAiCompatibleModelInfo>>
|
||||
|
||||
export const modelsDevProviderOptions = ${JSON.stringify(providerOptions, null, "\t")} as const satisfies ReadonlyArray<{ value: ApiProvider; label: string }>
|
||||
|
||||
export function getModelsDevProviderModels(provider: ApiProvider | string): Record<string, ModelInfo> {
|
||||
\treturn (modelsDevProviderModels[provider as keyof typeof modelsDevProviderModels] ?? {}) as Record<string, ModelInfo>
|
||||
}
|
||||
|
||||
export const modelsDevAnthropicModels = getModelsDevProviderModels("anthropic")
|
||||
export const modelsDevBedrockModels = getModelsDevProviderModels("bedrock")
|
||||
export const modelsDevCerebrasModels = getModelsDevProviderModels("cerebras")
|
||||
export const modelsDevDeepSeekModels = getModelsDevProviderModels("deepseek")
|
||||
export const modelsDevDoubaoModels = getModelsDevProviderModels("doubao")
|
||||
export const modelsDevFireworksModels = getModelsDevProviderModels("fireworks")
|
||||
export const modelsDevGeminiModels = getModelsDevProviderModels("gemini")
|
||||
export const modelsDevGroqModels = getModelsDevProviderModels("groq")
|
||||
export const modelsDevHuggingFaceModels = getModelsDevProviderModels("huggingface")
|
||||
export const modelsDevMinimaxModels = getModelsDevProviderModels("minimax")
|
||||
export const modelsDevMistralModels = getModelsDevProviderModels("mistral")
|
||||
export const modelsDevMoonshotModels = getModelsDevProviderModels("moonshot")
|
||||
export const modelsDevNebiusModels = getModelsDevProviderModels("nebius")
|
||||
export const modelsDevNousResearchModels = getModelsDevProviderModels("nousResearch")
|
||||
export const modelsDevOpenAiCodexModels = getModelsDevProviderModels("openai-codex")
|
||||
export const modelsDevOpenAiNativeModels = getModelsDevProviderModels("openai-native")
|
||||
export const modelsDevSambanovaModels = getModelsDevProviderModels("sambanova")
|
||||
export const modelsDevSapAiCoreModels = getModelsDevProviderModels("sapaicore")
|
||||
export const modelsDevVertexModels = getModelsDevProviderModels("vertex")
|
||||
export const modelsDevWandbModels = getModelsDevProviderModels("wandb")
|
||||
export const modelsDevXaiModels = getModelsDevProviderModels("xai")
|
||||
`;
|
||||
|
||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||
await fs.writeFile(outputPath, file);
|
||||
await execFileAsync(
|
||||
process.execPath,
|
||||
[
|
||||
require.resolve("@biomejs/biome/bin/biome"),
|
||||
"format",
|
||||
"--write",
|
||||
outputPath,
|
||||
],
|
||||
{ cwd: path.resolve(__dirname, "..") },
|
||||
);
|
||||
@@ -1,8 +1,13 @@
|
||||
import { ApiConfiguration, clinePassDefaultModelId, ModelInfo, QwenApiRegions, resolveClinePassModelInfo } from "@shared/api"
|
||||
import {
|
||||
ApiConfiguration,
|
||||
buildModelInfoNameMap,
|
||||
clinePassDefaultModelId,
|
||||
ModelInfo,
|
||||
QwenApiRegions,
|
||||
resolveClinePassModelInfo,
|
||||
} from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { featureFlagsService } from "@/services/feature-flags"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { FeatureFlag } from "@/shared/services/feature-flags/feature-flags"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { AIhubmixHandler } from "./providers/aihubmix"
|
||||
@@ -80,10 +85,7 @@ function createHandlerForProvider(
|
||||
options: Omit<ApiConfiguration, "apiProvider">,
|
||||
mode: Mode,
|
||||
): ApiHandler {
|
||||
const effectiveApiProvider =
|
||||
apiProvider === "cline-pass" && !featureFlagsService.getBooleanFlagEnabled(FeatureFlag.CLINE_PASS) ? "cline" : apiProvider
|
||||
|
||||
switch (effectiveApiProvider) {
|
||||
switch (apiProvider) {
|
||||
case "anthropic":
|
||||
return new AnthropicHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
@@ -204,6 +206,7 @@ function createHandlerForProvider(
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
deepSeekApiKey: options.deepSeekApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
})
|
||||
case "requesty":
|
||||
return new RequestyHandler({
|
||||
@@ -292,7 +295,12 @@ function createHandlerForProvider(
|
||||
const clineModelId = configuredClinePassModelId?.startsWith("cline-pass/")
|
||||
? configuredClinePassModelId
|
||||
: clinePassDefaultModelId
|
||||
const clineModelInfo = configuredClinePassModelInfo || resolveClinePassModelInfo(clineModelId)
|
||||
const clineModelInfo = resolveClinePassModelInfo(
|
||||
clineModelId,
|
||||
configuredClinePassModelInfo
|
||||
? buildModelInfoNameMap({ [clineModelId]: configuredClinePassModelInfo })
|
||||
: undefined,
|
||||
)
|
||||
return new ClineHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
clineAccountId: options.clineAccountId,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { DeepSeekModelId, deepSeekDefaultModelId, deepSeekModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import type {
|
||||
ChatCompletionReasoningEffort,
|
||||
ChatCompletionTool as OpenAITool,
|
||||
} from "openai/resources/chat/completions"
|
||||
import { buildExternalBasicHeaders } from "@/services/EnvUtils"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
@@ -15,6 +18,7 @@ import { getOpenAIToolParams, ToolCallProcessor } from "../transform/tool-call-p
|
||||
interface DeepSeekHandlerOptions extends CommonApiHandlerOptions {
|
||||
deepSeekApiKey?: string
|
||||
apiModelId?: string
|
||||
reasoningEffort?: string
|
||||
}
|
||||
|
||||
export class DeepSeekHandler implements ApiHandler {
|
||||
@@ -98,6 +102,11 @@ export class DeepSeekHandler implements ApiHandler {
|
||||
stream_options: { include_usage: true },
|
||||
// Only set temperature for non-thinking models
|
||||
...(isDeepSeekThinkingModel ? {} : { temperature: 0 }),
|
||||
// DeepSeek thinking models accept reasoning effort (low/medium map to high, xhigh maps to max).
|
||||
// "none" isn't a valid DeepSeek value, so omit it and let the API use its default.
|
||||
...(isDeepSeekThinkingModel && this.options.reasoningEffort && this.options.reasoningEffort !== "none"
|
||||
? { reasoning_effort: this.options.reasoningEffort as ChatCompletionReasoningEffort }
|
||||
: {}),
|
||||
...getOpenAIToolParams(tools),
|
||||
})
|
||||
|
||||
|
||||
@@ -114,4 +114,87 @@ describe("createOpenRouterStream", () => {
|
||||
should(payload.temperature).equal(undefined)
|
||||
should(payload.top_p).equal(undefined)
|
||||
})
|
||||
|
||||
it("includes reasoning for Claude budget-based reasoning models", async () => {
|
||||
const { client, create } = createClient()
|
||||
|
||||
await createOpenRouterStream(
|
||||
client as any,
|
||||
"system prompt",
|
||||
[{ role: "user", content: "hello" }] as any,
|
||||
{
|
||||
id: "anthropic/claude-sonnet-4.5",
|
||||
info: createModelInfo(64_000),
|
||||
},
|
||||
undefined,
|
||||
16_384,
|
||||
)
|
||||
|
||||
const payload = create.firstCall.args[0] as any
|
||||
payload.should.have.property("include_reasoning", true)
|
||||
payload.reasoning.should.deepEqual({ max_tokens: 16_384 })
|
||||
should(payload.temperature).equal(undefined)
|
||||
})
|
||||
|
||||
it("sends reasoning effort instead of token budgets for supported OpenRouter/Cline model families", async () => {
|
||||
for (const modelId of [
|
||||
"zai/glm-5.2",
|
||||
"z-ai/glm-5.2",
|
||||
"moonshotai/kimi-k2-thinking",
|
||||
"accounts/fireworks/models/minimax-m3",
|
||||
"provider/mimo-vl",
|
||||
"qwen/qwen3.7-max",
|
||||
"deepseek/deepseek-r1",
|
||||
]) {
|
||||
const { client, create } = createClient()
|
||||
|
||||
await createOpenRouterStream(
|
||||
client as any,
|
||||
"system prompt",
|
||||
[{ role: "user", content: "hello" }] as any,
|
||||
{
|
||||
id: modelId,
|
||||
info: { ...createModelInfo(131_072), thinkingConfig: { maxBudget: 16_384 } },
|
||||
},
|
||||
"high",
|
||||
16_384,
|
||||
)
|
||||
|
||||
const payload = create.firstCall.args[0] as any
|
||||
payload.should.have.property("include_reasoning", true)
|
||||
payload.reasoning.should.deepEqual({ effort: "high" })
|
||||
}
|
||||
})
|
||||
|
||||
it("does not send reasoning effort for ClinePass requests when unset", async () => {
|
||||
const { client, create } = createClient()
|
||||
|
||||
await createOpenRouterStream(client as any, "system prompt", [{ role: "user", content: "hello" }] as any, {
|
||||
id: "cline-pass/glm-5.2",
|
||||
info: createModelInfo(131_072),
|
||||
})
|
||||
|
||||
const payload = create.firstCall.args[0] as any
|
||||
payload.should.have.property("include_reasoning", true)
|
||||
payload.should.not.have.property("reasoning")
|
||||
})
|
||||
|
||||
it("sends the selected reasoning effort for ClinePass requests", async () => {
|
||||
const { client, create } = createClient()
|
||||
|
||||
await createOpenRouterStream(
|
||||
client as any,
|
||||
"system prompt",
|
||||
[{ role: "user", content: "hello" }] as any,
|
||||
{
|
||||
id: "cline-pass/glm-5.1",
|
||||
info: createModelInfo(131_072),
|
||||
},
|
||||
"high",
|
||||
)
|
||||
|
||||
const payload = create.firstCall.args[0] as any
|
||||
payload.should.have.property("include_reasoning", true)
|
||||
payload.reasoning.should.deepEqual({ effort: "high" })
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -135,4 +135,72 @@ describe("refreshClineModels", () => {
|
||||
expect(fable1m.contextWindow).to.equal(1_000_000)
|
||||
expect(fable1m.tiers).to.not.equal(undefined)
|
||||
})
|
||||
|
||||
it("prefers Vercel-style Z.ai IDs when the Cline model list includes OpenRouter aliases", async () => {
|
||||
sandbox.stub(getFeatureFlagsService(), "getBooleanFlagEnabled").callsFake((flag) => {
|
||||
return flag === FeatureFlag.EXTENSION_CLINE_MODELS_ENDPOINT
|
||||
})
|
||||
sandbox.stub(ClineEnv, "config").returns({
|
||||
environment: Environment.production,
|
||||
appBaseUrl: "https://app.cline-mock.bot",
|
||||
apiBaseUrl: "https://api.cline-mock.bot",
|
||||
mcpBaseUrl: "https://api.cline-mock.bot/v1/mcp",
|
||||
})
|
||||
sandbox.stub(StateManager, "get").returns({
|
||||
getModelsCache: () => null,
|
||||
setModelsCache: () => {},
|
||||
} as unknown as StateManager)
|
||||
sandbox.stub(disk, "ensureCacheDirectoryExists").resolves("/tmp")
|
||||
sandbox.stub(fs, "writeFile").resolves()
|
||||
sandbox.stub(axios, "get").resolves({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
id: "z-ai/glm-5.2",
|
||||
name: "OpenRouter GLM 5.2",
|
||||
description: "OpenRouter alias",
|
||||
context_length: 128_000,
|
||||
top_provider: {
|
||||
max_completion_tokens: 8_192,
|
||||
context_length: 128_000,
|
||||
is_moderated: false,
|
||||
},
|
||||
architecture: {
|
||||
modality: "text->text",
|
||||
},
|
||||
pricing: {
|
||||
prompt: "0.00000098",
|
||||
completion: "0.00000308",
|
||||
},
|
||||
supported_parameters: ["include_reasoning", "reasoning"],
|
||||
},
|
||||
{
|
||||
id: "zai/glm-5.2",
|
||||
name: "GLM 5.2",
|
||||
description: "Vercel canonical ID",
|
||||
context_length: 1_000_000,
|
||||
top_provider: {
|
||||
max_completion_tokens: 131_072,
|
||||
context_length: 1_000_000,
|
||||
is_moderated: false,
|
||||
},
|
||||
architecture: {
|
||||
modality: "text->text",
|
||||
},
|
||||
pricing: {
|
||||
prompt: "0.0000015",
|
||||
completion: "0.0000045",
|
||||
},
|
||||
supported_parameters: ["include_reasoning", "reasoning"],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const models = await refreshClineModels({} as Controller)
|
||||
|
||||
expect(models["zai/glm-5.2"]).to.not.equal(undefined)
|
||||
expect(models["zai/glm-5.2"].contextWindow).to.equal(1_000_000)
|
||||
expect(models["z-ai/glm-5.2"]).to.equal(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
+95
@@ -127,4 +127,99 @@ describe("refreshClineRecommendedModels", () => {
|
||||
expect(axiosGetStub.calledOnce).to.equal(true);
|
||||
expect(secondResult).to.deep.equal(firstResult);
|
||||
});
|
||||
|
||||
it("normalizes Cline provider Z.ai recommended IDs to the Cline API alias", async () => {
|
||||
sandbox.stub(ClineEnv, "config").returns({
|
||||
environment: Environment.production,
|
||||
appBaseUrl: "https://app.cline-mock.bot",
|
||||
apiBaseUrl: "https://api.cline-mock.bot",
|
||||
mcpBaseUrl: "https://api.cline-mock.bot/v1/mcp",
|
||||
});
|
||||
sandbox.stub(disk, "ensureCacheDirectoryExists").resolves("/tmp");
|
||||
sandbox.stub(fs, "writeFile").resolves();
|
||||
sandbox.stub(axios, "get").resolves({
|
||||
data: {
|
||||
recommended: [
|
||||
{
|
||||
id: "zai/glm-5.2",
|
||||
name: "zai/glm-5.2",
|
||||
description: "Recommended GLM",
|
||||
},
|
||||
],
|
||||
free: [
|
||||
{
|
||||
id: "zai/free-glm",
|
||||
description: "Free GLM",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await refreshClineRecommendedModels();
|
||||
|
||||
expect(result.recommended[0]).to.include({
|
||||
id: "z-ai/glm-5.2",
|
||||
name: "z-ai/glm-5.2",
|
||||
});
|
||||
expect(result.free[0]).to.include({
|
||||
id: "z-ai/free-glm",
|
||||
name: "z-ai/free-glm",
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes cached Cline provider Z.ai recommended IDs", async () => {
|
||||
sandbox.stub(ClineEnv, "config").returns({
|
||||
environment: Environment.production,
|
||||
appBaseUrl: "https://app.cline-mock.bot",
|
||||
apiBaseUrl: "https://api.cline-mock.bot",
|
||||
mcpBaseUrl: "https://api.cline-mock.bot/v1/mcp",
|
||||
});
|
||||
sandbox.stub(disk, "ensureCacheDirectoryExists").resolves("/tmp");
|
||||
sandbox.stub(axios, "get").rejects(new Error("network unavailable"));
|
||||
sandbox.stub(fs, "access").resolves();
|
||||
sandbox.stub(fs, "readFile").resolves(
|
||||
JSON.stringify({
|
||||
recommended: [
|
||||
{
|
||||
id: "zai/glm-5.2",
|
||||
name: "zai/glm-5.2",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await refreshClineRecommendedModels();
|
||||
|
||||
expect(result.recommended.map((model) => model.id)).to.deep.equal(["z-ai/glm-5.2"]);
|
||||
expect(result.recommended.map((model) => model.name)).to.deep.equal(["z-ai/glm-5.2"]);
|
||||
});
|
||||
|
||||
it("prefers canonical ClinePass Z.ai IDs when aliases are also present", async () => {
|
||||
sandbox.stub(ClineEnv, "config").returns({
|
||||
environment: Environment.production,
|
||||
appBaseUrl: "https://app.cline-mock.bot",
|
||||
apiBaseUrl: "https://api.cline-mock.bot",
|
||||
mcpBaseUrl: "https://api.cline-mock.bot/v1/mcp",
|
||||
});
|
||||
sandbox.stub(disk, "ensureCacheDirectoryExists").resolves("/tmp");
|
||||
sandbox.stub(fs, "writeFile").resolves();
|
||||
sandbox.stub(axios, "get").resolves({
|
||||
data: {
|
||||
clinePass: [
|
||||
{
|
||||
id: "cline-pass/z-ai/glm-5.2",
|
||||
description: "OpenRouter alias",
|
||||
},
|
||||
{
|
||||
id: "cline-pass/zai/glm-5.2",
|
||||
description: "Canonical ID",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await refreshClineRecommendedModels();
|
||||
|
||||
expect(result.clinePass.map((model) => model.id)).to.deep.equal(["cline-pass/zai/glm-5.2"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,6 +87,37 @@ interface ClineRawModelInfo {
|
||||
// Track pending refresh promise to prevent duplicate concurrent fetches
|
||||
let pendingRefresh: Promise<Record<string, ModelInfo>> | null = null
|
||||
|
||||
interface ModelIdAliasRule {
|
||||
canonicalPrefix: string
|
||||
aliasPrefix: string
|
||||
}
|
||||
|
||||
// Mirrors @cline/llms VERCEL_OPENROUTER_MODEL_ID_ALIAS_RULES.
|
||||
const VERCEL_OPENROUTER_MODEL_ID_ALIAS_RULES = [
|
||||
{ canonicalPrefix: "zai/", aliasPrefix: "z-ai/" },
|
||||
] as const satisfies readonly ModelIdAliasRule[]
|
||||
|
||||
function preferCanonicalModelIds<T>(models: Record<string, T>, rules: readonly ModelIdAliasRule[]): Record<string, T> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(models).filter(([modelId]) => {
|
||||
for (const rule of rules) {
|
||||
if (!modelId.startsWith(rule.aliasPrefix)) {
|
||||
continue
|
||||
}
|
||||
const canonicalModelId = `${rule.canonicalPrefix}${modelId.slice(rule.aliasPrefix.length)}`
|
||||
if (canonicalModelId in models) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function preferClineCanonicalModelIds(models: Record<string, ModelInfo>): Record<string, ModelInfo> {
|
||||
return preferCanonicalModelIds(models, VERCEL_OPENROUTER_MODEL_ID_ALIAS_RULES)
|
||||
}
|
||||
|
||||
async function fetchRawClineModels(): Promise<ClineRawModelInfo[]> {
|
||||
const apiBaseUrl = ClineEnv.config().apiBaseUrl
|
||||
const response = await axios.get(`${apiBaseUrl}/api/v1/ai/cline/models`, getAxiosSettings())
|
||||
@@ -107,7 +138,7 @@ async function fetchRawClineModels(): Promise<ClineRawModelInfo[]> {
|
||||
export async function refreshClineModels(controller: Controller): Promise<Record<string, ModelInfo>> {
|
||||
const shouldUseClineEndpointSource = featureFlagsService.getBooleanFlagEnabled(FeatureFlag.EXTENSION_CLINE_MODELS_ENDPOINT)
|
||||
if (!shouldUseClineEndpointSource) {
|
||||
return refreshOpenRouterModels(controller)
|
||||
return preferClineCanonicalModelIds(await refreshOpenRouterModels(controller))
|
||||
}
|
||||
|
||||
// Check in-memory cache first
|
||||
@@ -319,8 +350,9 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
|
||||
if (Object.keys(models).length === 0) {
|
||||
throw new Error("No Cline models returned from API")
|
||||
}
|
||||
|
||||
// Save models and cache them in memory
|
||||
await fs.writeFile(clineModelsFilePath, JSON.stringify(models))
|
||||
await fs.writeFile(clineModelsFilePath, JSON.stringify(preferClineCanonicalModelIds(models)))
|
||||
Logger.log("Cline models fetched and saved")
|
||||
} catch (error) {
|
||||
Logger.error("Error fetching Cline models:", error)
|
||||
@@ -340,6 +372,7 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
|
||||
|
||||
// Avoid poisoning in-memory cache with an empty model map after transient failures.
|
||||
if (Object.keys(models).length > 0) {
|
||||
models = preferClineCanonicalModelIds(models)
|
||||
StateManager.get().setModelsCache("cline", models)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,31 @@ export interface ClineRecommendedModelsData {
|
||||
}
|
||||
|
||||
const RECOMMENDED_MODELS_CACHE_TTL_MS = 60 * 60 * 1000;
|
||||
const CLINE_PASS_MODEL_ID_ALIAS_RULES = [
|
||||
{ canonicalPrefix: "cline-pass/zai/", aliasPrefix: "cline-pass/z-ai/" },
|
||||
] as const;
|
||||
|
||||
function normalizeClineProviderRecommendedModelId(modelId: string): string {
|
||||
const zaiPrefix = "zai/";
|
||||
return modelId.startsWith(zaiPrefix) ? `z-ai/${modelId.slice(zaiPrefix.length)}` : modelId;
|
||||
}
|
||||
|
||||
function normalizeClineProviderRecommendedModels(
|
||||
models: ClineRecommendedModelData[],
|
||||
): ClineRecommendedModelData[] {
|
||||
return models.map((model) => {
|
||||
const id = normalizeClineProviderRecommendedModelId(model.id);
|
||||
if (id === model.id) {
|
||||
return model;
|
||||
}
|
||||
|
||||
return {
|
||||
...model,
|
||||
id,
|
||||
name: model.name === model.id ? id : model.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let pendingRefresh: Promise<ClineRecommendedModelsData> | null = null;
|
||||
let inMemoryCache: {
|
||||
@@ -30,6 +55,26 @@ let inMemoryCache: {
|
||||
timestamp: number;
|
||||
} | null = null;
|
||||
|
||||
function preferCanonicalRecommendedModels(
|
||||
models: ClineRecommendedModelData[],
|
||||
): ClineRecommendedModelData[] {
|
||||
const modelIds = new Set(models.map((model) => model.id));
|
||||
return models.filter((model) => {
|
||||
for (const rule of CLINE_PASS_MODEL_ID_ALIAS_RULES) {
|
||||
if (!model.id.startsWith(rule.aliasPrefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const canonicalModelId = `${rule.canonicalPrefix}${model.id.slice(rule.aliasPrefix.length)}`;
|
||||
if (modelIds.has(canonicalModelId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeRecommendedModel(
|
||||
raw: unknown,
|
||||
): ClineRecommendedModelData | null {
|
||||
@@ -89,7 +134,11 @@ function normalizeRecommendedModelsResponse(
|
||||
.map((model) => normalizeRecommendedModel(model))
|
||||
.filter((model): model is ClineRecommendedModelData => model !== null);
|
||||
|
||||
return { recommended, free, clinePass };
|
||||
return {
|
||||
recommended: normalizeClineProviderRecommendedModels(recommended),
|
||||
free: normalizeClineProviderRecommendedModels(free),
|
||||
clinePass: preferCanonicalRecommendedModels(clinePass),
|
||||
};
|
||||
}
|
||||
|
||||
export async function refreshClineRecommendedModels(): Promise<ClineRecommendedModelsData> {
|
||||
@@ -161,14 +210,9 @@ async function fetchAndCacheClineRecommendedModels(): Promise<ClineRecommendedMo
|
||||
"utf8",
|
||||
);
|
||||
const parsed = JSON.parse(fileContents);
|
||||
if (parsed) {
|
||||
result = {
|
||||
recommended: Array.isArray(parsed.recommended)
|
||||
? parsed.recommended
|
||||
: [],
|
||||
free: Array.isArray(parsed.free) ? parsed.free : [],
|
||||
clinePass: Array.isArray(parsed.clinePass) ? parsed.clinePass : [],
|
||||
};
|
||||
const normalized = normalizeRecommendedModelsResponse(parsed);
|
||||
if (normalized) {
|
||||
result = normalized;
|
||||
Logger.log("Loaded Cline recommended models from cache");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@ import {
|
||||
} from "@shared/Languages";
|
||||
import { USER_CONTENT_TAGS } from "@shared/messages/constants";
|
||||
import { convertClineMessageToProto } from "@shared/proto-conversions/cline-message";
|
||||
import { FeatureFlag } from "@shared/services/feature-flags/feature-flags";
|
||||
import { type ClineDefaultTool, READ_ONLY_TOOLS } from "@shared/tools";
|
||||
import type { ClineAskResponse } from "@shared/WebviewMessage";
|
||||
import {
|
||||
@@ -2039,11 +2038,7 @@ export class Task {
|
||||
? apiConfig.planModeApiProvider
|
||||
: apiConfig.actModeApiProvider
|
||||
) as string;
|
||||
const providerId =
|
||||
configuredProviderId === "cline-pass" &&
|
||||
!featureFlagsService.getBooleanFlagEnabled(FeatureFlag.CLINE_PASS)
|
||||
? "cline"
|
||||
: configuredProviderId;
|
||||
const providerId = configuredProviderId;
|
||||
const customPrompt = this.stateManager.getGlobalSettingsKey("customPrompt");
|
||||
return { model, providerId, customPrompt, mode };
|
||||
}
|
||||
|
||||
+478
-320
File diff suppressed because it is too large
Load Diff
@@ -1,137 +1,147 @@
|
||||
// type that represents json data that is sent from extension to webview, called ExtensionMessage and has 'type' enum which can be 'plusButtonClicked' or 'settingsButtonClicked' or 'hello'
|
||||
|
||||
import { WorkspaceRoot } from "@shared/multi-root/types"
|
||||
import { RemoteConfigFields } from "@shared/storage/state-keys"
|
||||
import type { Environment } from "../config"
|
||||
import { AutoApprovalSettings } from "./AutoApprovalSettings"
|
||||
import { ApiConfiguration } from "./api"
|
||||
import { BrowserSettings } from "./BrowserSettings"
|
||||
import { ClineFeatureSetting } from "./ClineFeatureSetting"
|
||||
import { BannerCardData } from "./cline/banner"
|
||||
import { ClineRulesToggles } from "./cline-rules"
|
||||
import { FocusChainSettings } from "./FocusChainSettings"
|
||||
import { HistoryItem } from "./HistoryItem"
|
||||
import { McpDisplayMode } from "./McpDisplayMode"
|
||||
import { ClineMessageModelInfo } from "./messages"
|
||||
import { OnboardingModelGroup } from "./proto/cline/state"
|
||||
import { Mode } from "./storage/types"
|
||||
import { TelemetrySetting } from "./TelemetrySetting"
|
||||
import { UserInfo } from "./UserInfo"
|
||||
import type { WorkspaceRoot } from "@shared/multi-root/types";
|
||||
import type { RemoteConfigFields } from "@shared/storage/state-keys";
|
||||
import type { Environment } from "../config";
|
||||
import type { AutoApprovalSettings } from "./AutoApprovalSettings";
|
||||
import type { ApiConfiguration } from "./api";
|
||||
import type { BrowserSettings } from "./BrowserSettings";
|
||||
import type { ClineFeatureSetting } from "./ClineFeatureSetting";
|
||||
import type { BannerCardData } from "./cline/banner";
|
||||
import type { ClineRulesToggles } from "./cline-rules";
|
||||
import type { FocusChainSettings } from "./FocusChainSettings";
|
||||
import type { HistoryItem } from "./HistoryItem";
|
||||
import type { McpDisplayMode } from "./McpDisplayMode";
|
||||
import type { ClineMessageModelInfo } from "./messages";
|
||||
import type { ModelsDevProviderModels } from "./models/models-dev-catalog";
|
||||
import type { OnboardingModelGroup } from "./proto/cline/state";
|
||||
import type { Mode } from "./storage/types";
|
||||
import type { TelemetrySetting } from "./TelemetrySetting";
|
||||
import type { UserInfo } from "./UserInfo";
|
||||
// webview will hold state
|
||||
export interface ExtensionMessage {
|
||||
type: "grpc_response" // New type for gRPC responses
|
||||
grpc_response?: GrpcResponse
|
||||
type: "grpc_response"; // New type for gRPC responses
|
||||
grpc_response?: GrpcResponse;
|
||||
}
|
||||
|
||||
export type GrpcResponse = {
|
||||
message?: any // JSON serialized protobuf message
|
||||
request_id: string // Same ID as the request
|
||||
error?: string // Optional error message
|
||||
is_streaming?: boolean // Whether this is part of a streaming response
|
||||
sequence_number?: number // For ordering chunks in streaming responses
|
||||
}
|
||||
message?: unknown; // JSON serialized protobuf message
|
||||
request_id: string; // Same ID as the request
|
||||
error?: string; // Optional error message
|
||||
is_streaming?: boolean; // Whether this is part of a streaming response
|
||||
sequence_number?: number; // For ordering chunks in streaming responses
|
||||
};
|
||||
|
||||
export type Platform = "aix" | "darwin" | "freebsd" | "linux" | "openbsd" | "sunos" | "win32" | "unknown"
|
||||
export type Platform =
|
||||
| "aix"
|
||||
| "darwin"
|
||||
| "freebsd"
|
||||
| "linux"
|
||||
| "openbsd"
|
||||
| "sunos"
|
||||
| "win32"
|
||||
| "unknown";
|
||||
|
||||
export const DEFAULT_PLATFORM = "unknown"
|
||||
export const DEFAULT_PLATFORM = "unknown";
|
||||
|
||||
export const COMMAND_CANCEL_TOKEN = "__cline_command_cancel__"
|
||||
export const COMMAND_CANCEL_TOKEN = "__cline_command_cancel__";
|
||||
export interface ExtensionState {
|
||||
isNewUser: boolean
|
||||
welcomeViewCompleted: boolean
|
||||
onboardingModels: OnboardingModelGroup | undefined
|
||||
apiConfiguration?: ApiConfiguration
|
||||
autoApprovalSettings: AutoApprovalSettings
|
||||
browserSettings: BrowserSettings
|
||||
remoteBrowserHost?: string
|
||||
preferredLanguage?: string
|
||||
mode: Mode
|
||||
checkpointManagerErrorMessage?: string
|
||||
clineMessages: ClineMessage[]
|
||||
currentTaskItem?: HistoryItem
|
||||
currentFocusChainChecklist?: string | null
|
||||
mcpMarketplaceEnabled?: boolean
|
||||
mcpDisplayMode: McpDisplayMode
|
||||
planActSeparateModelsSetting: boolean
|
||||
enableCheckpointsSetting?: boolean
|
||||
platform: Platform
|
||||
environment?: Environment
|
||||
shouldShowAnnouncement: boolean
|
||||
taskHistory: HistoryItem[]
|
||||
telemetrySetting: TelemetrySetting
|
||||
shellIntegrationTimeout: number
|
||||
terminalReuseEnabled?: boolean
|
||||
terminalOutputLineLimit: number
|
||||
maxConsecutiveMistakes: number
|
||||
defaultTerminalProfile?: string
|
||||
vscodeTerminalExecutionMode: string
|
||||
backgroundCommandRunning?: boolean
|
||||
backgroundCommandTaskId?: string
|
||||
lastCompletedCommandTs?: number
|
||||
userInfo?: UserInfo
|
||||
version: string
|
||||
distinctId: string
|
||||
globalClineRulesToggles: ClineRulesToggles
|
||||
localClineRulesToggles: ClineRulesToggles
|
||||
localWorkflowToggles: ClineRulesToggles
|
||||
globalWorkflowToggles: ClineRulesToggles
|
||||
localCursorRulesToggles: ClineRulesToggles
|
||||
localWindsurfRulesToggles: ClineRulesToggles
|
||||
remoteRulesToggles?: ClineRulesToggles
|
||||
remoteWorkflowToggles?: ClineRulesToggles
|
||||
localAgentsRulesToggles: ClineRulesToggles
|
||||
mcpResponsesCollapsed?: boolean
|
||||
strictPlanModeEnabled?: boolean
|
||||
yoloModeToggled?: boolean
|
||||
useAutoCondense?: boolean
|
||||
subagentsEnabled?: boolean
|
||||
clineWebToolsEnabled?: ClineFeatureSetting
|
||||
worktreesEnabled?: ClineFeatureSetting
|
||||
focusChainSettings: FocusChainSettings
|
||||
customPrompt?: string
|
||||
favoritedModelIds: string[]
|
||||
isNewUser: boolean;
|
||||
welcomeViewCompleted: boolean;
|
||||
onboardingModels: OnboardingModelGroup | undefined;
|
||||
apiConfiguration?: ApiConfiguration;
|
||||
autoApprovalSettings: AutoApprovalSettings;
|
||||
browserSettings: BrowserSettings;
|
||||
remoteBrowserHost?: string;
|
||||
preferredLanguage?: string;
|
||||
mode: Mode;
|
||||
checkpointManagerErrorMessage?: string;
|
||||
clineMessages: ClineMessage[];
|
||||
currentTaskItem?: HistoryItem;
|
||||
currentFocusChainChecklist?: string | null;
|
||||
mcpMarketplaceEnabled?: boolean;
|
||||
mcpDisplayMode: McpDisplayMode;
|
||||
planActSeparateModelsSetting: boolean;
|
||||
enableCheckpointsSetting?: boolean;
|
||||
platform: Platform;
|
||||
environment?: Environment;
|
||||
shouldShowAnnouncement: boolean;
|
||||
taskHistory: HistoryItem[];
|
||||
telemetrySetting: TelemetrySetting;
|
||||
shellIntegrationTimeout: number;
|
||||
terminalReuseEnabled?: boolean;
|
||||
terminalOutputLineLimit: number;
|
||||
maxConsecutiveMistakes: number;
|
||||
defaultTerminalProfile?: string;
|
||||
vscodeTerminalExecutionMode: string;
|
||||
backgroundCommandRunning?: boolean;
|
||||
backgroundCommandTaskId?: string;
|
||||
lastCompletedCommandTs?: number;
|
||||
userInfo?: UserInfo;
|
||||
version: string;
|
||||
distinctId: string;
|
||||
globalClineRulesToggles: ClineRulesToggles;
|
||||
localClineRulesToggles: ClineRulesToggles;
|
||||
localWorkflowToggles: ClineRulesToggles;
|
||||
globalWorkflowToggles: ClineRulesToggles;
|
||||
localCursorRulesToggles: ClineRulesToggles;
|
||||
localWindsurfRulesToggles: ClineRulesToggles;
|
||||
remoteRulesToggles?: ClineRulesToggles;
|
||||
remoteWorkflowToggles?: ClineRulesToggles;
|
||||
localAgentsRulesToggles: ClineRulesToggles;
|
||||
mcpResponsesCollapsed?: boolean;
|
||||
strictPlanModeEnabled?: boolean;
|
||||
yoloModeToggled?: boolean;
|
||||
useAutoCondense?: boolean;
|
||||
subagentsEnabled?: boolean;
|
||||
clineWebToolsEnabled?: ClineFeatureSetting;
|
||||
worktreesEnabled?: ClineFeatureSetting;
|
||||
focusChainSettings: FocusChainSettings;
|
||||
customPrompt?: string;
|
||||
favoritedModelIds: string[];
|
||||
// NEW: Add workspace information
|
||||
workspaceRoots: WorkspaceRoot[]
|
||||
primaryRootIndex: number
|
||||
isMultiRootWorkspace: boolean
|
||||
multiRootSetting: ClineFeatureSetting
|
||||
lastDismissedInfoBannerVersion: number
|
||||
lastDismissedModelBannerVersion: number
|
||||
lastDismissedCliBannerVersion: number
|
||||
dismissedBanners?: Array<{ bannerId: string; dismissedAt: number }>
|
||||
hooksEnabled?: boolean
|
||||
remoteConfigSettings?: Partial<RemoteConfigFields>
|
||||
globalSkillsToggles?: Record<string, boolean>
|
||||
localSkillsToggles?: Record<string, boolean>
|
||||
nativeToolCallSetting?: boolean
|
||||
enableParallelToolCalling?: boolean
|
||||
backgroundEditEnabled?: boolean
|
||||
optOutOfRemoteConfig?: boolean
|
||||
doubleCheckCompletionEnabled?: boolean
|
||||
lazyTeammateModeEnabled?: boolean
|
||||
showFeatureTips?: boolean
|
||||
banners?: BannerCardData[]
|
||||
welcomeBanners?: BannerCardData[]
|
||||
openAiCodexIsAuthenticated?: boolean
|
||||
workspaceRoots: WorkspaceRoot[];
|
||||
primaryRootIndex: number;
|
||||
isMultiRootWorkspace: boolean;
|
||||
multiRootSetting: ClineFeatureSetting;
|
||||
lastDismissedInfoBannerVersion: number;
|
||||
lastDismissedModelBannerVersion: number;
|
||||
lastDismissedCliBannerVersion: number;
|
||||
dismissedBanners?: Array<{ bannerId: string; dismissedAt: number }>;
|
||||
hooksEnabled?: boolean;
|
||||
remoteConfigSettings?: Partial<RemoteConfigFields>;
|
||||
globalSkillsToggles?: Record<string, boolean>;
|
||||
localSkillsToggles?: Record<string, boolean>;
|
||||
nativeToolCallSetting?: boolean;
|
||||
enableParallelToolCalling?: boolean;
|
||||
backgroundEditEnabled?: boolean;
|
||||
optOutOfRemoteConfig?: boolean;
|
||||
doubleCheckCompletionEnabled?: boolean;
|
||||
lazyTeammateModeEnabled?: boolean;
|
||||
showFeatureTips?: boolean;
|
||||
banners?: BannerCardData[];
|
||||
welcomeBanners?: BannerCardData[];
|
||||
openAiCodexIsAuthenticated?: boolean;
|
||||
modelsDevProviderModels?: ModelsDevProviderModels;
|
||||
}
|
||||
|
||||
export interface ClineMessage {
|
||||
ts: number
|
||||
type: "ask" | "say"
|
||||
ask?: ClineAsk
|
||||
say?: ClineSay
|
||||
text?: string
|
||||
reasoning?: string
|
||||
images?: string[]
|
||||
files?: string[]
|
||||
partial?: boolean
|
||||
commandCompleted?: boolean
|
||||
lastCheckpointHash?: string
|
||||
isCheckpointCheckedOut?: boolean
|
||||
isOperationOutsideWorkspace?: boolean
|
||||
conversationHistoryIndex?: number
|
||||
conversationHistoryDeletedRange?: [number, number] // for when conversation history is truncated for API requests
|
||||
modelInfo?: ClineMessageModelInfo
|
||||
ts: number;
|
||||
type: "ask" | "say";
|
||||
ask?: ClineAsk;
|
||||
say?: ClineSay;
|
||||
text?: string;
|
||||
reasoning?: string;
|
||||
images?: string[];
|
||||
files?: string[];
|
||||
partial?: boolean;
|
||||
commandCompleted?: boolean;
|
||||
lastCheckpointHash?: string;
|
||||
isCheckpointCheckedOut?: boolean;
|
||||
isOperationOutsideWorkspace?: boolean;
|
||||
conversationHistoryIndex?: number;
|
||||
conversationHistoryDeletedRange?: [number, number]; // for when conversation history is truncated for API requests
|
||||
modelInfo?: ClineMessageModelInfo;
|
||||
}
|
||||
|
||||
export type ClineAsk =
|
||||
@@ -152,7 +162,7 @@ export type ClineAsk =
|
||||
| "condense"
|
||||
| "summarize_task"
|
||||
| "report_bug"
|
||||
| "use_subagents"
|
||||
| "use_subagents";
|
||||
|
||||
export type ClineSay =
|
||||
| "task"
|
||||
@@ -192,7 +202,7 @@ export type ClineSay =
|
||||
| "subagent"
|
||||
| "use_subagents"
|
||||
| "subagent_usage"
|
||||
| "conditional_rules_applied"
|
||||
| "conditional_rules_applied";
|
||||
|
||||
export interface ClineSayTool {
|
||||
tool:
|
||||
@@ -207,167 +217,181 @@ export interface ClineSayTool {
|
||||
| "webFetch"
|
||||
| "webSearch"
|
||||
| "summarizeTask"
|
||||
| "useSkill"
|
||||
path?: string
|
||||
diff?: string
|
||||
content?: string
|
||||
regex?: string
|
||||
filePattern?: string
|
||||
operationIsLocatedInWorkspace?: boolean
|
||||
| "useSkill";
|
||||
path?: string;
|
||||
diff?: string;
|
||||
content?: string;
|
||||
regex?: string;
|
||||
filePattern?: string;
|
||||
operationIsLocatedInWorkspace?: boolean;
|
||||
/** Starting line numbers in the original file where each SEARCH block matched */
|
||||
startLineNumbers?: number[]
|
||||
startLineNumbers?: number[];
|
||||
/** Inclusive line range actually returned by read_file (for UI summaries). */
|
||||
readLineStart?: number
|
||||
readLineEnd?: number
|
||||
readLineStart?: number;
|
||||
readLineEnd?: number;
|
||||
}
|
||||
|
||||
export interface ClineSayHook {
|
||||
hookName: string // Name of the hook (e.g., "PreToolUse", "PostToolUse")
|
||||
toolName?: string // Tool name if applicable (for PreToolUse/PostToolUse)
|
||||
status: "running" | "completed" | "failed" | "cancelled" // Execution status
|
||||
exitCode?: number // Exit code when completed
|
||||
hasJsonResponse?: boolean // Whether a JSON response was parsed
|
||||
hookName: string; // Name of the hook (e.g., "PreToolUse", "PostToolUse")
|
||||
toolName?: string; // Tool name if applicable (for PreToolUse/PostToolUse)
|
||||
status: "running" | "completed" | "failed" | "cancelled"; // Execution status
|
||||
exitCode?: number; // Exit code when completed
|
||||
hasJsonResponse?: boolean; // Whether a JSON response was parsed
|
||||
// Pending tool information (only present during PreToolUse "running" status)
|
||||
pendingToolInfo?: {
|
||||
tool: string // Tool name (e.g., "write_to_file", "execute_command")
|
||||
path?: string // File path for file operations
|
||||
command?: string // Command for execute_command
|
||||
content?: string // Content preview (first 200 chars)
|
||||
diff?: string // Diff preview (first 200 chars)
|
||||
regex?: string // Regex pattern for search_files
|
||||
url?: string // URL for web_fetch or browser_action
|
||||
mcpTool?: string // MCP tool name
|
||||
mcpServer?: string // MCP server name
|
||||
resourceUri?: string // MCP resource URI
|
||||
}
|
||||
tool: string; // Tool name (e.g., "write_to_file", "execute_command")
|
||||
path?: string; // File path for file operations
|
||||
command?: string; // Command for execute_command
|
||||
content?: string; // Content preview (first 200 chars)
|
||||
diff?: string; // Diff preview (first 200 chars)
|
||||
regex?: string; // Regex pattern for search_files
|
||||
url?: string; // URL for web_fetch or browser_action
|
||||
mcpTool?: string; // MCP tool name
|
||||
mcpServer?: string; // MCP server name
|
||||
resourceUri?: string; // MCP resource URI
|
||||
};
|
||||
// Structured error information (only present when status is "failed")
|
||||
error?: {
|
||||
type: "timeout" | "validation" | "execution" | "cancellation" // Type of error
|
||||
message: string // User-friendly error message
|
||||
details?: string // Technical details for expansion
|
||||
scriptPath?: string // Path to the hook script
|
||||
}
|
||||
type: "timeout" | "validation" | "execution" | "cancellation"; // Type of error
|
||||
message: string; // User-friendly error message
|
||||
details?: string; // Technical details for expansion
|
||||
scriptPath?: string; // Path to the hook script
|
||||
};
|
||||
}
|
||||
|
||||
export type HookOutputStreamMeta = {
|
||||
/** Which hook configuration the script originated from (global vs workspace). */
|
||||
source: "global" | "workspace"
|
||||
source: "global" | "workspace";
|
||||
/** Full path to the hook script that emitted the output. */
|
||||
scriptPath: string
|
||||
}
|
||||
scriptPath: string;
|
||||
};
|
||||
|
||||
// must keep in sync with system prompt
|
||||
export const browserActions = ["launch", "click", "type", "scroll_down", "scroll_up", "close"] as const
|
||||
export type BrowserAction = (typeof browserActions)[number]
|
||||
export const browserActions = [
|
||||
"launch",
|
||||
"click",
|
||||
"type",
|
||||
"scroll_down",
|
||||
"scroll_up",
|
||||
"close",
|
||||
] as const;
|
||||
export type BrowserAction = (typeof browserActions)[number];
|
||||
|
||||
export interface ClineSayBrowserAction {
|
||||
action: BrowserAction
|
||||
coordinate?: string
|
||||
text?: string
|
||||
action: BrowserAction;
|
||||
coordinate?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface ClineSayGenerateExplanation {
|
||||
title: string
|
||||
fromRef: string
|
||||
toRef: string
|
||||
status: "generating" | "complete" | "error"
|
||||
error?: string
|
||||
title: string;
|
||||
fromRef: string;
|
||||
toRef: string;
|
||||
status: "generating" | "complete" | "error";
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type SubagentExecutionStatus = "pending" | "running" | "completed" | "failed"
|
||||
export type SubagentExecutionStatus =
|
||||
| "pending"
|
||||
| "running"
|
||||
| "completed"
|
||||
| "failed";
|
||||
|
||||
export interface SubagentStatusItem {
|
||||
index: number
|
||||
prompt: string
|
||||
status: SubagentExecutionStatus
|
||||
toolCalls: number
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
totalCost: number
|
||||
contextTokens: number
|
||||
contextWindow: number
|
||||
contextUsagePercentage: number
|
||||
latestToolCall?: string
|
||||
result?: string
|
||||
error?: string
|
||||
index: number;
|
||||
prompt: string;
|
||||
status: SubagentExecutionStatus;
|
||||
toolCalls: number;
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
totalCost: number;
|
||||
contextTokens: number;
|
||||
contextWindow: number;
|
||||
contextUsagePercentage: number;
|
||||
latestToolCall?: string;
|
||||
result?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ClineSaySubagentStatus {
|
||||
status: "running" | "completed" | "failed"
|
||||
total: number
|
||||
completed: number
|
||||
successes: number
|
||||
failures: number
|
||||
toolCalls: number
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
contextWindow: number
|
||||
maxContextTokens: number
|
||||
maxContextUsagePercentage: number
|
||||
items: SubagentStatusItem[]
|
||||
status: "running" | "completed" | "failed";
|
||||
total: number;
|
||||
completed: number;
|
||||
successes: number;
|
||||
failures: number;
|
||||
toolCalls: number;
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
contextWindow: number;
|
||||
maxContextTokens: number;
|
||||
maxContextUsagePercentage: number;
|
||||
items: SubagentStatusItem[];
|
||||
}
|
||||
|
||||
export type BrowserActionResult = {
|
||||
screenshot?: string
|
||||
logs?: string
|
||||
currentUrl?: string
|
||||
currentMousePosition?: string
|
||||
}
|
||||
screenshot?: string;
|
||||
logs?: string;
|
||||
currentUrl?: string;
|
||||
currentMousePosition?: string;
|
||||
};
|
||||
|
||||
export interface ClineAskUseMcpServer {
|
||||
serverName: string
|
||||
type: "use_mcp_tool" | "access_mcp_resource"
|
||||
toolName?: string
|
||||
arguments?: string
|
||||
uri?: string
|
||||
serverName: string;
|
||||
type: "use_mcp_tool" | "access_mcp_resource";
|
||||
toolName?: string;
|
||||
arguments?: string;
|
||||
uri?: string;
|
||||
}
|
||||
|
||||
export interface ClineAskUseSubagents {
|
||||
prompts: string[]
|
||||
prompts: string[];
|
||||
}
|
||||
|
||||
export interface ClinePlanModeResponse {
|
||||
response: string
|
||||
options?: string[]
|
||||
selected?: string
|
||||
response: string;
|
||||
options?: string[];
|
||||
selected?: string;
|
||||
}
|
||||
|
||||
export interface ClineAskQuestion {
|
||||
question: string
|
||||
options?: string[]
|
||||
selected?: string
|
||||
question: string;
|
||||
options?: string[];
|
||||
selected?: string;
|
||||
}
|
||||
|
||||
export interface ClineAskNewTask {
|
||||
context: string
|
||||
context: string;
|
||||
}
|
||||
|
||||
export interface ClineApiReqInfo {
|
||||
request?: string
|
||||
tokensIn?: number
|
||||
tokensOut?: number
|
||||
cacheWrites?: number
|
||||
cacheReads?: number
|
||||
cost?: number
|
||||
cancelReason?: ClineApiReqCancelReason
|
||||
streamingFailedMessage?: string
|
||||
request?: string;
|
||||
tokensIn?: number;
|
||||
tokensOut?: number;
|
||||
cacheWrites?: number;
|
||||
cacheReads?: number;
|
||||
cost?: number;
|
||||
cancelReason?: ClineApiReqCancelReason;
|
||||
streamingFailedMessage?: string;
|
||||
retryStatus?: {
|
||||
attempt: number
|
||||
maxAttempts: number
|
||||
delaySec: number
|
||||
errorSnippet?: string
|
||||
}
|
||||
attempt: number;
|
||||
maxAttempts: number;
|
||||
delaySec: number;
|
||||
errorSnippet?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ClineSubagentUsageInfo {
|
||||
source: "subagents"
|
||||
tokensIn: number
|
||||
tokensOut: number
|
||||
cacheWrites: number
|
||||
cacheReads: number
|
||||
cost: number
|
||||
source: "subagents";
|
||||
tokensIn: number;
|
||||
tokensOut: number;
|
||||
cacheWrites: number;
|
||||
cacheReads: number;
|
||||
cost: number;
|
||||
}
|
||||
|
||||
export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled" | "retries_exhausted"
|
||||
export type ClineApiReqCancelReason =
|
||||
| "streaming_failed"
|
||||
| "user_cancelled"
|
||||
| "retries_exhausted";
|
||||
|
||||
export const COMPLETION_RESULT_CHANGES_FLAG = "HAS_CHANGES"
|
||||
export const COMPLETION_RESULT_CHANGES_FLAG = "HAS_CHANGES";
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { expect } from "chai"
|
||||
import {
|
||||
buildModelInfoNameMap,
|
||||
internationalZAiModels,
|
||||
mainlandZAiModels,
|
||||
type ModelInfo,
|
||||
resolveClinePassModelInfo,
|
||||
} from "../api"
|
||||
|
||||
describe("ClinePass model info", () => {
|
||||
const createModelInfo = (name: string, contextWindow: number): ModelInfo => ({
|
||||
name,
|
||||
contextWindow,
|
||||
maxTokens: 8_192,
|
||||
supportsPromptCache: false,
|
||||
supportsReasoning: false,
|
||||
thinkingConfig: { maxBudget: 16_384 },
|
||||
})
|
||||
|
||||
it("prefers dynamic model metadata for ClinePass GLM aliases", () => {
|
||||
const modelInfo = resolveClinePassModelInfo(
|
||||
"cline-pass/z-ai/glm-5.2",
|
||||
buildModelInfoNameMap({
|
||||
"z-ai/glm-5.2": createModelInfo("OpenRouter GLM 5.2", 1_000_000),
|
||||
}),
|
||||
)
|
||||
|
||||
expect(modelInfo.name).to.equal("OpenRouter GLM 5.2")
|
||||
expect(modelInfo.contextWindow).to.equal(1_000_000)
|
||||
expect(modelInfo.thinkingConfig).to.deep.equal({ maxBudget: 16_384 })
|
||||
})
|
||||
|
||||
it("falls back to static ClinePass metadata when dynamic metadata is unavailable", () => {
|
||||
const modelInfo = resolveClinePassModelInfo("cline-pass/glm-5.2")
|
||||
|
||||
expect(modelInfo.contextWindow).to.equal(202_752)
|
||||
expect(modelInfo.supportsReasoning).to.equal(true)
|
||||
expect(modelInfo.thinkingConfig).to.equal(undefined)
|
||||
})
|
||||
|
||||
it("preserves dynamic ClinePass model info when no static alias exists", () => {
|
||||
const modelInfo = resolveClinePassModelInfo(
|
||||
"cline-pass/new-model",
|
||||
buildModelInfoNameMap({
|
||||
"zai/new-model": createModelInfo("New model", 1_000_000),
|
||||
}),
|
||||
)
|
||||
|
||||
expect(modelInfo.name).to.equal("New model")
|
||||
expect(modelInfo.supportsReasoning).to.equal(false)
|
||||
expect(modelInfo.thinkingConfig).to.deep.equal({ maxBudget: 16_384 })
|
||||
})
|
||||
})
|
||||
|
||||
describe("Z AI model info", () => {
|
||||
it("includes GLM 5.2 for both direct Z AI entrypoints", () => {
|
||||
for (const models of [internationalZAiModels, mainlandZAiModels]) {
|
||||
expect(models["glm-5.2"].contextWindow).to.equal(1_000_000)
|
||||
expect(models["glm-5.2"].maxTokens).to.equal(128_000)
|
||||
expect(models["glm-5.2"].inputPrice).to.equal(1.4)
|
||||
expect(models["glm-5.2"].outputPrice).to.equal(4.4)
|
||||
expect(models["glm-5.2"].cacheReadsPrice).to.equal(0.26)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1025,7 +1025,7 @@ export const clineDevstralModelInfo: ModelInfo = {
|
||||
}
|
||||
|
||||
export type ClinePassModelId = keyof typeof clinePassModels
|
||||
export const clinePassDefaultModelId = "cline-pass/glm-5.1"
|
||||
export const clinePassDefaultModelId = "cline-pass/glm-5.2"
|
||||
export const clinePassModelInfoSaneDefaults: ModelInfo = {
|
||||
maxTokens: 8_192,
|
||||
contextWindow: 128_000,
|
||||
@@ -1039,6 +1039,19 @@ export const clinePassModelInfoSaneDefaults: ModelInfo = {
|
||||
description: "",
|
||||
}
|
||||
export const clinePassModels = {
|
||||
"cline-pass/glm-5.2": {
|
||||
name: "cline-pass/glm-5.2",
|
||||
maxTokens: 131_072,
|
||||
contextWindow: 202_752,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoning: true,
|
||||
inputPrice: 0.98,
|
||||
outputPrice: 3.08,
|
||||
cacheReadsPrice: 0.182,
|
||||
cacheWritesPrice: 0,
|
||||
description: "",
|
||||
},
|
||||
"cline-pass/glm-5.1": {
|
||||
name: "cline-pass/glm-5.1",
|
||||
maxTokens: 131_072,
|
||||
@@ -1069,9 +1082,12 @@ export function buildModelInfoNameMap(models: Record<string, ModelInfo>): Record
|
||||
}
|
||||
|
||||
export function resolveClinePassModelInfo(modelId: string, modelInfoByName?: Record<string, ModelInfo>): ModelInfo {
|
||||
const modelSlug = getModelSlug(modelId)
|
||||
const clinePassSlugModelId = `cline-pass/${modelSlug}`
|
||||
return (
|
||||
modelInfoByName?.[modelSlug] ??
|
||||
clinePassModels[modelId as keyof typeof clinePassModels] ??
|
||||
modelInfoByName?.[getModelSlug(modelId)] ??
|
||||
clinePassModels[clinePassSlugModelId as keyof typeof clinePassModels] ??
|
||||
clinePassModelInfoSaneDefaults
|
||||
)
|
||||
}
|
||||
@@ -4983,12 +4999,22 @@ export type BasetenModelId = keyof typeof basetenModels
|
||||
export const basetenDefaultModelId = "zai-org/GLM-4.6" satisfies BasetenModelId
|
||||
|
||||
// Z AI
|
||||
// https://docs.z.ai/guides/llm/glm-5.2
|
||||
// https://docs.z.ai/guides/llm/glm-5.1
|
||||
// https://docs.z.ai/guides/llm/glm-5
|
||||
// https://docs.z.ai/guides/overview/pricing
|
||||
export type internationalZAiModelId = keyof typeof internationalZAiModels
|
||||
export const internationalZAiDefaultModelId: internationalZAiModelId = "glm-5.1"
|
||||
export const internationalZAiModels = {
|
||||
"glm-5.2": {
|
||||
maxTokens: 128_000,
|
||||
contextWindow: 1_000_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
cacheReadsPrice: 0.26,
|
||||
inputPrice: 1.4,
|
||||
outputPrice: 4.4,
|
||||
},
|
||||
"glm-5.1": {
|
||||
maxTokens: 128_000,
|
||||
contextWindow: 200_000,
|
||||
@@ -5054,6 +5080,15 @@ export const internationalZAiModels = {
|
||||
export type mainlandZAiModelId = keyof typeof mainlandZAiModels
|
||||
export const mainlandZAiDefaultModelId: mainlandZAiModelId = "glm-5.1"
|
||||
export const mainlandZAiModels = {
|
||||
"glm-5.2": {
|
||||
maxTokens: 128_000,
|
||||
contextWindow: 1_000_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
cacheReadsPrice: 0.26,
|
||||
inputPrice: 1.4,
|
||||
outputPrice: 4.4,
|
||||
},
|
||||
"glm-5.1": {
|
||||
maxTokens: 128_000,
|
||||
contextWindow: 200_000,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { describe, it } from "mocha";
|
||||
import "should";
|
||||
import { normalizeModelsDevProviderModels } from "../models-dev-catalog";
|
||||
|
||||
describe("models.dev catalog facade", () => {
|
||||
it("normalizes live models for providers that have generated fallback exports", () => {
|
||||
const providerModels = normalizeModelsDevProviderModels({
|
||||
mistral: {
|
||||
models: {
|
||||
"mistral-new": {
|
||||
name: "Mistral New",
|
||||
tool_call: true,
|
||||
release_date: "2026-04-01",
|
||||
},
|
||||
},
|
||||
},
|
||||
doubao: {
|
||||
models: {
|
||||
"doubao-new": {
|
||||
name: "Doubao New",
|
||||
tool_call: true,
|
||||
release_date: "2026-03-01",
|
||||
},
|
||||
},
|
||||
},
|
||||
"nous-research": {
|
||||
models: {
|
||||
"nous-new": {
|
||||
name: "Nous New",
|
||||
tool_call: true,
|
||||
release_date: "2026-02-01",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Object.keys(providerModels.mistral).should.deepEqual(["mistral-new"]);
|
||||
Object.keys(providerModels.doubao).should.deepEqual(["doubao-new"]);
|
||||
Object.keys(providerModels.nousResearch).should.deepEqual(["nous-new"]);
|
||||
});
|
||||
|
||||
it("sorts live models by release date before falling back to model id", () => {
|
||||
const providerModels = normalizeModelsDevProviderModels({
|
||||
anthropic: {
|
||||
models: {
|
||||
"z-old": {
|
||||
name: "Z Old",
|
||||
tool_call: true,
|
||||
release_date: "2025-01-01",
|
||||
},
|
||||
"a-new": {
|
||||
name: "A New",
|
||||
tool_call: true,
|
||||
release_date: "2026-01-01",
|
||||
},
|
||||
"b-same-date": {
|
||||
name: "B Same Date",
|
||||
tool_call: true,
|
||||
release_date: "2026-01-01",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Object.keys(providerModels.anthropic).should.deepEqual([
|
||||
"a-new",
|
||||
"b-same-date",
|
||||
"z-old",
|
||||
]);
|
||||
});
|
||||
|
||||
it("excludes deprecated models and models without tool calls", () => {
|
||||
const providerModels = normalizeModelsDevProviderModels({
|
||||
anthropic: {
|
||||
models: {
|
||||
active: {
|
||||
name: "Active",
|
||||
tool_call: true,
|
||||
status: "active",
|
||||
},
|
||||
deprecated: {
|
||||
name: "Deprecated",
|
||||
tool_call: true,
|
||||
status: "deprecated",
|
||||
},
|
||||
"no-tools": {
|
||||
name: "No Tools",
|
||||
tool_call: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Object.keys(providerModels.anthropic).should.deepEqual(["active"]);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,290 @@
|
||||
import type { ApiProvider, ModelInfo, OpenAiCompatibleModelInfo } from "../api";
|
||||
import {
|
||||
modelsDevProviderModels as generatedModelsDevProviderModels,
|
||||
modelsDevProviderOptions,
|
||||
} from "./models-dev-catalog.generated";
|
||||
|
||||
export { modelsDevProviderOptions };
|
||||
|
||||
export type ModelsDevProviderModels = Record<
|
||||
string,
|
||||
Record<string, ModelInfo | OpenAiCompatibleModelInfo>
|
||||
>;
|
||||
|
||||
type ModelsDevModel = {
|
||||
name?: string;
|
||||
tool_call?: boolean;
|
||||
reasoning?: boolean;
|
||||
structured_output?: boolean;
|
||||
temperature?: boolean;
|
||||
release_date?: string;
|
||||
family?: string;
|
||||
limit?: {
|
||||
context?: number;
|
||||
input?: number;
|
||||
output?: number;
|
||||
};
|
||||
cost?: {
|
||||
input?: number;
|
||||
output?: number;
|
||||
cache_read?: number;
|
||||
cache_write?: number;
|
||||
};
|
||||
modalities?: {
|
||||
input?: string[];
|
||||
};
|
||||
status?: string;
|
||||
};
|
||||
|
||||
type ModelsDevPayload = Record<
|
||||
string,
|
||||
{ models?: Record<string, ModelsDevModel> }
|
||||
>;
|
||||
|
||||
const MODELS_DEV_PROVIDER_KEY_MAP: ReadonlyArray<{
|
||||
source: string;
|
||||
target: ApiProvider;
|
||||
}> = [
|
||||
{ source: "openai", target: "openai-native" },
|
||||
{ source: "openai", target: "openai-codex" },
|
||||
{ source: "anthropic", target: "anthropic" },
|
||||
{ source: "google", target: "gemini" },
|
||||
{ source: "deepseek", target: "deepseek" },
|
||||
{ source: "doubao", target: "doubao" },
|
||||
{ source: "xai", target: "xai" },
|
||||
{ source: "mistral", target: "mistral" },
|
||||
{ source: "togetherai", target: "together" },
|
||||
{ source: "sap-ai-core", target: "sapaicore" },
|
||||
{ source: "ollama-cloud", target: "ollama" },
|
||||
{ source: "fireworks-ai", target: "fireworks" },
|
||||
{ source: "groq", target: "groq" },
|
||||
{ source: "cerebras", target: "cerebras" },
|
||||
{ source: "sambanova", target: "sambanova" },
|
||||
{ source: "nebius", target: "nebius" },
|
||||
{ source: "huggingface", target: "huggingface" },
|
||||
{ source: "openrouter", target: "openrouter" },
|
||||
{ source: "openrouter", target: "cline" },
|
||||
{ source: "nous-research", target: "nousResearch" },
|
||||
{ source: "vercel", target: "vercel-ai-gateway" },
|
||||
{ source: "aihubmix", target: "aihubmix" },
|
||||
{ source: "baseten", target: "baseten" },
|
||||
{ source: "google-vertex", target: "vertex" },
|
||||
{ source: "lmstudio", target: "lmstudio" },
|
||||
{ source: "zai", target: "zai" },
|
||||
{ source: "requesty", target: "requesty" },
|
||||
{ source: "amazon-bedrock", target: "bedrock" },
|
||||
{ source: "moonshotai", target: "moonshot" },
|
||||
{ source: "minimax", target: "minimax" },
|
||||
{ source: "wandb", target: "wandb" },
|
||||
];
|
||||
|
||||
const DEFAULT_MAX_INPUT_TOKENS = 4096;
|
||||
const DEFAULT_MAX_TOKENS = 4096;
|
||||
|
||||
let liveModelsDevProviderModels: ModelsDevProviderModels | undefined;
|
||||
|
||||
type ModelsDevModelInfo = (ModelInfo | OpenAiCompatibleModelInfo) & {
|
||||
releaseDate?: string;
|
||||
};
|
||||
|
||||
export function setLiveModelsDevProviderModels(
|
||||
models: ModelsDevProviderModels | undefined,
|
||||
): void {
|
||||
liveModelsDevProviderModels =
|
||||
models && Object.keys(models).length > 0 ? models : undefined;
|
||||
refreshNamedModelExports();
|
||||
}
|
||||
|
||||
export function getModelsDevProviderModels(
|
||||
provider: ApiProvider | string,
|
||||
): Record<string, ModelInfo> {
|
||||
return (liveModelsDevProviderModels?.[provider] ??
|
||||
generatedModelsDevProviderModels[
|
||||
provider as keyof typeof generatedModelsDevProviderModels
|
||||
] ??
|
||||
{}) as Record<string, ModelInfo>;
|
||||
}
|
||||
|
||||
export let modelsDevAnthropicModels = getModelsDevProviderModels("anthropic");
|
||||
export let modelsDevBedrockModels = getModelsDevProviderModels("bedrock");
|
||||
export let modelsDevCerebrasModels = getModelsDevProviderModels("cerebras");
|
||||
export let modelsDevDeepSeekModels = getModelsDevProviderModels("deepseek");
|
||||
export let modelsDevDoubaoModels = getModelsDevProviderModels("doubao");
|
||||
export let modelsDevFireworksModels = getModelsDevProviderModels("fireworks");
|
||||
export let modelsDevGeminiModels = getModelsDevProviderModels("gemini");
|
||||
export let modelsDevGroqModels = getModelsDevProviderModels("groq");
|
||||
export let modelsDevHuggingFaceModels =
|
||||
getModelsDevProviderModels("huggingface");
|
||||
export let modelsDevMinimaxModels = getModelsDevProviderModels("minimax");
|
||||
export let modelsDevMistralModels = getModelsDevProviderModels("mistral");
|
||||
export let modelsDevMoonshotModels = getModelsDevProviderModels("moonshot");
|
||||
export let modelsDevNebiusModels = getModelsDevProviderModels("nebius");
|
||||
export let modelsDevNousResearchModels =
|
||||
getModelsDevProviderModels("nousResearch");
|
||||
export let modelsDevOpenAiCodexModels =
|
||||
getModelsDevProviderModels("openai-codex");
|
||||
export let modelsDevOpenAiNativeModels =
|
||||
getModelsDevProviderModels("openai-native");
|
||||
export let modelsDevSambanovaModels = getModelsDevProviderModels("sambanova");
|
||||
export let modelsDevSapAiCoreModels = getModelsDevProviderModels("sapaicore");
|
||||
export let modelsDevVertexModels = getModelsDevProviderModels("vertex");
|
||||
export let modelsDevWandbModels = getModelsDevProviderModels("wandb");
|
||||
export let modelsDevXaiModels = getModelsDevProviderModels("xai");
|
||||
|
||||
function refreshNamedModelExports(): void {
|
||||
modelsDevAnthropicModels = getModelsDevProviderModels("anthropic");
|
||||
modelsDevBedrockModels = getModelsDevProviderModels("bedrock");
|
||||
modelsDevCerebrasModels = getModelsDevProviderModels("cerebras");
|
||||
modelsDevDeepSeekModels = getModelsDevProviderModels("deepseek");
|
||||
modelsDevDoubaoModels = getModelsDevProviderModels("doubao");
|
||||
modelsDevFireworksModels = getModelsDevProviderModels("fireworks");
|
||||
modelsDevGeminiModels = getModelsDevProviderModels("gemini");
|
||||
modelsDevGroqModels = getModelsDevProviderModels("groq");
|
||||
modelsDevHuggingFaceModels = getModelsDevProviderModels("huggingface");
|
||||
modelsDevMinimaxModels = getModelsDevProviderModels("minimax");
|
||||
modelsDevMistralModels = getModelsDevProviderModels("mistral");
|
||||
modelsDevMoonshotModels = getModelsDevProviderModels("moonshot");
|
||||
modelsDevNebiusModels = getModelsDevProviderModels("nebius");
|
||||
modelsDevNousResearchModels = getModelsDevProviderModels("nousResearch");
|
||||
modelsDevOpenAiCodexModels = getModelsDevProviderModels("openai-codex");
|
||||
modelsDevOpenAiNativeModels = getModelsDevProviderModels("openai-native");
|
||||
modelsDevSambanovaModels = getModelsDevProviderModels("sambanova");
|
||||
modelsDevSapAiCoreModels = getModelsDevProviderModels("sapaicore");
|
||||
modelsDevVertexModels = getModelsDevProviderModels("vertex");
|
||||
modelsDevWandbModels = getModelsDevProviderModels("wandb");
|
||||
modelsDevXaiModels = getModelsDevProviderModels("xai");
|
||||
}
|
||||
|
||||
export async function fetchLiveModelsDevProviderModels(
|
||||
url = "https://models.dev/api.json",
|
||||
fetcher: typeof fetch = fetch,
|
||||
): Promise<ModelsDevProviderModels> {
|
||||
const response = await fetcher(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to load model catalog from ${url}: HTTP ${response.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
return normalizeModelsDevProviderModels(
|
||||
(await response.json()) as ModelsDevPayload,
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeModelsDevProviderModels(
|
||||
payload: ModelsDevPayload,
|
||||
): ModelsDevProviderModels {
|
||||
const providerModels: ModelsDevProviderModels = {};
|
||||
|
||||
for (const {
|
||||
source: sourceProviderKey,
|
||||
target: targetProviderId,
|
||||
} of MODELS_DEV_PROVIDER_KEY_MAP) {
|
||||
const source = payload[sourceProviderKey];
|
||||
if (!source?.models) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const models: Record<string, ModelInfo | OpenAiCompatibleModelInfo> = {};
|
||||
for (const [modelId, model] of Object.entries(source.models)) {
|
||||
if (model.tool_call !== true || model.status === "deprecated") {
|
||||
continue;
|
||||
}
|
||||
models[modelId] = toModelInfo(modelId, model);
|
||||
}
|
||||
|
||||
if (Object.keys(models).length > 0) {
|
||||
providerModels[targetProviderId] = sortModelsByReleaseDate(models);
|
||||
}
|
||||
}
|
||||
|
||||
return providerModels;
|
||||
}
|
||||
|
||||
function toModelInfo(
|
||||
_modelId: string,
|
||||
model: ModelsDevModel,
|
||||
): ModelsDevModelInfo {
|
||||
const inputLimit = resolveMaxInputTokens(model.limit);
|
||||
const outputLimit = Math.floor(model.limit?.output ?? DEFAULT_MAX_TOKENS);
|
||||
const capabilities = toCapabilities(model);
|
||||
|
||||
return {
|
||||
name: model.name,
|
||||
contextWindow: model.limit?.context ?? inputLimit,
|
||||
maxTokens: outputLimit,
|
||||
supportsImages: capabilities.has("images"),
|
||||
supportsPromptCache: capabilities.has("prompt-cache"),
|
||||
supportsReasoning: capabilities.has("reasoning"),
|
||||
inputPrice: model.cost?.input ?? 0,
|
||||
outputPrice: model.cost?.output ?? 0,
|
||||
cacheReadsPrice: model.cost?.cache_read ?? 0,
|
||||
cacheWritesPrice: model.cost?.cache_write ?? 0,
|
||||
supportsTools: capabilities.has("tools"),
|
||||
releaseDate: model.release_date,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveMaxInputTokens(
|
||||
limit: ModelsDevModel["limit"] | undefined,
|
||||
): number {
|
||||
const contextLimit = limit?.context;
|
||||
const inputLimit = limit?.input;
|
||||
if (typeof contextLimit === "number" && typeof inputLimit === "number") {
|
||||
return Math.min(contextLimit, inputLimit);
|
||||
}
|
||||
return inputLimit ?? contextLimit ?? DEFAULT_MAX_INPUT_TOKENS;
|
||||
}
|
||||
|
||||
function toCapabilities(model: ModelsDevModel): Set<string> {
|
||||
const capabilities = new Set<string>();
|
||||
if (model.modalities?.input?.includes("image")) {
|
||||
capabilities.add("images");
|
||||
}
|
||||
if (model.tool_call === true) {
|
||||
capabilities.add("tools");
|
||||
}
|
||||
if (model.reasoning === true) {
|
||||
capabilities.add("reasoning");
|
||||
}
|
||||
if (model.structured_output === true) {
|
||||
capabilities.add("structured_output");
|
||||
}
|
||||
if (model.temperature === true) {
|
||||
capabilities.add("temperature");
|
||||
}
|
||||
if (
|
||||
(model.cost?.cache_read !== undefined && model.cost.cache_read >= 0) ||
|
||||
(model.cost?.cache_write !== undefined && model.cost.cache_write >= 0)
|
||||
) {
|
||||
capabilities.add("prompt-cache");
|
||||
}
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
function sortModelsByReleaseDate<
|
||||
T extends ModelInfo | OpenAiCompatibleModelInfo,
|
||||
>(models: Record<string, T>): Record<string, T> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(models).sort(([modelIdA, modelA], [modelIdB, modelB]) => {
|
||||
const releaseDateA = parseReleaseDate(
|
||||
(modelA as ModelsDevModelInfo).releaseDate,
|
||||
);
|
||||
const releaseDateB = parseReleaseDate(
|
||||
(modelB as ModelsDevModelInfo).releaseDate,
|
||||
);
|
||||
if (releaseDateA !== releaseDateB) {
|
||||
return releaseDateB - releaseDateA;
|
||||
}
|
||||
return modelIdA.localeCompare(modelIdB);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function parseReleaseDate(value: string | undefined): number {
|
||||
if (!value) {
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
const timestamp = Date.parse(value);
|
||||
return Number.isNaN(timestamp) ? Number.NEGATIVE_INFINITY : timestamp;
|
||||
}
|
||||
@@ -37,11 +37,20 @@ export function supportsReasoningEffortForModel(modelId?: string): boolean {
|
||||
|
||||
const id = modelId.toLowerCase()
|
||||
return (
|
||||
id.includes("deepseek") ||
|
||||
id.includes("gemini") ||
|
||||
id.includes("glm") ||
|
||||
id.includes("gpt") ||
|
||||
id.includes("kimi") ||
|
||||
id.includes("mimo") ||
|
||||
id.includes("minimax") ||
|
||||
id.includes("moonshot") ||
|
||||
id.startsWith("openai/o") ||
|
||||
id.includes("/o") ||
|
||||
id.startsWith("o") ||
|
||||
id.includes("qwen") ||
|
||||
id.includes("z-ai") ||
|
||||
id.includes("zai") ||
|
||||
id.includes("grok")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
isPoolsideModelFamily,
|
||||
modelDoesntSupportWebp,
|
||||
shouldSkipReasoningForModel,
|
||||
supportsReasoningEffortForModel,
|
||||
} from "../model-utils"
|
||||
|
||||
// Minimal helper — modelDoesntSupportWebp only reads apiHandlerModel.id
|
||||
@@ -37,6 +38,7 @@ describe("shouldSkipReasoningForModel", () => {
|
||||
shouldSkipReasoningForModel("claude-3-sonnet").should.equal(false)
|
||||
shouldSkipReasoningForModel("gpt-4").should.equal(false)
|
||||
shouldSkipReasoningForModel("gemini-pro").should.equal(false)
|
||||
shouldSkipReasoningForModel("zai/glm-5.2").should.equal(false)
|
||||
})
|
||||
|
||||
it("should return false for undefined or empty model IDs", () => {
|
||||
@@ -50,6 +52,31 @@ describe("shouldSkipReasoningForModel", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("supportsReasoningEffortForModel", () => {
|
||||
it("should return true for OpenRouter/Cline model families that use reasoning effort", () => {
|
||||
for (const modelId of [
|
||||
"zai/glm-5.2",
|
||||
"z-ai/glm-5.2",
|
||||
"cline-pass/glm-5.2",
|
||||
"moonshotai/kimi-k2-thinking",
|
||||
"kimi-k2-thinking",
|
||||
"accounts/fireworks/models/kimi-k2p6",
|
||||
"accounts/fireworks/models/minimax-m3",
|
||||
"minimax/MiniMax-M2.7",
|
||||
"provider/mimo-vl",
|
||||
"qwen/qwen3.7-max",
|
||||
"deepseek/deepseek-r1",
|
||||
]) {
|
||||
supportsReasoningEffortForModel(modelId).should.equal(true)
|
||||
}
|
||||
})
|
||||
|
||||
it("should return false for undefined and unrelated model IDs", () => {
|
||||
supportsReasoningEffortForModel(undefined).should.equal(false)
|
||||
supportsReasoningEffortForModel("anthropic/claude-sonnet-4.5").should.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isClaude4PlusModelFamily", () => {
|
||||
it("should return true for Claude 4+ model IDs with version numbers", () => {
|
||||
isClaude4PlusModelFamily("claude-sonnet-4-5-20250929").should.equal(true)
|
||||
|
||||
@@ -46,7 +46,7 @@ export function shouldSkipReasoningForModel(modelId?: string): boolean {
|
||||
if (!modelId) {
|
||||
return false
|
||||
}
|
||||
return modelId.includes("grok-4") || modelId.includes("devstral") || modelId.includes("glm")
|
||||
return modelId.includes("grok-4") || modelId.includes("devstral")
|
||||
}
|
||||
|
||||
export function isAnthropicModelId(modelId: string): modelId is AnthropicModelId {
|
||||
|
||||
@@ -1106,6 +1106,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
switch (selectedProvider) {
|
||||
case "cline":
|
||||
return `${selectedProvider}:${selectedModelId}`
|
||||
case "cline-pass":
|
||||
return `${selectedProvider}:${selectedModelId.replace(/^cline-pass\//, "")}`
|
||||
case "openai":
|
||||
return `openai-compat:${selectedModelId}`
|
||||
case "vscode-lm":
|
||||
|
||||
@@ -3,7 +3,9 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import VSCodeButtonLink from "@/components/common/VSCodeButtonLink"
|
||||
import { useClineAuth } from "@/context/ClineAuthContext"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { AccountServiceClient, TaskServiceClient } from "@/services/grpc-client"
|
||||
import { useApiConfigurationHandlers } from "../settings/utils/useApiConfigurationHandlers"
|
||||
|
||||
interface CreditLimitErrorProps {
|
||||
currentBalance: number
|
||||
@@ -26,7 +28,11 @@ const CreditLimitError: React.FC<CreditLimitErrorProps> = ({
|
||||
totalSpent,
|
||||
}) => {
|
||||
const { activeOrganization } = useClineAuth()
|
||||
const { mode, navigateToSettings } = useExtensionState()
|
||||
const { handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
const [fullBuyCreditsUrl, setFullBuyCreditsUrl] = useState<string>("")
|
||||
const [isSwitchingToClinePass, setIsSwitchingToClinePass] = useState(false)
|
||||
const [didSwitchToClinePass, setDidSwitchToClinePass] = useState(false)
|
||||
|
||||
const dashboardUrl = useMemo(() => {
|
||||
return buyCreditsUrl ?? (activeOrganization?.organizationId ? DEFAULT_BUY_CREDITS_URL.ORG : DEFAULT_BUY_CREDITS_URL.USER)
|
||||
@@ -48,6 +54,19 @@ const CreditLimitError: React.FC<CreditLimitErrorProps> = ({
|
||||
fetchCallbackUrl()
|
||||
}, [dashboardUrl])
|
||||
|
||||
const handleSwitchToClinePass = async () => {
|
||||
setIsSwitchingToClinePass(true)
|
||||
try {
|
||||
await handleModeFieldChange({ plan: "planModeApiProvider", act: "actModeApiProvider" }, "cline-pass", mode)
|
||||
setDidSwitchToClinePass(true)
|
||||
navigateToSettings("api-config")
|
||||
} catch (error) {
|
||||
console.error("Failed to switch to ClinePass:", error)
|
||||
} finally {
|
||||
setIsSwitchingToClinePass(false)
|
||||
}
|
||||
}
|
||||
|
||||
// We have to divide because the balance is stored in microcredits
|
||||
return (
|
||||
<div className="p-2 border-none rounded-md mb-2 bg-(--vscode-textBlockQuote-background)">
|
||||
@@ -66,6 +85,24 @@ const CreditLimitError: React.FC<CreditLimitErrorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<div className="text-(--vscode-descriptionForeground) text-xs mb-2">
|
||||
Trying to use ClinePass instead of credits?
|
||||
</div>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
className="w-full"
|
||||
disabled={isSwitchingToClinePass || didSwitchToClinePass}
|
||||
onClick={handleSwitchToClinePass}>
|
||||
<span className="codicon codicon-arrow-swap mr-1.5" />
|
||||
{isSwitchingToClinePass
|
||||
? "Switching..."
|
||||
: didSwitchToClinePass
|
||||
? "Switched to ClinePass"
|
||||
: "Switch to ClinePass"}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<VSCodeButtonLink className="w-full mb-2" href={fullBuyCreditsUrl}>
|
||||
<span className="codicon codicon-credit-card mr-[6px] text-[14px]" />
|
||||
Buy Credits
|
||||
|
||||
+73
-5
@@ -2,12 +2,13 @@ import { BANNER_DATA, BannerAction, BannerActionType, BannerCardData } from "@sh
|
||||
import { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import type { Worktree } from "@shared/proto/cline/worktree"
|
||||
import { TrackWorktreeViewOpenedRequest } from "@shared/proto/cline/worktree"
|
||||
import { GitBranch } from "lucide-react"
|
||||
import { GitBranch, Sparkles } from "lucide-react"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import BannerCarousel from "@/components/common/BannerCarousel"
|
||||
import BannerCarousel, { type BannerData } from "@/components/common/BannerCarousel"
|
||||
import WhatsNewModal from "@/components/common/WhatsNewModal"
|
||||
import HistoryPreview from "@/components/history/HistoryPreview"
|
||||
import { useApiConfigurationHandlers } from "@/components/settings/utils/useApiConfigurationHandlers"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
import HomeHeader from "@/components/welcome/HomeHeader"
|
||||
import { SuggestedTasks } from "@/components/welcome/SuggestedTasks"
|
||||
@@ -16,9 +17,12 @@ import { useClineAuth } from "@/context/ClineAuthContext"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { AccountServiceClient, StateServiceClient, UiServiceClient, WorktreeServiceClient } from "@/services/grpc-client"
|
||||
import { convertBannerData } from "@/utils/bannerUtils"
|
||||
import { buildClinePassSubscriptionUrl } from "@/utils/clinePassSubscription"
|
||||
import { getCurrentPlatform } from "@/utils/platformUtils"
|
||||
import { WelcomeSectionProps } from "../../types/chatTypes"
|
||||
|
||||
const CLINE_PASS_PROMO_BANNER_ID = "cline-pass-home-promo-v2"
|
||||
|
||||
/**
|
||||
* Welcome section shown when there's no active task
|
||||
* Includes info banner, announcements, home header, and history preview
|
||||
@@ -68,6 +72,7 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
|
||||
welcomeBanners,
|
||||
} = useExtensionState()
|
||||
const { handleFieldsChange } = useApiConfigurationHandlers()
|
||||
const [dismissedLocalBanners, setDismissedLocalBanners] = useState<Set<string>>(() => new Set())
|
||||
|
||||
// Open modal once we have welcome banners
|
||||
useEffect(() => {
|
||||
@@ -210,6 +215,8 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
|
||||
* Dismissal handler - updates version tracking
|
||||
*/
|
||||
const handleBannerDismiss = useCallback((bannerId: string) => {
|
||||
setDismissedLocalBanners((previous) => new Set(previous).add(bannerId))
|
||||
|
||||
// !! Do not continue use these version numbers or add new banners that don't have unique IDs. !!
|
||||
// Banner versions are **deprecated**. Going forward, we are tracking which banners have
|
||||
// been dismissed using the **banner ID**.
|
||||
@@ -225,6 +232,65 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
|
||||
}
|
||||
}, [])
|
||||
|
||||
const clinePassPromoBanner = useMemo((): BannerData | undefined => {
|
||||
if (
|
||||
isBannerDismissed(CLINE_PASS_PROMO_BANNER_ID) ||
|
||||
dismissedLocalBanners.has(CLINE_PASS_PROMO_BANNER_ID)
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const dismissPromoBanner = () => handleBannerDismiss(CLINE_PASS_PROMO_BANNER_ID)
|
||||
const subscriptionUrl = buildClinePassSubscriptionUrl(clineUser?.appBaseUrl)
|
||||
|
||||
return {
|
||||
id: CLINE_PASS_PROMO_BANNER_ID,
|
||||
icon: <Sparkles className="size-4 text-[var(--vscode-charts-yellow)]" />,
|
||||
title: "Try ClinePass",
|
||||
description: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="m-0">
|
||||
A $9.99/month subscription for the latest open-weights models, at much lower cost than
|
||||
paying for direct API access.
|
||||
</p>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
UiServiceClient.openUrl({ value: subscriptionUrl }).catch(console.error)
|
||||
}}
|
||||
size="sm">
|
||||
Get ClinePass
|
||||
</Button>
|
||||
</div>
|
||||
<button
|
||||
className="w-fit cursor-pointer border-0 bg-transparent p-0 text-left text-xs text-[var(--vscode-textLink-foreground)] underline hover:cursor-pointer hover:text-[var(--vscode-textLink-activeForeground,var(--vscode-textLink-foreground))]"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await handleFieldsChange({
|
||||
planModeApiProvider: "cline-pass",
|
||||
actModeApiProvider: "cline-pass",
|
||||
})
|
||||
navigateToSettings("api-config")
|
||||
} catch (error) {
|
||||
console.error("Failed to switch to ClinePass:", error)
|
||||
}
|
||||
}}
|
||||
type="button">
|
||||
Switch to ClinePass provider to access subscription.
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
onDismiss: dismissPromoBanner,
|
||||
}
|
||||
}, [
|
||||
clineUser?.appBaseUrl,
|
||||
dismissedLocalBanners,
|
||||
handleBannerDismiss,
|
||||
handleFieldsChange,
|
||||
isBannerDismissed,
|
||||
navigateToSettings,
|
||||
])
|
||||
|
||||
/**
|
||||
* Build array of active banners for carousel
|
||||
* Combines hardcoded banners (bannerConfig) with dynamic banners from extension state
|
||||
@@ -247,8 +313,9 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
|
||||
)
|
||||
|
||||
// Combine both sources: extension state banners first, then hardcoded banners
|
||||
return [...extensionStateBanners, ...hardcodedBanners]
|
||||
}, [bannerConfig, banners, clineUser, handleBannerAction, handleBannerDismiss])
|
||||
const carouselBanners = [...extensionStateBanners, ...hardcodedBanners]
|
||||
return clinePassPromoBanner ? [clinePassPromoBanner, ...carouselBanners] : carouselBanners
|
||||
}, [bannerConfig, banners, clinePassPromoBanner, handleBannerAction, handleBannerDismiss])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 w-full h-full p-0 m-0">
|
||||
@@ -260,10 +327,10 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
|
||||
welcomeBanners={welcomeBanners}
|
||||
/>
|
||||
<div className="overflow-y-auto flex flex-col pb-2.5">
|
||||
<HomeHeader shouldShowQuickWins={shouldShowQuickWins} />
|
||||
{!showWhatsNewModal && (
|
||||
<>
|
||||
<BannerCarousel banners={activeBanners} />
|
||||
<HomeHeader shouldShowQuickWins={shouldShowQuickWins} />
|
||||
{!shouldShowQuickWins && taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
|
||||
{/* Quick launch worktree button */}
|
||||
{isGitRepo && worktreesEnabled?.featureFlag && worktreesEnabled?.user && (
|
||||
@@ -313,6 +380,7 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{showWhatsNewModal && <HomeHeader shouldShowQuickWins={shouldShowQuickWins} />}
|
||||
</div>
|
||||
<SuggestedTasks shouldShowQuickWins={shouldShowQuickWins} />
|
||||
|
||||
|
||||
@@ -49,17 +49,22 @@ const BannerCardContent: React.FC<BannerCardContentProps> = ({ banner, isActive,
|
||||
}}>
|
||||
{/* Title with optional icon */}
|
||||
<h3
|
||||
className={cn("font-semibold mb-2 flex items-center gap-2 text-base pr-0", {
|
||||
className={cn("font-semibold mb-2 flex items-center text-base pr-0", {
|
||||
"gap-2": banner.icon,
|
||||
"pr-6": showDismissButton,
|
||||
})}>
|
||||
<span className="shrink-0">{banner.icon}</span>
|
||||
{banner.icon && <span className="shrink-0">{banner.icon}</span>}
|
||||
{banner.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<div className="text-sm text-description leading-relaxed [&>*:last-child]:mb-0 [&_a]:hover:underline">
|
||||
{markdownContent}
|
||||
</div>
|
||||
{typeof banner.description === "string" ? (
|
||||
<div className="text-sm text-description leading-relaxed [&>*:last-child]:mb-0 [&_a]:hover:underline">
|
||||
{markdownContent}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-description leading-relaxed">{banner.description}</div>
|
||||
)}
|
||||
|
||||
{/* Action buttons */}
|
||||
{banner.actions?.length ? (
|
||||
|
||||
@@ -7,15 +7,13 @@ import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Item, ItemContent, ItemDescription, ItemHeader, ItemMedia, ItemTitle } from "@/components/ui/item"
|
||||
import { CLINE_PASS_FEATURE_FLAG } from "@/constants/featureFlags"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useHasFeatureFlag } from "@/hooks/useFeatureFlag"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { AccountServiceClient, StateServiceClient } from "@/services/grpc-client"
|
||||
import { setPendingClinePassSubscribe } from "./clinePassSubscribe"
|
||||
import ApiConfigurationSection from "../settings/sections/ApiConfigurationSection"
|
||||
import { useApiConfigurationHandlers } from "../settings/utils/useApiConfigurationHandlers"
|
||||
import WelcomeView from "../welcome/WelcomeView"
|
||||
import { setPendingClinePassSubscribe } from "./clinePassSubscribe"
|
||||
import {
|
||||
getCapabilities,
|
||||
getClineUIOnboardingGroups,
|
||||
@@ -301,8 +299,7 @@ const OnboardingStepContent = ({
|
||||
const OnboardingViewContent = ({ onboardingModels }: { onboardingModels: OnboardingModelGroup }) => {
|
||||
const { handleFieldsChange } = useApiConfigurationHandlers()
|
||||
const { openRouterModels, hideSettings, hideAccount, setShowWelcome } = useExtensionState()
|
||||
const isClinePassEnabled = useHasFeatureFlag(CLINE_PASS_FEATURE_FLAG)
|
||||
const userTypeSelections = useMemo(() => getUserTypeSelections(isClinePassEnabled), [isClinePassEnabled])
|
||||
const userTypeSelections = useMemo(() => getUserTypeSelections(true), [])
|
||||
|
||||
const [stepNumber, setStepNumber] = useState(0)
|
||||
const [isActionLoading, setIsActionLoading] = useState(false)
|
||||
|
||||
@@ -4,9 +4,7 @@ import { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import type { ClineRecommendedModel } from "@shared/proto/cline/models"
|
||||
import type { OnboardingModel, OnboardingModelGroup } from "@shared/proto/cline/state"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { CLINE_PASS_FEATURE_FLAG } from "@/constants/featureFlags"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useHasFeatureFlag } from "@/hooks/useFeatureFlag"
|
||||
import { ModelsServiceClient } from "@/services/grpc-client"
|
||||
import { CLINEPASS_GROUP, getRecommendedModelsData, type RecommendedModelsData } from "./data-models"
|
||||
|
||||
@@ -51,7 +49,6 @@ type FetchState = { status: "loading" } | { status: "success"; data: Recommended
|
||||
|
||||
export function useOnboardingModels(): UseOnboardingModelsResult {
|
||||
const { openRouterModels, clineModels, refreshClineModels } = useExtensionState()
|
||||
const isClinePassEnabled = useHasFeatureFlag(CLINE_PASS_FEATURE_FLAG)
|
||||
const [fetchState, setFetchState] = useState<FetchState>({ status: "loading" })
|
||||
|
||||
useEffect(() => {
|
||||
@@ -61,7 +58,7 @@ export function useOnboardingModels(): UseOnboardingModelsResult {
|
||||
try {
|
||||
const response = await ModelsServiceClient.refreshClineRecommendedModelsRpc(EmptyRequest.create({}))
|
||||
if (!cancelled) {
|
||||
const data = getRecommendedModelsData(response, isClinePassEnabled)
|
||||
const data = getRecommendedModelsData(response, true)
|
||||
if (!data) {
|
||||
setFetchState({ status: "empty" })
|
||||
} else {
|
||||
@@ -80,7 +77,7 @@ export function useOnboardingModels(): UseOnboardingModelsResult {
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [isClinePassEnabled])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
refreshClineModels()
|
||||
|
||||
@@ -1,75 +1,84 @@
|
||||
import { StringRequest } from "@shared/proto/cline/common"
|
||||
import PROVIDERS from "@shared/providers/providers.json"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import Fuse from "fuse.js"
|
||||
import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useInterval } from "react-use"
|
||||
import styled from "styled-components"
|
||||
import { normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
import { PLATFORM_CONFIG, PlatformType } from "@/config/platform.config"
|
||||
import { CLINE_PASS_FEATURE_FLAG } from "@/constants/featureFlags"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useHasFeatureFlag } from "@/hooks/useFeatureFlag"
|
||||
import { ModelsServiceClient } from "@/services/grpc-client"
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
|
||||
import { AIhubmixProvider } from "./providers/AihubmixProvider"
|
||||
import { AnthropicProvider } from "./providers/AnthropicProvider"
|
||||
import { AskSageProvider } from "./providers/AskSageProvider"
|
||||
import { BasetenProvider } from "./providers/BasetenProvider"
|
||||
import { BedrockProvider } from "./providers/BedrockProvider"
|
||||
import { CerebrasProvider } from "./providers/CerebrasProvider"
|
||||
import { ClaudeCodeProvider } from "./providers/ClaudeCodeProvider"
|
||||
import { ClinePassProvider } from "./providers/ClinePassProvider"
|
||||
import { ClineProvider } from "./providers/ClineProvider"
|
||||
import { DeepSeekProvider } from "./providers/DeepSeekProvider"
|
||||
import { DifyProvider } from "./providers/DifyProvider"
|
||||
import { DoubaoProvider } from "./providers/DoubaoProvider"
|
||||
import { FireworksProvider } from "./providers/FireworksProvider"
|
||||
import { GeminiProvider } from "./providers/GeminiProvider"
|
||||
import { GroqProvider } from "./providers/GroqProvider"
|
||||
import { HicapProvider } from "./providers/HicapProvider"
|
||||
import { HuaweiCloudMaasProvider } from "./providers/HuaweiCloudMaasProvider"
|
||||
import { HuggingFaceProvider } from "./providers/HuggingFaceProvider"
|
||||
import { LiteLlmProvider } from "./providers/LiteLlmProvider"
|
||||
import { LMStudioProvider } from "./providers/LMStudioProvider"
|
||||
import { MinimaxProvider } from "./providers/MiniMaxProvider"
|
||||
import { MistralProvider } from "./providers/MistralProvider"
|
||||
import { MoonshotProvider } from "./providers/MoonshotProvider"
|
||||
import { NebiusProvider } from "./providers/NebiusProvider"
|
||||
import { NousResearchProvider } from "./providers/NousresearchProvider"
|
||||
import { OcaProvider } from "./providers/OcaProvider"
|
||||
import { OllamaProvider } from "./providers/OllamaProvider"
|
||||
import { OpenAICompatibleProvider } from "./providers/OpenAICompatible"
|
||||
import { OpenAINativeProvider } from "./providers/OpenAINative"
|
||||
import { OpenAiCodexProvider } from "./providers/OpenAiCodexProvider"
|
||||
import { OpenRouterProvider } from "./providers/OpenRouterProvider"
|
||||
import { QwenCodeProvider } from "./providers/QwenCodeProvider"
|
||||
import { QwenProvider } from "./providers/QwenProvider"
|
||||
import { RequestyProvider } from "./providers/RequestyProvider"
|
||||
import { SambanovaProvider } from "./providers/SambanovaProvider"
|
||||
import { SapAiCoreProvider } from "./providers/SapAiCoreProvider"
|
||||
import { TogetherProvider } from "./providers/TogetherProvider"
|
||||
import { VercelAIGatewayProvider } from "./providers/VercelAIGatewayProvider"
|
||||
import { VertexProvider } from "./providers/VertexProvider"
|
||||
import { VSCodeLmProvider } from "./providers/VSCodeLmProvider"
|
||||
import { WandbProvider } from "./providers/WandbProvider"
|
||||
import { XaiProvider } from "./providers/XaiProvider"
|
||||
import { ZAiProvider } from "./providers/ZAiProvider"
|
||||
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
|
||||
import type { ApiProvider } from "@shared/api";
|
||||
import { modelsDevProviderOptions } from "@shared/models/models-dev-catalog";
|
||||
import { StringRequest } from "@shared/proto/cline/common";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react";
|
||||
import Fuse from "fuse.js";
|
||||
import {
|
||||
type KeyboardEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useInterval } from "react-use";
|
||||
import styled from "styled-components";
|
||||
import { normalizeApiConfiguration } from "@/components/settings/utils/providerUtils";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { PLATFORM_CONFIG, PlatformType } from "@/config/platform.config";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ModelsServiceClient } from "@/services/grpc-client";
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker";
|
||||
import { AIhubmixProvider } from "./providers/AihubmixProvider";
|
||||
import { AnthropicProvider } from "./providers/AnthropicProvider";
|
||||
import { AskSageProvider } from "./providers/AskSageProvider";
|
||||
import { BasetenProvider } from "./providers/BasetenProvider";
|
||||
import { BedrockProvider } from "./providers/BedrockProvider";
|
||||
import { CerebrasProvider } from "./providers/CerebrasProvider";
|
||||
import { ClaudeCodeProvider } from "./providers/ClaudeCodeProvider";
|
||||
import { ClineProvider } from "./providers/ClineProvider";
|
||||
import { DeepSeekProvider } from "./providers/DeepSeekProvider";
|
||||
import { DifyProvider } from "./providers/DifyProvider";
|
||||
import { DoubaoProvider } from "./providers/DoubaoProvider";
|
||||
import { FireworksProvider } from "./providers/FireworksProvider";
|
||||
import { GeminiProvider } from "./providers/GeminiProvider";
|
||||
import { GroqProvider } from "./providers/GroqProvider";
|
||||
import { HicapProvider } from "./providers/HicapProvider";
|
||||
import { HuaweiCloudMaasProvider } from "./providers/HuaweiCloudMaasProvider";
|
||||
import { HuggingFaceProvider } from "./providers/HuggingFaceProvider";
|
||||
import { LiteLlmProvider } from "./providers/LiteLlmProvider";
|
||||
import { LMStudioProvider } from "./providers/LMStudioProvider";
|
||||
import { MinimaxProvider } from "./providers/MiniMaxProvider";
|
||||
import { MistralProvider } from "./providers/MistralProvider";
|
||||
import { MoonshotProvider } from "./providers/MoonshotProvider";
|
||||
import { NebiusProvider } from "./providers/NebiusProvider";
|
||||
import { NousResearchProvider } from "./providers/NousresearchProvider";
|
||||
import { OcaProvider } from "./providers/OcaProvider";
|
||||
import { OllamaProvider } from "./providers/OllamaProvider";
|
||||
import { OpenAICompatibleProvider } from "./providers/OpenAICompatible";
|
||||
import { OpenAINativeProvider } from "./providers/OpenAINative";
|
||||
import { OpenAiCodexProvider } from "./providers/OpenAiCodexProvider";
|
||||
import { OpenRouterProvider } from "./providers/OpenRouterProvider";
|
||||
import { QwenCodeProvider } from "./providers/QwenCodeProvider";
|
||||
import { QwenProvider } from "./providers/QwenProvider";
|
||||
import { RequestyProvider } from "./providers/RequestyProvider";
|
||||
import { SambanovaProvider } from "./providers/SambanovaProvider";
|
||||
import { SapAiCoreProvider } from "./providers/SapAiCoreProvider";
|
||||
import { TogetherProvider } from "./providers/TogetherProvider";
|
||||
import { VercelAIGatewayProvider } from "./providers/VercelAIGatewayProvider";
|
||||
import { VertexProvider } from "./providers/VertexProvider";
|
||||
import { VSCodeLmProvider } from "./providers/VSCodeLmProvider";
|
||||
import { WandbProvider } from "./providers/WandbProvider";
|
||||
import { XaiProvider } from "./providers/XaiProvider";
|
||||
import { ZAiProvider } from "./providers/ZAiProvider";
|
||||
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers";
|
||||
|
||||
interface ApiOptionsProps {
|
||||
showModelOptions: boolean
|
||||
apiErrorMessage?: string
|
||||
modelIdErrorMessage?: string
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
initialModelTab?: "recommended" | "free"
|
||||
showModelOptions: boolean;
|
||||
apiErrorMessage?: string;
|
||||
modelIdErrorMessage?: string;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
initialModelTab?: "recommended" | "free";
|
||||
}
|
||||
|
||||
// This is necessary to ensure dropdown opens downward, important for when this is used in popup
|
||||
export const DROPDOWN_Z_INDEX = OPENROUTER_MODEL_PICKER_Z_INDEX + 2 // Higher than the OpenRouterModelPicker's and ModelSelectorTooltip's z-index
|
||||
export const DROPDOWN_Z_INDEX = OPENROUTER_MODEL_PICKER_Z_INDEX + 2; // Higher than the OpenRouterModelPicker's and ModelSelectorTooltip's z-index
|
||||
|
||||
export const DropdownContainer = styled.div<{ zIndex?: number }>`
|
||||
position: relative;
|
||||
@@ -81,14 +90,14 @@ export const DropdownContainer = styled.div<{ zIndex?: number }>`
|
||||
top: 100% !important;
|
||||
bottom: auto !important;
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
declare module "vscode" {
|
||||
interface LanguageModelChatSelector {
|
||||
vendor?: string
|
||||
family?: string
|
||||
version?: string
|
||||
id?: string
|
||||
vendor?: string;
|
||||
family?: string;
|
||||
version?: string;
|
||||
id?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,14 +110,17 @@ const ApiOptions = ({
|
||||
initialModelTab,
|
||||
}: ApiOptionsProps) => {
|
||||
// Use full context state for immediate save payload
|
||||
const { apiConfiguration, remoteConfigSettings } = useExtensionState()
|
||||
const isClinePassEnabled = useHasFeatureFlag(CLINE_PASS_FEATURE_FLAG)
|
||||
const { apiConfiguration, remoteConfigSettings } = useExtensionState();
|
||||
|
||||
const { selectedProvider } = normalizeApiConfiguration(apiConfiguration, currentMode, { isClinePassEnabled })
|
||||
const { selectedProvider } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
{ isClinePassEnabled: true },
|
||||
);
|
||||
|
||||
const { handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
const { handleModeFieldChange } = useApiConfigurationHandlers();
|
||||
|
||||
const [_ollamaModels, setOllamaModels] = useState<string[]>([])
|
||||
const [_ollamaModels, setOllamaModels] = useState<string[]>([]);
|
||||
|
||||
// Poll ollama/vscode-lm models
|
||||
const requestLocalModels = useCallback(async () => {
|
||||
@@ -118,141 +130,179 @@ const ApiOptions = ({
|
||||
StringRequest.create({
|
||||
value: apiConfiguration?.ollamaBaseUrl || "",
|
||||
}),
|
||||
)
|
||||
if (response && response.values) {
|
||||
setOllamaModels(response.values)
|
||||
);
|
||||
if (response?.values) {
|
||||
setOllamaModels(response.values);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch Ollama models:", error)
|
||||
setOllamaModels([])
|
||||
console.error("Failed to fetch Ollama models:", error);
|
||||
setOllamaModels([]);
|
||||
}
|
||||
}
|
||||
}, [selectedProvider, apiConfiguration?.ollamaBaseUrl])
|
||||
}, [selectedProvider, apiConfiguration?.ollamaBaseUrl]);
|
||||
useEffect(() => {
|
||||
if (selectedProvider === "ollama") {
|
||||
requestLocalModels()
|
||||
requestLocalModels();
|
||||
}
|
||||
}, [selectedProvider, requestLocalModels])
|
||||
useInterval(requestLocalModels, selectedProvider === "ollama" ? 2000 : null)
|
||||
}, [selectedProvider, requestLocalModels]);
|
||||
useInterval(requestLocalModels, selectedProvider === "ollama" ? 2000 : null);
|
||||
|
||||
// Provider search state
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const itemRefs = useRef<(HTMLDivElement | null)[]>([])
|
||||
const dropdownListRef = useRef<HTMLDivElement>(null)
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const dropdownListRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const providerOptions = useMemo(() => {
|
||||
let providers = PROVIDERS.list
|
||||
if (!isClinePassEnabled) {
|
||||
providers = providers.filter((option) => option.value !== "cline-pass")
|
||||
}
|
||||
let providers = [...modelsDevProviderOptions];
|
||||
// Filter by platform
|
||||
if (PLATFORM_CONFIG.type !== PlatformType.VSCODE) {
|
||||
// Don't include VS Code LM API for non-VSCode platforms
|
||||
providers = providers.filter((option) => option.value !== "vscode-lm")
|
||||
providers = providers.filter((option) => option.value !== "vscode-lm");
|
||||
}
|
||||
|
||||
// Filter by remote config if remoteConfiguredProviders is set
|
||||
const remoteProviders: string[] = remoteConfigSettings?.remoteConfiguredProviders || []
|
||||
const remoteProviders: string[] =
|
||||
remoteConfigSettings?.remoteConfiguredProviders || [];
|
||||
if (remoteProviders.length > 0) {
|
||||
providers = providers.filter((option) => remoteProviders.includes(option.value))
|
||||
const effectiveRemoteProviders =
|
||||
remoteProviders.includes("cline-pass") &&
|
||||
!remoteProviders.includes("cline")
|
||||
? [...remoteProviders, "cline"]
|
||||
: remoteProviders;
|
||||
providers = providers.filter((option) =>
|
||||
effectiveRemoteProviders.includes(option.value),
|
||||
);
|
||||
}
|
||||
|
||||
return providers
|
||||
}, [isClinePassEnabled, remoteConfigSettings])
|
||||
return providers;
|
||||
}, [remoteConfigSettings]);
|
||||
|
||||
const getProviderDisplayLabel = useCallback(
|
||||
(option: (typeof modelsDevProviderOptions)[number]) => {
|
||||
return option.value === "cline" ? "Cline Usage-Billing" : option.label;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const currentProviderLabel = useMemo(() => {
|
||||
return providerOptions.find((option) => option.value === selectedProvider)?.label || selectedProvider
|
||||
}, [providerOptions, selectedProvider])
|
||||
const selectedOption = providerOptions.find(
|
||||
(option) => option.value === selectedProvider,
|
||||
);
|
||||
return selectedOption
|
||||
? getProviderDisplayLabel(selectedOption)
|
||||
: selectedProvider;
|
||||
}, [getProviderDisplayLabel, providerOptions, selectedProvider]);
|
||||
|
||||
// Sync search term with current provider when not searching
|
||||
useEffect(() => {
|
||||
if (!isDropdownVisible) {
|
||||
setSearchTerm(currentProviderLabel)
|
||||
setSearchTerm(currentProviderLabel);
|
||||
}
|
||||
}, [currentProviderLabel, isDropdownVisible])
|
||||
}, [currentProviderLabel, isDropdownVisible]);
|
||||
|
||||
const searchableItems = useMemo(() => {
|
||||
return providerOptions.map((option) => ({
|
||||
value: option.value,
|
||||
html: option.label,
|
||||
}))
|
||||
}, [providerOptions])
|
||||
html: getProviderDisplayLabel(option),
|
||||
searchText:
|
||||
option.value === "cline"
|
||||
? "Cline Usage Billing usage based pay as you go"
|
||||
: option.value === "cline-pass"
|
||||
? "ClinePass subscription included models"
|
||||
: option.label,
|
||||
}));
|
||||
}, [getProviderDisplayLabel, providerOptions]);
|
||||
|
||||
const fuse = useMemo(() => {
|
||||
return new Fuse(searchableItems, {
|
||||
keys: ["html"],
|
||||
keys: ["html", "searchText"],
|
||||
threshold: 0.3,
|
||||
shouldSort: true,
|
||||
isCaseSensitive: false,
|
||||
ignoreLocation: false,
|
||||
includeMatches: true,
|
||||
minMatchCharLength: 1,
|
||||
})
|
||||
}, [searchableItems])
|
||||
});
|
||||
}, [searchableItems]);
|
||||
|
||||
const providerSearchResults = useMemo(() => {
|
||||
return searchTerm && searchTerm !== currentProviderLabel ? fuse.search(searchTerm)?.map((r) => r.item) : searchableItems
|
||||
}, [searchableItems, searchTerm, fuse, currentProviderLabel])
|
||||
return searchTerm && searchTerm !== currentProviderLabel
|
||||
? fuse.search(searchTerm)?.map((r) => r.item)
|
||||
: searchableItems;
|
||||
}, [searchableItems, searchTerm, fuse, currentProviderLabel]);
|
||||
|
||||
const handleProviderChange = (newProvider: string) => {
|
||||
handleModeFieldChange({ plan: "planModeApiProvider", act: "actModeApiProvider" }, newProvider as any, currentMode)
|
||||
setIsDropdownVisible(false)
|
||||
setSelectedIndex(-1)
|
||||
}
|
||||
const handleProviderChange = (newProvider: ApiProvider) => {
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiProvider", act: "actModeApiProvider" },
|
||||
newProvider,
|
||||
currentMode,
|
||||
);
|
||||
setIsDropdownVisible(false);
|
||||
setSelectedIndex(-1);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!isDropdownVisible) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowDown":
|
||||
event.preventDefault()
|
||||
setSelectedIndex((prev) => (prev < providerSearchResults.length - 1 ? prev + 1 : prev))
|
||||
break
|
||||
event.preventDefault();
|
||||
setSelectedIndex((prev) =>
|
||||
prev < providerSearchResults.length - 1 ? prev + 1 : prev,
|
||||
);
|
||||
break;
|
||||
case "ArrowUp":
|
||||
event.preventDefault()
|
||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev))
|
||||
break
|
||||
event.preventDefault();
|
||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev));
|
||||
break;
|
||||
case "Enter":
|
||||
event.preventDefault()
|
||||
if (selectedIndex >= 0 && selectedIndex < providerSearchResults.length) {
|
||||
handleProviderChange(providerSearchResults[selectedIndex].value)
|
||||
event.preventDefault();
|
||||
if (
|
||||
selectedIndex >= 0 &&
|
||||
selectedIndex < providerSearchResults.length
|
||||
) {
|
||||
handleProviderChange(providerSearchResults[selectedIndex].value);
|
||||
}
|
||||
break
|
||||
break;
|
||||
case "Escape":
|
||||
setIsDropdownVisible(false)
|
||||
setSelectedIndex(-1)
|
||||
setSearchTerm(currentProviderLabel)
|
||||
break
|
||||
setIsDropdownVisible(false);
|
||||
setSelectedIndex(-1);
|
||||
setSearchTerm(currentProviderLabel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsDropdownVisible(false)
|
||||
setSearchTerm(currentProviderLabel)
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsDropdownVisible(false);
|
||||
setSearchTerm(currentProviderLabel);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside)
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside)
|
||||
}
|
||||
}, [currentProviderLabel])
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [currentProviderLabel]);
|
||||
|
||||
// Reset selection when search term changes
|
||||
useEffect(() => {
|
||||
setSelectedIndex(-1)
|
||||
void searchTerm;
|
||||
setSelectedIndex(-1);
|
||||
if (dropdownListRef.current) {
|
||||
dropdownListRef.current.scrollTop = 0
|
||||
dropdownListRef.current.scrollTop = 0;
|
||||
}
|
||||
}, [searchTerm])
|
||||
}, [searchTerm]);
|
||||
|
||||
// Scroll selected item into view
|
||||
useEffect(() => {
|
||||
@@ -260,9 +310,9 @@ const ApiOptions = ({
|
||||
itemRefs.current[selectedIndex]?.scrollIntoView({
|
||||
block: "nearest",
|
||||
behavior: "smooth",
|
||||
})
|
||||
});
|
||||
}
|
||||
}, [selectedIndex])
|
||||
}, [selectedIndex]);
|
||||
|
||||
/*
|
||||
VSCodeDropdown has an open bug where dynamically rendered options don't auto select the provided value prop. You can see this for yourself by comparing it with normal select/option elements, which work as expected.
|
||||
@@ -274,7 +324,14 @@ const ApiOptions = ({
|
||||
*/
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5, marginBottom: isPopup ? -10 : 0 }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
marginBottom: isPopup ? -10 : 0,
|
||||
}}
|
||||
>
|
||||
<style>
|
||||
{`
|
||||
.provider-item-highlight {
|
||||
@@ -284,7 +341,8 @@ const ApiOptions = ({
|
||||
`}
|
||||
</style>
|
||||
<DropdownContainer className="dropdown-container">
|
||||
{remoteConfigSettings?.remoteConfiguredProviders && remoteConfigSettings.remoteConfiguredProviders.length > 0 ? (
|
||||
{remoteConfigSettings?.remoteConfiguredProviders &&
|
||||
remoteConfigSettings.remoteConfiguredProviders.length > 0 ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
@@ -294,7 +352,10 @@ const ApiOptions = ({
|
||||
<i className="codicon codicon-lock text-description text-sm" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Provider options are managed by your organization's remote configuration</TooltipContent>
|
||||
<TooltipContent>
|
||||
Provider options are managed by your organization's remote
|
||||
configuration
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<label htmlFor="api-provider">
|
||||
@@ -306,12 +367,12 @@ const ApiOptions = ({
|
||||
data-testid="provider-selector-input"
|
||||
id="api-provider"
|
||||
onFocus={() => {
|
||||
setIsDropdownVisible(true)
|
||||
setSearchTerm("")
|
||||
setIsDropdownVisible(true);
|
||||
setSearchTerm("");
|
||||
}}
|
||||
onInput={(e) => {
|
||||
setSearchTerm((e.target as HTMLInputElement)?.value || "")
|
||||
setIsDropdownVisible(true)
|
||||
setSearchTerm((e.target as HTMLInputElement)?.value || "");
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Search and select provider..."
|
||||
@@ -322,22 +383,28 @@ const ApiOptions = ({
|
||||
position: "relative",
|
||||
minWidth: 130,
|
||||
}}
|
||||
value={searchTerm}>
|
||||
value={searchTerm}
|
||||
>
|
||||
{searchTerm && searchTerm !== currentProviderLabel && (
|
||||
<div
|
||||
<button
|
||||
aria-label="Clear search"
|
||||
className="input-icon-button codicon codicon-close"
|
||||
onClick={() => {
|
||||
setSearchTerm("")
|
||||
setIsDropdownVisible(true)
|
||||
setSearchTerm("");
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
slot="end"
|
||||
style={{
|
||||
background: "none",
|
||||
border: 0,
|
||||
color: "inherit",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
}}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
</VSCodeTextField>
|
||||
@@ -351,9 +418,10 @@ const ApiOptions = ({
|
||||
onClick={() => handleProviderChange(item.value)}
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
ref={(el) => {
|
||||
itemRefs.current[index] = el
|
||||
itemRefs.current[index] = el;
|
||||
}}
|
||||
role="option">
|
||||
role="option"
|
||||
>
|
||||
<span>{item.html}</span>
|
||||
</ProviderDropdownItem>
|
||||
))}
|
||||
@@ -363,175 +431,333 @@ const ApiOptions = ({
|
||||
</DropdownContainer>
|
||||
|
||||
{apiConfiguration && selectedProvider === "hicap" && (
|
||||
<HicapProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "cline" && (
|
||||
<ClineProvider
|
||||
<HicapProvider
|
||||
currentMode={currentMode}
|
||||
initialModelTab={initialModelTab}
|
||||
isClinePassEnabled={isClinePassEnabled}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && isClinePassEnabled && selectedProvider === "cline-pass" && (
|
||||
<ClinePassProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
)}
|
||||
{apiConfiguration &&
|
||||
(selectedProvider === "cline" || selectedProvider === "cline-pass") && (
|
||||
<ClineProvider
|
||||
currentMode={currentMode}
|
||||
initialModelTab={initialModelTab}
|
||||
isClinePassEnabled={true}
|
||||
isPopup={isPopup}
|
||||
selectedProvider={selectedProvider}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "asksage" && (
|
||||
<AskSageProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<AskSageProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "anthropic" && (
|
||||
<AnthropicProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<AnthropicProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "claude-code" && (
|
||||
<ClaudeCodeProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<ClaudeCodeProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "openai-native" && (
|
||||
<OpenAINativeProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<OpenAINativeProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "openai-codex" && (
|
||||
<OpenAiCodexProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<OpenAiCodexProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "qwen" && (
|
||||
<QwenProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<QwenProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "qwen-code" && (
|
||||
<QwenCodeProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<QwenCodeProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "doubao" && (
|
||||
<DoubaoProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<DoubaoProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "mistral" && (
|
||||
<MistralProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<MistralProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "openrouter" && (
|
||||
<OpenRouterProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<OpenRouterProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "deepseek" && (
|
||||
<DeepSeekProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<DeepSeekProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "together" && (
|
||||
<TogetherProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<TogetherProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "openai" && (
|
||||
<OpenAICompatibleProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<OpenAICompatibleProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "vercel-ai-gateway" && (
|
||||
<VercelAIGatewayProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<VercelAIGatewayProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "sambanova" && (
|
||||
<SambanovaProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<SambanovaProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "bedrock" && (
|
||||
<BedrockProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<BedrockProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "vertex" && (
|
||||
<VertexProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<VertexProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "gemini" && (
|
||||
<GeminiProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<GeminiProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "requesty" && (
|
||||
<RequestyProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<RequestyProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "fireworks" && (
|
||||
<FireworksProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<FireworksProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "vscode-lm" && <VSCodeLmProvider currentMode={currentMode} />}
|
||||
{apiConfiguration && selectedProvider === "vscode-lm" && (
|
||||
<VSCodeLmProvider currentMode={currentMode} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "groq" && (
|
||||
<GroqProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<GroqProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
{apiConfiguration && selectedProvider === "baseten" && (
|
||||
<BasetenProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<BasetenProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
{apiConfiguration && selectedProvider === "litellm" && (
|
||||
<LiteLlmProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<LiteLlmProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "lmstudio" && (
|
||||
<LMStudioProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<LMStudioProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "ollama" && (
|
||||
<OllamaProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<OllamaProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "moonshot" && (
|
||||
<MoonshotProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<MoonshotProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "huggingface" && (
|
||||
<HuggingFaceProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<HuggingFaceProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "nebius" && (
|
||||
<NebiusProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<NebiusProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "wandb" && (
|
||||
<WandbProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<WandbProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "xai" && (
|
||||
<XaiProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<XaiProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "cerebras" && (
|
||||
<CerebrasProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<CerebrasProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "sapaicore" && (
|
||||
<SapAiCoreProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<SapAiCoreProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "huawei-cloud-maas" && (
|
||||
<HuaweiCloudMaasProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<HuaweiCloudMaasProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "dify" && (
|
||||
<DifyProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<DifyProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "zai" && (
|
||||
<ZAiProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<ZAiProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "minimax" && (
|
||||
<MinimaxProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<MinimaxProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "nousResearch" && (
|
||||
<NousResearchProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<NousResearchProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "oca" && <OcaProvider currentMode={currentMode} isPopup={isPopup} />}
|
||||
{apiConfiguration && selectedProvider === "oca" && (
|
||||
<OcaProvider currentMode={currentMode} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "aihubmix" && (
|
||||
<AIhubmixProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
|
||||
<AIhubmixProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{apiErrorMessage && (
|
||||
@@ -540,7 +766,8 @@ const ApiOptions = ({
|
||||
margin: "-10px 0 4px 0",
|
||||
fontSize: 12,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{apiErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
@@ -550,20 +777,21 @@ const ApiOptions = ({
|
||||
margin: "-10px 0 4px 0",
|
||||
fontSize: 12,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{modelIdErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiOptions
|
||||
export default ApiOptions;
|
||||
|
||||
const ProviderDropdownWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
`;
|
||||
|
||||
const ProviderDropdownList = styled.div`
|
||||
position: absolute;
|
||||
@@ -577,7 +805,7 @@ const ProviderDropdownList = styled.div`
|
||||
z-index: ${DROPDOWN_Z_INDEX - 1};
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
`
|
||||
`;
|
||||
|
||||
const ProviderDropdownItem = styled.div<{ isSelected: boolean }>`
|
||||
padding: 5px 10px;
|
||||
@@ -590,4 +818,4 @@ const ProviderDropdownItem = styled.div<{ isSelected: boolean }>`
|
||||
&:hover {
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
@@ -29,7 +29,7 @@ export const ClineAccountInfoCard = () => {
|
||||
<div className="max-w-[600px]">
|
||||
{user ? (
|
||||
<VSCodeButton appearance="secondary" onClick={handleShowAccount}>
|
||||
View Billing & Usage
|
||||
View Billing History
|
||||
</VSCodeButton>
|
||||
) : (
|
||||
<div>
|
||||
@@ -37,7 +37,7 @@ export const ClineAccountInfoCard = () => {
|
||||
Sign Up with Cline
|
||||
{isLoading && (
|
||||
<span className="ml-1 animate-spin">
|
||||
<span className="codicon codicon-refresh"></span>
|
||||
<span className="codicon codicon-refresh" />
|
||||
</span>
|
||||
)}
|
||||
</VSCodeButton>
|
||||
|
||||
@@ -189,8 +189,9 @@ export const ModelInfoView = ({
|
||||
const [advancedExpanded, setAdvancedExpanded] = useState(false)
|
||||
|
||||
const isGemini = Object.keys(geminiModels).includes(selectedModelId)
|
||||
const hidePricing = selectedModelId.trim().toLowerCase().startsWith("cline-pass/")
|
||||
const hasThinkingConfig = hasThinkingBudget(modelInfo)
|
||||
const hasTiers = !!modelInfo.tiers && modelInfo.tiers.length > 0
|
||||
const hasTiers = !hidePricing && !!modelInfo.tiers && modelInfo.tiers.length > 0
|
||||
|
||||
// Capability checks
|
||||
const hasImages = supportsImages(modelInfo)
|
||||
@@ -198,7 +199,8 @@ export const ModelInfoView = ({
|
||||
const hasCaching = !isGemini && supportsPromptCache(modelInfo)
|
||||
|
||||
// Check if we have cache pricing to show in Advanced section
|
||||
const hasCachePricing = modelInfo.supportsPromptCache && (modelInfo.cacheWritesPrice || modelInfo.cacheReadsPrice)
|
||||
const hasCachePricing =
|
||||
!hidePricing && modelInfo.supportsPromptCache && (modelInfo.cacheWritesPrice || modelInfo.cacheReadsPrice)
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: 4 }}>
|
||||
@@ -215,13 +217,13 @@ export const ModelInfoView = ({
|
||||
<InfoValue>{formatCompactContext(modelInfo.contextWindow)}</InfoValue>
|
||||
</InfoItem>
|
||||
)}
|
||||
{modelInfo.inputPrice !== undefined && (
|
||||
{!hidePricing && modelInfo.inputPrice !== undefined && (
|
||||
<InfoItem>
|
||||
<InfoLabel>Input: </InfoLabel>
|
||||
<InfoValue>{formatCompactPrice(modelInfo.inputPrice)}</InfoValue>
|
||||
</InfoItem>
|
||||
)}
|
||||
{modelInfo.outputPrice !== undefined && (
|
||||
{!hidePricing && modelInfo.outputPrice !== undefined && (
|
||||
<InfoItem>
|
||||
<InfoLabel>Output: </InfoLabel>
|
||||
<InfoValue>
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import styled from "styled-components"
|
||||
import type { ModelInfo } from "@shared/api";
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
||||
import styled from "styled-components";
|
||||
|
||||
export type ModelSelectorChangeEvent = Event & {
|
||||
target: EventTarget & { value: string };
|
||||
};
|
||||
|
||||
/**
|
||||
* Container for dropdowns that ensures proper z-index handling
|
||||
* This is necessary to ensure dropdown opens downward
|
||||
*/
|
||||
export const DropdownContainer = styled.div.attrs<{ zIndex?: number }>(({ zIndex }) => ({
|
||||
style: {
|
||||
zIndex: zIndex || 1000,
|
||||
},
|
||||
}))`
|
||||
export const DropdownContainer = styled.div.attrs<{ zIndex?: number }>(
|
||||
({ zIndex }) => ({
|
||||
style: {
|
||||
zIndex: zIndex || 1000,
|
||||
},
|
||||
}),
|
||||
)`
|
||||
position: relative;
|
||||
|
||||
// Force dropdowns to open downward
|
||||
@@ -19,17 +25,17 @@ export const DropdownContainer = styled.div.attrs<{ zIndex?: number }>(({ zIndex
|
||||
top: 100% !important;
|
||||
bottom: auto !important;
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
/**
|
||||
* Props for the ModelSelector component
|
||||
*/
|
||||
interface ModelSelectorProps {
|
||||
models: Record<string, ModelInfo>
|
||||
selectedModelId: string | undefined
|
||||
onChange: (e: any) => void
|
||||
zIndex?: number
|
||||
label?: string
|
||||
models: Record<string, ModelInfo>;
|
||||
selectedModelId: string | undefined;
|
||||
onChange: (e: ModelSelectorChangeEvent) => void;
|
||||
zIndex?: number;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -46,20 +52,35 @@ OG Saoud Note:
|
||||
/**
|
||||
* A reusable component for selecting models from a dropdown
|
||||
*/
|
||||
export const ModelSelector = ({ models, selectedModelId, onChange, zIndex, label = "Model" }: ModelSelectorProps) => {
|
||||
export const ModelSelector = ({
|
||||
models,
|
||||
selectedModelId,
|
||||
onChange,
|
||||
zIndex,
|
||||
label = "Model",
|
||||
}: ModelSelectorProps) => {
|
||||
return (
|
||||
<DropdownContainer className="dropdown-container" zIndex={zIndex}>
|
||||
<label htmlFor="model-id">
|
||||
<span className="font-medium">{label}</span>
|
||||
</label>
|
||||
<VSCodeDropdown className="w-full" id="model-id" onChange={onChange} value={selectedModelId}>
|
||||
<VSCodeDropdown
|
||||
className="w-full"
|
||||
id="model-id"
|
||||
onChange={onChange}
|
||||
value={selectedModelId}
|
||||
>
|
||||
<VSCodeOption value="">Select a model...</VSCodeOption>
|
||||
{Object.keys(models).map((modelId) => (
|
||||
<VSCodeOption className="break-words whitespace-normal max-w-full" key={modelId} value={modelId}>
|
||||
<VSCodeOption
|
||||
className="break-words whitespace-normal max-w-full"
|
||||
key={modelId}
|
||||
value={modelId}
|
||||
>
|
||||
{modelId}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
import { ANTHROPIC_FAST_MODE_SUFFIX, anthropicModels, CLAUDE_SONNET_1M_SUFFIX } from "@shared/api"
|
||||
import type { Mode } from "@shared/storage/types"
|
||||
import { isClaudeOpusAdaptiveThinkingModel, resolveClaudeOpusAdaptiveThinking } from "@shared/utils/reasoning-support"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { BaseUrlField } from "../common/BaseUrlField"
|
||||
import { ContextWindowSwitcher } from "../common/ContextWindowSwitcher"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { RemotelyConfiguredInputWrapper } from "../common/RemotelyConfiguredInputWrapper"
|
||||
import ReasoningEffortSelector from "../ReasoningEffortSelector"
|
||||
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
|
||||
import { getModeSpecificFields, normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import {
|
||||
ANTHROPIC_FAST_MODE_SUFFIX,
|
||||
CLAUDE_SONNET_1M_SUFFIX,
|
||||
} from "@shared/api";
|
||||
import { modelsDevAnthropicModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import {
|
||||
isClaudeOpusAdaptiveThinkingModel,
|
||||
resolveClaudeOpusAdaptiveThinking,
|
||||
} from "@shared/utils/reasoning-support";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { BaseUrlField } from "../common/BaseUrlField";
|
||||
import { ContextWindowSwitcher } from "../common/ContextWindowSwitcher";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { RemotelyConfiguredInputWrapper } from "../common/RemotelyConfiguredInputWrapper";
|
||||
import ReasoningEffortSelector from "../ReasoningEffortSelector";
|
||||
import ThinkingBudgetSlider from "../ThinkingBudgetSlider";
|
||||
import {
|
||||
getModeSpecificFields,
|
||||
normalizeApiConfiguration,
|
||||
} from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
// Anthropic models that support thinking/reasoning mode
|
||||
export const SUPPORTED_ANTHROPIC_THINKING_MODELS = [
|
||||
@@ -25,35 +35,51 @@ export const SUPPORTED_ANTHROPIC_THINKING_MODELS = [
|
||||
"claude-sonnet-4-5-20250929",
|
||||
`claude-sonnet-4-5-20250929${CLAUDE_SONNET_1M_SUFFIX}`,
|
||||
"claude-haiku-4-5-20251001",
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Props for the AnthropicProvider component
|
||||
*/
|
||||
interface AnthropicProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Anthropic provider configuration component
|
||||
*/
|
||||
export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: AnthropicProviderProps) => {
|
||||
const { apiConfiguration, remoteConfigSettings } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
|
||||
export const AnthropicProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: AnthropicProviderProps) => {
|
||||
const { apiConfiguration, remoteConfigSettings } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
const modeFields = getModeSpecificFields(apiConfiguration, currentMode);
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const isAdaptiveThinkingModel = isClaudeOpusAdaptiveThinkingModel(selectedModelId)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
const isAdaptiveThinkingModel =
|
||||
isClaudeOpusAdaptiveThinkingModel(selectedModelId);
|
||||
const adaptiveThinkingDefaultEffort =
|
||||
resolveClaudeOpusAdaptiveThinking(modeFields.reasoningEffort, modeFields.thinkingBudgetTokens).effort ?? "none"
|
||||
resolveClaudeOpusAdaptiveThinking(
|
||||
modeFields.reasoningEffort,
|
||||
modeFields.thinkingBudgetTokens,
|
||||
).effort ?? "none";
|
||||
|
||||
// Helper function for model switching
|
||||
const handleModelChange = (modelId: string) => {
|
||||
handleModeFieldChange({ plan: "planModeApiModelId", act: "actModeApiModelId" }, modelId, currentMode)
|
||||
}
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
modelId,
|
||||
currentMode,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -64,7 +90,9 @@ export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: An
|
||||
signupUrl="https://console.anthropic.com/settings/keys"
|
||||
/>
|
||||
|
||||
<RemotelyConfiguredInputWrapper hidden={remoteConfigSettings?.anthropicBaseUrl === undefined}>
|
||||
<RemotelyConfiguredInputWrapper
|
||||
hidden={remoteConfigSettings?.anthropicBaseUrl === undefined}
|
||||
>
|
||||
<BaseUrlField
|
||||
disabled={!!remoteConfigSettings?.anthropicBaseUrl}
|
||||
initialValue={apiConfiguration?.anthropicBaseUrl}
|
||||
@@ -79,7 +107,7 @@ export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: An
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={anthropicModels}
|
||||
models={modelsDevAnthropicModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
@@ -131,19 +159,28 @@ export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: An
|
||||
|
||||
{isAdaptiveThinkingModel ? (
|
||||
<ReasoningEffortSelector
|
||||
allowedEfforts={["none", "low", "medium", "high", "xhigh"] as const}
|
||||
allowedEfforts={
|
||||
["none", "low", "medium", "high", "xhigh"] as const
|
||||
}
|
||||
currentMode={currentMode}
|
||||
defaultEffort={adaptiveThinkingDefaultEffort}
|
||||
description="Use None to disable adaptive thinking. Higher effort increases response detail and token usage."
|
||||
label="Adaptive Thinking"
|
||||
/>
|
||||
) : SUPPORTED_ANTHROPIC_THINKING_MODELS.includes(selectedModelId) ? (
|
||||
<ThinkingBudgetSlider currentMode={currentMode} maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} />
|
||||
<ThinkingBudgetSlider
|
||||
currentMode={currentMode}
|
||||
maxBudget={selectedModelInfo.thinkingConfig?.maxBudget}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import { cerebrasModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevCerebrasModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the CerebrasProvider component
|
||||
*/
|
||||
interface CerebrasProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Cerebras provider configuration component
|
||||
*/
|
||||
export const CerebrasProvider = ({ showModelOptions, isPopup, currentMode }: CerebrasProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const CerebrasProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: CerebrasProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -39,8 +47,8 @@ export const CerebrasProvider = ({ showModelOptions, isPopup, currentMode }: Cer
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={cerebrasModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevCerebrasModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -50,9 +58,13 @@ export const CerebrasProvider = ({ showModelOptions, isPopup, currentMode }: Cer
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,14 +6,26 @@ import {
|
||||
resolveClinePassModelInfo,
|
||||
} from "@shared/api"
|
||||
import { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import type { Mode } from "@shared/storage/types"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ModelsServiceClient } from "@/services/grpc-client"
|
||||
import { ClineAccountInfoCard } from "../ClineAccountInfoCard"
|
||||
import ClineModelPicker from "../ClineModelPicker"
|
||||
import { ClineProvider } from "./ClineProvider"
|
||||
|
||||
export const ClinePassProvider: typeof ClineProvider = (props) => {
|
||||
interface ClinePassProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showAccountCard?: boolean
|
||||
}
|
||||
|
||||
export const ClinePassProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
showAccountCard = true,
|
||||
}: ClinePassProviderProps) => {
|
||||
const { openRouterModels } = useExtensionState()
|
||||
const openRouterModelsByName = useMemo(() => buildModelInfoNameMap(openRouterModels), [openRouterModels])
|
||||
const [clinePassRecommendedModels, setClinePassRecommendedModels] = useState<Record<string, ModelInfo> | undefined>(undefined)
|
||||
@@ -63,18 +75,23 @@ export const ClinePassProvider: typeof ClineProvider = (props) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 14, marginTop: 4 }}>
|
||||
<ClineAccountInfoCard />
|
||||
</div>
|
||||
{showAccountCard && (
|
||||
<div style={{ marginBottom: 14, marginTop: 4 }}>
|
||||
<ClineAccountInfoCard />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ClineModelPicker
|
||||
{...props}
|
||||
defaultModelId={clinePassDefaultModel}
|
||||
modelIdFieldPair={{ plan: "planModeClinePassModelId", act: "actModeClinePassModelId" }}
|
||||
modelInfoFieldPair={{ plan: "planModeClinePassModelInfo", act: "actModeClinePassModelInfo" }}
|
||||
models={clinePassModelOptions}
|
||||
showFeaturedModels={false}
|
||||
/>
|
||||
{showModelOptions && (
|
||||
<ClineModelPicker
|
||||
currentMode={currentMode}
|
||||
defaultModelId={clinePassDefaultModel}
|
||||
isPopup={isPopup}
|
||||
modelIdFieldPair={{ plan: "planModeClinePassModelId", act: "actModeClinePassModelId" }}
|
||||
modelInfoFieldPair={{ plan: "planModeClinePassModelInfo", act: "actModeClinePassModelInfo" }}
|
||||
models={clinePassModelOptions}
|
||||
showFeaturedModels={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import type { ApiProvider } from "@shared/api"
|
||||
import type { Mode } from "@shared/storage/types"
|
||||
import styled from "styled-components"
|
||||
import VSCodeButtonLink from "@/components/common/VSCodeButtonLink"
|
||||
import { useClineAuth } from "@/context/ClineAuthContext"
|
||||
import { buildClinePassSubscriptionUrl } from "@/utils/clinePassSubscription"
|
||||
import { ClineAccountInfoCard } from "../ClineAccountInfoCard"
|
||||
import ClineModelPicker from "../ClineModelPicker"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { ClinePassProvider } from "./ClinePassProvider"
|
||||
|
||||
/**
|
||||
* Props for the ClineProvider component
|
||||
*/
|
||||
interface ClineProviderProps {
|
||||
export interface ClineProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
initialModelTab?: "recommended" | "free"
|
||||
isClinePassEnabled?: boolean
|
||||
selectedProvider?: ApiProvider
|
||||
}
|
||||
|
||||
type ClineBillingRoute = "cline" | "cline-pass"
|
||||
|
||||
/**
|
||||
* The Cline provider configuration component
|
||||
*/
|
||||
@@ -22,23 +32,128 @@ export const ClineProvider = ({
|
||||
currentMode,
|
||||
initialModelTab,
|
||||
isClinePassEnabled,
|
||||
selectedProvider,
|
||||
}: ClineProviderProps) => {
|
||||
const { handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
const { clineUser } = useClineAuth()
|
||||
const activeRoute: ClineBillingRoute = selectedProvider === "cline-pass" && isClinePassEnabled ? "cline-pass" : "cline"
|
||||
const clinePassSubscribeUrl = clineUser ? buildClinePassSubscriptionUrl(clineUser.appBaseUrl) : undefined
|
||||
|
||||
const handleRouteChange = async (route: ClineBillingRoute) => {
|
||||
if (route === activeRoute) {
|
||||
return
|
||||
}
|
||||
|
||||
await handleModeFieldChange({ plan: "planModeApiProvider", act: "actModeApiProvider" }, route, currentMode)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Cline Account Info Card */}
|
||||
<div style={{ marginBottom: 14, marginTop: 4 }}>
|
||||
<ClineAccountInfoCard />
|
||||
</div>
|
||||
{isClinePassEnabled && (
|
||||
<RouteContainer>
|
||||
<RouteStatus>
|
||||
{activeRoute === "cline-pass" ? (
|
||||
<>
|
||||
ClinePass lets you use the best open weights models.{" "}
|
||||
<RouteLink
|
||||
href="#"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
handleRouteChange("cline").catch((error) => console.error("Failed to switch to Cline:", error))
|
||||
}}>
|
||||
Switch to Cline Usage-Billing for other models.
|
||||
</RouteLink>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Usage-Billing models bill to your Cline account balance.{" "}
|
||||
<RouteLink
|
||||
href="#"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
handleRouteChange("cline-pass").catch((error) =>
|
||||
console.error("Failed to switch to ClinePass:", error),
|
||||
)
|
||||
}}>
|
||||
Switch to ClinePass provider to access subscription.
|
||||
</RouteLink>
|
||||
</>
|
||||
)}
|
||||
</RouteStatus>
|
||||
<RouteActions>
|
||||
{activeRoute === "cline" && <ClineAccountInfoCard />}
|
||||
{activeRoute === "cline-pass" && (
|
||||
clinePassSubscribeUrl ? (
|
||||
<VSCodeButtonLink appearance="secondary" href={clinePassSubscribeUrl}>
|
||||
Manage ClinePass or See Usage
|
||||
</VSCodeButtonLink>
|
||||
) : (
|
||||
<ClineAccountInfoCard />
|
||||
)
|
||||
)}
|
||||
</RouteActions>
|
||||
</RouteContainer>
|
||||
)}
|
||||
{!isClinePassEnabled && (
|
||||
<StandaloneRouteActions>
|
||||
<ClineAccountInfoCard />
|
||||
</StandaloneRouteActions>
|
||||
)}
|
||||
|
||||
{showModelOptions && (
|
||||
<ClineModelPicker
|
||||
currentMode={currentMode}
|
||||
initialTab={initialModelTab}
|
||||
isClinePassEnabled={isClinePassEnabled}
|
||||
isPopup={isPopup}
|
||||
showProviderRouting={true}
|
||||
/>
|
||||
activeRoute === "cline-pass" ? (
|
||||
<ClinePassProvider
|
||||
currentMode={currentMode}
|
||||
isPopup={isPopup}
|
||||
showAccountCard={false}
|
||||
showModelOptions={showModelOptions}
|
||||
/>
|
||||
) : (
|
||||
<ClineModelPicker
|
||||
currentMode={currentMode}
|
||||
initialTab={initialModelTab}
|
||||
isClinePassEnabled={isClinePassEnabled}
|
||||
isPopup={isPopup}
|
||||
showProviderRouting={true}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const RouteContainer = styled.div`
|
||||
margin-bottom: 10px;
|
||||
`
|
||||
|
||||
const RouteStatus = styled.div`
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
`
|
||||
|
||||
const RouteLink = styled.a`
|
||||
color: var(--vscode-textLink-foreground);
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
cursor: pointer !important;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--vscode-textLink-activeForeground, var(--vscode-textLink-foreground));
|
||||
cursor: pointer !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`
|
||||
|
||||
const RouteActions = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
`
|
||||
|
||||
const StandaloneRouteActions = styled(RouteActions)`
|
||||
margin: 4px 0 14px;
|
||||
`
|
||||
|
||||
@@ -1,30 +1,47 @@
|
||||
import { deepSeekModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevDeepSeekModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import ReasoningEffortSelector from "../ReasoningEffortSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
const DEEPSEEK_REASONING_EFFORT_MODELS = new Set([
|
||||
"deepseek-v4-flash",
|
||||
"deepseek-v4-pro",
|
||||
"deepseek-reasoner",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Props for the DeepSeekProvider component
|
||||
*/
|
||||
interface DeepSeekProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The DeepSeek provider configuration component
|
||||
*/
|
||||
export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: DeepSeekProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const DeepSeekProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: DeepSeekProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
const showReasoningEffort =
|
||||
DEEPSEEK_REASONING_EFFORT_MODELS.has(selectedModelId);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -39,8 +56,8 @@ export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: Dee
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={deepSeekModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevDeepSeekModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -50,9 +67,17 @@ export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: Dee
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
{showReasoningEffort && (
|
||||
<ReasoningEffortSelector currentMode={currentMode} />
|
||||
)}
|
||||
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import { doubaoModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevDoubaoModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the DoubaoProvider component
|
||||
*/
|
||||
interface DoubaoProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ByteDance Doubao provider configuration component
|
||||
*/
|
||||
export const DoubaoProvider = ({ showModelOptions, isPopup, currentMode }: DoubaoProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const DoubaoProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: DoubaoProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -39,8 +47,8 @@ export const DoubaoProvider = ({ showModelOptions, isPopup, currentMode }: Douba
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={doubaoModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevDoubaoModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -50,9 +58,13 @@ export const DoubaoProvider = ({ showModelOptions, isPopup, currentMode }: Douba
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
import { fireworksModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevFireworksModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the FireworksProvider component
|
||||
*/
|
||||
interface FireworksProviderProps {
|
||||
currentMode: Mode
|
||||
isPopup?: boolean
|
||||
showModelOptions: boolean
|
||||
currentMode: Mode;
|
||||
isPopup?: boolean;
|
||||
showModelOptions: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Fireworks provider configuration component
|
||||
*/
|
||||
export const FireworksProvider = ({ currentMode, isPopup, showModelOptions }: FireworksProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleModeFieldChange, handleFieldChange } = useApiConfigurationHandlers()
|
||||
export const FireworksProvider = ({
|
||||
currentMode,
|
||||
isPopup,
|
||||
showModelOptions,
|
||||
}: FireworksProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleModeFieldChange, handleFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -33,23 +41,31 @@ export const FireworksProvider = ({ currentMode, isPopup, showModelOptions }: Fi
|
||||
providerName="Fireworks"
|
||||
signupUrl="https://fireworks.ai/"
|
||||
/>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={fireworksModels}
|
||||
onChange={(e: any) => {
|
||||
handleModeFieldChange(
|
||||
{
|
||||
plan: "planModeFireworksModelId",
|
||||
act: "actModeFireworksModelId",
|
||||
},
|
||||
e.target.value,
|
||||
currentMode,
|
||||
)
|
||||
}}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
{showModelOptions && (
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={modelsDevFireworksModels}
|
||||
onChange={(e) => {
|
||||
handleModeFieldChange(
|
||||
{
|
||||
plan: "planModeFireworksModelId",
|
||||
act: "actModeFireworksModelId",
|
||||
},
|
||||
e.target.value,
|
||||
currentMode,
|
||||
);
|
||||
}}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,33 +1,45 @@
|
||||
import { geminiModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { BaseUrlField } from "../common/BaseUrlField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import ReasoningEffortSelector from "../ReasoningEffortSelector"
|
||||
import { normalizeApiConfiguration, supportsReasoningEffortForModelId } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevGeminiModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { BaseUrlField } from "../common/BaseUrlField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import ReasoningEffortSelector from "../ReasoningEffortSelector";
|
||||
import {
|
||||
normalizeApiConfiguration,
|
||||
supportsReasoningEffortForModelId,
|
||||
} from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the GeminiProvider component
|
||||
*/
|
||||
interface GeminiProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Gemini provider configuration component
|
||||
*/
|
||||
export const GeminiProvider = ({ showModelOptions, isPopup, currentMode }: GeminiProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const GeminiProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: GeminiProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const showReasoningEffort = supportsReasoningEffortForModelId(selectedModelId)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
const showReasoningEffort =
|
||||
supportsReasoningEffortForModelId(selectedModelId);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -49,8 +61,8 @@ export const GeminiProvider = ({ showModelOptions, isPopup, currentMode }: Gemin
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={geminiModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevGeminiModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -60,11 +72,17 @@ export const GeminiProvider = ({ showModelOptions, isPopup, currentMode }: Gemin
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
{showReasoningEffort && <ReasoningEffortSelector currentMode={currentMode} />}
|
||||
{showReasoningEffort && (
|
||||
<ReasoningEffortSelector currentMode={currentMode} />
|
||||
)}
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
+42
-23
@@ -1,23 +1,32 @@
|
||||
import { huaweiCloudMaasModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { getModelsDevProviderModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
interface HuaweiCloudMaasProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
export const HuaweiCloudMaasProvider = ({ showModelOptions, isPopup, currentMode }: HuaweiCloudMaasProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldsChange } = useApiConfigurationHandlers()
|
||||
export const HuaweiCloudMaasProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: HuaweiCloudMaasProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldsChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
const huaweiCloudMaasModels = getModelsDevProviderModels("huawei-cloud-maas");
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -32,12 +41,18 @@ export const HuaweiCloudMaasProvider = ({ showModelOptions, isPopup, currentMode
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={huaweiCloudMaasModels}
|
||||
onChange={(e: any) => {
|
||||
const modelId = e.target.value
|
||||
const modelInfo = huaweiCloudMaasModels[modelId as keyof typeof huaweiCloudMaasModels]
|
||||
onChange={(e) => {
|
||||
const modelId = e.target.value;
|
||||
const modelInfo =
|
||||
huaweiCloudMaasModels[
|
||||
modelId as keyof typeof huaweiCloudMaasModels
|
||||
];
|
||||
handleModeFieldsChange(
|
||||
{
|
||||
apiModelId: { plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
apiModelId: {
|
||||
plan: "planModeApiModelId",
|
||||
act: "actModeApiModelId",
|
||||
},
|
||||
huaweiCloudMaaSModelId: {
|
||||
plan: "planModeHuaweiCloudMaasModelId",
|
||||
act: "actModeHuaweiCloudMaasModelId",
|
||||
@@ -53,13 +68,17 @@ export const HuaweiCloudMaasProvider = ({ showModelOptions, isPopup, currentMode
|
||||
huaweiCloudMaaSModelInfo: modelInfo,
|
||||
},
|
||||
currentMode,
|
||||
)
|
||||
);
|
||||
}}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,47 +1,66 @@
|
||||
import { minimaxModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { DropdownContainer, ModelSelector } from "../common/ModelSelector"
|
||||
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevMinimaxModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { DropdownContainer, ModelSelector } from "../common/ModelSelector";
|
||||
import ThinkingBudgetSlider from "../ThinkingBudgetSlider";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the MinimaxProvider component
|
||||
*/
|
||||
interface MinimaxProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Minimax AI Studio provider configuration component
|
||||
*/
|
||||
export const MinimaxProvider = ({ showModelOptions, isPopup, currentMode }: MinimaxProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const MinimaxProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: MinimaxProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DropdownContainer className="dropdown-container" style={{ position: "inherit" }}>
|
||||
<DropdownContainer
|
||||
className="dropdown-container"
|
||||
style={{ position: "inherit" }}
|
||||
>
|
||||
<label htmlFor="minimax-entrypoint">
|
||||
<span style={{ fontWeight: 500, marginTop: 5 }}>MiniMax Entrypoint</span>
|
||||
<span style={{ fontWeight: 500, marginTop: 5 }}>
|
||||
MiniMax Entrypoint
|
||||
</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="minimax-entrypoint"
|
||||
onChange={(e) => handleFieldChange("minimaxApiLine", (e.target as any).value)}
|
||||
onChange={(e) =>
|
||||
handleFieldChange(
|
||||
"minimaxApiLine",
|
||||
(e.target as EventTarget & { value: string }).value,
|
||||
)
|
||||
}
|
||||
style={{
|
||||
minWidth: 130,
|
||||
position: "relative",
|
||||
}}
|
||||
value={apiConfiguration?.minimaxApiLine || "international"}>
|
||||
value={apiConfiguration?.minimaxApiLine || "international"}
|
||||
>
|
||||
<VSCodeOption value="international">api.minimax.io</VSCodeOption>
|
||||
<VSCodeOption value="china">api.minimaxi.com</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
@@ -51,9 +70,11 @@ export const MinimaxProvider = ({ showModelOptions, isPopup, currentMode }: Mini
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Select the API endpoint according to your region: <code>api.minimaxi.com</code> for China, or{" "}
|
||||
<code>api.minimax.io</code> for all other locations.
|
||||
}}
|
||||
>
|
||||
Select the API endpoint according to your region:{" "}
|
||||
<code>api.minimaxi.com</code> for China, or <code>api.minimax.io</code>{" "}
|
||||
for all other locations.
|
||||
</p>
|
||||
<ApiKeyField
|
||||
initialValue={apiConfiguration?.minimaxApiKey || ""}
|
||||
@@ -70,8 +91,8 @@ export const MinimaxProvider = ({ showModelOptions, isPopup, currentMode }: Mini
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={minimaxModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevMinimaxModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -82,12 +103,19 @@ export const MinimaxProvider = ({ showModelOptions, isPopup, currentMode }: Mini
|
||||
/>
|
||||
|
||||
{selectedModelInfo?.supportsReasoning && (
|
||||
<ThinkingBudgetSlider currentMode={currentMode} showEnableToggle={false} />
|
||||
<ThinkingBudgetSlider
|
||||
currentMode={currentMode}
|
||||
showEnableToggle={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import { mistralModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevMistralModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the MistralProvider component
|
||||
*/
|
||||
interface MistralProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Mistral provider configuration component
|
||||
*/
|
||||
export const MistralProvider = ({ showModelOptions, isPopup, currentMode }: MistralProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const MistralProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: MistralProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -39,8 +47,8 @@ export const MistralProvider = ({ showModelOptions, isPopup, currentMode }: Mist
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={mistralModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevMistralModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -50,9 +58,13 @@ export const MistralProvider = ({ showModelOptions, isPopup, currentMode }: Mist
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,42 +1,54 @@
|
||||
import { moonshotModels } from "@shared/api"
|
||||
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ModelsServiceClient } from "@/services/grpc-client"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { DropdownContainer, ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { modelsDevMoonshotModels } from "@shared/models/models-dev-catalog";
|
||||
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ModelsServiceClient } from "@/services/grpc-client";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { DropdownContainer, ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
|
||||
/**
|
||||
* Props for the MoonshotProvider component
|
||||
*/
|
||||
interface MoonshotProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Moonshot AI Studio provider configuration component
|
||||
*/
|
||||
export const MoonshotProvider = ({ showModelOptions, isPopup, currentMode }: MoonshotProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
export const MoonshotProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: MoonshotProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DropdownContainer className="dropdown-container" style={{ position: "inherit" }}>
|
||||
<DropdownContainer
|
||||
className="dropdown-container"
|
||||
style={{ position: "inherit" }}
|
||||
>
|
||||
<label htmlFor="moonshot-entrypoint">
|
||||
<span style={{ fontWeight: 500, marginTop: 5 }}>Moonshot Entrypoint</span>
|
||||
<span style={{ fontWeight: 500, marginTop: 5 }}>
|
||||
Moonshot Entrypoint
|
||||
</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="moonshot-entrypoint"
|
||||
onChange={async (e) => {
|
||||
const value = (e.target as any).value
|
||||
const value = (e.target as EventTarget & { value: string }).value;
|
||||
await ModelsServiceClient.updateApiConfiguration(
|
||||
UpdateApiConfigurationRequestNew.create({
|
||||
updates: {
|
||||
@@ -46,13 +58,14 @@ export const MoonshotProvider = ({ showModelOptions, isPopup, currentMode }: Moo
|
||||
},
|
||||
updateMask: ["options.moonshotApiLine"],
|
||||
}),
|
||||
)
|
||||
);
|
||||
}}
|
||||
style={{
|
||||
minWidth: 130,
|
||||
position: "relative",
|
||||
}}
|
||||
value={apiConfiguration?.moonshotApiLine || "international"}>
|
||||
value={apiConfiguration?.moonshotApiLine || "international"}
|
||||
>
|
||||
<VSCodeOption value="international">api.moonshot.ai</VSCodeOption>
|
||||
<VSCodeOption value="china">api.moonshot.cn</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
@@ -70,7 +83,7 @@ export const MoonshotProvider = ({ showModelOptions, isPopup, currentMode }: Moo
|
||||
},
|
||||
updateMask: ["secrets.moonshotApiKey"],
|
||||
}),
|
||||
)
|
||||
);
|
||||
}}
|
||||
providerName="Moonshot"
|
||||
signupUrl={
|
||||
@@ -84,9 +97,9 @@ export const MoonshotProvider = ({ showModelOptions, isPopup, currentMode }: Moo
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={moonshotModels}
|
||||
onChange={async (e: any) => {
|
||||
const value = e.target.value
|
||||
models={modelsDevMoonshotModels}
|
||||
onChange={async (e) => {
|
||||
const value = e.target.value;
|
||||
|
||||
await ModelsServiceClient.updateApiConfiguration(
|
||||
UpdateApiConfigurationRequestNew.create(
|
||||
@@ -100,14 +113,18 @@ export const MoonshotProvider = ({ showModelOptions, isPopup, currentMode }: Moo
|
||||
updateMask: ["options.actModeApiModelId"],
|
||||
},
|
||||
),
|
||||
)
|
||||
);
|
||||
}}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
import { nebiusModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevNebiusModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the NebiusProvider component
|
||||
*/
|
||||
interface NebiusProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Nebius AI Studio provider configuration component
|
||||
*/
|
||||
export const NebiusProvider = ({ showModelOptions, isPopup, currentMode }: NebiusProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const NebiusProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: NebiusProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -39,8 +47,8 @@ export const NebiusProvider = ({ showModelOptions, isPopup, currentMode }: Nebiu
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={nebiusModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevNebiusModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -50,9 +58,13 @@ export const NebiusProvider = ({ showModelOptions, isPopup, currentMode }: Nebiu
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import { nousResearchModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevNousResearchModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the NousResearchProvider component
|
||||
*/
|
||||
interface NousResearchProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The NousResearch provider configuration component
|
||||
*/
|
||||
export const NousResearchProvider = ({ showModelOptions, isPopup, currentMode }: NousResearchProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const NousResearchProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: NousResearchProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -38,10 +46,13 @@ export const NousResearchProvider = ({ showModelOptions, isPopup, currentMode }:
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={nousResearchModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevNousResearchModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeNousResearchModelId", act: "actModeNousResearchModelId" },
|
||||
{
|
||||
plan: "planModeNousResearchModelId",
|
||||
act: "actModeNousResearchModelId",
|
||||
},
|
||||
e.target.value,
|
||||
currentMode,
|
||||
)
|
||||
@@ -49,21 +60,27 @@ export const NousResearchProvider = ({ showModelOptions, isPopup, currentMode }:
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude
|
||||
models. Less capable models may not work as expected.)
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex
|
||||
prompts and works best with Claude models. Less capable models may
|
||||
not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
import { openAiNativeModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import ReasoningEffortSelector from "../ReasoningEffortSelector"
|
||||
import { normalizeApiConfiguration, supportsReasoningEffortForModelId } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevOpenAiNativeModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import ReasoningEffortSelector from "../ReasoningEffortSelector";
|
||||
import {
|
||||
normalizeApiConfiguration,
|
||||
supportsReasoningEffortForModelId,
|
||||
} from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the OpenAINativeProvider component
|
||||
*/
|
||||
interface OpenAINativeProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The OpenAI (native) provider configuration component
|
||||
*/
|
||||
export const OpenAINativeProvider = ({ showModelOptions, isPopup, currentMode }: OpenAINativeProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const OpenAINativeProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: OpenAINativeProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const showReasoningEffort = supportsReasoningEffortForModelId(selectedModelId, true)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
const showReasoningEffort = supportsReasoningEffortForModelId(
|
||||
selectedModelId,
|
||||
true,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -41,8 +55,8 @@ export const OpenAINativeProvider = ({ showModelOptions, isPopup, currentMode }:
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={openAiNativeModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevOpenAiNativeModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -51,11 +65,17 @@ export const OpenAINativeProvider = ({ showModelOptions, isPopup, currentMode }:
|
||||
}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
{showReasoningEffort && <ReasoningEffortSelector currentMode={currentMode} />}
|
||||
{showReasoningEffort && (
|
||||
<ReasoningEffortSelector currentMode={currentMode} />
|
||||
)}
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import { sambanovaModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevSambanovaModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the SambanovaProvider component
|
||||
*/
|
||||
interface SambanovaProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SambaNova provider configuration component
|
||||
*/
|
||||
export const SambanovaProvider = ({ showModelOptions, isPopup, currentMode }: SambanovaProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const SambanovaProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: SambanovaProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -39,8 +47,8 @@ export const SambanovaProvider = ({ showModelOptions, isPopup, currentMode }: Sa
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={sambanovaModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevSambanovaModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -50,9 +58,13 @@ export const SambanovaProvider = ({ showModelOptions, isPopup, currentMode }: Sa
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import { wandbModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { ModelSelector } from "../common/ModelSelector"
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevWandbModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { ModelSelector } from "../common/ModelSelector";
|
||||
import { normalizeApiConfiguration } from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
interface WandbProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
export const WandbProvider = ({ showModelOptions, isPopup, currentMode }: WandbProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const WandbProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: WandbProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -33,8 +41,8 @@ export const WandbProvider = ({ showModelOptions, isPopup, currentMode }: WandbP
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={wandbModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevWandbModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -44,9 +52,13 @@ export const WandbProvider = ({ showModelOptions, isPopup, currentMode }: WandbP
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,35 +1,52 @@
|
||||
import { xaiModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useState } from "react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { DROPDOWN_Z_INDEX } from "../ApiOptions"
|
||||
import { ApiKeyField } from "../common/ApiKeyField"
|
||||
import { ModelInfoView } from "../common/ModelInfoView"
|
||||
import { DropdownContainer, ModelSelector } from "../common/ModelSelector"
|
||||
import { getModeSpecificFields, normalizeApiConfiguration } from "../utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { modelsDevXaiModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import {
|
||||
VSCodeCheckbox,
|
||||
VSCodeDropdown,
|
||||
VSCodeOption,
|
||||
} from "@vscode/webview-ui-toolkit/react";
|
||||
import { useState } from "react";
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext";
|
||||
import { DROPDOWN_Z_INDEX } from "../ApiOptions";
|
||||
import { ApiKeyField } from "../common/ApiKeyField";
|
||||
import { ModelInfoView } from "../common/ModelInfoView";
|
||||
import { DropdownContainer, ModelSelector } from "../common/ModelSelector";
|
||||
import {
|
||||
getModeSpecificFields,
|
||||
normalizeApiConfiguration,
|
||||
} from "../utils/providerUtils";
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers";
|
||||
|
||||
/**
|
||||
* Props for the XaiProvider component
|
||||
*/
|
||||
interface XaiProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
showModelOptions: boolean;
|
||||
isPopup?: boolean;
|
||||
currentMode: Mode;
|
||||
}
|
||||
|
||||
export const XaiProvider = ({ showModelOptions, isPopup, currentMode }: XaiProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
export const XaiProvider = ({
|
||||
showModelOptions,
|
||||
isPopup,
|
||||
currentMode,
|
||||
}: XaiProviderProps) => {
|
||||
const { apiConfiguration } = useExtensionState();
|
||||
const { handleFieldChange, handleModeFieldChange } =
|
||||
useApiConfigurationHandlers();
|
||||
|
||||
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
|
||||
const modeFields = getModeSpecificFields(apiConfiguration, currentMode);
|
||||
|
||||
// Get the normalized configuration
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(
|
||||
apiConfiguration,
|
||||
currentMode,
|
||||
);
|
||||
|
||||
// Local state for reasoning effort toggle
|
||||
const [reasoningEffortSelected, setReasoningEffortSelected] = useState(!!modeFields.reasoningEffort)
|
||||
const [reasoningEffortSelected, setReasoningEffortSelected] = useState(
|
||||
!!modeFields.reasoningEffort,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -45,10 +62,12 @@ export const XaiProvider = ({ showModelOptions, isPopup, currentMode }: XaiProvi
|
||||
fontSize: "12px",
|
||||
marginTop: -10,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude
|
||||
models. Less capable models may not work as expected.)
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex
|
||||
prompts and works best with Claude models. Less capable models may
|
||||
not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -57,8 +76,8 @@ export const XaiProvider = ({ showModelOptions, isPopup, currentMode }: XaiProvi
|
||||
<>
|
||||
<ModelSelector
|
||||
label="Model"
|
||||
models={xaiModels}
|
||||
onChange={(e: any) =>
|
||||
models={modelsDevXaiModels}
|
||||
onChange={(e) =>
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
|
||||
e.target.value,
|
||||
@@ -68,22 +87,28 @@ export const XaiProvider = ({ showModelOptions, isPopup, currentMode }: XaiProvi
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
{selectedModelId && selectedModelId.includes("3-mini") && (
|
||||
{selectedModelId?.includes("3-mini") && (
|
||||
<>
|
||||
<VSCodeCheckbox
|
||||
checked={reasoningEffortSelected}
|
||||
onChange={(e: any) => {
|
||||
const isChecked = e.target.checked === true
|
||||
setReasoningEffortSelected(isChecked)
|
||||
onChange={(e) => {
|
||||
const isChecked =
|
||||
(e.target as EventTarget & { checked: boolean }).checked ===
|
||||
true;
|
||||
setReasoningEffortSelected(isChecked);
|
||||
if (!isChecked) {
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeReasoningEffort", act: "actModeReasoningEffort" },
|
||||
{
|
||||
plan: "planModeReasoningEffort",
|
||||
act: "actModeReasoningEffort",
|
||||
},
|
||||
"",
|
||||
currentMode,
|
||||
)
|
||||
);
|
||||
}
|
||||
}}
|
||||
style={{ marginTop: 0 }}>
|
||||
style={{ marginTop: 0 }}
|
||||
>
|
||||
Modify reasoning effort
|
||||
</VSCodeCheckbox>
|
||||
|
||||
@@ -92,18 +117,25 @@ export const XaiProvider = ({ showModelOptions, isPopup, currentMode }: XaiProvi
|
||||
<label htmlFor="reasoning-effort-dropdown">
|
||||
<span style={{}}>Reasoning Effort</span>
|
||||
</label>
|
||||
<DropdownContainer className="dropdown-container" zIndex={DROPDOWN_Z_INDEX - 100}>
|
||||
<DropdownContainer
|
||||
className="dropdown-container"
|
||||
zIndex={DROPDOWN_Z_INDEX - 100}
|
||||
>
|
||||
<VSCodeDropdown
|
||||
id="reasoning-effort-dropdown"
|
||||
onChange={(e: any) => {
|
||||
onChange={(e) => {
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeReasoningEffort", act: "actModeReasoningEffort" },
|
||||
e.target.value,
|
||||
{
|
||||
plan: "planModeReasoningEffort",
|
||||
act: "actModeReasoningEffort",
|
||||
},
|
||||
(e.target as EventTarget & { value: string }).value,
|
||||
currentMode,
|
||||
)
|
||||
);
|
||||
}}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
value={modeFields.reasoningEffort || "high"}>
|
||||
value={modeFields.reasoningEffort || "high"}
|
||||
>
|
||||
<VSCodeOption value="low">low</VSCodeOption>
|
||||
<VSCodeOption value="high">high</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
@@ -114,17 +146,23 @@ export const XaiProvider = ({ showModelOptions, isPopup, currentMode }: XaiProvi
|
||||
marginTop: 3,
|
||||
marginBottom: 0,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
High effort may produce more thorough analysis but takes longer and uses more tokens.
|
||||
}}
|
||||
>
|
||||
High effort may produce more thorough analysis but takes
|
||||
longer and uses more tokens.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
|
||||
<ModelInfoView
|
||||
isPopup={isPopup}
|
||||
modelInfo={selectedModelInfo}
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
basetenModels,
|
||||
bedrockDefaultModelId,
|
||||
bedrockModels,
|
||||
buildModelInfoNameMap,
|
||||
cerebrasDefaultModelId,
|
||||
cerebrasModels,
|
||||
claudeCodeDefaultModelId,
|
||||
@@ -73,6 +74,7 @@ import {
|
||||
xaiDefaultModelId,
|
||||
xaiModels,
|
||||
} from "@shared/api";
|
||||
import { getModelsDevProviderModels } from "@shared/models/models-dev-catalog";
|
||||
import type { Mode } from "@shared/storage/types";
|
||||
import * as reasoningSupport from "@shared/utils/reasoning-support";
|
||||
|
||||
@@ -96,25 +98,29 @@ export function getModelsForProvider(
|
||||
basetenModels?: Record<string, ModelInfo>;
|
||||
} = {},
|
||||
): Record<string, ModelInfo> | undefined {
|
||||
const modelsDevModels = getModelsDevProviderModels(provider);
|
||||
const modelsDevOrFallback = (fallback: Record<string, ModelInfo>) =>
|
||||
Object.keys(modelsDevModels).length > 0 ? modelsDevModels : fallback;
|
||||
|
||||
switch (provider) {
|
||||
case "anthropic":
|
||||
return anthropicModels;
|
||||
return modelsDevOrFallback(anthropicModels);
|
||||
case "claude-code":
|
||||
return claudeCodeModels;
|
||||
case "bedrock":
|
||||
return bedrockModels;
|
||||
return modelsDevOrFallback(bedrockModels);
|
||||
case "vertex":
|
||||
return vertexModels;
|
||||
return modelsDevOrFallback(vertexModels);
|
||||
case "gemini":
|
||||
return geminiModels;
|
||||
return modelsDevOrFallback(geminiModels);
|
||||
case "openai-native":
|
||||
return openAiNativeModels;
|
||||
return modelsDevOrFallback(openAiNativeModels);
|
||||
case "openai-codex":
|
||||
return openAiCodexModels;
|
||||
return modelsDevOrFallback(openAiCodexModels);
|
||||
case "cline-pass":
|
||||
return clinePassModels;
|
||||
case "deepseek":
|
||||
return deepSeekModels;
|
||||
return modelsDevOrFallback(deepSeekModels);
|
||||
case "qwen":
|
||||
return apiConfiguration?.qwenApiLine === "china"
|
||||
? mainlandQwenModels
|
||||
@@ -122,59 +128,45 @@ export function getModelsForProvider(
|
||||
case "qwen-code":
|
||||
return qwenCodeModels;
|
||||
case "doubao":
|
||||
return doubaoModels;
|
||||
return modelsDevOrFallback(doubaoModels);
|
||||
case "mistral":
|
||||
return mistralModels;
|
||||
return modelsDevOrFallback(mistralModels);
|
||||
case "asksage":
|
||||
return askSageModels;
|
||||
case "xai":
|
||||
return xaiModels;
|
||||
return modelsDevOrFallback(xaiModels);
|
||||
case "moonshot":
|
||||
return moonshotModels;
|
||||
return modelsDevOrFallback(moonshotModels);
|
||||
case "nebius":
|
||||
return nebiusModels;
|
||||
return modelsDevOrFallback(nebiusModels);
|
||||
case "wandb":
|
||||
return wandbModels;
|
||||
return modelsDevOrFallback(wandbModels);
|
||||
case "sambanova":
|
||||
return sambanovaModels;
|
||||
return modelsDevOrFallback(sambanovaModels);
|
||||
case "cerebras":
|
||||
return cerebrasModels;
|
||||
return modelsDevOrFallback(cerebrasModels);
|
||||
case "groq":
|
||||
return groqModels;
|
||||
return modelsDevOrFallback(groqModels);
|
||||
case "baseten":
|
||||
return dynamicModels?.basetenModels || basetenModels;
|
||||
return dynamicModels?.basetenModels || modelsDevOrFallback(basetenModels);
|
||||
case "sapaicore":
|
||||
return sapAiCoreModels;
|
||||
return modelsDevOrFallback(sapAiCoreModels);
|
||||
case "huawei-cloud-maas":
|
||||
return huaweiCloudMaasModels;
|
||||
return modelsDevOrFallback(huaweiCloudMaasModels);
|
||||
case "zai":
|
||||
return apiConfiguration?.zaiApiLine === "china"
|
||||
? mainlandZAiModels
|
||||
: internationalZAiModels;
|
||||
case "fireworks":
|
||||
return fireworksModels;
|
||||
return modelsDevOrFallback(fireworksModels);
|
||||
case "minimax":
|
||||
return minimaxModels;
|
||||
return modelsDevOrFallback(minimaxModels);
|
||||
case "huggingface":
|
||||
return huggingFaceModels;
|
||||
return modelsDevOrFallback(huggingFaceModels);
|
||||
case "nousResearch":
|
||||
return nousResearchModels;
|
||||
return modelsDevOrFallback(nousResearchModels);
|
||||
case "litellm":
|
||||
return dynamicModels?.liteLlmModels;
|
||||
// Providers with dynamic models - return undefined
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
case "openai":
|
||||
case "ollama":
|
||||
case "lmstudio":
|
||||
case "vscode-lm":
|
||||
case "requesty":
|
||||
case "hicap":
|
||||
case "dify":
|
||||
case "vercel-ai-gateway":
|
||||
case "oca":
|
||||
case "aihubmix":
|
||||
case "together":
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
@@ -233,10 +225,29 @@ export function normalizeApiConfiguration(
|
||||
selectedModelInfo,
|
||||
};
|
||||
};
|
||||
const getModelsDevProviderData = (
|
||||
providerId: ApiProvider,
|
||||
fallbackModels: Record<string, ModelInfo>,
|
||||
defaultId: string,
|
||||
) => {
|
||||
const modelsDevModels = getModelsDevProviderModels(providerId);
|
||||
const models =
|
||||
Object.keys(modelsDevModels).length > 0
|
||||
? modelsDevModels
|
||||
: fallbackModels;
|
||||
const resolvedDefaultId = models[defaultId]
|
||||
? defaultId
|
||||
: (Object.keys(models)[0] ?? defaultId);
|
||||
return getProviderData(models, resolvedDefaultId);
|
||||
};
|
||||
|
||||
switch (provider) {
|
||||
case "anthropic":
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"anthropic",
|
||||
anthropicModels,
|
||||
anthropicDefaultModelId,
|
||||
);
|
||||
case "claude-code":
|
||||
return getProviderData(claudeCodeModels, claudeCodeDefaultModelId);
|
||||
case "bedrock": {
|
||||
@@ -259,18 +270,42 @@ export function normalizeApiConfiguration(
|
||||
bedrockModels[bedrockDefaultModelId],
|
||||
};
|
||||
}
|
||||
return getProviderData(bedrockModels, bedrockDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"bedrock",
|
||||
bedrockModels,
|
||||
bedrockDefaultModelId,
|
||||
);
|
||||
}
|
||||
case "vertex":
|
||||
return getProviderData(vertexModels, vertexDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"vertex",
|
||||
vertexModels,
|
||||
vertexDefaultModelId,
|
||||
);
|
||||
case "gemini":
|
||||
return getProviderData(geminiModels, geminiDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"gemini",
|
||||
geminiModels,
|
||||
geminiDefaultModelId,
|
||||
);
|
||||
case "openai-native":
|
||||
return getProviderData(openAiNativeModels, openAiNativeDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"openai-native",
|
||||
openAiNativeModels,
|
||||
openAiNativeDefaultModelId,
|
||||
);
|
||||
case "openai-codex":
|
||||
return getProviderData(openAiCodexModels, openAiCodexDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"openai-codex",
|
||||
openAiCodexModels,
|
||||
openAiCodexDefaultModelId,
|
||||
);
|
||||
case "deepseek":
|
||||
return getProviderData(deepSeekModels, deepSeekDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"deepseek",
|
||||
deepSeekModels,
|
||||
deepSeekDefaultModelId,
|
||||
);
|
||||
case "qwen": {
|
||||
const qwenModels =
|
||||
apiConfiguration?.qwenApiLine === "china"
|
||||
@@ -285,9 +320,17 @@ export function normalizeApiConfiguration(
|
||||
case "qwen-code":
|
||||
return getProviderData(qwenCodeModels, qwenCodeDefaultModelId);
|
||||
case "doubao":
|
||||
return getProviderData(doubaoModels, doubaoDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"doubao",
|
||||
doubaoModels,
|
||||
doubaoDefaultModelId,
|
||||
);
|
||||
case "mistral":
|
||||
return getProviderData(mistralModels, mistralDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"mistral",
|
||||
mistralModels,
|
||||
mistralDefaultModelId,
|
||||
);
|
||||
case "asksage":
|
||||
return getProviderData(askSageModels, askSageDefaultModelId);
|
||||
case "openrouter": {
|
||||
@@ -360,17 +403,20 @@ export function normalizeApiConfiguration(
|
||||
? configuredClinePassModelId
|
||||
: clinePassDefaultModelId;
|
||||
const clinePassModelInfo =
|
||||
(currentMode === "plan"
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeClinePassModelInfo
|
||||
: apiConfiguration?.actModeClinePassModelInfo) ||
|
||||
resolveClinePassModelInfo(
|
||||
clinePassModelId,
|
||||
options.clinePassModelInfoByName,
|
||||
);
|
||||
: apiConfiguration?.actModeClinePassModelInfo;
|
||||
const clinePassModelInfoByName = clinePassModelInfo
|
||||
? buildModelInfoNameMap({ [clinePassModelId]: clinePassModelInfo })
|
||||
: options.clinePassModelInfoByName;
|
||||
const resolvedClinePassModelInfo = resolveClinePassModelInfo(
|
||||
clinePassModelId,
|
||||
clinePassModelInfoByName,
|
||||
);
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: clinePassModelId,
|
||||
selectedModelInfo: clinePassModelInfo,
|
||||
selectedModelInfo: resolvedClinePassModelInfo,
|
||||
};
|
||||
}
|
||||
case "openai": {
|
||||
@@ -461,9 +507,13 @@ export function normalizeApiConfiguration(
|
||||
};
|
||||
}
|
||||
case "xai":
|
||||
return getProviderData(xaiModels, xaiDefaultModelId);
|
||||
return getModelsDevProviderData("xai", xaiModels, xaiDefaultModelId);
|
||||
case "moonshot":
|
||||
return getProviderData(moonshotModels, moonshotDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"moonshot",
|
||||
moonshotModels,
|
||||
moonshotDefaultModelId,
|
||||
);
|
||||
case "huggingface": {
|
||||
const huggingFaceModelId =
|
||||
currentMode === "plan"
|
||||
@@ -473,21 +523,41 @@ export function normalizeApiConfiguration(
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeHuggingFaceModelInfo
|
||||
: apiConfiguration?.actModeHuggingFaceModelInfo;
|
||||
const huggingFaceModelCatalog = getModelsDevProviderModels("huggingface");
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: huggingFaceModelId || huggingFaceDefaultModelId,
|
||||
selectedModelInfo:
|
||||
huggingFaceModelInfo || huggingFaceModels[huggingFaceDefaultModelId],
|
||||
huggingFaceModelInfo ||
|
||||
huggingFaceModelCatalog[huggingFaceDefaultModelId] ||
|
||||
Object.values(huggingFaceModelCatalog)[0] ||
|
||||
huggingFaceModels[huggingFaceDefaultModelId],
|
||||
};
|
||||
}
|
||||
case "nebius":
|
||||
return getProviderData(nebiusModels, nebiusDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"nebius",
|
||||
nebiusModels,
|
||||
nebiusDefaultModelId,
|
||||
);
|
||||
case "wandb":
|
||||
return getProviderData(wandbModels, wandbDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"wandb",
|
||||
wandbModels,
|
||||
wandbDefaultModelId,
|
||||
);
|
||||
case "sambanova":
|
||||
return getProviderData(sambanovaModels, sambanovaDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"sambanova",
|
||||
sambanovaModels,
|
||||
sambanovaDefaultModelId,
|
||||
);
|
||||
case "cerebras":
|
||||
return getProviderData(cerebrasModels, cerebrasDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"cerebras",
|
||||
cerebrasModels,
|
||||
cerebrasDefaultModelId,
|
||||
);
|
||||
case "groq": {
|
||||
const groqModelId =
|
||||
currentMode === "plan"
|
||||
@@ -497,10 +567,15 @@ export function normalizeApiConfiguration(
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeGroqModelInfo
|
||||
: apiConfiguration?.actModeGroqModelInfo;
|
||||
const groqModelCatalog = getModelsDevProviderModels("groq");
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: groqModelId || groqDefaultModelId,
|
||||
selectedModelInfo: groqModelInfo || groqModels[groqDefaultModelId],
|
||||
selectedModelInfo:
|
||||
groqModelInfo ||
|
||||
groqModelCatalog[groqDefaultModelId] ||
|
||||
Object.values(groqModelCatalog)[0] ||
|
||||
groqModels[groqDefaultModelId],
|
||||
};
|
||||
}
|
||||
case "baseten": {
|
||||
@@ -513,10 +588,14 @@ export function normalizeApiConfiguration(
|
||||
? apiConfiguration?.planModeBasetenModelInfo
|
||||
: apiConfiguration?.actModeBasetenModelInfo;
|
||||
const finalBasetenModelId = basetenModelId || basetenDefaultModelId;
|
||||
const basetenModelCatalog = getModelsDevProviderModels("baseten");
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: finalBasetenModelId,
|
||||
selectedModelInfo: basetenModelInfo ||
|
||||
basetenModelCatalog[finalBasetenModelId] ||
|
||||
basetenModelCatalog[basetenDefaultModelId] ||
|
||||
Object.values(basetenModelCatalog)[0] ||
|
||||
basetenModels[finalBasetenModelId as keyof typeof basetenModels] ||
|
||||
basetenModels[basetenDefaultModelId] || {
|
||||
description: "Baseten model",
|
||||
@@ -524,7 +603,11 @@ export function normalizeApiConfiguration(
|
||||
};
|
||||
}
|
||||
case "sapaicore":
|
||||
return getProviderData(sapAiCoreModels, sapAiCoreDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"sapaicore",
|
||||
sapAiCoreModels,
|
||||
sapAiCoreDefaultModelId,
|
||||
);
|
||||
case "huawei-cloud-maas": {
|
||||
const huaweiCloudMaasModelId =
|
||||
currentMode === "plan"
|
||||
@@ -534,12 +617,16 @@ export function normalizeApiConfiguration(
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeHuaweiCloudMaasModelInfo
|
||||
: apiConfiguration?.actModeHuaweiCloudMaasModelInfo;
|
||||
const huaweiCloudMaasModelCatalog =
|
||||
getModelsDevProviderModels("huawei-cloud-maas");
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId:
|
||||
huaweiCloudMaasModelId || huaweiCloudMaasDefaultModelId,
|
||||
selectedModelInfo:
|
||||
huaweiCloudMaasModelInfo ||
|
||||
huaweiCloudMaasModelCatalog[huaweiCloudMaasDefaultModelId] ||
|
||||
Object.values(huaweiCloudMaasModelCatalog)[0] ||
|
||||
huaweiCloudMaasModels[huaweiCloudMaasDefaultModelId],
|
||||
};
|
||||
}
|
||||
@@ -590,13 +677,17 @@ export function normalizeApiConfiguration(
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeFireworksModelId
|
||||
: apiConfiguration?.actModeFireworksModelId;
|
||||
const fireworksModelCatalog = getModelsDevProviderModels("fireworks");
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: fireworksModelId || fireworksDefaultModelId,
|
||||
selectedModelInfo:
|
||||
fireworksModelId && fireworksModelId in fireworksModels
|
||||
(fireworksModelId && fireworksModelCatalog[fireworksModelId]) ||
|
||||
fireworksModelCatalog[fireworksDefaultModelId] ||
|
||||
Object.values(fireworksModelCatalog)[0] ||
|
||||
(fireworksModelId && fireworksModelId in fireworksModels
|
||||
? fireworksModels[fireworksModelId as keyof typeof fireworksModels]
|
||||
: fireworksModels[fireworksDefaultModelId],
|
||||
: fireworksModels[fireworksDefaultModelId]),
|
||||
};
|
||||
}
|
||||
case "oca": {
|
||||
@@ -630,21 +721,31 @@ export function normalizeApiConfiguration(
|
||||
};
|
||||
}
|
||||
case "minimax":
|
||||
return getProviderData(minimaxModels, minimaxDefaultModelId);
|
||||
return getModelsDevProviderData(
|
||||
"minimax",
|
||||
minimaxModels,
|
||||
minimaxDefaultModelId,
|
||||
);
|
||||
case "nousResearch": {
|
||||
const nousResearchModelId =
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeNousResearchModelId
|
||||
: apiConfiguration?.actModeNousResearchModelId;
|
||||
const nousResearchModelCatalog =
|
||||
getModelsDevProviderModels("nousResearch");
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: nousResearchModelId || nousResearchDefaultModelId,
|
||||
selectedModelInfo:
|
||||
nousResearchModelId && nousResearchModelId in nousResearchModels
|
||||
(nousResearchModelId &&
|
||||
nousResearchModelCatalog[nousResearchModelId]) ||
|
||||
nousResearchModelCatalog[nousResearchDefaultModelId] ||
|
||||
Object.values(nousResearchModelCatalog)[0] ||
|
||||
(nousResearchModelId && nousResearchModelId in nousResearchModels
|
||||
? nousResearchModels[
|
||||
nousResearchModelId as keyof typeof nousResearchModels
|
||||
]
|
||||
: nousResearchModels[nousResearchDefaultModelId],
|
||||
: nousResearchModels[nousResearchDefaultModelId]),
|
||||
};
|
||||
}
|
||||
default:
|
||||
@@ -1085,26 +1186,6 @@ export async function syncModeConfigurations(
|
||||
updates.actModeAihubmixModelInfo = sourceFields.aihubmixModelInfo;
|
||||
break;
|
||||
|
||||
// Providers that use apiProvider + apiModelId fields
|
||||
case "anthropic":
|
||||
case "claude-code":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "openai-native":
|
||||
case "openai-codex":
|
||||
case "deepseek":
|
||||
case "qwen":
|
||||
case "doubao":
|
||||
case "mistral":
|
||||
case "asksage":
|
||||
case "xai":
|
||||
case "nebius":
|
||||
case "wandb":
|
||||
case "sambanova":
|
||||
case "cerebras":
|
||||
case "sapaicore":
|
||||
case "zai":
|
||||
case "minimax":
|
||||
default:
|
||||
updates.planModeApiModelId = sourceFields.apiModelId;
|
||||
updates.actModeApiModelId = sourceFields.apiModelId;
|
||||
@@ -1120,7 +1201,7 @@ export { filterOpenRouterModelIds } from "@shared/utils/model-filters";
|
||||
// Helper to get provider-specific configuration info and empty state guidance
|
||||
export const getProviderInfo = (
|
||||
provider: ApiProvider,
|
||||
apiConfiguration: any,
|
||||
apiConfiguration: ApiConfiguration,
|
||||
effectiveMode: "plan" | "act",
|
||||
): { modelId?: string; baseUrl?: string; helpText: string } => {
|
||||
switch (provider) {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Webview copies of feature-flag strings. Must match the extension's FeatureFlag
|
||||
// enum, which can't be imported here (it pulls in Node-only deps).
|
||||
export const CLINE_PASS_FEATURE_FLAG = "ext-cline-pass"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
const DEFAULT_CLINE_APP_BASE_URL = "https://app.cline.bot"
|
||||
const CLINE_PASS_SUBSCRIPTION_PATH = "dashboard/subscription"
|
||||
|
||||
export function buildClinePassSubscriptionUrl(appBaseUrl?: string): string {
|
||||
try {
|
||||
const baseUrl = appBaseUrl || DEFAULT_CLINE_APP_BASE_URL
|
||||
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`
|
||||
const url = new URL(CLINE_PASS_SUBSCRIPTION_PATH, base)
|
||||
url.searchParams.set("personal", "true")
|
||||
return url.toString()
|
||||
} catch {
|
||||
return `${DEFAULT_CLINE_APP_BASE_URL}/${CLINE_PASS_SUBSCRIPTION_PATH}?personal=true`
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ApiConfiguration, ApiProvider } from "@shared/api"
|
||||
import PROVIDERS from "@shared/providers/providers.json"
|
||||
import type { RemoteConfigFields } from "@shared/storage/state-keys"
|
||||
import type { ApiConfiguration, ApiProvider } from "@shared/api";
|
||||
import { modelsDevProviderOptions } from "@shared/models/models-dev-catalog";
|
||||
import type { RemoteConfigFields } from "@shared/storage/state-keys";
|
||||
|
||||
/**
|
||||
* Returns a list of API providers that are configured (have required credentials/settings)
|
||||
@@ -11,114 +11,114 @@ export function getConfiguredProviders(
|
||||
apiConfiguration: ApiConfiguration | undefined,
|
||||
): ApiProvider[] {
|
||||
if (remoteConfig?.remoteConfiguredProviders?.length) {
|
||||
return remoteConfig.remoteConfiguredProviders
|
||||
return remoteConfig.remoteConfiguredProviders;
|
||||
}
|
||||
|
||||
const configured: ApiProvider[] = []
|
||||
const configured: ApiProvider[] = [];
|
||||
|
||||
if (!apiConfiguration) {
|
||||
return ["cline"] // Cline is always available
|
||||
return ["cline"]; // Cline is always available
|
||||
}
|
||||
|
||||
// Cline - always available (uses account-based auth)
|
||||
configured.push("cline")
|
||||
configured.push("cline");
|
||||
|
||||
// Anthropic - requires API key
|
||||
if (apiConfiguration.apiKey) {
|
||||
configured.push("anthropic")
|
||||
configured.push("anthropic");
|
||||
}
|
||||
|
||||
// OpenRouter - requires API key
|
||||
if (apiConfiguration.openRouterApiKey) {
|
||||
configured.push("openrouter")
|
||||
configured.push("openrouter");
|
||||
}
|
||||
|
||||
// Bedrock - requires region
|
||||
if (apiConfiguration.awsRegion) {
|
||||
configured.push("bedrock")
|
||||
configured.push("bedrock");
|
||||
}
|
||||
|
||||
// Vertex - requires project ID and region
|
||||
if (apiConfiguration.vertexProjectId && apiConfiguration.vertexRegion) {
|
||||
configured.push("vertex")
|
||||
configured.push("vertex");
|
||||
}
|
||||
|
||||
// Gemini - requires API key
|
||||
if (apiConfiguration.geminiApiKey) {
|
||||
configured.push("gemini")
|
||||
configured.push("gemini");
|
||||
}
|
||||
|
||||
// OpenAI Native - requires API key
|
||||
if (apiConfiguration.openAiNativeApiKey) {
|
||||
configured.push("openai-native")
|
||||
configured.push("openai-native");
|
||||
}
|
||||
|
||||
// OpenAI Codex - subscription-based OAuth, always available
|
||||
configured.push("openai-codex")
|
||||
configured.push("openai-codex");
|
||||
|
||||
// DeepSeek - requires API key
|
||||
if (apiConfiguration.deepSeekApiKey) {
|
||||
configured.push("deepseek")
|
||||
configured.push("deepseek");
|
||||
}
|
||||
|
||||
// xAI - requires API key
|
||||
if (apiConfiguration.xaiApiKey) {
|
||||
configured.push("xai")
|
||||
configured.push("xai");
|
||||
}
|
||||
|
||||
// Qwen - requires API key
|
||||
if (apiConfiguration.qwenApiKey) {
|
||||
configured.push("qwen")
|
||||
configured.push("qwen");
|
||||
}
|
||||
|
||||
// Doubao - requires API key
|
||||
if (apiConfiguration.doubaoApiKey) {
|
||||
configured.push("doubao")
|
||||
configured.push("doubao");
|
||||
}
|
||||
|
||||
// Mistral - requires API key
|
||||
if (apiConfiguration.mistralApiKey) {
|
||||
configured.push("mistral")
|
||||
configured.push("mistral");
|
||||
}
|
||||
|
||||
// Requesty - requires API key
|
||||
if (apiConfiguration.requestyApiKey) {
|
||||
configured.push("requesty")
|
||||
configured.push("requesty");
|
||||
}
|
||||
|
||||
// Fireworks - requires API key
|
||||
if (apiConfiguration.fireworksApiKey) {
|
||||
configured.push("fireworks")
|
||||
configured.push("fireworks");
|
||||
}
|
||||
|
||||
// Together - requires API key
|
||||
if (apiConfiguration.togetherApiKey) {
|
||||
configured.push("together")
|
||||
configured.push("together");
|
||||
}
|
||||
|
||||
// Moonshot - requires API key
|
||||
if (apiConfiguration.moonshotApiKey) {
|
||||
configured.push("moonshot")
|
||||
configured.push("moonshot");
|
||||
}
|
||||
|
||||
// Nebius - requires API key
|
||||
if (apiConfiguration.nebiusApiKey) {
|
||||
configured.push("nebius")
|
||||
configured.push("nebius");
|
||||
}
|
||||
|
||||
// AskSage - requires API key
|
||||
if (apiConfiguration.asksageApiKey) {
|
||||
configured.push("asksage")
|
||||
configured.push("asksage");
|
||||
}
|
||||
|
||||
// SambaNova - requires API key
|
||||
if (apiConfiguration.sambanovaApiKey) {
|
||||
configured.push("sambanova")
|
||||
configured.push("sambanova");
|
||||
}
|
||||
|
||||
// Cerebras - requires API key
|
||||
if (apiConfiguration.cerebrasApiKey) {
|
||||
configured.push("cerebras")
|
||||
configured.push("cerebras");
|
||||
}
|
||||
|
||||
// SAP AI Core - requires base URL, client ID, client secret, and token URL
|
||||
@@ -128,62 +128,62 @@ export function getConfiguredProviders(
|
||||
apiConfiguration.sapAiCoreClientSecret &&
|
||||
apiConfiguration.sapAiCoreTokenUrl
|
||||
) {
|
||||
configured.push("sapaicore")
|
||||
configured.push("sapaicore");
|
||||
}
|
||||
|
||||
// Z AI - requires API key
|
||||
if (apiConfiguration.zaiApiKey) {
|
||||
configured.push("zai")
|
||||
configured.push("zai");
|
||||
}
|
||||
|
||||
// Groq - requires API key
|
||||
if (apiConfiguration.groqApiKey) {
|
||||
configured.push("groq")
|
||||
configured.push("groq");
|
||||
}
|
||||
|
||||
// Hugging Face - requires API key
|
||||
if (apiConfiguration.huggingFaceApiKey) {
|
||||
configured.push("huggingface")
|
||||
configured.push("huggingface");
|
||||
}
|
||||
|
||||
// Baseten - requires API key
|
||||
if (apiConfiguration.basetenApiKey) {
|
||||
configured.push("baseten")
|
||||
configured.push("baseten");
|
||||
}
|
||||
|
||||
// Dify - requires base URL and API key
|
||||
if (apiConfiguration.difyBaseUrl && apiConfiguration.difyApiKey) {
|
||||
configured.push("dify")
|
||||
configured.push("dify");
|
||||
}
|
||||
|
||||
// Minimax - requires API key
|
||||
if (apiConfiguration.minimaxApiKey) {
|
||||
configured.push("minimax")
|
||||
configured.push("minimax");
|
||||
}
|
||||
|
||||
// Hicap - requires API key
|
||||
if (apiConfiguration.hicapApiKey) {
|
||||
configured.push("hicap")
|
||||
configured.push("hicap");
|
||||
}
|
||||
|
||||
// Huawei Cloud MaaS - requires API key
|
||||
if (apiConfiguration.huaweiCloudMaasApiKey) {
|
||||
configured.push("huawei-cloud-maas")
|
||||
configured.push("huawei-cloud-maas");
|
||||
}
|
||||
|
||||
// Vercel AI Gateway - requires API key
|
||||
if (apiConfiguration.vercelAiGatewayApiKey) {
|
||||
configured.push("vercel-ai-gateway")
|
||||
configured.push("vercel-ai-gateway");
|
||||
}
|
||||
|
||||
// AIHubMix - requires API key
|
||||
if (apiConfiguration.aihubmixApiKey) {
|
||||
configured.push("aihubmix")
|
||||
configured.push("aihubmix");
|
||||
}
|
||||
|
||||
// NousResearch - requires API key
|
||||
if (apiConfiguration.nousResearchApiKey) {
|
||||
configured.push("nousResearch")
|
||||
configured.push("nousResearch");
|
||||
}
|
||||
|
||||
// OpenAI Compatible - requires base URL and API key, OR has model configured
|
||||
@@ -192,17 +192,25 @@ export function getConfiguredProviders(
|
||||
apiConfiguration.planModeOpenAiModelId ||
|
||||
apiConfiguration.actModeOpenAiModelId
|
||||
) {
|
||||
configured.push("openai")
|
||||
configured.push("openai");
|
||||
}
|
||||
|
||||
// Ollama - local provider, check base URL OR model configured
|
||||
if (apiConfiguration.ollamaBaseUrl || apiConfiguration.planModeOllamaModelId || apiConfiguration.actModeOllamaModelId) {
|
||||
configured.push("ollama")
|
||||
if (
|
||||
apiConfiguration.ollamaBaseUrl ||
|
||||
apiConfiguration.planModeOllamaModelId ||
|
||||
apiConfiguration.actModeOllamaModelId
|
||||
) {
|
||||
configured.push("ollama");
|
||||
}
|
||||
|
||||
// LM Studio - local provider, check base URL OR model configured
|
||||
if (apiConfiguration.lmStudioBaseUrl || apiConfiguration.planModeLmStudioModelId || apiConfiguration.actModeLmStudioModelId) {
|
||||
configured.push("lmstudio")
|
||||
if (
|
||||
apiConfiguration.lmStudioBaseUrl ||
|
||||
apiConfiguration.planModeLmStudioModelId ||
|
||||
apiConfiguration.actModeLmStudioModelId
|
||||
) {
|
||||
configured.push("lmstudio");
|
||||
}
|
||||
|
||||
// LiteLLM - check base URL, API key OR model configured
|
||||
@@ -212,35 +220,37 @@ export function getConfiguredProviders(
|
||||
apiConfiguration.planModeLiteLlmModelId ||
|
||||
apiConfiguration.actModeLiteLlmModelId
|
||||
) {
|
||||
configured.push("litellm")
|
||||
configured.push("litellm");
|
||||
}
|
||||
|
||||
// VSCode LM - always potentially available
|
||||
configured.push("vscode-lm")
|
||||
configured.push("vscode-lm");
|
||||
|
||||
// Claude Code - requires path
|
||||
if (apiConfiguration.claudeCodePath) {
|
||||
configured.push("claude-code")
|
||||
configured.push("claude-code");
|
||||
}
|
||||
|
||||
// Qwen Code - requires API key (same as Qwen)
|
||||
if (apiConfiguration.qwenApiKey) {
|
||||
configured.push("qwen-code")
|
||||
configured.push("qwen-code");
|
||||
}
|
||||
|
||||
// OCA - requires base URL
|
||||
if (apiConfiguration.ocaBaseUrl) {
|
||||
configured.push("oca")
|
||||
configured.push("oca");
|
||||
}
|
||||
|
||||
return configured
|
||||
return configured;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get provider display label from provider value
|
||||
* Uses the canonical providers.json as source of truth
|
||||
* Uses the generated models.dev provider catalog as source of truth
|
||||
*/
|
||||
export function getProviderLabel(provider: ApiProvider): string {
|
||||
const providerEntry = PROVIDERS.list.find((p) => p.value === provider)
|
||||
return providerEntry?.label || provider
|
||||
const providerEntry = modelsDevProviderOptions.find(
|
||||
(p) => p.value === provider,
|
||||
);
|
||||
return providerEntry?.label || provider;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/// <reference types="vitest/config" />
|
||||
|
||||
import { writeFileSync } from "node:fs"
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
import react from "@vitejs/plugin-react-swc"
|
||||
import { resolve } from "path"
|
||||
import { defineConfig, type Plugin, ViteDevServer } from "vite"
|
||||
import { writeFileSync } from "node:fs";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import { resolve } from "path";
|
||||
import { defineConfig, loadEnv, type Plugin, type ViteDevServer } from "vite";
|
||||
|
||||
// Custom plugin to write the server port to a file
|
||||
const writePortToFile = (): Plugin => {
|
||||
@@ -12,135 +12,166 @@ const writePortToFile = (): Plugin => {
|
||||
name: "write-port-to-file",
|
||||
configureServer(server: ViteDevServer) {
|
||||
server.httpServer?.once("listening", () => {
|
||||
const address = server.httpServer?.address()
|
||||
const port = typeof address === "object" && address ? address.port : null
|
||||
const address = server.httpServer?.address();
|
||||
const port =
|
||||
typeof address === "object" && address ? address.port : null;
|
||||
|
||||
if (port) {
|
||||
const portFilePath = resolve(__dirname, ".vite-port")
|
||||
writeFileSync(portFilePath, port.toString())
|
||||
const portFilePath = resolve(__dirname, ".vite-port");
|
||||
writeFileSync(portFilePath, port.toString());
|
||||
} else {
|
||||
console.warn("[writePortToFile] Could not determine server port")
|
||||
console.warn("[writePortToFile] Could not determine server port");
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const isDevBuild = process.argv.includes("--dev-build")
|
||||
const isDevBuild = process.argv.includes("--dev-build");
|
||||
|
||||
// Valid platforms, these should the keys in platform-configs.json
|
||||
const VALID_PLATFORMS = ["vscode", "standalone"]
|
||||
const platform = process.env.PLATFORM || "vscode" // Default to vscode
|
||||
const VALID_PLATFORMS = ["vscode", "standalone"];
|
||||
|
||||
if (!VALID_PLATFORMS.includes(platform)) {
|
||||
throw new Error(`Invalid PLATFORM "${platform}". Must be one of: ${VALID_PLATFORMS.join(", ")}`)
|
||||
}
|
||||
console.log("Building webview for", platform)
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = {
|
||||
...loadEnv(mode, __dirname, ""),
|
||||
...process.env,
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
base: "./",
|
||||
optimizeDeps: {
|
||||
force: true, // Forces re-optimization
|
||||
},
|
||||
plugins: [react(), tailwindcss(), writePortToFile()],
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
setupFiles: ["./src/setupTests.ts"],
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reportOnFailure: true,
|
||||
reporter: ["html", "lcov", "text"],
|
||||
reportsDirectory: "./coverage",
|
||||
exclude: [
|
||||
"**/*.{spec,test}.{js,jsx,ts,tsx,mjs,cjs}",
|
||||
const platform = env.PLATFORM || "vscode"; // Default to vscode
|
||||
|
||||
"**/*.d.ts",
|
||||
"**/vite-env.d.ts",
|
||||
"**/*.{config,setup}.{js,ts,mjs,cjs}",
|
||||
if (!VALID_PLATFORMS.includes(platform)) {
|
||||
throw new Error(
|
||||
`Invalid PLATFORM "${platform}". Must be one of: ${VALID_PLATFORMS.join(", ")}`,
|
||||
);
|
||||
}
|
||||
console.log("Building webview for", platform);
|
||||
|
||||
"**/*.{css,scss,sass,less,styl}",
|
||||
"**/*.{svg,png,jpg,jpeg,gif,ico}",
|
||||
|
||||
"**/*.{json,yaml,yml}",
|
||||
|
||||
"**/__mocks__/**",
|
||||
"node_modules/**",
|
||||
"build/**",
|
||||
"coverage/**",
|
||||
"dist/**",
|
||||
"public/**",
|
||||
|
||||
"src/services/grpc-client.ts",
|
||||
],
|
||||
return {
|
||||
base: "./",
|
||||
optimizeDeps: {
|
||||
force: true, // Forces re-optimization
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
reportCompressedSize: false,
|
||||
// Only minify in production build
|
||||
minify: !isDevBuild,
|
||||
// Enable inline source maps for dev build
|
||||
sourcemap: isDevBuild ? "inline" : false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
inlineDynamicImports: true,
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`,
|
||||
// Disable compact output for dev build
|
||||
compact: !isDevBuild,
|
||||
// Add generous formatting for dev build
|
||||
...(isDevBuild && {
|
||||
generatedCode: {
|
||||
constBindings: false,
|
||||
objectShorthand: false,
|
||||
arrowFunctions: false,
|
||||
},
|
||||
}),
|
||||
plugins: [react(), tailwindcss(), writePortToFile()],
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
setupFiles: ["./src/setupTests.ts"],
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reportOnFailure: true,
|
||||
reporter: ["html", "lcov", "text"],
|
||||
reportsDirectory: "./coverage",
|
||||
exclude: [
|
||||
"**/*.{spec,test}.{js,jsx,ts,tsx,mjs,cjs}",
|
||||
|
||||
"**/*.d.ts",
|
||||
"**/vite-env.d.ts",
|
||||
"**/*.{config,setup}.{js,ts,mjs,cjs}",
|
||||
|
||||
"**/*.{css,scss,sass,less,styl}",
|
||||
"**/*.{svg,png,jpg,jpeg,gif,ico}",
|
||||
|
||||
"**/*.{json,yaml,yml}",
|
||||
|
||||
"**/__mocks__/**",
|
||||
"node_modules/**",
|
||||
"build/**",
|
||||
"coverage/**",
|
||||
"dist/**",
|
||||
"public/**",
|
||||
|
||||
"src/services/grpc-client.ts",
|
||||
],
|
||||
},
|
||||
},
|
||||
chunkSizeWarningLimit: 100000,
|
||||
},
|
||||
server: {
|
||||
port: 25463,
|
||||
hmr: {
|
||||
host: "localhost",
|
||||
protocol: "ws",
|
||||
build: {
|
||||
outDir: "build",
|
||||
reportCompressedSize: false,
|
||||
// Only minify in production build
|
||||
minify: !isDevBuild,
|
||||
// Enable inline source maps for dev build
|
||||
sourcemap: isDevBuild ? "inline" : false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
inlineDynamicImports: true,
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`,
|
||||
// Disable compact output for dev build
|
||||
compact: !isDevBuild,
|
||||
// Add generous formatting for dev build
|
||||
...(isDevBuild && {
|
||||
generatedCode: {
|
||||
constBindings: false,
|
||||
objectShorthand: false,
|
||||
arrowFunctions: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
chunkSizeWarningLimit: 100000,
|
||||
},
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: "*",
|
||||
allowedHeaders: "*",
|
||||
server: {
|
||||
port: 25463,
|
||||
hmr: {
|
||||
host: "localhost",
|
||||
protocol: "ws",
|
||||
},
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: "*",
|
||||
allowedHeaders: "*",
|
||||
},
|
||||
},
|
||||
},
|
||||
define: {
|
||||
__PLATFORM__: JSON.stringify(platform),
|
||||
__NODE_PLATFORM__: JSON.stringify(process.platform),
|
||||
"process.env.CLINE_ENVIRONMENT": JSON.stringify(process.env.CLINE_ENVIRONMENT ?? "production"),
|
||||
"process.env.IS_DEV": JSON.stringify(process.env.IS_DEV),
|
||||
"process.env.IS_TEST": JSON.stringify(process.env.IS_TEST),
|
||||
"process.env.CI": JSON.stringify(process.env.CI),
|
||||
// PostHog environment variables
|
||||
"process.env.TELEMETRY_SERVICE_API_KEY": JSON.stringify(process.env.TELEMETRY_SERVICE_API_KEY),
|
||||
"process.env.ERROR_SERVICE_API_KEY": JSON.stringify(process.env.ERROR_SERVICE_API_KEY),
|
||||
"process.env.ENABLE_ERROR_AUTOCAPTURE": JSON.stringify(process.env.ENABLE_ERROR_AUTOCAPTURE),
|
||||
// OpenTelemetry environment variables
|
||||
"process.env.OTEL_TELEMETRY_ENABLED": JSON.stringify(process.env.OTEL_TELEMETRY_ENABLED),
|
||||
"process.env.OTEL_METRICS_EXPORTER": JSON.stringify(process.env.OTEL_METRICS_EXPORTER),
|
||||
"process.env.OTEL_LOGS_EXPORTER": JSON.stringify(process.env.OTEL_LOGS_EXPORTER),
|
||||
"process.env.OTEL_EXPORTER_OTLP_PROTOCOL": JSON.stringify(process.env.OTEL_EXPORTER_OTLP_PROTOCOL),
|
||||
"process.env.OTEL_EXPORTER_OTLP_ENDPOINT": JSON.stringify(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
|
||||
"process.env.OTEL_EXPORTER_OTLP_HEADERS": JSON.stringify(process.env.OTEL_EXPORTER_OTLP_HEADERS),
|
||||
"process.env.OTEL_METRIC_EXPORT_INTERVAL": JSON.stringify(process.env.OTEL_METRIC_EXPORT_INTERVAL),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": resolve(__dirname, "./src"),
|
||||
"@components": resolve(__dirname, "./src/components"),
|
||||
"@context": resolve(__dirname, "./src/context"),
|
||||
"@shared": resolve(__dirname, "../src/shared"),
|
||||
"@utils": resolve(__dirname, "./src/utils"),
|
||||
define: {
|
||||
__PLATFORM__: JSON.stringify(platform),
|
||||
__NODE_PLATFORM__: JSON.stringify(process.platform),
|
||||
"process.env.CLINE_ENVIRONMENT": JSON.stringify(
|
||||
env.CLINE_ENVIRONMENT ?? "production",
|
||||
),
|
||||
"process.env.IS_DEV": JSON.stringify(env.IS_DEV),
|
||||
"process.env.IS_TEST": JSON.stringify(env.IS_TEST),
|
||||
"process.env.CI": JSON.stringify(env.CI),
|
||||
// PostHog environment variables
|
||||
"process.env.TELEMETRY_SERVICE_API_KEY": JSON.stringify(
|
||||
env.TELEMETRY_SERVICE_API_KEY,
|
||||
),
|
||||
"process.env.ERROR_SERVICE_API_KEY": JSON.stringify(
|
||||
env.ERROR_SERVICE_API_KEY,
|
||||
),
|
||||
"process.env.ENABLE_ERROR_AUTOCAPTURE": JSON.stringify(
|
||||
env.ENABLE_ERROR_AUTOCAPTURE,
|
||||
),
|
||||
// OpenTelemetry environment variables
|
||||
"process.env.OTEL_TELEMETRY_ENABLED": JSON.stringify(
|
||||
env.OTEL_TELEMETRY_ENABLED,
|
||||
),
|
||||
"process.env.OTEL_METRICS_EXPORTER": JSON.stringify(
|
||||
env.OTEL_METRICS_EXPORTER,
|
||||
),
|
||||
"process.env.OTEL_LOGS_EXPORTER": JSON.stringify(env.OTEL_LOGS_EXPORTER),
|
||||
"process.env.OTEL_EXPORTER_OTLP_PROTOCOL": JSON.stringify(
|
||||
env.OTEL_EXPORTER_OTLP_PROTOCOL,
|
||||
),
|
||||
"process.env.OTEL_EXPORTER_OTLP_ENDPOINT": JSON.stringify(
|
||||
env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
||||
),
|
||||
"process.env.OTEL_EXPORTER_OTLP_HEADERS": JSON.stringify(
|
||||
env.OTEL_EXPORTER_OTLP_HEADERS,
|
||||
),
|
||||
"process.env.OTEL_METRIC_EXPORT_INTERVAL": JSON.stringify(
|
||||
env.OTEL_METRIC_EXPORT_INTERVAL,
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": resolve(__dirname, "./src"),
|
||||
"@components": resolve(__dirname, "./src/components"),
|
||||
"@context": resolve(__dirname, "./src/context"),
|
||||
"@shared": resolve(__dirname, "../src/shared"),
|
||||
"@utils": resolve(__dirname, "./src/utils"),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -31,7 +31,9 @@ const PROVIDER_IDS_MAP: ReadonlyArray<{
|
||||
generatedProviderId: "deepseek",
|
||||
runtimeProviderId: "deepseek",
|
||||
},
|
||||
{ modelsDevKey: "doubao", generatedProviderId: "doubao" },
|
||||
{ modelsDevKey: "xai", generatedProviderId: "xai" },
|
||||
{ modelsDevKey: "mistral", generatedProviderId: "mistral" },
|
||||
{
|
||||
modelsDevKey: "togetherai",
|
||||
generatedProviderId: "together",
|
||||
@@ -98,7 +100,11 @@ const PROVIDER_IDS_MAP: ReadonlyArray<{
|
||||
runtimeProviderId: "aihubmix",
|
||||
},
|
||||
{ modelsDevKey: "hicap", runtimeProviderId: "hicap" },
|
||||
{ modelsDevKey: "nous-research", runtimeProviderId: "nousResearch" },
|
||||
{
|
||||
modelsDevKey: "nous-research",
|
||||
generatedProviderId: "nousResearch",
|
||||
runtimeProviderId: "nousResearch",
|
||||
},
|
||||
{ modelsDevKey: "huawei-cloud-maas", runtimeProviderId: "huawei-cloud-maas" },
|
||||
{
|
||||
modelsDevKey: "baseten",
|
||||
|
||||
Reference in New Issue
Block a user