mirror of
https://github.com/cline/cline.git
synced 2026-10-30 18:12:49 +08:00
feat(vscode): expose additional SDK providers (#11703)
* feat(vscode): expose additional SDK providers * fix(vscode): reserve skipped provider enum slots * fix(vscode): keep Z.AI Coding Plan provider-specific
This commit is contained in:
committed by
Dominic Cooney
parent
80036cef44
commit
52bbda183d
@@ -613,6 +613,12 @@ enum ApiProvider {
|
||||
OPENAI_CODEX = 40;
|
||||
WANDB = 41;
|
||||
CLINE_PASS = 42;
|
||||
POOLSIDE = 45;
|
||||
V0 = 46;
|
||||
XIAOMI = 47;
|
||||
ZAI_CODING_PLAN = 49;
|
||||
reserved 43, 44, 48;
|
||||
reserved "OPENAI_CODEX_CLI", "OPENCODE", "KILO";
|
||||
}
|
||||
|
||||
enum ApiFormat {
|
||||
|
||||
@@ -117,6 +117,31 @@ describe("buildEffectiveProviderConfig", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("reads the Z.AI Coding Plan API key from provider-specific settings", async () => {
|
||||
const { buildEffectiveProviderConfig } = await import("./effective-config")
|
||||
mocks.setProviderSettings({
|
||||
"zai-coding-plan": { provider: "zai-coding-plan", apiKey: "provider-zai-coding-plan-key" },
|
||||
})
|
||||
mocks.setApiConfiguration({ zaiApiKey: "state-zai-key" })
|
||||
|
||||
expect(buildEffectiveProviderConfig(parseProviderId("zai-coding-plan"))).toEqual({
|
||||
providerId: parseProviderId("zai-coding-plan"),
|
||||
apiKey: "provider-zai-coding-plan-key",
|
||||
})
|
||||
})
|
||||
|
||||
it("does not reuse the legacy Z.AI API key for Z.AI Coding Plan", async () => {
|
||||
const { buildEffectiveProviderConfig } = await import("./effective-config")
|
||||
mocks.setProviderSettings({
|
||||
"zai-coding-plan": { provider: "zai-coding-plan" },
|
||||
})
|
||||
mocks.setApiConfiguration({ zaiApiKey: "state-zai-key" })
|
||||
|
||||
expect(buildEffectiveProviderConfig(parseProviderId("zai-coding-plan"))).toEqual({
|
||||
providerId: parseProviderId("zai-coding-plan"),
|
||||
})
|
||||
})
|
||||
|
||||
it("respects remote-config-locked LiteLLM key already applied by StateManager", async () => {
|
||||
const { buildEffectiveProviderConfig } = await import("./effective-config")
|
||||
mocks.setProviderSettings({
|
||||
|
||||
@@ -45,6 +45,10 @@ describe("parseProviderId", () => {
|
||||
parseProviderId("anthropic")
|
||||
parseProviderId("openai")
|
||||
parseProviderId("nousResearch")
|
||||
parseProviderId("zai-coding-plan")
|
||||
parseProviderId("poolside")
|
||||
parseProviderId("v0")
|
||||
parseProviderId("xiaomi")
|
||||
|
||||
expect(warnSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -56,6 +60,10 @@ describe("isKnownProviderId", () => {
|
||||
expect(isKnownProviderId(parseProviderId("openai"))).toBe(true)
|
||||
expect(isKnownProviderId(parseProviderId("deepseek"))).toBe(true)
|
||||
expect(isKnownProviderId(parseProviderId("nousResearch"))).toBe(true)
|
||||
expect(isKnownProviderId(parseProviderId("zai-coding-plan"))).toBe(true)
|
||||
expect(isKnownProviderId(parseProviderId("poolside"))).toBe(true)
|
||||
expect(isKnownProviderId(parseProviderId("v0"))).toBe(true)
|
||||
expect(isKnownProviderId(parseProviderId("xiaomi"))).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false for a custom provider id", () => {
|
||||
|
||||
@@ -41,18 +41,22 @@ const KNOWN_API_PROVIDERS = {
|
||||
cerebras: true,
|
||||
sapaicore: true,
|
||||
groq: true,
|
||||
poolside: true,
|
||||
huggingface: true,
|
||||
"huawei-cloud-maas": true,
|
||||
dify: true,
|
||||
baseten: true,
|
||||
"vercel-ai-gateway": true,
|
||||
v0: true,
|
||||
zai: true,
|
||||
"zai-coding-plan": true,
|
||||
oca: true,
|
||||
aihubmix: true,
|
||||
minimax: true,
|
||||
hicap: true,
|
||||
nousResearch: true,
|
||||
wandb: true,
|
||||
xiaomi: true,
|
||||
"cline-pass": true,
|
||||
} satisfies Record<ApiProvider, true>
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ const mocks = vi.hoisted(() => {
|
||||
getSavedProviderSettings(providerId: string): Record<string, unknown> | undefined {
|
||||
return providerSettingsById[providerId]
|
||||
},
|
||||
getApiConfiguration(): MockApiConfiguration {
|
||||
return { ...apiConfiguration }
|
||||
},
|
||||
getSaveProviderSettingsMock(): typeof saveProviderSettings {
|
||||
return saveProviderSettings
|
||||
},
|
||||
@@ -185,6 +188,22 @@ describe("createProviderConfigStore", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("writes Z.AI Coding Plan API keys only to provider-specific settings", async () => {
|
||||
const { createProviderConfigStore } = await import("./store")
|
||||
mocks.setApiConfiguration({ zaiApiKey: "shared-zai-key" })
|
||||
const store = createProviderConfigStore()
|
||||
const providerId = parseProviderId("zai-coding-plan")
|
||||
|
||||
const written = store.write(providerId, { apiKey: "coding-plan-key" })
|
||||
|
||||
expect(written).toEqual({ providerId, apiKey: "coding-plan-key" })
|
||||
expect(mocks.getSavedProviderSettings("zai-coding-plan")).toMatchObject({
|
||||
provider: "zai-coding-plan",
|
||||
apiKey: "coding-plan-key",
|
||||
})
|
||||
expect(mocks.getApiConfiguration().zaiApiKey).toBe("shared-zai-key")
|
||||
})
|
||||
|
||||
it("returns undefined from readSelection when modelId or modelInfo is missing", async () => {
|
||||
const { createProviderConfigStore } = await import("./store")
|
||||
const store = createProviderConfigStore()
|
||||
|
||||
@@ -33,18 +33,22 @@ export type ApiProvider =
|
||||
| "cerebras"
|
||||
| "sapaicore"
|
||||
| "groq"
|
||||
| "poolside"
|
||||
| "huggingface"
|
||||
| "huawei-cloud-maas"
|
||||
| "dify"
|
||||
| "baseten"
|
||||
| "vercel-ai-gateway"
|
||||
| "v0"
|
||||
| "zai"
|
||||
| "zai-coding-plan"
|
||||
| "oca"
|
||||
| "aihubmix"
|
||||
| "minimax"
|
||||
| "hicap"
|
||||
| "nousResearch"
|
||||
| "wandb"
|
||||
| "xiaomi"
|
||||
|
||||
export const DEFAULT_API_PROVIDER = "openrouter" as ApiProvider
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { ApiProvider } from "@shared/api"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { convertApiConfigurationToProto, convertProtoToApiConfiguration } from "./api-configuration-conversion"
|
||||
|
||||
describe("api configuration provider conversion", () => {
|
||||
it("round-trips SDK provider ids added after the legacy enum list", () => {
|
||||
const providers: ApiProvider[] = ["poolside", "v0", "xiaomi", "zai-coding-plan"]
|
||||
|
||||
for (const provider of providers) {
|
||||
const proto = convertApiConfigurationToProto({
|
||||
actModeApiProvider: provider,
|
||||
planModeApiProvider: provider,
|
||||
})
|
||||
|
||||
expect(convertProtoToApiConfiguration(proto)).toMatchObject({
|
||||
actModeApiProvider: provider,
|
||||
planModeApiProvider: provider,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -330,6 +330,14 @@ function convertApiProviderToProto(provider: string | undefined): ProtoApiProvid
|
||||
return ProtoApiProvider.NOUSRESEARCH
|
||||
case "openai-codex":
|
||||
return ProtoApiProvider.OPENAI_CODEX
|
||||
case "poolside":
|
||||
return ProtoApiProvider.POOLSIDE
|
||||
case "v0":
|
||||
return ProtoApiProvider.V0
|
||||
case "xiaomi":
|
||||
return ProtoApiProvider.XIAOMI
|
||||
case "zai-coding-plan":
|
||||
return ProtoApiProvider.ZAI_CODING_PLAN
|
||||
default:
|
||||
return ProtoApiProvider.ANTHROPIC
|
||||
}
|
||||
@@ -424,6 +432,14 @@ export function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvid
|
||||
return "nousResearch"
|
||||
case ProtoApiProvider.OPENAI_CODEX:
|
||||
return "openai-codex"
|
||||
case ProtoApiProvider.POOLSIDE:
|
||||
return "poolside"
|
||||
case ProtoApiProvider.V0:
|
||||
return "v0"
|
||||
case ProtoApiProvider.XIAOMI:
|
||||
return "xiaomi"
|
||||
case ProtoApiProvider.ZAI_CODING_PLAN:
|
||||
return "zai-coding-plan"
|
||||
default:
|
||||
return "anthropic"
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
"value": "openai-codex",
|
||||
"label": "ChatGPT Subscription"
|
||||
},
|
||||
{
|
||||
"value": "zai-coding-plan",
|
||||
"label": "Z.AI Coding Plan"
|
||||
},
|
||||
{
|
||||
"value": "gemini",
|
||||
"label": "Google Gemini"
|
||||
@@ -76,6 +80,10 @@
|
||||
"value": "groq",
|
||||
"label": "Groq"
|
||||
},
|
||||
{
|
||||
"value": "poolside",
|
||||
"label": "Poolside"
|
||||
},
|
||||
{
|
||||
"value": "cerebras",
|
||||
"label": "Cerebras"
|
||||
@@ -84,6 +92,10 @@
|
||||
"value": "vercel-ai-gateway",
|
||||
"label": "Vercel AI Gateway"
|
||||
},
|
||||
{
|
||||
"value": "v0",
|
||||
"label": "Vercel v0"
|
||||
},
|
||||
{
|
||||
"value": "baseten",
|
||||
"label": "Baseten"
|
||||
@@ -171,6 +183,10 @@
|
||||
{
|
||||
"value": "wandb",
|
||||
"label": "W&B Inference by CoreWeave"
|
||||
},
|
||||
{
|
||||
"value": "xiaomi",
|
||||
"label": "Xiaomi"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default defineConfig({
|
||||
include: [
|
||||
"src/sdk/**/*.test.ts",
|
||||
"src/shared/vsCodeSelectorUtils.test.ts",
|
||||
"src/shared/proto-conversions/models/**/*.test.ts",
|
||||
"src/core/storage/remote-config/**/*.test.ts",
|
||||
"src/services/mcp/__tests__/settingsLock.test.ts",
|
||||
"src/shared/model-catalog/provider-helpers.test.ts",
|
||||
|
||||
+9
@@ -67,9 +67,13 @@ describe("providerSettingsRegistry", () => {
|
||||
["mistral", "Mistral", "https://console.mistral.ai/codestral"],
|
||||
["nebius", "Nebius", "https://auth.tokenfactory.nebius.com/ui/login"],
|
||||
["nousResearch", "NousResearch", undefined],
|
||||
["poolside", "Poolside", undefined],
|
||||
["sambanova", "SambaNova", "https://docs.sambanova.ai/cloud/docs/get-started/overview"],
|
||||
["vercel-ai-gateway", "Vercel AI Gateway", "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai"],
|
||||
["v0", "Vercel v0", undefined],
|
||||
["wandb", "W&B", "https://wandb.ai"],
|
||||
["xiaomi", "Xiaomi", undefined],
|
||||
["zai-coding-plan", "Z.AI Coding Plan", undefined],
|
||||
] as const
|
||||
|
||||
for (const [providerId, providerName, signupUrl] of migratedProviders) {
|
||||
@@ -171,6 +175,11 @@ describe("providerSettingsRegistry", () => {
|
||||
providerName: "Together",
|
||||
signupUrl: "https://api.together.ai/settings/api-keys",
|
||||
})
|
||||
expect(getFallbackGenericProviderSettings("zai-coding-plan")).toEqual({
|
||||
allowsCustomIds: false,
|
||||
providerId: "zai-coding-plan",
|
||||
providerName: "Z.AI Coding Plan",
|
||||
})
|
||||
expect(getFallbackGenericProviderSettings("openai")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -50,6 +50,7 @@ const GENERIC_PROVIDER_PRESENTATION_OVERRIDES: Record<string, GenericProviderPre
|
||||
groq: {
|
||||
signupUrl: "https://console.groq.com/keys",
|
||||
},
|
||||
poolside: {},
|
||||
cerebras: {
|
||||
signupUrl: "https://cloud.cerebras.ai/",
|
||||
},
|
||||
@@ -90,9 +91,12 @@ const GENERIC_PROVIDER_PRESENTATION_OVERRIDES: Record<string, GenericProviderPre
|
||||
"vercel-ai-gateway": {
|
||||
signupUrl: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai",
|
||||
},
|
||||
v0: {},
|
||||
wandb: {
|
||||
signupUrl: "https://wandb.ai",
|
||||
},
|
||||
xiaomi: {},
|
||||
"zai-coding-plan": {},
|
||||
}
|
||||
|
||||
const GENERIC_PROVIDER_PROTOCOLS = new Set(["anthropic", "gemini", "openai-chat", "openai-responses"])
|
||||
@@ -137,8 +141,12 @@ const FALLBACK_GENERIC_PROVIDER_NAMES = {
|
||||
minimax: "MiniMax",
|
||||
mistral: "Mistral",
|
||||
nousResearch: "NousResearch",
|
||||
poolside: "Poolside",
|
||||
together: "Together",
|
||||
v0: "Vercel v0",
|
||||
wandb: "W&B",
|
||||
xiaomi: "Xiaomi",
|
||||
"zai-coding-plan": "Z.AI Coding Plan",
|
||||
} as const
|
||||
|
||||
export function getFallbackGenericProviderSettings(providerId: string): GenericProviderSettingsConfig | undefined {
|
||||
|
||||
Reference in New Issue
Block a user