From cf445c3e542e0a12abc2f65865b4741dbfc0f68f Mon Sep 17 00:00:00 2001 From: xyOz Date: Sat, 17 May 2025 21:49:24 +0100 Subject: [PATCH 1/4] Log Cleanup to Remove Cline (#3704) * Rename Errors & Fix Spelling Mistake * Update src/core/task/Task.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --------- Co-authored-by: Matt Rubens Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- src/core/task/Task.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 918041e459..9a23272d28 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -350,7 +350,7 @@ export class Task extends EventEmitter { await this.providerRef.deref()?.updateTaskHistory(historyItem) } catch (error) { - console.error("Failed to save cline messages:", error) + console.error("Failed to save Roo messages:", error) } } @@ -372,7 +372,7 @@ export class Task extends EventEmitter { // simply removes the reference to this instance, but the instance is // still alive until this promise resolves or rejects.) if (this.abort) { - throw new Error(`[Cline#ask] task ${this.taskId}.${this.instanceId} aborted`) + throw new Error(`[RooCode#ask] task ${this.taskId}.${this.instanceId} aborted`) } let askTs: number @@ -492,7 +492,7 @@ export class Task extends EventEmitter { } = {}, ): Promise { if (this.abort) { - throw new Error(`[Cline#say] task ${this.taskId}.${this.instanceId} aborted`) + throw new Error(`[RooCode#say] task ${this.taskId}.${this.instanceId} aborted`) } if (partial !== undefined) { @@ -623,7 +623,7 @@ export class Task extends EventEmitter { } catch (error) { this.providerRef .deref() - ?.log(`Error failed to add reply from subtast into conversation of parent task, error: ${error}`) + ?.log(`Error failed to add reply from subtask into conversation of parent task, error: ${error}`) throw error } @@ -957,7 +957,7 @@ export class Task extends EventEmitter { includeFileDetails: boolean = false, ): Promise { if (this.abort) { - throw new Error(`[Cline#recursivelyMakeClineRequests] task ${this.taskId}.${this.instanceId} aborted`) + throw new Error(`[RooCode#recursivelyMakeRooRequests] task ${this.taskId}.${this.instanceId} aborted`) } if (this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) { @@ -1253,7 +1253,7 @@ export class Task extends EventEmitter { // Need to call here in case the stream was aborted. if (this.abort || this.abandoned) { - throw new Error(`[Cline#recursivelyMakeClineRequests] task ${this.taskId}.${this.instanceId} aborted`) + throw new Error(`[RooCode#recursivelyMakeRooRequests] task ${this.taskId}.${this.instanceId} aborted`) } this.didCompleteReadingStream = true From 4550a9195075bbb05359115a79ff9961c3d00167 Mon Sep 17 00:00:00 2001 From: vagadiya <32499123+vagadiya@users.noreply.github.com> Date: Sat, 17 May 2025 21:53:33 +0100 Subject: [PATCH 2/4] #3679 - Fixes packaging to include correct tiktoken.wasm (lite) (#3697) - also, additions to .gitignore and .vscodeignore to prevent the IntelliJ .idea and .qodo folders from being included for git and packaging. --- .gitignore | 4 ++++ .vscodeignore | 4 ++++ esbuild.js | 9 ++++++++- tsconfig.json | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3a541f1b09..b2ddc6d9f0 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,7 @@ logs # Vite development .vite-port + +# IntelliJ and Qodo plugin folders +.idea/ +.qodo/ diff --git a/.vscodeignore b/.vscodeignore index ce2e88a388..50f21d23c7 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -68,3 +68,7 @@ assets/docs/** # Include .env file for telemetry !.env + +# Ignore IntelliJ and Qodo plugin folders +.idea/** +.qodo/** \ No newline at end of file diff --git a/esbuild.js b/esbuild.js index f38de8c15f..2b684ea248 100644 --- a/esbuild.js +++ b/esbuild.js @@ -34,10 +34,17 @@ const copyWasmFiles = { // tiktoken WASM file fs.copyFileSync( - path.join(nodeModulesDir, "tiktoken", "tiktoken_bg.wasm"), + path.join(nodeModulesDir, "tiktoken", "lite", "tiktoken_bg.wasm"), path.join(distDir, "tiktoken_bg.wasm"), ) + // Also copy to the workers directory + fs.mkdirSync(path.join(distDir, "workers"), { recursive: true }) + fs.copyFileSync( + path.join(nodeModulesDir, "tiktoken", "lite", "tiktoken_bg.wasm"), + path.join(distDir, "workers", "tiktoken_bg.wasm"), + ) + // Main tree-sitter WASM file fs.copyFileSync( path.join(nodeModulesDir, "web-tree-sitter", "tree-sitter.wasm"), diff --git a/tsconfig.json b/tsconfig.json index 1d70336fc1..0cddfc71dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,5 @@ "useUnknownInCatchVariables": false }, "include": ["src/**/*", "scripts/**/*", ".changeset/**/*"], - "exclude": ["node_modules", ".vscode-test", "webview-ui"] + "exclude": ["node_modules", ".vscode-test", "webview-ui", ".idea", ".qodo"] } From d8515860426b2c177aaf91d7140d2099f77d57bc Mon Sep 17 00:00:00 2001 From: pugazhendhi-m <132246623+pugazhendhi-m@users.noreply.github.com> Date: Sun, 18 May 2025 06:39:39 +0530 Subject: [PATCH 3/4] Adds refresh models button for Unbound provider (#3663) * Adds refresh models button for Unbound provider * Adds changeset * Optimizes code to prevent memory leak, add error messages * Adds unbound messages to all supported languages --------- Co-authored-by: Pugazhendhi --- .changeset/seven-kids-return.md | 10 ++ src/api/providers/fetchers/modelCache.ts | 3 +- src/api/providers/fetchers/unbound.ts | 11 +- .../components/settings/providers/Unbound.tsx | 114 +++++++++++++++++- webview-ui/src/i18n/locales/ca/settings.json | 2 + webview-ui/src/i18n/locales/de/settings.json | 2 + webview-ui/src/i18n/locales/en/settings.json | 2 + webview-ui/src/i18n/locales/es/settings.json | 2 + webview-ui/src/i18n/locales/fr/settings.json | 2 + webview-ui/src/i18n/locales/hi/settings.json | 2 + webview-ui/src/i18n/locales/it/settings.json | 2 + webview-ui/src/i18n/locales/ja/settings.json | 2 + webview-ui/src/i18n/locales/ko/settings.json | 2 + webview-ui/src/i18n/locales/nl/settings.json | 2 + webview-ui/src/i18n/locales/pl/settings.json | 2 + .../src/i18n/locales/pt-BR/settings.json | 2 + webview-ui/src/i18n/locales/ru/settings.json | 2 + webview-ui/src/i18n/locales/tr/settings.json | 2 + webview-ui/src/i18n/locales/vi/settings.json | 2 + .../src/i18n/locales/zh-CN/settings.json | 2 + .../src/i18n/locales/zh-TW/settings.json | 2 + 21 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 .changeset/seven-kids-return.md diff --git a/.changeset/seven-kids-return.md b/.changeset/seven-kids-return.md new file mode 100644 index 0000000000..d4da5cbc03 --- /dev/null +++ b/.changeset/seven-kids-return.md @@ -0,0 +1,10 @@ +--- +"roo-cline": minor +--- + +Adds refresh models button for Unbound provider +Adds a button above model picker to refresh models based on the current API Key. + +1. Clicking the refresh button saves the API Key and calls /models endpoint using that. +2. Gets the new models and updates the current model if it is invalid for the given API Key. +3. The refresh button also flushes existing Unbound models and refetches them. diff --git a/src/api/providers/fetchers/modelCache.ts b/src/api/providers/fetchers/modelCache.ts index 9ab4b851fc..c3c662415b 100644 --- a/src/api/providers/fetchers/modelCache.ts +++ b/src/api/providers/fetchers/modelCache.ts @@ -65,7 +65,8 @@ export const getModels = async ( models = await getGlamaModels() break case "unbound": - models = await getUnboundModels() + // Unbound models endpoint requires an API key to fetch application specific models + models = await getUnboundModels(apiKey) break case "litellm": if (apiKey && baseUrl) { diff --git a/src/api/providers/fetchers/unbound.ts b/src/api/providers/fetchers/unbound.ts index 73a8c2f897..7834debf35 100644 --- a/src/api/providers/fetchers/unbound.ts +++ b/src/api/providers/fetchers/unbound.ts @@ -2,11 +2,17 @@ import axios from "axios" import { ModelInfo } from "../../../shared/api" -export async function getUnboundModels(): Promise> { +export async function getUnboundModels(apiKey?: string | null): Promise> { const models: Record = {} try { - const response = await axios.get("https://api.getunbound.ai/models") + const headers: Record = {} + + if (apiKey) { + headers["Authorization"] = `Bearer ${apiKey}` + } + + const response = await axios.get("https://api.getunbound.ai/models", { headers }) if (response.data) { const rawModels: Record = response.data @@ -40,6 +46,7 @@ export async function getUnboundModels(): Promise> { } } catch (error) { console.error(`Error fetching Unbound models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) + throw new Error(`Failed to fetch Unbound models: ${error instanceof Error ? error.message : "Unknown error"}`) } return models diff --git a/webview-ui/src/components/settings/providers/Unbound.tsx b/webview-ui/src/components/settings/providers/Unbound.tsx index 77b24bb7cc..3d5aa0c67a 100644 --- a/webview-ui/src/components/settings/providers/Unbound.tsx +++ b/webview-ui/src/components/settings/providers/Unbound.tsx @@ -1,10 +1,13 @@ -import { useCallback } from "react" +import { useCallback, useState, useRef } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" +import { useQueryClient } from "@tanstack/react-query" import { ProviderSettings, RouterModels, unboundDefaultModelId } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" +import { vscode } from "@src/utils/vscode" +import { Button } from "@src/components/ui" import { inputEventTransform } from "../transforms" import { ModelPicker } from "../ModelPicker" @@ -17,6 +20,13 @@ type UnboundProps = { export const Unbound = ({ apiConfiguration, setApiConfigurationField, routerModels }: UnboundProps) => { const { t } = useAppTranslation() + const [didRefetch, setDidRefetch] = useState() + const [isInvalidKey, setIsInvalidKey] = useState(false) + const queryClient = useQueryClient() + + // Add refs to store timer IDs + const didRefetchTimerRef = useRef() + const invalidKeyTimerRef = useRef() const handleInputChange = useCallback( ( @@ -29,6 +39,90 @@ export const Unbound = ({ apiConfiguration, setApiConfigurationField, routerMode [setApiConfigurationField], ) + const saveConfiguration = useCallback(async () => { + vscode.postMessage({ + type: "upsertApiConfiguration", + text: "default", + apiConfiguration: apiConfiguration, + }) + + const waitForStateUpdate = new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + window.removeEventListener("message", messageHandler) + reject(new Error("Timeout waiting for state update")) + }, 10000) // 10 second timeout + + const messageHandler = (event: MessageEvent) => { + const message = event.data + if (message.type === "state") { + clearTimeout(timeoutId) + window.removeEventListener("message", messageHandler) + resolve() + } + } + window.addEventListener("message", messageHandler) + }) + + try { + await waitForStateUpdate + } catch (error) { + console.error("Failed to save configuration:", error) + } + }, [apiConfiguration]) + + const requestModels = useCallback(async () => { + vscode.postMessage({ type: "flushRouterModels", text: "unbound" }) + + const modelsPromise = new Promise((resolve) => { + const messageHandler = (event: MessageEvent) => { + const message = event.data + if (message.type === "routerModels") { + window.removeEventListener("message", messageHandler) + resolve() + } + } + window.addEventListener("message", messageHandler) + }) + + vscode.postMessage({ type: "requestRouterModels" }) + + await modelsPromise + + await queryClient.invalidateQueries({ queryKey: ["routerModels"] }) + + // After refreshing models, check if current model is in the updated list + // If not, select the first available model + const updatedModels = queryClient.getQueryData<{ unbound: RouterModels }>(["routerModels"])?.unbound + if (updatedModels && Object.keys(updatedModels).length > 0) { + const currentModelId = apiConfiguration?.unboundModelId + const modelExists = currentModelId && Object.prototype.hasOwnProperty.call(updatedModels, currentModelId) + + if (!currentModelId || !modelExists) { + const firstAvailableModelId = Object.keys(updatedModels)[0] + setApiConfigurationField("unboundModelId", firstAvailableModelId) + } + } + + if (!updatedModels || Object.keys(updatedModels).includes("error")) { + return false + } else { + return true + } + }, [queryClient, apiConfiguration, setApiConfigurationField]) + + const handleRefresh = useCallback(async () => { + await saveConfiguration() + const requestModelsResult = await requestModels() + + if (requestModelsResult) { + setDidRefetch(true) + didRefetchTimerRef.current = setTimeout(() => setDidRefetch(false), 3000) + } else { + setIsInvalidKey(true) + invalidKeyTimerRef.current = setTimeout(() => setIsInvalidKey(false), 3000) + } + }, [saveConfiguration, requestModels]) + return ( <> )} +
+ +
+ {didRefetch && ( +
+ {t("settings:providers.unboundRefreshModelsSuccess")} +
+ )} + {isInvalidKey && ( +
+ {t("settings:providers.unboundInvalidApiKey")} +
+ )} Date: Sun, 18 May 2025 21:30:45 +0500 Subject: [PATCH 4/4] Add Qwen3 model series to the Chutes provider (#3710) * Add Qwen3 model series to the Chutes provider New models for the Chutes provider: - Qwen/Qwen3-235B-A22B - Qwen/Qwen3-32B - Qwen/Qwen3-30B-A3B - Qwen/Qwen3-14B - Qwen/Qwen3-8B * add changeset --- .changeset/curly-plants-pull.md | 11 ++++++++ src/shared/api.ts | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 .changeset/curly-plants-pull.md diff --git a/.changeset/curly-plants-pull.md b/.changeset/curly-plants-pull.md new file mode 100644 index 0000000000..0f425cb859 --- /dev/null +++ b/.changeset/curly-plants-pull.md @@ -0,0 +1,11 @@ +--- +"roo-cline": patch +--- + +New models for the Chutes provider: + +- Qwen/Qwen3-235B-A22B +- Qwen/Qwen3-32B +- Qwen/Qwen3-30B-A3B +- Qwen/Qwen3-14B +- Qwen/Qwen3-8B diff --git a/src/shared/api.ts b/src/shared/api.ts index afe994fbf7..2a573a1bbc 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -1533,6 +1533,11 @@ export type ChutesModelId = | "deepseek-ai/DeepSeek-V3-Base" | "deepseek-ai/DeepSeek-R1-Zero" | "deepseek-ai/DeepSeek-V3-0324" + | "Qwen/Qwen3-235B-A22B" + | "Qwen/Qwen3-32B" + | "Qwen/Qwen3-30B-A3B" + | "Qwen/Qwen3-14B" + | "Qwen/Qwen3-8B" | "microsoft/MAI-DS-R1-FP8" | "tngtech/DeepSeek-R1T-Chimera" export const chutesDefaultModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" @@ -1663,6 +1668,51 @@ export const chutesModels = { outputPrice: 0, description: "DeepSeek V3 (0324) model.", }, + "Qwen/Qwen3-235B-A22B": { + maxTokens: 32768, + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen3 235B A22B model.", + }, + "Qwen/Qwen3-32B": { + maxTokens: 32768, + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen3 32B model.", + }, + "Qwen/Qwen3-30B-A3B": { + maxTokens: 32768, + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen3 30B A3B model.", + }, + "Qwen/Qwen3-14B": { + maxTokens: 32768, + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen3 14B model.", + }, + "Qwen/Qwen3-8B": { + maxTokens: 32768, + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen3 8B model.", + }, "microsoft/MAI-DS-R1-FP8": { maxTokens: 32768, contextWindow: 163840,