From 89458bb70b71564598fab6b3ed8e6e727ad24e9b Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Mon, 11 Aug 2025 10:11:47 +0530 Subject: [PATCH 01/19] WIP: starting Qwen code support --- qwen-cli.ts | Bin 0 -> 80 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 qwen-cli.ts diff --git a/qwen-cli.ts b/qwen-cli.ts new file mode 100644 index 0000000000000000000000000000000000000000..39e8a7d65af025797e4c0c5a98a3802f93b508d6 GIT binary patch literal 80 zcmezWPoF`bL4hHh!IL3?!3xL Date: Mon, 11 Aug 2025 10:14:10 +0530 Subject: [PATCH 02/19] WIP: starting Qwen code support --- src/api/providers/qwen-code.ts | 256 +++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 src/api/providers/qwen-code.ts diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts new file mode 100644 index 0000000000..8b355f1e9f --- /dev/null +++ b/src/api/providers/qwen-code.ts @@ -0,0 +1,256 @@ +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 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 { + 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 | null = null + + constructor(options: ApiHandlerOptions) { + super() + this.options = options + } + + private async loadCachedQwenCredentials(): Promise { + 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 { + 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)) + console.log("Successfully refreshed and cached new credentials.") + + 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 { + if (!this.credentials) { + this.credentials = await this.loadCachedQwenCredentials() + } + + if (!this.isTokenValid(this.credentials)) { + this.credentials = await this.refreshAccessToken(this.credentials) + } + + if (!this.client || this.client.apiKey !== this.credentials.access_token) { + this.setupClient() + } + } + + 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 setupClient(): void { + if (!this.credentials) { + throw new Error("Credentials not loaded.") + } + const headers = { ...DEFAULT_HEADERS } + + this.client = new OpenAI({ + apiKey: this.credentials.access_token, + baseURL: this.getBaseUrl(this.credentials), + defaultHeaders: headers, + }) + } + + private async callApiWithRetry(apiCall: () => Promise): Promise { + try { + return await apiCall() + } catch (error: any) { + if (error.status === 401) { + console.log("Authentication failed. Forcing token refresh and retrying...") + this.credentials = await this.refreshAccessToken(this.credentials!) + this.setupClient() + 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 }, + } + + if (this.options.includeMaxTokens) { + requestOptions.max_tokens = this.options.modelMaxTokens || modelInfo.maxTokens + } + + const stream = await this.callApiWithRetry(() => this.client!.chat.completions.create(requestOptions)) + + let lastUsage: any + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta ?? {} + + if (delta.content) { + yield { + type: "text", + text: delta.content, + } + } + if (chunk.usage) { + lastUsage = chunk.usage + } + } + + if (lastUsage) { + yield this.processUsageMetrics(lastUsage, modelInfo) + } + } + + async completePrompt(prompt: string): Promise { + await this.ensureAuthenticated() + + const { id: modelId, info: modelInfo } = this.getModel() + + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { + model: modelId, + messages: [{ role: "user", content: prompt }], + } + + if (this.options.includeMaxTokens) { + requestOptions.max_tokens = this.options.modelMaxTokens || modelInfo.maxTokens + } + + 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, + } + } +} \ No newline at end of file From d73b1a5320a2a9dee8e49ceacf96ddcd663c1ada Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Mon, 11 Aug 2025 11:33:23 +0530 Subject: [PATCH 03/19] Resolve Conflicts --- packages/types/src/provider-settings.ts | 7 ++ packages/types/src/providers/index.ts | 1 + packages/types/src/providers/qwen-code.ts | 29 +++++ src/api/index.ts | 3 + src/api/providers/index.ts | 1 + src/api/providers/qwen-code.ts | 107 ++++++++++++++++-- src/shared/checkExistApiConfig.ts | 2 +- .../src/components/settings/ApiOptions.tsx | 7 ++ .../src/components/settings/constants.ts | 3 + .../settings/providers/QwenCode.tsx | 58 ++++++++++ .../components/settings/providers/index.ts | 1 + .../components/ui/hooks/useSelectedModel.ts | 7 ++ webview-ui/src/i18n/locales/en/settings.json | 7 ++ webview-ui/src/utils/validate.ts | 3 + 14 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 packages/types/src/providers/qwen-code.ts create mode 100644 webview-ui/src/components/settings/providers/QwenCode.tsx diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index c13319a956..35a0f75375 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -64,6 +64,7 @@ export const providerNames = [ "featherless", "io-intelligence", "roo", + "qwen-code", ] as const export const providerNamesSchema = z.enum(providerNames) @@ -291,6 +292,10 @@ const sambaNovaSchema = apiModelIdProviderModelSchema.extend({ sambaNovaApiKey: z.string().optional(), }) +const qwenCodeSchema = apiModelIdProviderModelSchema.extend({ + qwenCodeOAuthPath: z.string().optional(), +}) + const zaiSchema = apiModelIdProviderModelSchema.extend({ zaiApiKey: z.string().optional(), zaiApiLine: z.union([z.literal("china"), z.literal("international")]).optional(), @@ -346,6 +351,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv litellmSchema.merge(z.object({ apiProvider: z.literal("litellm") })), cerebrasSchema.merge(z.object({ apiProvider: z.literal("cerebras") })), sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })), + qwenCodeSchema.merge(z.object({ apiProvider: z.literal("qwen-code") })), zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })), fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })), featherlessSchema.merge(z.object({ apiProvider: z.literal("featherless") })), @@ -384,6 +390,7 @@ export const providerSettingsSchema = z.object({ ...litellmSchema.shape, ...cerebrasSchema.shape, ...sambaNovaSchema.shape, + ...qwenCodeSchema.shape, ...zaiSchema.shape, ...fireworksSchema.shape, ...featherlessSchema.shape, diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 8ca9c2c9b2..42ec06abff 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -22,6 +22,7 @@ export * from "./openrouter.js" export * from "./requesty.js" export * from "./roo.js" export * from "./sambanova.js" +export * from "./qwen-code.js" export * from "./unbound.js" export * from "./vertex.js" export * from "./vscode-llm.js" diff --git a/packages/types/src/providers/qwen-code.ts b/packages/types/src/providers/qwen-code.ts new file mode 100644 index 0000000000..324b1a86b5 --- /dev/null +++ b/packages/types/src/providers/qwen-code.ts @@ -0,0 +1,29 @@ +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: true, + }, +} + +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] +} diff --git a/src/api/index.ts b/src/api/index.ts index 48a0a89ec5..cfdecf9359 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -34,6 +34,7 @@ import { IOIntelligenceHandler, DoubaoHandler, ZAiHandler, + QwenCodeHandler, FireworksHandler, RooHandler, FeatherlessHandler, @@ -138,6 +139,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { return new SambaNovaHandler(options) case "zai": return new ZAiHandler(options) + case "qwen-code": + return new QwenCodeHandler(options) case "fireworks": return new FireworksHandler(options) case "io-intelligence": diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index d256fbbe55..7f05ebf996 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -28,6 +28,7 @@ export { VertexHandler } from "./vertex" export { VsCodeLmHandler } from "./vscode-lm" export { XAIHandler } from "./xai" export { ZAiHandler } from "./zai" +export { QwenCodeHandler } from "./qwen-code" export { FireworksHandler } from "./fireworks" export { RooHandler } from "./roo" export { FeatherlessHandler } from "./featherless" diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index 8b355f1e9f..e24d2efa58 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -32,7 +32,10 @@ interface QwenOAuthCredentials { resource_url?: string } -function getQwenCachedCredentialPath(): string { +function getQwenCachedCredentialPath(customPath?: string): string { + if (customPath) { + return path.resolve(customPath) + } return path.join(os.homedir(), QWEN_DIR, QWEN_CREDENTIAL_FILENAME) } @@ -46,19 +49,62 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan protected options: ApiHandlerOptions private credentials: QwenOAuthCredentials | null = null private client: OpenAI | null = null + private pendingThinkingContent: string = "" constructor(options: ApiHandlerOptions) { super() this.options = options } + private processContentChunk(content: string): { text: string; reasoning: string } { + // Accumulate content to handle incomplete thinking tags + this.pendingThinkingContent += content + + let processedText = "" + let reasoningText = "" + + // Handle complete thinking blocks + const thinkingRegex = /([\s\S]*?)<\/thinking>/g + let match + let lastIndex = 0 + + while ((match = thinkingRegex.exec(this.pendingThinkingContent)) !== null) { + // Add text before thinking block + processedText += this.pendingThinkingContent.slice(lastIndex, match.index) + // Extract thinking content + reasoningText += match[1] + lastIndex = match.index + match[0].length + } + + // Handle remaining content after last complete thinking block + const remainingContent = this.pendingThinkingContent.slice(lastIndex) + + // Check if we have an incomplete thinking tag + const incompleteThinkingMatch = remainingContent.match(/(?![\s\S]*<\/think>)([\s\S]*)$/) + if (incompleteThinkingMatch) { + // Keep incomplete thinking content for next chunk + this.pendingThinkingContent = remainingContent + } else { + // No incomplete thinking, add to processed text and clear pending + processedText += remainingContent + this.pendingThinkingContent = "" + } + + // Filter out malformed thinking tags like { try { - const keyFile = getQwenCachedCredentialPath() + const keyFile = getQwenCachedCredentialPath(this.options.qwenCodeOAuthPath) const credsStr = await fs.readFile(keyFile, "utf-8") return JSON.parse(credsStr) } catch (error) { - console.error(`Error reading or parsing credentials file at ${getQwenCachedCredentialPath()}`) + console.error( + `Error reading or parsing credentials file at ${getQwenCachedCredentialPath(this.options.qwenCodeOAuthPath)}`, + ) throw new Error(t("common:errors.qwenCode.oauthLoadFailed", { error })) } } @@ -202,17 +248,56 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan for await (const chunk of stream) { const delta = chunk.choices[0]?.delta ?? {} - if (delta.content) { + // Handle reasoning content separately (if present) + if (delta && "reasoning_content" in delta && delta.reasoning_content) { yield { - type: "text", - text: delta.content, + type: "reasoning", + text: delta.reasoning_content as string, } } + + // Handle regular content with thinking processing + if (delta.content) { + const { text, reasoning } = this.processContentChunk(delta.content) + + // Yield reasoning content if any + if (reasoning.trim()) { + yield { + type: "reasoning", + text: reasoning, + } + } + + // Yield regular text content if any + if (text.trim()) { + yield { + type: "text", + text: text, + } + } + } + if (chunk.usage) { lastUsage = chunk.usage } } + // Handle any remaining pending thinking content at the end + if (this.pendingThinkingContent.trim()) { + // If there's incomplete thinking content, treat it as regular text + const cleanedContent = this.pendingThinkingContent.replace( + / this.client!.chat.completions.create(requestOptions)) - return response.choices[0]?.message.content || "" + let content = response.choices[0]?.message.content || "" + + // Clean up any thinking content from non-streaming response + content = content.replace(/[\s\S]*?<\/thinking>/g, "") + content = content.replace(/ )} + {selectedProvider === "qwen-code" && ( + + )} + {selectedProvider === "litellm" && ( void +} + +export const QwenCode = ({ apiConfiguration, setApiConfigurationField }: QwenCodeProps) => { + const { t } = useAppTranslation() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ProviderSettings[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + + + +
+ {t("settings:providers.qwenCode.oauthPathDescription")} +
+ +
+ {t("settings:providers.qwenCode.description")} +
+ +
+ {t("settings:providers.qwenCode.instructions")} +
+ + + {t("settings:providers.qwenCode.setupLink")} + + + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts index eff33e1298..ce029bf86a 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -24,6 +24,7 @@ export { Vertex } from "./Vertex" export { VSCodeLM } from "./VSCodeLM" export { XAI } from "./XAI" export { ZAi } from "./ZAi" +export { QwenCode } from "./QwenCode" export { LiteLLM } from "./LiteLLM" export { Fireworks } from "./Fireworks" export { Featherless } from "./Featherless" diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 75a4a968ad..36ea6c83e8 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -53,6 +53,8 @@ import { rooDefaultModelId, rooModels, BEDROCK_CLAUDE_SONNET_4_MODEL_ID, + qwenCodeModels, + qwenCodeDefaultModelId, } from "@roo-code/types" import type { ModelRecord, RouterModels } from "@roo/api" @@ -310,6 +312,11 @@ function getSelectedModel({ const info = rooModels[id as keyof typeof rooModels] return { id, info } } + case "qwen-code": { + const id = apiConfiguration.apiModelId ?? qwenCodeDefaultModelId + const info = qwenCodeModels[id as keyof typeof qwenCodeModels] + return { id, info } + } // case "anthropic": // case "human-relay": // case "fake-ai": diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index d18a3bbd5e..5099d76afe 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -389,6 +389,13 @@ "learnMore": "Learn more about provider routing" } }, + "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" + }, "customModel": { "capabilities": "Configure the capabilities and pricing for your custom OpenAI-compatible model. Be careful when specifying the model capabilities, as they can affect how Roo Code performs.", "maxTokens": { diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index 348a373059..84b92232b3 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -116,6 +116,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 "fireworks": if (!apiConfiguration.fireworksApiKey) { return i18next.t("settings:validation.apiKey") From 2e4132a61fc032064a78424646581c8b5a6c5aef Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Mon, 11 Aug 2025 12:13:06 +0530 Subject: [PATCH 04/19] Updated Translation --- qwen-cli.ts | Bin 80 -> 0 bytes src/api/providers/qwen-code.ts | 2 +- webview-ui/src/i18n/locales/ca/settings.json | 7 +++++++ webview-ui/src/i18n/locales/de/settings.json | 7 +++++++ webview-ui/src/i18n/locales/es/settings.json | 7 +++++++ webview-ui/src/i18n/locales/fr/settings.json | 7 +++++++ webview-ui/src/i18n/locales/hi/settings.json | 7 +++++++ webview-ui/src/i18n/locales/id/settings.json | 7 +++++++ webview-ui/src/i18n/locales/it/settings.json | 7 +++++++ webview-ui/src/i18n/locales/ja/settings.json | 7 +++++++ webview-ui/src/i18n/locales/ko/settings.json | 7 +++++++ webview-ui/src/i18n/locales/nl/settings.json | 7 +++++++ webview-ui/src/i18n/locales/pl/settings.json | 7 +++++++ webview-ui/src/i18n/locales/pt-BR/settings.json | 7 +++++++ webview-ui/src/i18n/locales/ru/settings.json | 7 +++++++ webview-ui/src/i18n/locales/tr/settings.json | 7 +++++++ webview-ui/src/i18n/locales/vi/settings.json | 7 +++++++ webview-ui/src/i18n/locales/zh-CN/settings.json | 7 +++++++ webview-ui/src/i18n/locales/zh-TW/settings.json | 7 +++++++ 19 files changed, 120 insertions(+), 1 deletion(-) delete mode 100644 qwen-cli.ts diff --git a/qwen-cli.ts b/qwen-cli.ts deleted file mode 100644 index 39e8a7d65af025797e4c0c5a98a3802f93b508d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80 zcmezWPoF`bL4hHh!IL3?!3xL(?![\s\S]*<\/think>)([\s\S]*)$/) + const incompleteThinkingMatch = remainingContent.match(/(?![\s\S]*<\/thinking>)([\s\S]*)$/) if (incompleteThinkingMatch) { // Keep incomplete thinking content for next chunk this.pendingThinkingContent = remainingContent diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index d9c7ce7ee2..6c4925dbb4 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -390,6 +390,13 @@ "learnMore": "Més informació sobre l'encaminament de proveïdors" } }, + "qwenCode": { + "oauthPath": "Ruta de credencials OAuth (opcional)", + "oauthPathDescription": "Ruta al fitxer de credencials OAuth. Deixeu-ho buit per utilitzar la ubicació predeterminada (~/.qwen/oauth_creds.json).", + "description": "Aquest proveïdor utilitza autenticació OAuth del servei Qwen i no requereix claus d'API.", + "instructions": "Si us plau, seguiu la documentació oficial per obtenir el fitxer d'autorització i col·loqueu-lo a la ruta especificada.", + "setupLink": "Documentació oficial de Qwen" + }, "customModel": { "capabilities": "Configureu les capacitats i preus per al vostre model personalitzat compatible amb OpenAI. Tingueu cura en especificar les capacitats del model, ja que poden afectar com funciona Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 7f09401e57..e9a1129f9a 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -390,6 +390,13 @@ "learnMore": "Mehr über Anbieter-Routing erfahren" } }, + "qwenCode": { + "oauthPath": "OAuth-Anmeldedaten-Pfad (optional)", + "oauthPathDescription": "Pfad zur OAuth-Anmeldedatei. Leer lassen, um den Standardspeicherort zu verwenden (~/.qwen/oauth_creds.json).", + "description": "Dieser Anbieter verwendet OAuth-Authentifizierung vom Qwen-Dienst und benötigt keine API-Schlüssel.", + "instructions": "Bitte folgen Sie der offiziellen Dokumentation, um die Autorisierungsdatei zu erhalten und sie im angegebenen Pfad zu platzieren.", + "setupLink": "Qwen Offizielle Dokumentation" + }, "customModel": { "capabilities": "Konfiguriere die Fähigkeiten und Preise für dein benutzerdefiniertes OpenAI-kompatibles Modell. Sei vorsichtig bei der Angabe der Modellfähigkeiten, da diese beeinflussen können, wie Roo Code funktioniert.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index ec9795d8b0..f227207ce9 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -390,6 +390,13 @@ "learnMore": "Más información sobre el enrutamiento de proveedores" } }, + "qwenCode": { + "oauthPath": "Ruta de credenciales OAuth (opcional)", + "oauthPathDescription": "Ruta al archivo de credenciales OAuth. Déjelo 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 de API.", + "instructions": "Por favor, siga la documentación oficial para obtener el archivo de autorización y colóquelo en la ruta especificada.", + "setupLink": "Documentación oficial de Qwen" + }, "customModel": { "capabilities": "Configure las capacidades y precios para su modelo personalizado compatible con OpenAI. Tenga cuidado al especificar las capacidades del modelo, ya que pueden afectar cómo funciona Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 68230b1a60..5fcecb9624 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -390,6 +390,13 @@ "learnMore": "En savoir plus sur le routage des fournisseurs" } }, + "qwenCode": { + "oauthPath": "Chemin des identifiants OAuth (optionnel)", + "oauthPathDescription": "Chemin vers le 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" + }, "customModel": { "capabilities": "Configurez les capacités et les prix pour votre modèle personnalisé compatible OpenAI. Soyez prudent lors de la spécification des capacités du modèle, car elles peuvent affecter le fonctionnement de Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index f98f7e6510..0e811302c4 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -390,6 +390,13 @@ "learnMore": "प्रदाता रूटिंग के बारे में अधिक जानें" } }, + "qwenCode": { + "oauthPath": "OAuth क्रेडेंशियल पथ (वैकल्पिक)", + "oauthPathDescription": "OAuth क्रेडेंशियल फ़ाइल का पथ। डिफ़ॉल्ट स्थान (~/.qwen/oauth_creds.json) का उपयोग करने के लिए खाली छोड़ें।", + "description": "यह प्रदाता Qwen सेवा से OAuth प्रमाणीकरण का उपयोग करता है और API कुंजियों की आवश्यकता नहीं है।", + "instructions": "कृपया आधिकारिक दस्तावेज़ीकरण का पालन करें और प्राधिकरण फ़ाइल प्राप्त करें और इसे निर्दिष्ट पथ में रखें।", + "setupLink": "Qwen आधिकारिक दस्तावेज़ीकरण" + }, "customModel": { "capabilities": "अपने कस्टम OpenAI-संगत मॉडल के लिए क्षमताओं और मूल्य निर्धारण को कॉन्फ़िगर करें। मॉडल क्षमताओं को निर्दिष्ट करते समय सावधान रहें, क्योंकि वे Roo Code के प्रदर्शन को प्रभावित कर सकती हैं।", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 748bf198eb..2b63f487b0 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -394,6 +394,13 @@ "learnMore": "Pelajari lebih lanjut tentang provider routing" } }, + "qwenCode": { + "oauthPath": "Jalur Kredensial OAuth (opsional)", + "oauthPathDescription": "Jalur ke file kredensial OAuth. Biarkan kosong untuk menggunakan lokasi default (~/.qwen/oauth_creds.json).", + "description": "Penyedia ini menggunakan autentikasi OAuth dari layanan Qwen dan tidak memerlukan kunci API.", + "instructions": "Silakan ikuti dokumentasi resmi untuk mendapatkan file otorisasi dan tempatkan di jalur yang ditentukan.", + "setupLink": "Dokumentasi Resmi Qwen" + }, "customModel": { "capabilities": "Konfigurasi kemampuan dan harga untuk model kustom yang kompatibel dengan OpenAI. Hati-hati saat menentukan kemampuan model, karena dapat mempengaruhi performa Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index b97d96a61b..37502d73cf 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -390,6 +390,13 @@ "learnMore": "Scopri di più sul routing dei fornitori" } }, + "qwenCode": { + "oauthPath": "Percorso credenziali OAuth (opzionale)", + "oauthPathDescription": "Percorso del file delle credenziali OAuth. Lasciare 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": "Si prega di seguire la documentazione ufficiale per ottenere il file di autorizzazione e posizionarlo nel percorso specificato.", + "setupLink": "Documentazione ufficiale Qwen" + }, "customModel": { "capabilities": "Configura le capacità e i prezzi del tuo modello personalizzato compatibile con OpenAI. Fai attenzione quando specifichi le capacità del modello, poiché possono influenzare le prestazioni di Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 8061418d38..195ed9c422 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -390,6 +390,13 @@ "learnMore": "プロバイダールーティングについて詳しく知る" } }, + "qwenCode": { + "oauthPath": "OAuth認証情報パス(オプション)", + "oauthPathDescription": "OAuth認証情報ファイルへのパス。デフォルトの場所(~/.qwen/oauth_creds.json)を使用する場合は空のままにしてください。", + "description": "このプロバイダーはQwenサービスからのOAuth認証を使用し、APIキーは必要ありません。", + "instructions": "公式ドキュメントに従って認証ファイルを取得し、指定されたパスに配置してください。", + "setupLink": "Qwen公式ドキュメント" + }, "customModel": { "capabilities": "カスタムOpenAI互換モデルの機能と価格を設定します。モデルの機能はRoo Codeのパフォーマンスに影響を与える可能性があるため、慎重に指定してください。", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 08f3a8c4e7..b6119d31f3 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -390,6 +390,13 @@ "learnMore": "제공자 라우팅에 대해 자세히 알아보기" } }, + "qwenCode": { + "oauthPath": "OAuth 자격 증명 경로 (선택사항)", + "oauthPathDescription": "OAuth 자격 증명 파일의 경로입니다. 기본 위치(~/.qwen/oauth_creds.json)를 사용하려면 비워두세요.", + "description": "이 공급자는 Qwen 서비스의 OAuth 인증을 사용하며 API 키가 필요하지 않습니다.", + "instructions": "공식 문서를 따라 인증 파일을 얻고 지정된 경로에 배치하세요.", + "setupLink": "Qwen 공식 문서" + }, "customModel": { "capabilities": "사용자 정의 OpenAI 호환 모델의 기능과 가격을 구성하세요. 모델 기능이 Roo Code의 성능에 영향을 미칠 수 있으므로 신중하게 지정하세요.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index cc4eb2c90f..325ed5e4e8 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -390,6 +390,13 @@ "learnMore": "Meer informatie over providerroutering" } }, + "qwenCode": { + "oauthPath": "OAuth-referentiepad (optioneel)", + "oauthPathDescription": "Pad naar het OAuth-referentiebestand. 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 plaats het in het opgegeven pad.", + "setupLink": "Qwen Officiële Documentatie" + }, "customModel": { "capabilities": "Stel de mogelijkheden en prijzen in voor je aangepaste OpenAI-compatibele model. Wees voorzichtig met het opgeven van de modelmogelijkheden, want deze kunnen de prestaties van Roo Code beïnvloeden.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 107be09fdd..0af06a613e 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -390,6 +390,13 @@ "learnMore": "Dowiedz się więcej o routingu dostawców" } }, + "qwenCode": { + "oauthPath": "Ścieżka poświadczeń OAuth (opcjonalna)", + "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": "Proszę postępować zgodnie z oficjalną dokumentacją, aby uzyskać plik autoryzacji i umieścić go w określonej ścieżce.", + "setupLink": "Oficjalna dokumentacja Qwen" + }, "customModel": { "capabilities": "Skonfiguruj możliwości i ceny swojego niestandardowego modelu zgodnego z OpenAI. Zachowaj ostrożność podczas określania możliwości modelu, ponieważ mogą one wpływać na wydajność Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 54343a2fc5..151984b34c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -390,6 +390,13 @@ "learnMore": "Saiba mais sobre roteamento de provedores" } }, + "qwenCode": { + "oauthPath": "Caminho das credenciais OAuth (opcional)", + "oauthPathDescription": "Caminho para o arquivo de credenciais OAuth. Deixe vazio para usar o local 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": "Por favor, siga a documentação oficial para obter o arquivo de autorização e colocá-lo no caminho especificado.", + "setupLink": "Documentação oficial do Qwen" + }, "customModel": { "capabilities": "Configure as capacidades e preços para seu modelo personalizado compatível com OpenAI. Tenha cuidado ao especificar as capacidades do modelo, pois elas podem afetar como o Roo Code funciona.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index ac5fafdc17..29c4cee2aa 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -390,6 +390,13 @@ "learnMore": "Подробнее о маршрутизации провайдеров" } }, + "qwenCode": { + "oauthPath": "Путь к учетным данным OAuth (необязательно)", + "oauthPathDescription": "Путь к файлу учетных данных OAuth. Оставьте пустым для использования местоположения по умолчанию (~/.qwen/oauth_creds.json).", + "description": "Этот провайдер использует OAuth-аутентификацию от сервиса Qwen и не требует API-ключей.", + "instructions": "Пожалуйста, следуйте официальной документации для получения файла авторизации и размещения его по указанному пути.", + "setupLink": "Официальная документация Qwen" + }, "customModel": { "capabilities": "Настройте возможности и стоимость вашей пользовательской модели, совместимой с OpenAI. Будьте осторожны при указании возможностей модели, это может повлиять на работу Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 0c4138e783..901c79acfe 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -390,6 +390,13 @@ "learnMore": "Sağlayıcı yönlendirmesi hakkında daha fazla bilgi edinin" } }, + "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 anahtarları gerektirmez.", + "instructions": "Lütfen yetkilendirme dosyasını almak ve belirtilen yola yerleştirmek için resmi belgeleri takip edin.", + "setupLink": "Qwen Resmi Belgeleri" + }, "customModel": { "capabilities": "Özel OpenAI uyumlu modelinizin yeteneklerini ve fiyatlandırmasını yapılandırın. Model yeteneklerini belirtirken dikkatli olun, çünkü bunlar Roo Code'un performansını etkileyebilir.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 5b2a5a6ef8..50063c2c1d 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -390,6 +390,13 @@ "learnMore": "Tìm hiểu thêm về định tuyến nhà cung cấp" } }, + "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 làm theo tài liệu chính thức để lấy tệp ủy quyền và đặt nó trong đường dẫn được chỉ định.", + "setupLink": "Tài liệu chính thức Qwen" + }, "customModel": { "capabilities": "Cấu hình các khả năng và giá cả cho mô hình tương thích OpenAI tùy chỉnh của bạn. Hãy cẩn thận khi chỉ định khả năng của mô hình, vì chúng có thể ảnh hưởng đến cách Roo Code hoạt động.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 9e56ba74ee..b5204a1201 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -390,6 +390,13 @@ "learnMore": "了解更多" } }, + "qwenCode": { + "oauthPath": "OAuth 凭据路径(可选)", + "oauthPathDescription": "OAuth 凭据文件的路径。留空以使用默认位置 (~/.qwen/oauth_creds.json)。", + "description": "此提供商使用来自 Qwen 服务的 OAuth 身份验证,不需要 API 密钥。", + "instructions": "请按照官方文档获取授权文件并将其放置在指定路径中。", + "setupLink": "Qwen 官方文档" + }, "customModel": { "capabilities": "自定义模型配置注意事项:\n• 确保兼容OpenAI接口规范\n• 错误配置可能导致功能异常\n• 价格参数影响费用统计", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 6bfab1435c..e264d68b30 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -390,6 +390,13 @@ "learnMore": "了解更多關於供應商路由的資訊" } }, + "qwenCode": { + "oauthPath": "OAuth 憑證路徑(可選)", + "oauthPathDescription": "OAuth 憑證檔案的路徑。留空以使用預設位置 (~/.qwen/oauth_creds.json)。", + "description": "此供應商使用來自 Qwen 服務的 OAuth 身份驗證,不需要 API 金鑰。", + "instructions": "請按照官方文件取得授權檔案並將其放置在指定路徑中。", + "setupLink": "Qwen 官方文件" + }, "customModel": { "capabilities": "設定自訂 OpenAI 相容模型的功能和定價。請謹慎設定模型功能,因為這會影響 Roo Code 的運作方式。", "maxTokens": { From 5c87784d0634c8a915152ef7c8487ce75954fc97 Mon Sep 17 00:00:00 2001 From: NITIN PRAJWAL R <145345201+nitinprajwal@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:59:57 +0530 Subject: [PATCH 05/19] Update src/api/providers/qwen-code.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- src/api/providers/qwen-code.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index d6b11f14db..f8b01aa75d 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -148,7 +148,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan expiry_date: Date.now() + tokenData.expires_in * 1000, } - const filePath = getQwenCachedCredentialPath() + const filePath = getQwenCachedCredentialPath(this.options.qwenCodeOAuthPath) await fs.writeFile(filePath, JSON.stringify(newCredentials, null, 2)) console.log("Successfully refreshed and cached new credentials.") From 5ffde87b40f4e113799b582363da89b15a9ab9c7 Mon Sep 17 00:00:00 2001 From: NITIN PRAJWAL R <145345201+nitinprajwal@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:09:23 +0530 Subject: [PATCH 06/19] Update src/api/providers/qwen-code.ts Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com> --- src/api/providers/qwen-code.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index f8b01aa75d..9b90d86e7a 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -80,7 +80,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan const remainingContent = this.pendingThinkingContent.slice(lastIndex) // Check if we have an incomplete thinking tag - const incompleteThinkingMatch = remainingContent.match(/(?![\s\S]*<\/thinking>)([\s\S]*)$/) + const incompleteThinkingMatch = remainingContent.match(/(?![\s\S]*<\/thinking>)([\s\S]*)$/) if (incompleteThinkingMatch) { // Keep incomplete thinking content for next chunk this.pendingThinkingContent = remainingContent From 44a90ef99b86978bb505ae57532e98ac1e6175ad Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Mon, 11 Aug 2025 15:38:32 +0530 Subject: [PATCH 07/19] Resolve Conflicts --- src/api/providers/qwen-code.ts | 110 +++-------------------------- src/i18n/locales/de/common.json | 3 + src/i18n/locales/en/common.json | 3 + src/i18n/locales/es/common.json | 3 + src/i18n/locales/fr/common.json | 3 + src/i18n/locales/ja/common.json | 3 + src/i18n/locales/ko/common.json | 3 + src/i18n/locales/pt-BR/common.json | 3 + src/i18n/locales/zh-CN/common.json | 3 + 9 files changed, 32 insertions(+), 102 deletions(-) diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index 9b90d86e7a..7829d491cc 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -23,6 +23,7 @@ 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" +const TOKEN_REFRESH_BUFFER_MS = 30 * 1000 // 30s buffer interface QwenOAuthCredentials { access_token: string @@ -32,10 +33,7 @@ interface QwenOAuthCredentials { resource_url?: string } -function getQwenCachedCredentialPath(customPath?: string): string { - if (customPath) { - return path.resolve(customPath) - } +function getQwenCachedCredentialPath(): string { return path.join(os.homedir(), QWEN_DIR, QWEN_CREDENTIAL_FILENAME) } @@ -49,62 +47,18 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan protected options: ApiHandlerOptions private credentials: QwenOAuthCredentials | null = null private client: OpenAI | null = null - private pendingThinkingContent: string = "" constructor(options: ApiHandlerOptions) { super() this.options = options } - private processContentChunk(content: string): { text: string; reasoning: string } { - // Accumulate content to handle incomplete thinking tags - this.pendingThinkingContent += content - - let processedText = "" - let reasoningText = "" - - // Handle complete thinking blocks - const thinkingRegex = /([\s\S]*?)<\/thinking>/g - let match - let lastIndex = 0 - - while ((match = thinkingRegex.exec(this.pendingThinkingContent)) !== null) { - // Add text before thinking block - processedText += this.pendingThinkingContent.slice(lastIndex, match.index) - // Extract thinking content - reasoningText += match[1] - lastIndex = match.index + match[0].length - } - - // Handle remaining content after last complete thinking block - const remainingContent = this.pendingThinkingContent.slice(lastIndex) - - // Check if we have an incomplete thinking tag - const incompleteThinkingMatch = remainingContent.match(/(?![\s\S]*<\/thinking>)([\s\S]*)$/) - if (incompleteThinkingMatch) { - // Keep incomplete thinking content for next chunk - this.pendingThinkingContent = remainingContent - } else { - // No incomplete thinking, add to processed text and clear pending - processedText += remainingContent - this.pendingThinkingContent = "" - } - - // Filter out malformed thinking tags like { try { - const keyFile = getQwenCachedCredentialPath(this.options.qwenCodeOAuthPath) + 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(this.options.qwenCodeOAuthPath)}`, - ) throw new Error(t("common:errors.qwenCode.oauthLoadFailed", { error })) } } @@ -148,15 +102,13 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan expiry_date: Date.now() + tokenData.expires_in * 1000, } - const filePath = getQwenCachedCredentialPath(this.options.qwenCodeOAuthPath) + const filePath = getQwenCachedCredentialPath() await fs.writeFile(filePath, JSON.stringify(newCredentials, null, 2)) - console.log("Successfully refreshed and cached new credentials.") return newCredentials } private isTokenValid(credentials: QwenOAuthCredentials): boolean { - const TOKEN_REFRESH_BUFFER_MS = 30 * 1000 // 30s buffer if (!credentials.expiry_date) { return false } @@ -203,7 +155,6 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan return await apiCall() } catch (error: any) { if (error.status === 401) { - console.log("Authentication failed. Forcing token refresh and retrying...") this.credentials = await this.refreshAccessToken(this.credentials!) this.setupClient() return await apiCall() @@ -248,56 +199,17 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan for await (const chunk of stream) { const delta = chunk.choices[0]?.delta ?? {} - // Handle reasoning content separately (if present) - if (delta && "reasoning_content" in delta && delta.reasoning_content) { - yield { - type: "reasoning", - text: delta.reasoning_content as string, - } - } - - // Handle regular content with thinking processing if (delta.content) { - const { text, reasoning } = this.processContentChunk(delta.content) - - // Yield reasoning content if any - if (reasoning.trim()) { - yield { - type: "reasoning", - text: reasoning, - } - } - - // Yield regular text content if any - if (text.trim()) { - yield { - type: "text", - text: text, - } + yield { + type: "text", + text: delta.content, } } - if (chunk.usage) { lastUsage = chunk.usage } } - // Handle any remaining pending thinking content at the end - if (this.pendingThinkingContent.trim()) { - // If there's incomplete thinking content, treat it as regular text - const cleanedContent = this.pendingThinkingContent.replace( - / this.client!.chat.completions.create(requestOptions)) - let content = response.choices[0]?.message.content || "" - - // Clean up any thinking content from non-streaming response - content = content.replace(/[\s\S]*?<\/thinking>/g, "") - content = content.replace(/ Date: Mon, 11 Aug 2025 15:50:18 +0530 Subject: [PATCH 08/19] Resolve Conflicts --- src/i18n/locales/ca/common.json | 3 +++ src/i18n/locales/hi/common.json | 3 +++ src/i18n/locales/id/common.json | 3 +++ src/i18n/locales/it/common.json | 3 +++ src/i18n/locales/nl/common.json | 3 +++ src/i18n/locales/pl/common.json | 3 +++ src/i18n/locales/ru/common.json | 3 +++ src/i18n/locales/tr/common.json | 3 +++ src/i18n/locales/vi/common.json | 3 +++ src/i18n/locales/zh-TW/common.json | 3 +++ 10 files changed, 30 insertions(+) diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 6235593f7e..9cdc9a7a07 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -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": "No s'han pogut carregar les credencials OAuth de QwenCode: {{error}}" + }, "mode_import_failed": "Ha fallat la importació del mode: {{error}}" }, "warnings": { diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index 05e0a622cc..8d538fbefb 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -103,6 +103,9 @@ }, "roo": { "authenticationRequired": "Roo प्रदाता को क्लाउड प्रमाणीकरण की आवश्यकता है। कृपया Roo Code Cloud में साइन इन करें।" + }, + "qwenCode": { + "oauthLoadFailed": "QwenCode OAuth क्रेडेंशियल लोड करने में विफल: {{error}}" } }, "warnings": { diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index 1595b795cf..4e1dc2d348 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -103,6 +103,9 @@ }, "roo": { "authenticationRequired": "Penyedia Roo memerlukan autentikasi cloud. Silakan masuk ke Roo Code Cloud." + }, + "qwenCode": { + "oauthLoadFailed": "Gagal memuat kredensial OAuth QwenCode: {{error}}" } }, "warnings": { diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 73f4d47788..1472e83917 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -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 QwenCode: {{error}}" } }, "warnings": { diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index fb2fcec9f9..06ae36f2fc 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -103,6 +103,9 @@ }, "roo": { "authenticationRequired": "Roo provider vereist cloud authenticatie. Log in bij Roo Code Cloud." + }, + "qwenCode": { + "oauthLoadFailed": "Kan QwenCode OAuth-referenties niet laden: {{error}}" } }, "warnings": { diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 2a6fee3e23..1290ed4128 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -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ć danych uwierzytelniających OAuth QwenCode: {{error}}" } }, "warnings": { diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 9c37cfe3ed..01e30f9296 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -103,6 +103,9 @@ }, "roo": { "authenticationRequired": "Провайдер Roo требует облачной аутентификации. Войдите в Roo Code Cloud." + }, + "qwenCode": { + "oauthLoadFailed": "Не удалось загрузить учетные данные OAuth QwenCode: {{error}}" } }, "warnings": { diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index d99008755e..121df15bf8 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -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": "QwenCode OAuth kimlik bilgileri yüklenemedi: {{error}}" } }, "warnings": { diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index d29525cc03..7f67c485a4 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -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 QwenCode: {{error}}" } }, "warnings": { diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 753463b9f5..902a8d47ae 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -103,6 +103,9 @@ "roo": { "authenticationRequired": "Roo 提供者需要雲端認證。請登入 Roo Code Cloud。" }, + "qwenCode": { + "oauthLoadFailed": "載入 QwenCode OAuth 憑證失敗:{{error}}" + }, "mode_import_failed": "匯入模式失敗:{{error}}" }, "warnings": { From 8b3b14678f2938167a02a5e0f02e3968c8e6de0b Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Mon, 11 Aug 2025 18:18:57 +0530 Subject: [PATCH 09/19] Fixed Thinking chucks --- packages/types/src/providers/qwen-code.ts | 19 +++- src/api/providers/qwen-code.ts | 102 +++++++++++++++------- 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/packages/types/src/providers/qwen-code.ts b/packages/types/src/providers/qwen-code.ts index 324b1a86b5..323166f700 100644 --- a/packages/types/src/providers/qwen-code.ts +++ b/packages/types/src/providers/qwen-code.ts @@ -8,9 +8,17 @@ export const qwenCodeModels = { provider: "qwen-code" as ProviderName, contextWindow: 1000000, maxTokens: 65536, - supportsPromptCache: true, + 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 @@ -27,3 +35,10 @@ export const getQwenCodeModelInfo = (modelId: string): ModelInfo => { // Fallback to a default or throw an error return qwenCodeModels[qwenCodeDefaultModelId] } + +export type QwenCodeProvider = { + id: "qwen-code" + apiKey?: string + baseUrl?: string + model: QwenCodeModelId +} diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index 7829d491cc..e9e88d1305 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -4,6 +4,7 @@ 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" @@ -23,7 +24,6 @@ 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" -const TOKEN_REFRESH_BUFFER_MS = 30 * 1000 // 30s buffer interface QwenOAuthCredentials { access_token: string @@ -46,11 +46,18 @@ function objectToUrlEncoded(data: Record): string { export class QwenCodeHandler extends BaseProvider implements SingleCompletionHandler { protected options: ApiHandlerOptions private credentials: QwenOAuthCredentials | null = null - private client: OpenAI | 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 { @@ -59,6 +66,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan 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 })) } } @@ -104,11 +112,13 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan const filePath = getQwenCachedCredentialPath() await fs.writeFile(filePath, JSON.stringify(newCredentials, null, 2)) + // console.log("Successfully refreshed and cached new credentials.") return newCredentials } private isTokenValid(credentials: QwenOAuthCredentials): boolean { + const TOKEN_REFRESH_BUFFER_MS = 30 * 1000 // 30s buffer if (!credentials.expiry_date) { return false } @@ -124,9 +134,9 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan this.credentials = await this.refreshAccessToken(this.credentials) } - if (!this.client || this.client.apiKey !== this.credentials.access_token) { - this.setupClient() - } + // 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 { @@ -137,26 +147,16 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan return baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1` } - private setupClient(): void { - if (!this.credentials) { - throw new Error("Credentials not loaded.") - } - const headers = { ...DEFAULT_HEADERS } - - this.client = new OpenAI({ - apiKey: this.credentials.access_token, - baseURL: this.getBaseUrl(this.credentials), - defaultHeaders: headers, - }) - } - private async callApiWithRetry(apiCall: () => Promise): Promise { try { return await apiCall() } catch (error: any) { if (error.status === 401) { + // console.log("Authentication failed. Forcing token refresh and retrying...") this.credentials = await this.refreshAccessToken(this.credentials!) - this.setupClient() + // 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 @@ -188,28 +188,56 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan stream_options: { include_usage: true }, } - if (this.options.includeMaxTokens) { - requestOptions.max_tokens = this.options.modelMaxTokens || modelInfo.maxTokens - } + this.addMaxTokensIfNeeded(requestOptions, modelInfo) const stream = await this.callApiWithRetry(() => this.client!.chat.completions.create(requestOptions)) + // XmlMatcher + const matcher = new XmlMatcher( + "think", + (chunk) => + ({ + type: chunk.matched ? "reasoning" : "text", + text: chunk.data, + }) as const, + ) + let lastUsage: any + let fullContent = "" - for await (const chunk of stream) { - const delta = chunk.choices[0]?.delta ?? {} + 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) { + // this.options.log?.(`[qwen-code] chunk: ${newText}`) + for (const processedChunk of matcher.update(newText)) { + yield processedChunk + } + } + } + + if ("reasoning_content" in delta && delta.reasoning_content) { yield { - type: "text", - text: delta.content, + type: "reasoning", + text: (delta.reasoning_content as string | undefined) || "", } } - if (chunk.usage) { - lastUsage = chunk.usage + if (apiChunk.usage) { + lastUsage = apiChunk.usage } } + for (const chunk of matcher.final()) { + yield chunk + } + if (lastUsage) { yield this.processUsageMetrics(lastUsage, modelInfo) } @@ -225,9 +253,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan messages: [{ role: "user", content: prompt }], } - if (this.options.includeMaxTokens) { - requestOptions.max_tokens = this.options.modelMaxTokens || modelInfo.maxTokens - } + this.addMaxTokensIfNeeded(requestOptions, modelInfo) const response = await this.callApiWithRetry(() => this.client!.chat.completions.create(requestOptions)) @@ -250,4 +276,18 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan 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 + } + } } From 4b829742e077320d03e47cea25fb90845e9a1d40 Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Fri, 15 Aug 2025 20:42:05 +0530 Subject: [PATCH 10/19] Removed Comments --- src/api/providers/qwen-code.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index e9e88d1305..b8a8cdacf0 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -112,7 +112,6 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan const filePath = getQwenCachedCredentialPath() await fs.writeFile(filePath, JSON.stringify(newCredentials, null, 2)) - // console.log("Successfully refreshed and cached new credentials.") return newCredentials } @@ -152,7 +151,6 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan return await apiCall() } catch (error: any) { if (error.status === 401) { - // console.log("Authentication failed. Forcing token refresh and retrying...") this.credentials = await this.refreshAccessToken(this.credentials!) // Just update the key, don't re-create the client this.client.apiKey = this.credentials.access_token @@ -192,7 +190,6 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan const stream = await this.callApiWithRetry(() => this.client!.chat.completions.create(requestOptions)) - // XmlMatcher const matcher = new XmlMatcher( "think", (chunk) => @@ -216,7 +213,6 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan fullContent = delta.content if (newText) { - // this.options.log?.(`[qwen-code] chunk: ${newText}`) for (const processedChunk of matcher.update(newText)) { yield processedChunk } From 58e044beaaa012a3a17b6f2c63008e8c4e47e43a Mon Sep 17 00:00:00 2001 From: NITIN PRAJWAL R <145345201+nitinprajwal@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:28:47 +0530 Subject: [PATCH 11/19] Update useSelectedModel.ts --- webview-ui/src/components/ui/hooks/useSelectedModel.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 36ea6c83e8..f8cbd96f59 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -317,6 +317,11 @@ function getSelectedModel({ const info = qwenCodeModels[id as keyof typeof qwenCodeModels] return { id, info } } + case "qwen-code": { + const id = apiConfiguration.apiModelId ?? qwenCodeDefaultModelId + const info = qwenCodeModels[id as keyof typeof qwenCodeModels] + return { id, info } + } // case "anthropic": // case "human-relay": // case "fake-ai": From a37b14ed0fcb1b57c8ae95880bcaf8d25a92bde4 Mon Sep 17 00:00:00 2001 From: NITIN PRAJWAL R <145345201+nitinprajwal@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:36:05 +0530 Subject: [PATCH 12/19] Update provider-settings.ts --- packages/types/src/provider-settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 35a0f75375..d8f9b8f554 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -65,6 +65,7 @@ export const providerNames = [ "io-intelligence", "roo", "qwen-code", + "io-intelligence", ] as const export const providerNamesSchema = z.enum(providerNames) From 758580a113bde8eb542f355341ed54120877569c Mon Sep 17 00:00:00 2001 From: NITIN PRAJWAL R <145345201+nitinprajwal@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:36:14 +0530 Subject: [PATCH 13/19] Update ApiOptions.tsx --- webview-ui/src/components/settings/ApiOptions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 80136ec4df..3c54c2fab8 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -35,6 +35,7 @@ import { ioIntelligenceDefaultModelId, rooDefaultModelId, qwenCodeDefaultModelId, + ioIntelligenceDefaultModelId, } from "@roo-code/types" import { vscode } from "@src/utils/vscode" From ca982d22f747600f869fb826373c17f3882134b1 Mon Sep 17 00:00:00 2001 From: NITIN PRAJWAL R <145345201+nitinprajwal@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:36:23 +0530 Subject: [PATCH 14/19] Update useSelectedModel.ts --- webview-ui/src/components/ui/hooks/useSelectedModel.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index f8cbd96f59..94d6a03d54 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -55,6 +55,9 @@ import { BEDROCK_CLAUDE_SONNET_4_MODEL_ID, qwenCodeModels, qwenCodeDefaultModelId, + ioIntelligenceDefaultModelId, + ioIntelligenceModels, + BEDROCK_CLAUDE_SONNET_4_MODEL_ID, } from "@roo-code/types" import type { ModelRecord, RouterModels } from "@roo/api" @@ -322,6 +325,11 @@ function getSelectedModel({ const info = qwenCodeModels[id as keyof typeof qwenCodeModels] return { id, info } } + case "io-intelligence": { + const id = apiConfiguration.ioIntelligenceModelId ?? ioIntelligenceDefaultModelId + const info = routerModels["io-intelligence"]?.[id] ?? ioIntelligenceModels[id as keyof typeof ioIntelligenceModels + return { id, info } + } // case "anthropic": // case "human-relay": // case "fake-ai": From 4122e5f925ae5b690a02a01a438148d1702b9b50 Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Sun, 17 Aug 2025 15:01:46 +0530 Subject: [PATCH 15/19] Fix typo in getSelectedModel function for io-intelligence provider --- webview-ui/src/components/ui/hooks/useSelectedModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 94d6a03d54..7aab8ec03b 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -327,7 +327,7 @@ function getSelectedModel({ } case "io-intelligence": { const id = apiConfiguration.ioIntelligenceModelId ?? ioIntelligenceDefaultModelId - const info = routerModels["io-intelligence"]?.[id] ?? ioIntelligenceModels[id as keyof typeof ioIntelligenceModels + const info = routerModels["io-intelligence"]?.[id] ?? ioIntelligenceModels[id as keyof typeof ioIntelligenceModels] return { id, info } } // case "anthropic": From 905947ca700124709790f8b62d07963d22dead36 Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Sun, 17 Aug 2025 15:07:24 +0530 Subject: [PATCH 16/19] Removed duplicates --- .../src/components/ui/hooks/useSelectedModel.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 7aab8ec03b..36ea6c83e8 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -55,9 +55,6 @@ import { BEDROCK_CLAUDE_SONNET_4_MODEL_ID, qwenCodeModels, qwenCodeDefaultModelId, - ioIntelligenceDefaultModelId, - ioIntelligenceModels, - BEDROCK_CLAUDE_SONNET_4_MODEL_ID, } from "@roo-code/types" import type { ModelRecord, RouterModels } from "@roo/api" @@ -320,16 +317,6 @@ function getSelectedModel({ const info = qwenCodeModels[id as keyof typeof qwenCodeModels] return { id, info } } - case "qwen-code": { - const id = apiConfiguration.apiModelId ?? qwenCodeDefaultModelId - const info = qwenCodeModels[id as keyof typeof qwenCodeModels] - return { id, info } - } - case "io-intelligence": { - const id = apiConfiguration.ioIntelligenceModelId ?? ioIntelligenceDefaultModelId - const info = routerModels["io-intelligence"]?.[id] ?? ioIntelligenceModels[id as keyof typeof ioIntelligenceModels] - return { id, info } - } // case "anthropic": // case "human-relay": // case "fake-ai": From 6dd2dba2837293efa1e95364e95e9bc9651eb887 Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Sun, 17 Aug 2025 15:09:46 +0530 Subject: [PATCH 17/19] Removed Duplicates --- packages/types/src/provider-settings.ts | 1 - webview-ui/src/components/settings/ApiOptions.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index d8f9b8f554..35a0f75375 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -65,7 +65,6 @@ export const providerNames = [ "io-intelligence", "roo", "qwen-code", - "io-intelligence", ] as const export const providerNamesSchema = z.enum(providerNames) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 3c54c2fab8..80136ec4df 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -35,7 +35,6 @@ import { ioIntelligenceDefaultModelId, rooDefaultModelId, qwenCodeDefaultModelId, - ioIntelligenceDefaultModelId, } from "@roo-code/types" import { vscode } from "@src/utils/vscode" From d9f5dad46596c5d24f1ae672a4714a7152573407 Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Wed, 20 Aug 2025 17:06:06 +0530 Subject: [PATCH 18/19] Refactor getQwenCachedCredentialPath to accept custom path and update usage in QwenCodeHandler; change setup link to README in QwenCode component --- src/api/providers/qwen-code.ts | 13 +++++++++---- .../src/components/settings/providers/QwenCode.tsx | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index b8a8cdacf0..a6f7c44d77 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -33,7 +33,10 @@ interface QwenOAuthCredentials { resource_url?: string } -function getQwenCachedCredentialPath(): string { +function getQwenCachedCredentialPath(customPath?: string): string { + if (customPath) { + return customPath + } return path.join(os.homedir(), QWEN_DIR, QWEN_CREDENTIAL_FILENAME) } @@ -62,11 +65,13 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan private async loadCachedQwenCredentials(): Promise { try { - const keyFile = getQwenCachedCredentialPath() + const keyFile = getQwenCachedCredentialPath(this.options.qwenCodeOAuthPath) const credsStr = await fs.readFile(keyFile, "utf-8") return JSON.parse(credsStr) } catch (error) { - console.error(`Error reading or parsing credentials file at ${getQwenCachedCredentialPath()}`) + console.error( + `Error reading or parsing credentials file at ${getQwenCachedCredentialPath(this.options.qwenCodeOAuthPath)}`, + ) throw new Error(t("common:errors.qwenCode.oauthLoadFailed", { error })) } } @@ -110,7 +115,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan expiry_date: Date.now() + tokenData.expires_in * 1000, } - const filePath = getQwenCachedCredentialPath() + const filePath = getQwenCachedCredentialPath(this.options.qwenCodeOAuthPath) await fs.writeFile(filePath, JSON.stringify(newCredentials, null, 2)) return newCredentials diff --git a/webview-ui/src/components/settings/providers/QwenCode.tsx b/webview-ui/src/components/settings/providers/QwenCode.tsx index 7b8f858874..4689b5fa9c 100644 --- a/webview-ui/src/components/settings/providers/QwenCode.tsx +++ b/webview-ui/src/components/settings/providers/QwenCode.tsx @@ -49,7 +49,7 @@ export const QwenCode = ({ apiConfiguration, setApiConfigurationField }: QwenCod {t("settings:providers.qwenCode.setupLink")} From 8a0e10bd45f6dc88e81da3fa4b2459bc593b8703 Mon Sep 17 00:00:00 2001 From: nitinprajwal Date: Thu, 21 Aug 2025 15:39:16 +0530 Subject: [PATCH 19/19] Fixed missing declaration --- packages/types/src/provider-settings.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 35a0f75375..813f36aa7f 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -18,6 +18,7 @@ import { mistralModels, moonshotModels, openAiNativeModels, + qwenCodeModels, rooModels, sambaNovaModels, vertexModels, @@ -63,8 +64,8 @@ export const providerNames = [ "fireworks", "featherless", "io-intelligence", - "roo", "qwen-code", + "roo", ] as const export const providerNamesSchema = z.enum(providerNames) @@ -511,6 +512,11 @@ export const MODELS_BY_PROVIDER: Record< label: "OpenAI", models: Object.keys(openAiNativeModels), }, + "qwen-code": { + id: "qwen-code", + label: "Qwen Code", + models: Object.keys(qwenCodeModels), + }, roo: { id: "roo", label: "Roo", models: Object.keys(rooModels) }, sambanova: { id: "sambanova",