mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Add alibaba qwen models plus/max/coder-plus/turbo both stable and latest to use. (#1648)
* add alibaba qwen-max qwen-plus qwen-turbo qwen-coder-plus stable/latest models * add alibaba qwen-max qwen-plus qwen-turbo qwen-coder-plus stable/latest models * Provide the api line choice for international user * Remove redundant code * Copy fixes * Create dry-socks-talk.md --------- Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
This commit is contained in:
parent
c88b5c67d5
commit
548338d39e
8 changed files with 257 additions and 0 deletions
5
.changeset/dry-socks-talk.md
Normal file
5
.changeset/dry-socks-talk.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add Alibaba qwen models plus/max/coder-plus/turbo
|
||||
|
|
@ -11,6 +11,7 @@ import { GeminiHandler } from "./providers/gemini"
|
|||
import { OpenAiNativeHandler } from "./providers/openai-native"
|
||||
import { ApiStream } from "./transform/stream"
|
||||
import { DeepSeekHandler } from "./providers/deepseek"
|
||||
import { QwenHandler } from "./providers/qwen"
|
||||
import { MistralHandler } from "./providers/mistral"
|
||||
import { VsCodeLmHandler } from "./providers/vscode-lm"
|
||||
import { LiteLlmHandler } from "./providers/litellm"
|
||||
|
|
@ -47,6 +48,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
|||
return new OpenAiNativeHandler(options)
|
||||
case "deepseek":
|
||||
return new DeepSeekHandler(options)
|
||||
case "qwen":
|
||||
return new QwenHandler(options)
|
||||
case "mistral":
|
||||
return new MistralHandler(options)
|
||||
case "vscode-lm":
|
||||
|
|
|
|||
76
src/api/providers/qwen.ts
Normal file
76
src/api/providers/qwen.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, QwenModelId, ModelInfo, qwenDefaultModelId, qwenModels } from "../../shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
export class QwenHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = new OpenAI({
|
||||
baseURL: this.options.qwenApiLine || "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||
apiKey: this.options.qwenApiKey,
|
||||
})
|
||||
}
|
||||
|
||||
getModel(): { id: QwenModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in qwenModels) {
|
||||
const id = modelId as QwenModelId
|
||||
return { id, info: qwenModels[id] }
|
||||
}
|
||||
return {
|
||||
id: qwenDefaultModelId,
|
||||
info: qwenModels[qwenDefaultModelId],
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const model = this.getModel()
|
||||
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const stream = await this.client.chat.completions.create({
|
||||
model: model.id,
|
||||
max_completion_tokens: model.info.maxTokens,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
})
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: (delta.reasoning_content as string | undefined) || "",
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
// @ts-ignore-next-line
|
||||
cacheReadTokens: chunk.usage.prompt_cache_hit_tokens || 0,
|
||||
// @ts-ignore-next-line
|
||||
cacheWriteTokens: chunk.usage.prompt_cache_miss_tokens || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ type SecretKey =
|
|||
| "geminiApiKey"
|
||||
| "openAiNativeApiKey"
|
||||
| "deepSeekApiKey"
|
||||
| "qwenApiKey"
|
||||
| "mistralApiKey"
|
||||
| "authToken"
|
||||
| "authNonce"
|
||||
|
|
@ -78,6 +79,7 @@ type GlobalStateKey =
|
|||
| "previousModeModelInfo"
|
||||
| "liteLlmBaseUrl"
|
||||
| "liteLlmModelId"
|
||||
| "qwenApiLine"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
|
|
@ -440,6 +442,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
geminiApiKey,
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
qwenApiKey,
|
||||
mistralApiKey,
|
||||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
|
|
@ -447,6 +450,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
vsCodeLmModelSelector,
|
||||
liteLlmBaseUrl,
|
||||
liteLlmModelId,
|
||||
qwenApiLine,
|
||||
} = message.apiConfiguration
|
||||
await this.updateGlobalState("apiProvider", apiProvider)
|
||||
await this.updateGlobalState("apiModelId", apiModelId)
|
||||
|
|
@ -470,6 +474,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.storeSecret("geminiApiKey", geminiApiKey)
|
||||
await this.storeSecret("openAiNativeApiKey", openAiNativeApiKey)
|
||||
await this.storeSecret("deepSeekApiKey", deepSeekApiKey)
|
||||
await this.storeSecret("qwenApiKey", qwenApiKey)
|
||||
await this.storeSecret("mistralApiKey", mistralApiKey)
|
||||
await this.updateGlobalState("azureApiVersion", azureApiVersion)
|
||||
await this.updateGlobalState("openRouterModelId", openRouterModelId)
|
||||
|
|
@ -477,6 +482,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.updateGlobalState("vsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
await this.updateGlobalState("liteLlmBaseUrl", liteLlmBaseUrl)
|
||||
await this.updateGlobalState("liteLlmModelId", liteLlmModelId)
|
||||
await this.updateGlobalState("qwenApiLine", qwenApiLine)
|
||||
if (this.cline) {
|
||||
this.cline.api = buildApiHandler(message.apiConfiguration)
|
||||
}
|
||||
|
|
@ -1365,6 +1371,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
geminiApiKey,
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
qwenApiKey,
|
||||
mistralApiKey,
|
||||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
|
|
@ -1383,6 +1390,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
previousModeApiProvider,
|
||||
previousModeModelId,
|
||||
previousModeModelInfo,
|
||||
qwenApiLine,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
|
|
@ -1406,6 +1414,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.getSecret("geminiApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("openAiNativeApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("deepSeekApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("qwenApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("mistralApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("azureApiVersion") as Promise<string | undefined>,
|
||||
this.getGlobalState("openRouterModelId") as Promise<string | undefined>,
|
||||
|
|
@ -1424,6 +1433,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.getGlobalState("previousModeApiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("previousModeModelId") as Promise<string | undefined>,
|
||||
this.getGlobalState("previousModeModelInfo") as Promise<ModelInfo | undefined>,
|
||||
this.getGlobalState("qwenApiLine") as Promise<string | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
|
|
@ -1464,6 +1474,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
geminiApiKey,
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
qwenApiKey,
|
||||
qwenApiLine,
|
||||
mistralApiKey,
|
||||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
|
|
@ -1559,6 +1571,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
"geminiApiKey",
|
||||
"openAiNativeApiKey",
|
||||
"deepSeekApiKey",
|
||||
"qwenApiKey",
|
||||
"mistralApiKey",
|
||||
"authToken",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export type ApiProvider =
|
|||
| "gemini"
|
||||
| "openai-native"
|
||||
| "deepseek"
|
||||
| "qwen"
|
||||
| "mistral"
|
||||
| "vscode-lm"
|
||||
| "litellm"
|
||||
|
|
@ -39,9 +40,11 @@ export interface ApiHandlerOptions {
|
|||
geminiApiKey?: string
|
||||
openAiNativeApiKey?: string
|
||||
deepSeekApiKey?: string
|
||||
qwenApiKey?: string
|
||||
mistralApiKey?: string
|
||||
azureApiVersion?: string
|
||||
vsCodeLmModelSelector?: any
|
||||
qwenApiLine?: string
|
||||
}
|
||||
|
||||
export type ApiConfiguration = ApiHandlerOptions & {
|
||||
|
|
@ -432,6 +435,93 @@ export const deepSeekModels = {
|
|||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
// Qwen
|
||||
// https://bailian.console.aliyun.com/
|
||||
export type QwenModelId = keyof typeof qwenModels
|
||||
export const qwenDefaultModelId: QwenModelId = "qwen-coder-plus-latest"
|
||||
export const qwenModels = {
|
||||
"qwen-coder-plus-latest": {
|
||||
maxTokens: 129_024,
|
||||
contextWindow: 131_072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.0035,
|
||||
outputPrice: 0.007,
|
||||
cacheWritesPrice: 0.0035,
|
||||
cacheReadsPrice: 0.007,
|
||||
},
|
||||
"qwen-plus-latest": {
|
||||
maxTokens: 129_024,
|
||||
contextWindow: 131_072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.0008,
|
||||
outputPrice: 0.002,
|
||||
cacheWritesPrice: 0.0004,
|
||||
cacheReadsPrice: 0.001,
|
||||
},
|
||||
"qwen-turbo-latest": {
|
||||
maxTokens: 1_000_000,
|
||||
contextWindow: 1_000_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.0003,
|
||||
outputPrice: 0.0006,
|
||||
cacheWritesPrice: 0.00015,
|
||||
cacheReadsPrice: 0.0003,
|
||||
},
|
||||
"qwen-max-latest": {
|
||||
maxTokens: 30_720,
|
||||
contextWindow: 32_768,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.0112,
|
||||
outputPrice: 0.0448,
|
||||
cacheWritesPrice: 0.0056,
|
||||
cacheReadsPrice: 0.0224,
|
||||
},
|
||||
"qwen-coder-plus": {
|
||||
maxTokens: 129_024,
|
||||
contextWindow: 131_072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.0035,
|
||||
outputPrice: 0.007,
|
||||
cacheWritesPrice: 0.0035,
|
||||
cacheReadsPrice: 0.007,
|
||||
},
|
||||
"qwen-plus": {
|
||||
maxTokens: 129_024,
|
||||
contextWindow: 131_072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.0008,
|
||||
outputPrice: 0.002,
|
||||
cacheWritesPrice: 0.0004,
|
||||
cacheReadsPrice: 0.001,
|
||||
},
|
||||
"qwen-turbo": {
|
||||
maxTokens: 1_000_000,
|
||||
contextWindow: 1_000_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.0003,
|
||||
outputPrice: 0.0006,
|
||||
cacheWritesPrice: 0.00015,
|
||||
cacheReadsPrice: 0.0003,
|
||||
},
|
||||
"qwen-max": {
|
||||
maxTokens: 30_720,
|
||||
contextWindow: 32_768,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.0112,
|
||||
outputPrice: 0.0448,
|
||||
cacheWritesPrice: 0.0056,
|
||||
cacheReadsPrice: 0.0224,
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
// Mistral
|
||||
// https://docs.mistral.ai/getting-started/models/models_overview/
|
||||
export type MistralModelId = keyof typeof mistralModels
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import {
|
|||
bedrockModels,
|
||||
deepSeekDefaultModelId,
|
||||
deepSeekModels,
|
||||
qwenDefaultModelId,
|
||||
qwenModels,
|
||||
geminiDefaultModelId,
|
||||
geminiModels,
|
||||
mistralDefaultModelId,
|
||||
|
|
@ -179,6 +181,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
|
||||
<VSCodeOption value="gemini">Google Gemini</VSCodeOption>
|
||||
<VSCodeOption value="deepseek">DeepSeek</VSCodeOption>
|
||||
<VSCodeOption value="qwen">Qwen</VSCodeOption>
|
||||
<VSCodeOption value="mistral">Mistral</VSCodeOption>
|
||||
<VSCodeOption value="vertex">GCP Vertex AI</VSCodeOption>
|
||||
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
|
||||
|
|
@ -310,6 +313,64 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "qwen" && (
|
||||
<div>
|
||||
<DropdownContainer className="dropdown-container">
|
||||
<label htmlFor="qwen-line-provider">
|
||||
<span style={{ fontWeight: 500, marginTop: 5 }}>Alibaba API Line</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="qwen-line-provider"
|
||||
value={apiConfiguration?.qwenApiLine || ""}
|
||||
onChange={handleInputChange("qwenApiLine")}
|
||||
style={{
|
||||
minWidth: 130,
|
||||
position: "relative",
|
||||
}}>
|
||||
<VSCodeOption value="https://dashscope.aliyuncs.com/compatible-mode/v1">China API</VSCodeOption>
|
||||
<VSCodeOption value="https://dashscope-intl.aliyuncs.com/compatible-mode/v1">
|
||||
International API
|
||||
</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Please select the appropriate API interface based on your location. If you are in China, choose the China
|
||||
API interface. Otherwise, choose the International API interface.
|
||||
</p>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.qwenApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("qwenApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>Qwen API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from this extension.
|
||||
{!apiConfiguration?.qwenApiKey && (
|
||||
<VSCodeLink
|
||||
href="https://bailian.console.aliyun.com/"
|
||||
style={{
|
||||
display: "inline",
|
||||
fontSize: "inherit",
|
||||
}}>
|
||||
You can get a Qwen API key by signing up here.
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "mistral" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
|
|
@ -861,6 +922,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
{selectedProvider === "gemini" && createDropdown(geminiModels)}
|
||||
{selectedProvider === "openai-native" && createDropdown(openAiNativeModels)}
|
||||
{selectedProvider === "deepseek" && createDropdown(deepSeekModels)}
|
||||
{selectedProvider === "qwen" && createDropdown(qwenModels)}
|
||||
{selectedProvider === "mistral" && createDropdown(mistralModels)}
|
||||
</DropdownContainer>
|
||||
|
||||
|
|
@ -1068,6 +1130,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
|
|||
return getProviderData(openAiNativeModels, openAiNativeDefaultModelId)
|
||||
case "deepseek":
|
||||
return getProviderData(deepSeekModels, deepSeekDefaultModelId)
|
||||
case "qwen":
|
||||
return getProviderData(qwenModels, qwenDefaultModelId)
|
||||
case "mistral":
|
||||
return getProviderData(mistralModels, mistralDefaultModelId)
|
||||
case "openrouter":
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
config.geminiApiKey,
|
||||
config.openAiNativeApiKey,
|
||||
config.deepSeekApiKey,
|
||||
config.qwenApiKey,
|
||||
config.mistralApiKey,
|
||||
config.vsCodeLmModelSelector,
|
||||
].some((key) => key !== undefined)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
|
|||
return "You must provide a valid API key or choose a different provider."
|
||||
}
|
||||
break
|
||||
case "qwen":
|
||||
if (!apiConfiguration.qwenApiKey) {
|
||||
return "You must provide a valid API key or choose a different provider."
|
||||
}
|
||||
break
|
||||
case "mistral":
|
||||
if (!apiConfiguration.mistralApiKey) {
|
||||
return "You must provide a valid API key or choose a different provider."
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue