mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add qwen-code provider support with complete translations
- Add QwenCode provider type definition and models in packages/types/src/providers/qwen-code.ts - Implement OAuth-based provider in src/api/providers/qwen-code.ts with proper error handling - Add comprehensive test coverage in src/api/providers/__tests__/qwen-code.spec.ts - Create QwenCode settings UI component in webview-ui/src/components/settings/providers/QwenCode.tsx - Add qwenCodeOAuthPath field to provider settings schema - Add qwenCode error translations to all backend common.json files (17 languages) - Add qwenCode provider settings to all frontend settings.json files (17 languages) - Include OAuth path configuration and setup instructions for all locales Supported languages: ca, de, es, fr, hi, id, it, ja, ko, nl, pl, pt-BR, ru, tr, vi, zh-CN, zh-TW
This commit is contained in:
parent
fc70012f54
commit
7f52b77fb7
50 changed files with 666 additions and 5 deletions
|
|
@ -50,6 +50,7 @@ export const providerNames = [
|
|||
"doubao",
|
||||
"unbound",
|
||||
"requesty",
|
||||
"qwen-code",
|
||||
"human-relay",
|
||||
"fake-ai",
|
||||
"xai",
|
||||
|
|
@ -311,6 +312,10 @@ const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({
|
|||
ioIntelligenceApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
const qwenCodeSchema = apiModelIdProviderModelSchema.extend({
|
||||
qwenCodeOAuthPath: z.string().optional(),
|
||||
})
|
||||
|
||||
const rooSchema = apiModelIdProviderModelSchema.extend({
|
||||
// No additional fields needed - uses cloud authentication
|
||||
})
|
||||
|
|
@ -352,6 +357,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
|
|||
fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
|
||||
featherlessSchema.merge(z.object({ apiProvider: z.literal("featherless") })),
|
||||
ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })),
|
||||
qwenCodeSchema.merge(z.object({ apiProvider: z.literal("qwen-code") })),
|
||||
rooSchema.merge(z.object({ apiProvider: z.literal("roo") })),
|
||||
defaultSchema,
|
||||
])
|
||||
|
|
@ -390,6 +396,7 @@ export const providerSettingsSchema = z.object({
|
|||
...fireworksSchema.shape,
|
||||
...featherlessSchema.shape,
|
||||
...ioIntelligenceSchema.shape,
|
||||
...qwenCodeSchema.shape,
|
||||
...rooSchema.shape,
|
||||
...codebaseIndexProviderSchema.shape,
|
||||
})
|
||||
|
|
@ -440,7 +447,7 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str
|
|||
}
|
||||
|
||||
export const MODELS_BY_PROVIDER: Record<
|
||||
Exclude<ProviderName, "fake-ai" | "human-relay" | "gemini-cli" | "lmstudio" | "openai" | "ollama">,
|
||||
Exclude<ProviderName, "fake-ai" | "human-relay" | "gemini-cli" | "lmstudio" | "openai" | "ollama" | "qwen-code">,
|
||||
{ id: ProviderName; label: string; models: string[] }
|
||||
> = {
|
||||
anthropic: {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export * from "./moonshot.js"
|
|||
export * from "./ollama.js"
|
||||
export * from "./openai.js"
|
||||
export * from "./openrouter.js"
|
||||
export * from "./qwen-code.js"
|
||||
export * from "./requesty.js"
|
||||
export * from "./roo.js"
|
||||
export * from "./sambanova.js"
|
||||
|
|
|
|||
44
packages/types/src/providers/qwen-code.ts
Normal file
44
packages/types/src/providers/qwen-code.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import type { ModelInfo } from "../model.js"
|
||||
import type { ProviderName } from "../provider-settings.js"
|
||||
|
||||
export const qwenCodeModels = {
|
||||
"qwen3-coder-plus": {
|
||||
id: "qwen3-coder-plus",
|
||||
name: "Qwen3 Coder Plus",
|
||||
provider: "qwen-code" as ProviderName,
|
||||
contextWindow: 1000000,
|
||||
maxTokens: 65536,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
"qwen3-coder-flash": {
|
||||
id: "qwen3-coder-flash",
|
||||
name: "Qwen3 Coder Flash",
|
||||
provider: "qwen-code" as ProviderName,
|
||||
contextWindow: 1000000,
|
||||
maxTokens: 65536,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
} as const
|
||||
|
||||
export type QwenCodeModelId = keyof typeof qwenCodeModels
|
||||
|
||||
export const qwenCodeDefaultModelId: QwenCodeModelId = "qwen3-coder-plus"
|
||||
|
||||
export const isQwenCodeModel = (modelId: string): modelId is QwenCodeModelId => {
|
||||
return modelId in qwenCodeModels
|
||||
}
|
||||
|
||||
export const getQwenCodeModelInfo = (modelId: string): ModelInfo => {
|
||||
if (isQwenCodeModel(modelId)) {
|
||||
return qwenCodeModels[modelId]
|
||||
}
|
||||
// Fallback to a default or throw an error
|
||||
return qwenCodeModels[qwenCodeDefaultModelId]
|
||||
}
|
||||
|
||||
export type QwenCodeProvider = {
|
||||
id: "qwen-code"
|
||||
apiKey?: string
|
||||
baseUrl?: string
|
||||
model: QwenCodeModelId
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import {
|
|||
OpenAiHandler,
|
||||
LmStudioHandler,
|
||||
GeminiHandler,
|
||||
QwenCodeHandler,
|
||||
OpenAiNativeHandler,
|
||||
DeepSeekHandler,
|
||||
MoonshotHandler,
|
||||
|
|
@ -148,6 +149,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
|
|||
return new RooHandler(options)
|
||||
case "featherless":
|
||||
return new FeatherlessHandler(options)
|
||||
case "qwen-code":
|
||||
return new QwenCodeHandler(options)
|
||||
default:
|
||||
apiProvider satisfies "gemini-cli" | undefined
|
||||
return new AnthropicHandler(options)
|
||||
|
|
|
|||
54
src/api/providers/__tests__/qwen-code.spec.ts
Normal file
54
src/api/providers/__tests__/qwen-code.spec.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { describe, it, expect, vi } from "vitest"
|
||||
import { QwenCodeHandler } from "../qwen-code"
|
||||
import { ApiHandlerOptions } from "../../../shared/api"
|
||||
|
||||
// Mock fs
|
||||
vi.mock("fs", () => ({
|
||||
existsSync: vi.fn(),
|
||||
readFileSync: vi.fn(),
|
||||
}))
|
||||
|
||||
// Mock os
|
||||
vi.mock("os", () => ({
|
||||
homedir: () => "/home/user",
|
||||
}))
|
||||
|
||||
// Mock path
|
||||
vi.mock("path", () => ({
|
||||
resolve: vi.fn((...args) => args.join("/")),
|
||||
join: vi.fn((...args) => args.join("/")),
|
||||
}))
|
||||
|
||||
describe("QwenCodeHandler", () => {
|
||||
it("should initialize with correct model configuration", () => {
|
||||
const options: ApiHandlerOptions = {
|
||||
apiModelId: "qwen3-coder-plus",
|
||||
}
|
||||
const handler = new QwenCodeHandler(options)
|
||||
|
||||
const model = handler.getModel()
|
||||
expect(model.id).toBe("qwen3-coder-plus")
|
||||
expect(model.info).toBeDefined()
|
||||
expect(model.info?.supportsPromptCache).toBe(false)
|
||||
})
|
||||
|
||||
it("should use default model when none specified", () => {
|
||||
const options: ApiHandlerOptions = {}
|
||||
const handler = new QwenCodeHandler(options)
|
||||
|
||||
const model = handler.getModel()
|
||||
expect(model.id).toBe("qwen3-coder-plus") // default model
|
||||
expect(model.info).toBeDefined()
|
||||
})
|
||||
|
||||
it("should use custom oauth path when provided", () => {
|
||||
const customPath = "/custom/path/oauth.json"
|
||||
const options: ApiHandlerOptions = {
|
||||
qwenCodeOAuthPath: customPath,
|
||||
}
|
||||
const handler = new QwenCodeHandler(options)
|
||||
|
||||
// Handler should initialize without throwing
|
||||
expect(handler).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
|
@ -21,6 +21,7 @@ export { OllamaHandler } from "./ollama"
|
|||
export { OpenAiNativeHandler } from "./openai-native"
|
||||
export { OpenAiHandler } from "./openai"
|
||||
export { OpenRouterHandler } from "./openrouter"
|
||||
export { QwenCodeHandler } from "./qwen-code"
|
||||
export { RequestyHandler } from "./requesty"
|
||||
export { SambaNovaHandler } from "./sambanova"
|
||||
export { UnboundHandler } from "./unbound"
|
||||
|
|
|
|||
289
src/api/providers/qwen-code.ts
Normal file
289
src/api/providers/qwen-code.ts
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { promises as fs } from "node:fs"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import { XmlMatcher } from "../../utils/xml-matcher"
|
||||
import type { ModelInfo, QwenCodeModelId } from "@roo-code/types"
|
||||
import { qwenCodeDefaultModelId, qwenCodeModels } from "@roo-code/types"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { t } from "../../i18n"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import type { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
import { getModelParams } from "../transform/model-params"
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { BaseProvider } from "./base-provider"
|
||||
import { DEFAULT_HEADERS } from "./constants"
|
||||
|
||||
// --- Constants from qwenOAuth2.js ---
|
||||
|
||||
const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai"
|
||||
const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/token`
|
||||
const QWEN_OAUTH_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56"
|
||||
const QWEN_DIR = ".qwen"
|
||||
const QWEN_CREDENTIAL_FILENAME = "oauth_creds.json"
|
||||
|
||||
interface QwenOAuthCredentials {
|
||||
access_token: string
|
||||
refresh_token: string
|
||||
token_type: string
|
||||
expiry_date: number
|
||||
resource_url?: string
|
||||
}
|
||||
|
||||
function getQwenCachedCredentialPath(): string {
|
||||
return path.join(os.homedir(), QWEN_DIR, QWEN_CREDENTIAL_FILENAME)
|
||||
}
|
||||
|
||||
function objectToUrlEncoded(data: Record<string, string>): string {
|
||||
return Object.keys(data)
|
||||
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
|
||||
.join("&")
|
||||
}
|
||||
|
||||
export class QwenCodeHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
protected options: ApiHandlerOptions
|
||||
private credentials: QwenOAuthCredentials | null = null
|
||||
private client: OpenAI
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
super()
|
||||
this.options = options
|
||||
// Create the client instance once in the constructor.
|
||||
// The API key will be updated dynamically via ensureAuthenticated.
|
||||
this.client = new OpenAI({
|
||||
apiKey: "dummy-key-will-be-replaced",
|
||||
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", // A default base URL
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
})
|
||||
}
|
||||
|
||||
private async loadCachedQwenCredentials(): Promise<QwenOAuthCredentials> {
|
||||
try {
|
||||
const keyFile = getQwenCachedCredentialPath()
|
||||
const credsStr = await fs.readFile(keyFile, "utf-8")
|
||||
return JSON.parse(credsStr)
|
||||
} catch (error) {
|
||||
console.error(`Error reading or parsing credentials file at ${getQwenCachedCredentialPath()}`)
|
||||
throw new Error(t("common:errors.qwenCode.oauthLoadFailed", { error }))
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshAccessToken(credentials: QwenOAuthCredentials): Promise<QwenOAuthCredentials> {
|
||||
if (!credentials.refresh_token) {
|
||||
throw new Error("No refresh token available in credentials.")
|
||||
}
|
||||
|
||||
const bodyData = {
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: credentials.refresh_token,
|
||||
client_id: QWEN_OAUTH_CLIENT_ID,
|
||||
}
|
||||
|
||||
const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: objectToUrlEncoded(bodyData),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}. Response: ${errorText}`)
|
||||
}
|
||||
|
||||
const tokenData = await response.json()
|
||||
|
||||
if (tokenData.error) {
|
||||
throw new Error(`Token refresh failed: ${tokenData.error} - ${tokenData.error_description}`)
|
||||
}
|
||||
|
||||
const newCredentials = {
|
||||
...credentials,
|
||||
access_token: tokenData.access_token,
|
||||
token_type: tokenData.token_type,
|
||||
refresh_token: tokenData.refresh_token || credentials.refresh_token,
|
||||
expiry_date: Date.now() + tokenData.expires_in * 1000,
|
||||
}
|
||||
|
||||
const filePath = getQwenCachedCredentialPath()
|
||||
await fs.writeFile(filePath, JSON.stringify(newCredentials, null, 2))
|
||||
|
||||
return newCredentials
|
||||
}
|
||||
|
||||
private isTokenValid(credentials: QwenOAuthCredentials): boolean {
|
||||
const TOKEN_REFRESH_BUFFER_MS = 30 * 1000 // 30s buffer
|
||||
if (!credentials.expiry_date) {
|
||||
return false
|
||||
}
|
||||
return Date.now() < credentials.expiry_date - TOKEN_REFRESH_BUFFER_MS
|
||||
}
|
||||
|
||||
private async ensureAuthenticated(): Promise<void> {
|
||||
if (!this.credentials) {
|
||||
this.credentials = await this.loadCachedQwenCredentials()
|
||||
}
|
||||
|
||||
if (!this.isTokenValid(this.credentials)) {
|
||||
this.credentials = await this.refreshAccessToken(this.credentials)
|
||||
}
|
||||
|
||||
// After authentication, just update the apiKey and baseURL on the existing client.
|
||||
this.client.apiKey = this.credentials.access_token
|
||||
this.client.baseURL = this.getBaseUrl(this.credentials)
|
||||
}
|
||||
|
||||
private getBaseUrl(creds: QwenOAuthCredentials): string {
|
||||
let baseUrl = creds.resource_url || "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
|
||||
baseUrl = `https://${baseUrl}`
|
||||
}
|
||||
return baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`
|
||||
}
|
||||
|
||||
private async callApiWithRetry<T>(apiCall: () => Promise<T>): Promise<T> {
|
||||
try {
|
||||
return await apiCall()
|
||||
} catch (error: any) {
|
||||
if (error.status === 401) {
|
||||
this.credentials = await this.refreshAccessToken(this.credentials!)
|
||||
// Just update the key, don't re-create the client
|
||||
this.client.apiKey = this.credentials.access_token
|
||||
this.client.baseURL = this.getBaseUrl(this.credentials)
|
||||
return await apiCall()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
await this.ensureAuthenticated()
|
||||
|
||||
const { id: modelId, info: modelInfo } = this.getModel()
|
||||
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
|
||||
role: "system",
|
||||
content: systemPrompt,
|
||||
}
|
||||
|
||||
const convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
|
||||
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model: modelId,
|
||||
temperature: this.options.modelTemperature ?? 0,
|
||||
messages: convertedMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
}
|
||||
|
||||
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
|
||||
|
||||
const stream = await this.callApiWithRetry(() => this.client!.chat.completions.create(requestOptions))
|
||||
|
||||
const matcher = new XmlMatcher(
|
||||
"think",
|
||||
(chunk) =>
|
||||
({
|
||||
type: chunk.matched ? "reasoning" : "text",
|
||||
text: chunk.data,
|
||||
}) as const,
|
||||
)
|
||||
|
||||
let lastUsage: any
|
||||
let fullContent = ""
|
||||
|
||||
for await (const apiChunk of stream) {
|
||||
const delta = apiChunk.choices[0]?.delta ?? {}
|
||||
|
||||
if (delta.content) {
|
||||
let newText = delta.content
|
||||
if (newText.startsWith(fullContent)) {
|
||||
newText = newText.substring(fullContent.length)
|
||||
}
|
||||
fullContent = delta.content
|
||||
|
||||
if (newText) {
|
||||
for (const processedChunk of matcher.update(newText)) {
|
||||
yield processedChunk
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ("reasoning_content" in delta && delta.reasoning_content) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
text: (delta.reasoning_content as string | undefined) || "",
|
||||
}
|
||||
}
|
||||
if (apiChunk.usage) {
|
||||
lastUsage = apiChunk.usage
|
||||
}
|
||||
}
|
||||
|
||||
for (const chunk of matcher.final()) {
|
||||
yield chunk
|
||||
}
|
||||
|
||||
if (lastUsage) {
|
||||
yield this.processUsageMetrics(lastUsage, modelInfo)
|
||||
}
|
||||
}
|
||||
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
await this.ensureAuthenticated()
|
||||
|
||||
const { id: modelId, info: modelInfo } = this.getModel()
|
||||
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
|
||||
model: modelId,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
}
|
||||
|
||||
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
|
||||
|
||||
const response = await this.callApiWithRetry(() => this.client!.chat.completions.create(requestOptions))
|
||||
|
||||
return response.choices[0]?.message.content || ""
|
||||
}
|
||||
|
||||
override getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
const id = modelId && modelId in qwenCodeModels ? (modelId as QwenCodeModelId) : qwenCodeDefaultModelId
|
||||
const info: ModelInfo = qwenCodeModels[id]
|
||||
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
|
||||
|
||||
return { id, info, ...params }
|
||||
}
|
||||
|
||||
protected processUsageMetrics(usage: any, _modelInfo?: ModelInfo): ApiStreamUsageChunk {
|
||||
return {
|
||||
type: "usage",
|
||||
inputTokens: usage?.prompt_tokens || 0,
|
||||
outputTokens: usage?.completion_tokens || 0,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds max_completion_tokens to the request body if needed based on provider configuration
|
||||
*/
|
||||
private addMaxTokensIfNeeded(
|
||||
requestOptions:
|
||||
| OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
||||
| OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming,
|
||||
modelInfo: ModelInfo,
|
||||
): void {
|
||||
if (this.options.includeMaxTokens === true) {
|
||||
requestOptions.max_completion_tokens = this.options.modelMaxTokens || modelInfo.maxTokens
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/i18n/locales/ca/common.json
generated
3
src/i18n/locales/ca/common.json
generated
|
|
@ -107,6 +107,9 @@
|
|||
"roo": {
|
||||
"authenticationRequired": "El proveïdor Roo requereix autenticació al núvol. Si us plau, inicieu sessió a Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Error en carregar les credencials OAuth. Si us plau, autentica't primer: {{error}}"
|
||||
},
|
||||
"mode_import_failed": "Ha fallat la importació del mode: {{error}}"
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/de/common.json
generated
3
src/i18n/locales/de/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo-Anbieter erfordert Cloud-Authentifizierung. Bitte melde dich bei Roo Code Cloud an."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Fehler beim Laden der OAuth-Anmeldedaten. Bitte authentifiziere dich zuerst: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo provider requires cloud authentication. Please sign in to Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Failed to load OAuth credentials. Please authenticate first: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/es/common.json
generated
3
src/i18n/locales/es/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "El proveedor Roo requiere autenticación en la nube. Por favor, inicia sesión en Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Error al cargar las credenciales OAuth. Por favor, autentícate primero: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/fr/common.json
generated
3
src/i18n/locales/fr/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Le fournisseur Roo nécessite une authentification cloud. Veuillez vous connecter à Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Échec du chargement des identifiants OAuth. Veuillez d'abord vous authentifier : {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/hi/common.json
generated
3
src/i18n/locales/hi/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo प्रदाता को क्लाउड प्रमाणीकरण की आवश्यकता है। कृपया Roo Code Cloud में साइन इन करें।"
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "OAuth क्रेडेंशियल्स लोड करने में विफल। कृपया पहले प्रमाणीकरण करें: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/id/common.json
generated
3
src/i18n/locales/id/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Penyedia Roo memerlukan autentikasi cloud. Silakan masuk ke Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Gagal memuat kredensial OAuth. Silakan autentikasi terlebih dahulu: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/it/common.json
generated
3
src/i18n/locales/it/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Il provider Roo richiede l'autenticazione cloud. Accedi a Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Impossibile caricare le credenziali OAuth. Autenticati prima: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/ja/common.json
generated
3
src/i18n/locales/ja/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Rooプロバイダーはクラウド認証が必要です。Roo Code Cloudにサインインしてください。"
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "OAuth認証情報の読み込みに失敗しました。最初に認証してください: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/ko/common.json
generated
3
src/i18n/locales/ko/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo 제공업체는 클라우드 인증이 필요합니다. Roo Code Cloud에 로그인하세요."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "OAuth 자격 증명을 로드하지 못했습니다. 먼저 인증하세요: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/nl/common.json
generated
3
src/i18n/locales/nl/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo provider vereist cloud authenticatie. Log in bij Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Fout bij laden van OAuth-inloggegevens. Authenticeer eerst: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/pl/common.json
generated
3
src/i18n/locales/pl/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Dostawca Roo wymaga uwierzytelnienia w chmurze. Zaloguj się do Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Nie udało się załadować poświadczeń OAuth. Najpierw się uwierzytelnij: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/pt-BR/common.json
generated
3
src/i18n/locales/pt-BR/common.json
generated
|
|
@ -107,6 +107,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "O provedor Roo requer autenticação na nuvem. Faça login no Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Falha ao carregar credenciais OAuth. Por favor, autentique primeiro: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/ru/common.json
generated
3
src/i18n/locales/ru/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Провайдер Roo требует облачной аутентификации. Войдите в Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Не удалось загрузить учетные данные OAuth. Сначала пройдите аутентификацию: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/tr/common.json
generated
3
src/i18n/locales/tr/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo sağlayıcısı bulut kimlik doğrulaması gerektirir. Lütfen Roo Code Cloud'a giriş yapın."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "OAuth kimlik bilgileri yüklenemedi. Lütfen önce kimlik doğrulaması yapın: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/vi/common.json
generated
3
src/i18n/locales/vi/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Nhà cung cấp Roo yêu cầu xác thực đám mây. Vui lòng đăng nhập vào Roo Code Cloud."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "Không thể tải thông tin xác thực OAuth. Vui lòng xác thực trước: {{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/zh-CN/common.json
generated
3
src/i18n/locales/zh-CN/common.json
generated
|
|
@ -108,6 +108,9 @@
|
|||
},
|
||||
"roo": {
|
||||
"authenticationRequired": "Roo 提供商需要云认证。请登录 Roo Code Cloud。"
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "加载 OAuth 凭据失败。请先进行身份验证:{{error}}"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
3
src/i18n/locales/zh-TW/common.json
generated
3
src/i18n/locales/zh-TW/common.json
generated
|
|
@ -103,6 +103,9 @@
|
|||
"roo": {
|
||||
"authenticationRequired": "Roo 提供者需要雲端認證。請登入 Roo Code Cloud。"
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthLoadFailed": "載入 OAuth 憑證失敗。請先進行驗證:{{error}}"
|
||||
},
|
||||
"mode_import_failed": "匯入模式失敗:{{error}}"
|
||||
},
|
||||
"warnings": {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ export function checkExistKey(config: ProviderSettings | undefined) {
|
|||
return false
|
||||
}
|
||||
|
||||
// Special case for human-relay, fake-ai, claude-code, and roo providers which don't need any configuration.
|
||||
if (config.apiProvider && ["human-relay", "fake-ai", "claude-code", "roo"].includes(config.apiProvider)) {
|
||||
// Special case for human-relay, fake-ai, claude-code, roo, and qwen-code providers which don't need any configuration.
|
||||
if (
|
||||
config.apiProvider &&
|
||||
["human-relay", "fake-ai", "claude-code", "roo", "qwen-code"].includes(config.apiProvider)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
fireworksDefaultModelId,
|
||||
featherlessDefaultModelId,
|
||||
ioIntelligenceDefaultModelId,
|
||||
qwenCodeDefaultModelId,
|
||||
rooDefaultModelId,
|
||||
} from "@roo-code/types"
|
||||
|
||||
|
|
@ -80,6 +81,7 @@ import {
|
|||
OpenAI,
|
||||
OpenAICompatible,
|
||||
OpenRouter,
|
||||
QwenCode,
|
||||
Requesty,
|
||||
SambaNova,
|
||||
Unbound,
|
||||
|
|
@ -331,6 +333,7 @@ const ApiOptions = ({
|
|||
fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
|
||||
featherless: { field: "apiModelId", default: featherlessDefaultModelId },
|
||||
"io-intelligence": { field: "ioIntelligenceModelId", default: ioIntelligenceDefaultModelId },
|
||||
"qwen-code": { field: "apiModelId", default: qwenCodeDefaultModelId },
|
||||
roo: { field: "apiModelId", default: rooDefaultModelId },
|
||||
openai: { field: "openAiModelId" },
|
||||
ollama: { field: "ollamaModelId" },
|
||||
|
|
@ -589,6 +592,10 @@ const ApiOptions = ({
|
|||
<Fireworks apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
||||
)}
|
||||
|
||||
{selectedProvider === "qwen-code" && (
|
||||
<QwenCode apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
||||
)}
|
||||
|
||||
{selectedProvider === "roo" && (
|
||||
<div className="flex flex-col gap-3">
|
||||
{cloudIsAuthenticated ? (
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
geminiModels,
|
||||
mistralModels,
|
||||
openAiNativeModels,
|
||||
qwenCodeModels,
|
||||
vertexModels,
|
||||
xaiModels,
|
||||
groqModels,
|
||||
|
|
@ -33,6 +34,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
|
|||
gemini: geminiModels,
|
||||
mistral: mistralModels,
|
||||
"openai-native": openAiNativeModels,
|
||||
"qwen-code": qwenCodeModels,
|
||||
vertex: vertexModels,
|
||||
xai: xaiModels,
|
||||
groq: groqModels,
|
||||
|
|
@ -75,5 +77,6 @@ export const PROVIDERS = [
|
|||
{ value: "fireworks", label: "Fireworks AI" },
|
||||
{ value: "featherless", label: "Featherless AI" },
|
||||
{ value: "io-intelligence", label: "IO Intelligence" },
|
||||
{ value: "qwen-code", label: "Qwen Code" },
|
||||
{ value: "roo", label: "Roo Code Cloud" },
|
||||
].sort((a, b) => a.label.localeCompare(b.label))
|
||||
|
|
|
|||
58
webview-ui/src/components/settings/providers/QwenCode.tsx
Normal file
58
webview-ui/src/components/settings/providers/QwenCode.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { useCallback } from "react"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import type { ProviderSettings } from "@roo-code/types"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import { inputEventTransform } from "../transforms"
|
||||
|
||||
type QwenCodeProps = {
|
||||
apiConfiguration: ProviderSettings
|
||||
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
|
||||
}
|
||||
|
||||
export const QwenCode = ({ apiConfiguration, setApiConfigurationField }: QwenCodeProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
<K extends keyof ProviderSettings, E>(
|
||||
field: K,
|
||||
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
|
||||
) =>
|
||||
(event: E | Event) => {
|
||||
setApiConfigurationField(field, transform(event as E))
|
||||
},
|
||||
[setApiConfigurationField],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.qwenCodeOAuthPath || ""}
|
||||
onInput={handleInputChange("qwenCodeOAuthPath")}
|
||||
placeholder="~/.qwen/oauth_creds.json"
|
||||
className="w-full">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.qwenCode.oauthPath")}</label>
|
||||
</VSCodeTextField>
|
||||
<div className="text-sm text-vscode-descriptionForeground -mt-2">
|
||||
{t("settings:providers.qwenCode.oauthPathDescription")}
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-vscode-descriptionForeground mt-3">
|
||||
{t("settings:providers.qwenCode.description")}
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-vscode-descriptionForeground mt-2">
|
||||
{t("settings:providers.qwenCode.instructions")}
|
||||
</div>
|
||||
|
||||
<VSCodeLink
|
||||
href="https://github.com/QwenLM/qwen-code/blob/main/README.md"
|
||||
className="text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground mt-2 inline-block">
|
||||
{t("settings:providers.qwenCode.setupLink")}
|
||||
</VSCodeLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ export { Ollama } from "./Ollama"
|
|||
export { OpenAI } from "./OpenAI"
|
||||
export { OpenAICompatible } from "./OpenAICompatible"
|
||||
export { OpenRouter } from "./OpenRouter"
|
||||
export { QwenCode } from "./QwenCode"
|
||||
export { Requesty } from "./Requesty"
|
||||
export { SambaNova } from "./SambaNova"
|
||||
export { Unbound } from "./Unbound"
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ import {
|
|||
featherlessDefaultModelId,
|
||||
ioIntelligenceDefaultModelId,
|
||||
ioIntelligenceModels,
|
||||
qwenCodeDefaultModelId,
|
||||
qwenCodeModels,
|
||||
rooDefaultModelId,
|
||||
rooModels,
|
||||
BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
|
||||
|
|
@ -305,6 +307,11 @@ function getSelectedModel({
|
|||
routerModels["io-intelligence"]?.[id] ?? ioIntelligenceModels[id as keyof typeof ioIntelligenceModels]
|
||||
return { id, info }
|
||||
}
|
||||
case "qwen-code": {
|
||||
const id = apiConfiguration.apiModelId ?? qwenCodeDefaultModelId
|
||||
const info = qwenCodeModels[id as keyof typeof qwenCodeModels]
|
||||
return { id, info }
|
||||
}
|
||||
case "roo": {
|
||||
const id = apiConfiguration.apiModelId ?? rooDefaultModelId
|
||||
const info = rooModels[id as keyof typeof rooModels]
|
||||
|
|
@ -314,7 +321,7 @@ function getSelectedModel({
|
|||
// case "human-relay":
|
||||
// case "fake-ai":
|
||||
default: {
|
||||
provider satisfies "anthropic" | "gemini-cli" | "human-relay" | "fake-ai"
|
||||
provider satisfies "anthropic" | "gemini-cli" | "human-relay" | "fake-ai" | "qwen-code"
|
||||
const id = apiConfiguration.apiModelId ?? anthropicDefaultModelId
|
||||
const baseInfo = anthropicModels[id as keyof typeof anthropicModels]
|
||||
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/ca/settings.json
generated
7
webview-ui/src/i18n/locales/ca/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Per defecte: claude",
|
||||
"maxTokensLabel": "Tokens màxims de sortida",
|
||||
"maxTokensDescription": "Nombre màxim de tokens de sortida per a les respostes de Claude Code. El valor per defecte és 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Ruta de credencials OAuth (opcional)",
|
||||
"oauthPathDescription": "Ruta del fitxer de credencials OAuth. Deixa-ho buit per utilitzar la ubicació per defecte (~/.qwen/oauth_creds.json).",
|
||||
"description": "Aquest proveïdor utilitza autenticació OAuth del servei Qwen i no requereix claus API.",
|
||||
"instructions": "Si us plau, seguiu la documentació oficial per obtenir el fitxer d'autorització i col·locar-lo en la ruta especificada.",
|
||||
"setupLink": "Documentació oficial de Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/de/settings.json
generated
7
webview-ui/src/i18n/locales/de/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Standard: claude",
|
||||
"maxTokensLabel": "Maximale Ausgabe-Tokens",
|
||||
"maxTokensDescription": "Maximale Anzahl an Ausgabe-Tokens für Claude Code-Antworten. Standard ist 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth-Anmeldedaten Pfad (optional)",
|
||||
"oauthPathDescription": "Pfad zur OAuth-Anmeldedatei. Leer lassen, um den Standardort zu verwenden (~/.qwen/oauth_creds.json).",
|
||||
"description": "Dieser Anbieter verwendet OAuth-Authentifizierung vom Qwen-Service und benötigt keine API-Schlüssel.",
|
||||
"instructions": "Bitte folge der offiziellen Dokumentation, um die Autorisierungsdatei zu erhalten und sie im angegebenen Pfad zu platzieren.",
|
||||
"setupLink": "Qwen Offizielle Dokumentation"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -462,6 +462,13 @@
|
|||
"placeholder": "Default: claude",
|
||||
"maxTokensLabel": "Max Output Tokens",
|
||||
"maxTokensDescription": "Maximum number of output tokens for Claude Code responses. Default is 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth Credentials Path (optional)",
|
||||
"oauthPathDescription": "Path to the OAuth credentials file. Leave empty to use the default location (~/.qwen/oauth_creds.json).",
|
||||
"description": "This provider uses OAuth authentication from the Qwen service and does not require API keys.",
|
||||
"instructions": "Please follow the official documentation to obtain the authorization file and place it in the specified path.",
|
||||
"setupLink": "Qwen Official Documentation"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/es/settings.json
generated
7
webview-ui/src/i18n/locales/es/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Por defecto: claude",
|
||||
"maxTokensLabel": "Tokens máximos de salida",
|
||||
"maxTokensDescription": "Número máximo de tokens de salida para las respuestas de Claude Code. El valor predeterminado es 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Ruta de credenciales OAuth (opcional)",
|
||||
"oauthPathDescription": "Ruta del archivo de credenciales OAuth. Déjalo vacío para usar la ubicación predeterminada (~/.qwen/oauth_creds.json).",
|
||||
"description": "Este proveedor utiliza autenticación OAuth del servicio Qwen y no requiere claves API.",
|
||||
"instructions": "Por favor, sigue la documentación oficial para obtener el archivo de autorización y colocarlo en la ruta especificada.",
|
||||
"setupLink": "Documentación oficial de Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/fr/settings.json
generated
7
webview-ui/src/i18n/locales/fr/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Défaut : claude",
|
||||
"maxTokensLabel": "Jetons de sortie max",
|
||||
"maxTokensDescription": "Nombre maximum de jetons de sortie pour les réponses de Claude Code. La valeur par défaut est 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Chemin des identifiants OAuth (optionnel)",
|
||||
"oauthPathDescription": "Chemin du fichier d'identifiants OAuth. Laissez vide pour utiliser l'emplacement par défaut (~/.qwen/oauth_creds.json).",
|
||||
"description": "Ce fournisseur utilise l'authentification OAuth du service Qwen et ne nécessite pas de clés API.",
|
||||
"instructions": "Veuillez suivre la documentation officielle pour obtenir le fichier d'autorisation et le placer dans le chemin spécifié.",
|
||||
"setupLink": "Documentation officielle Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/hi/settings.json
generated
7
webview-ui/src/i18n/locales/hi/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "डिफ़ॉल्ट: claude",
|
||||
"maxTokensLabel": "अधिकतम आउटपुट टोकन",
|
||||
"maxTokensDescription": "Claude Code प्रतिक्रियाओं के लिए आउटपुट टोकन की अधिकतम संख्या। डिफ़ॉल्ट 8000 है।"
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth क्रेडेंशियल्स पथ (वैकल्पिक)",
|
||||
"oauthPathDescription": "OAuth क्रेडेंशियल्स फ़ाइल का पथ। डिफ़ॉल्ट स्थान (~/.qwen/oauth_creds.json) का उपयोग करने के लिए खाली छोड़ें।",
|
||||
"description": "यह प्रदाता Qwen सेवा से OAuth प्रमाणीकरण का उपयोग करता है और API कुंजियों की आवश्यकता नहीं है।",
|
||||
"instructions": "कृपया प्राधिकरण फ़ाइल प्राप्त करने और इसे निर्दिष्ट पथ में रखने के लिए आधिकारिक दस्तावेज़ीकरण का पालन करें।",
|
||||
"setupLink": "Qwen आधिकारिक दस्तावेज़ीकरण"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/id/settings.json
generated
7
webview-ui/src/i18n/locales/id/settings.json
generated
|
|
@ -467,6 +467,13 @@
|
|||
"placeholder": "Default: claude",
|
||||
"maxTokensLabel": "Token Output Maks",
|
||||
"maxTokensDescription": "Jumlah maksimum token output untuk respons Claude Code. Default adalah 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Jalur Kredensial OAuth (opsional)",
|
||||
"oauthPathDescription": "Jalur ke file kredensial OAuth. Biarkan kosong untuk menggunakan lokasi default (~/.qwen/oauth_creds.json).",
|
||||
"description": "Provider ini menggunakan autentikasi OAuth dari layanan Qwen dan tidak memerlukan API key.",
|
||||
"instructions": "Silakan ikuti dokumentasi resmi untuk memperoleh file otorisasi dan menempatkannya di jalur yang ditentukan.",
|
||||
"setupLink": "Dokumentasi Resmi Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/it/settings.json
generated
7
webview-ui/src/i18n/locales/it/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Predefinito: claude",
|
||||
"maxTokensLabel": "Token di output massimi",
|
||||
"maxTokensDescription": "Numero massimo di token di output per le risposte di Claude Code. Il valore predefinito è 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Percorso credenziali OAuth (opzionale)",
|
||||
"oauthPathDescription": "Percorso del file delle credenziali OAuth. Lascia vuoto per utilizzare la posizione predefinita (~/.qwen/oauth_creds.json).",
|
||||
"description": "Questo provider utilizza l'autenticazione OAuth dal servizio Qwen e non richiede chiavi API.",
|
||||
"instructions": "Segui la documentazione ufficiale per ottenere il file di autorizzazione e posizionarlo nel percorso specificato.",
|
||||
"setupLink": "Documentazione ufficiale Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/ja/settings.json
generated
7
webview-ui/src/i18n/locales/ja/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "デフォルト:claude",
|
||||
"maxTokensLabel": "最大出力トークン",
|
||||
"maxTokensDescription": "Claude Codeレスポンスの最大出力トークン数。デフォルトは8000です。"
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth認証情報パス(オプション)",
|
||||
"oauthPathDescription": "OAuth認証情報ファイルのパス。デフォルトの場所(~/.qwen/oauth_creds.json)を使用する場合は空にしてください。",
|
||||
"description": "このプロバイダーはQwenサービスからのOAuth認証を使用し、APIキーは必要ありません。",
|
||||
"instructions": "認証ファイルを取得し、指定されたパスに配置するには、公式ドキュメントに従ってください。",
|
||||
"setupLink": "Qwen公式ドキュメント"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/ko/settings.json
generated
7
webview-ui/src/i18n/locales/ko/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "기본값: claude",
|
||||
"maxTokensLabel": "최대 출력 토큰",
|
||||
"maxTokensDescription": "Claude Code 응답의 최대 출력 토큰 수. 기본값은 8000입니다."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth 자격 증명 경로 (선택사항)",
|
||||
"oauthPathDescription": "OAuth 자격 증명 파일의 경로입니다. 기본 위치(~/.qwen/oauth_creds.json)를 사용하려면 비워두세요.",
|
||||
"description": "이 공급자는 Qwen 서비스의 OAuth 인증을 사용하며 API 키가 필요하지 않습니다.",
|
||||
"instructions": "인증 파일을 획득하고 지정된 경로에 배치하려면 공식 문서를 따르세요.",
|
||||
"setupLink": "Qwen 공식 문서"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/nl/settings.json
generated
7
webview-ui/src/i18n/locales/nl/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Standaard: claude",
|
||||
"maxTokensLabel": "Max Output Tokens",
|
||||
"maxTokensDescription": "Maximaal aantal output-tokens voor Claude Code-reacties. Standaard is 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth-inloggegevens pad (optioneel)",
|
||||
"oauthPathDescription": "Pad naar het OAuth-inloggegevens bestand. Laat leeg om de standaardlocatie (~/.qwen/oauth_creds.json) te gebruiken.",
|
||||
"description": "Deze provider gebruikt OAuth-authenticatie van de Qwen-service en vereist geen API-sleutels.",
|
||||
"instructions": "Volg de officiële documentatie om het autorisatiebestand te verkrijgen en het in het opgegeven pad te plaatsen.",
|
||||
"setupLink": "Qwen officiële documentatie"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/pl/settings.json
generated
7
webview-ui/src/i18n/locales/pl/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Domyślnie: claude",
|
||||
"maxTokensLabel": "Maksymalna liczba tokenów wyjściowych",
|
||||
"maxTokensDescription": "Maksymalna liczba tokenów wyjściowych dla odpowiedzi Claude Code. Domyślnie 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Ścieżka poświadczeń OAuth (opcjonalnie)",
|
||||
"oauthPathDescription": "Ścieżka do pliku poświadczeń OAuth. Pozostaw puste, aby użyć domyślnej lokalizacji (~/.qwen/oauth_creds.json).",
|
||||
"description": "Ten dostawca używa uwierzytelniania OAuth z usługi Qwen i nie wymaga kluczy API.",
|
||||
"instructions": "Postępuj zgodnie z oficjalną dokumentacją, aby uzyskać plik autoryzacji i umieścić go w określonej ścieżce.",
|
||||
"setupLink": "Oficjalna dokumentacja Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
7
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Padrão: claude",
|
||||
"maxTokensLabel": "Tokens de saída máximos",
|
||||
"maxTokensDescription": "Número máximo de tokens de saída para respostas do Claude Code. O padrão é 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Caminho das credenciais OAuth (opcional)",
|
||||
"oauthPathDescription": "Caminho para o arquivo de credenciais OAuth. Deixe vazio para usar a localização padrão (~/.qwen/oauth_creds.json).",
|
||||
"description": "Este provedor usa autenticação OAuth do serviço Qwen e não requer chaves de API.",
|
||||
"instructions": "Siga a documentação oficial para obter o arquivo de autorização e colocá-lo no caminho especificado.",
|
||||
"setupLink": "Documentação oficial do Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/ru/settings.json
generated
7
webview-ui/src/i18n/locales/ru/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "По умолчанию: claude",
|
||||
"maxTokensLabel": "Макс. выходных токенов",
|
||||
"maxTokensDescription": "Максимальное количество выходных токенов для ответов Claude Code. По умолчанию 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Путь OAuth-данных (необязательно)",
|
||||
"oauthPathDescription": "Путь к файлу OAuth-данных. Оставьте пустым для использования местоположения по умолчанию (~/.qwen/oauth_creds.json).",
|
||||
"description": "Этот провайдер использует OAuth-аутентификацию из сервиса Qwen и не требует API-ключей.",
|
||||
"instructions": "Следуйте официальной документации для получения файла авторизации и размещения его по указанному пути.",
|
||||
"setupLink": "Официальная документация Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/tr/settings.json
generated
7
webview-ui/src/i18n/locales/tr/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Varsayılan: claude",
|
||||
"maxTokensLabel": "Maksimum Çıktı Token sayısı",
|
||||
"maxTokensDescription": "Claude Code yanıtları için maksimum çıktı token sayısı. Varsayılan 8000'dir."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth kimlik bilgileri yolu (isteğe bağlı)",
|
||||
"oauthPathDescription": "OAuth kimlik bilgileri dosyasının yolu. Varsayılan konumu (~/.qwen/oauth_creds.json) kullanmak için boş bırakın.",
|
||||
"description": "Bu sağlayıcı Qwen hizmetinden OAuth kimlik doğrulaması kullanır ve API anahtarı gerektirmez.",
|
||||
"instructions": "Yetkilendirme dosyasını almak ve belirtilen yola yerleştirmek için resmi belgeleri takip edin.",
|
||||
"setupLink": "Qwen resmi belgeleri"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/vi/settings.json
generated
7
webview-ui/src/i18n/locales/vi/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "Mặc định: claude",
|
||||
"maxTokensLabel": "Số token đầu ra tối đa",
|
||||
"maxTokensDescription": "Số lượng token đầu ra tối đa cho các phản hồi của Claude Code. Mặc định là 8000."
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "Đường dẫn thông tin xác thực OAuth (tùy chọn)",
|
||||
"oauthPathDescription": "Đường dẫn đến tệp thông tin xác thực OAuth. Để trống để sử dụng vị trí mặc định (~/.qwen/oauth_creds.json).",
|
||||
"description": "Nhà cung cấp này sử dụng xác thực OAuth từ dịch vụ Qwen và không yêu cầu khóa API.",
|
||||
"instructions": "Vui lòng theo dõi tài liệu chính thức để lấy tệp ủy quyền và đặt nó trong đường dẫn đã chỉ định.",
|
||||
"setupLink": "Tài liệu chính thức Qwen"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
9
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
|
|
@ -459,10 +459,17 @@
|
|||
"setReasoningLevel": "启用推理工作量",
|
||||
"claudeCode": {
|
||||
"pathLabel": "Claude Code 路径",
|
||||
"description": "您的 Claude Code CLI 的可选路径。如果未设置,则默认为 “claude”。",
|
||||
"description": "您的 Claude Code CLI 的可选路径。如果未设置,则默认为 'claude'。",
|
||||
"placeholder": "默认:claude",
|
||||
"maxTokensLabel": "最大输出 Token",
|
||||
"maxTokensDescription": "Claude Code 响应的最大输出 Token 数量。默认为 8000。"
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth 凭据路径(可选)",
|
||||
"oauthPathDescription": "OAuth 凭据文件的路径。留空以使用默认位置 (~/.qwen/oauth_creds.json)。",
|
||||
"description": "此提供商使用来自 Qwen 服务的 OAuth 身份验证,不需要 API 密钥。",
|
||||
"instructions": "请参照官方文档获取授权文件并将其放置在指定路径。",
|
||||
"setupLink": "Qwen 官方文档"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
7
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
7
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
|
|
@ -463,6 +463,13 @@
|
|||
"placeholder": "預設:claude",
|
||||
"maxTokensLabel": "最大輸出 Token",
|
||||
"maxTokensDescription": "Claude Code 回應的最大輸出 Token 數量。預設為 8000。"
|
||||
},
|
||||
"qwenCode": {
|
||||
"oauthPath": "OAuth 憑證路徑(選用)",
|
||||
"oauthPathDescription": "OAuth 憑證檔案的路徑。留空以使用預設位置 (~/.qwen/oauth_creds.json)。",
|
||||
"description": "此供應商使用來自 Qwen 服務的 OAuth 身分驗證,不需要 API 金鑰。",
|
||||
"instructions": "請遵循官方說明文件取得授權檔案,並將其放置在指定路徑。",
|
||||
"setupLink": "Qwen 官方說明文件"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): stri
|
|||
return i18next.t("settings:validation.apiKey")
|
||||
}
|
||||
break
|
||||
case "qwen-code":
|
||||
// OAuth-based provider, no API key validation needed
|
||||
break
|
||||
case "openai-native":
|
||||
if (!apiConfiguration.openAiNativeApiKey) {
|
||||
return i18next.t("settings:validation.apiKey")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue