diff --git a/.husky/pre-push b/.husky/pre-push index 3c206835b7..7ee3ef6409 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -7,7 +7,13 @@ fi # Detect if running on Windows and use pnpm.cmd, otherwise use pnpm. if [ "$OS" = "Windows_NT" ]; then - pnpm_cmd="pnpm.cmd" + if command -v pnpm.cmd >/dev/null 2>&1; then + pnpm_cmd="pnpm.cmd" + elif command -v pnpm >/dev/null 2>&1; then + pnpm_cmd="pnpm" + else + pnpm_cmd="npx pnpm" + fi else if command -v pnpm >/dev/null 2>&1; then pnpm_cmd="pnpm" diff --git a/apps/vscode-e2e/package.json b/apps/vscode-e2e/package.json index 5a2c0d0dfc..4ef9d29f70 100644 --- a/apps/vscode-e2e/package.json +++ b/apps/vscode-e2e/package.json @@ -2,7 +2,7 @@ "name": "@roo-code/vscode-e2e", "private": true, "scripts": { - "lint": "eslint src --ext=ts --max-warnings=0", + "lint": "eslint src --max-warnings=0", "check-types": "tsc -p tsconfig.esm.json --noEmit", "format": "prettier --write src", "test:ci": "pnpm -w bundle && pnpm --filter @roo-code/vscode-webview build && pnpm test:run", diff --git a/packages/build/package.json b/packages/build/package.json index b6897732db..d09aa940ae 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -6,7 +6,7 @@ "main": "./dist/index.js", "types": "./src/index.ts", "scripts": { - "lint": "eslint src --ext=ts --max-warnings=0", + "lint": "eslint src --max-warnings=0", "check-types": "tsc --noEmit", "test": "vitest run", "build": "tsc", diff --git a/packages/cloud/package.json b/packages/cloud/package.json index 375838dc95..a1dc586e87 100644 --- a/packages/cloud/package.json +++ b/packages/cloud/package.json @@ -5,7 +5,7 @@ "type": "module", "exports": "./src/index.ts", "scripts": { - "lint": "eslint src --ext=ts --max-warnings=0", + "lint": "eslint src --max-warnings=0", "check-types": "tsc --noEmit", "test": "vitest run", "clean": "rimraf dist .turbo" diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index f9e228bc89..17470bcbc4 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -5,7 +5,7 @@ "type": "module", "exports": "./src/index.ts", "scripts": { - "lint": "eslint src --ext=ts --max-warnings=0", + "lint": "eslint src --max-warnings=0", "check-types": "tsc --noEmit", "test": "vitest run", "clean": "rimraf dist .turbo" diff --git a/packages/types/package.json b/packages/types/package.json index d35b9501df..3c131a3afc 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -14,7 +14,7 @@ } }, "scripts": { - "lint": "eslint src --ext=ts --max-warnings=0", + "lint": "eslint src --max-warnings=0", "check-types": "tsc --noEmit", "test": "vitest run", "build": "tsup", diff --git a/packages/types/src/providers/gemini.ts b/packages/types/src/providers/gemini.ts index 2ddf594704..4d89873ef8 100644 --- a/packages/types/src/providers/gemini.ts +++ b/packages/types/src/providers/gemini.ts @@ -15,7 +15,7 @@ export const geminiModels = { outputPrice: 3.5, maxThinkingTokens: 24_576, supportsReasoningBudget: true, - requiredReasoningBudget: true, + requiredReasoningBudget: false, }, "gemini-2.5-flash-preview-04-17": { maxTokens: 65_535, @@ -36,7 +36,7 @@ export const geminiModels = { cacheWritesPrice: 1.0, maxThinkingTokens: 24_576, supportsReasoningBudget: true, - requiredReasoningBudget: true, + requiredReasoningBudget: false, }, "gemini-2.5-flash-preview-05-20": { maxTokens: 65_535, diff --git a/packages/types/src/providers/vertex.ts b/packages/types/src/providers/vertex.ts index 11aa1aaa4a..f384adf1c6 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -15,7 +15,7 @@ export const vertexModels = { outputPrice: 3.5, maxThinkingTokens: 24_576, supportsReasoningBudget: true, - requiredReasoningBudget: true, + requiredReasoningBudget: false, }, "gemini-2.5-flash-preview-05-20": { maxTokens: 65_535, @@ -34,7 +34,7 @@ export const vertexModels = { outputPrice: 3.5, maxThinkingTokens: 24_576, supportsReasoningBudget: true, - requiredReasoningBudget: true, + requiredReasoningBudget: false, }, "gemini-2.5-flash-preview-04-17": { maxTokens: 65_535, diff --git a/src/api/providers/gemini.ts b/src/api/providers/gemini.ts index 5addc07a92..e59ec9ddb9 100644 --- a/src/api/providers/gemini.ts +++ b/src/api/providers/gemini.ts @@ -81,7 +81,28 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined for await (const chunk of result) { - if (chunk.text) { + // Process candidates and their parts to separate thoughts from content + if (chunk.candidates && chunk.candidates.length > 0) { + const candidate = chunk.candidates[0] + if (candidate.content && candidate.content.parts) { + for (const part of candidate.content.parts) { + if (part.thought) { + // This is a thinking/reasoning part + if (part.text) { + yield { type: "reasoning", text: part.text } + } + } else { + // This is regular content + if (part.text) { + yield { type: "text", text: part.text } + } + } + } + } + } + + // Fallback to the original text property if no candidates structure + else if (chunk.text) { yield { type: "text", text: chunk.text } } @@ -117,12 +138,14 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl if (geminiModels[id as GeminiModelId]) { info = geminiModels[id as GeminiModelId] + const thinkingConfig = this.options.modelMaxThinkingTokens + ? { includeThoughts: true, thinkingBudget: this.options.modelMaxThinkingTokens } + : { includeThoughts: true } + return { id, info, - thinkingConfig: this.options.modelMaxThinkingTokens - ? { thinkingBudget: this.options.modelMaxThinkingTokens } - : undefined, + thinkingConfig, maxOutputTokens: this.options.modelMaxTokens ?? info.maxTokens ?? undefined, } } diff --git a/src/api/providers/vertex.ts b/src/api/providers/vertex.ts index fdd51e0666..6dfbfb87bb 100644 --- a/src/api/providers/vertex.ts +++ b/src/api/providers/vertex.ts @@ -20,12 +20,14 @@ export class VertexHandler extends GeminiHandler implements SingleCompletionHand if (vertexModels[id as VertexModelId]) { info = vertexModels[id as VertexModelId] + const thinkingConfig = this.options.modelMaxThinkingTokens + ? { includeThoughts: true, thinkingBudget: this.options.modelMaxThinkingTokens } + : { includeThoughts: true } + return { id, info, - thinkingConfig: this.options.modelMaxThinkingTokens - ? { thinkingBudget: this.options.modelMaxThinkingTokens } - : undefined, + thinkingConfig, maxOutputTokens: this.options.modelMaxTokens ?? info.maxTokens ?? undefined, } } diff --git a/src/package.json b/src/package.json index ed12c22a65..60752cce57 100644 --- a/src/package.json +++ b/src/package.json @@ -339,7 +339,7 @@ } }, "scripts": { - "lint": "eslint . --ext=ts --max-warnings=0", + "lint": "eslint . --max-warnings=0", "check-types": "tsc --noEmit", "pretest": "turbo run bundle --cwd ..", "test": "jest -w=40% && vitest run", diff --git a/webview-ui/eslint.config.mjs b/webview-ui/eslint.config.mjs index db76f49211..833f5397c8 100644 --- a/webview-ui/eslint.config.mjs +++ b/webview-ui/eslint.config.mjs @@ -5,6 +5,16 @@ export default [ ...reactConfig, { rules: { + // Disable base ESLint rule to avoid conflicts with TypeScript ESLint version + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": [ + "error", + { + allowShortCircuit: true, + allowTernary: true, + allowTaggedTemplates: true, + }, + ], "@typescript-eslint/no-unused-vars": [ "error", { diff --git a/webview-ui/package.json b/webview-ui/package.json index df074c2d7b..959b24b8b0 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "lint": "eslint src --ext=ts,tsx --max-warnings=0", + "lint": "eslint src --max-warnings=0", "check-types": "tsc", "pretest": "turbo run bundle --cwd ..", "test": "jest -w=40%", diff --git a/webview-ui/src/components/settings/ThinkingBudget.tsx b/webview-ui/src/components/settings/ThinkingBudget.tsx index 456e0be17a..f4037b6b70 100644 --- a/webview-ui/src/components/settings/ThinkingBudget.tsx +++ b/webview-ui/src/components/settings/ThinkingBudget.tsx @@ -21,7 +21,9 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod const isReasoningBudgetRequired = !!modelInfo && modelInfo.requiredReasoningBudget const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort - const enableReasoningEffort = apiConfiguration.enableReasoningEffort + // For Gemini Flash thinking models, default to enabling reasoning if not explicitly set + const isGeminiFlashThinking = modelInfo && modelInfo.supportsReasoningBudget && !modelInfo.requiredReasoningBudget + const enableReasoningEffort = apiConfiguration.enableReasoningEffort ?? (isGeminiFlashThinking ? true : false) const customMaxOutputTokens = apiConfiguration.modelMaxTokens || DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS const customMaxThinkingTokens = apiConfiguration.modelMaxThinkingTokens || DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS @@ -32,6 +34,13 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod ? Math.min(modelInfo.maxThinkingTokens, Math.floor(0.8 * customMaxOutputTokens)) : Math.floor(0.8 * customMaxOutputTokens) + // Initialize enableReasoningEffort to true for Gemini Flash thinking models if not set + useEffect(() => { + if (isGeminiFlashThinking && apiConfiguration.enableReasoningEffort === undefined) { + setApiConfigurationField("enableReasoningEffort", true) + } + }, [isGeminiFlashThinking, apiConfiguration.enableReasoningEffort, setApiConfigurationField]) + // If the custom max thinking tokens are going to exceed it's limit due // to the custom max output tokens being reduced then we need to shrink it // appropriately. @@ -73,19 +82,54 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
{customMaxOutputTokens}
-
-
{t("settings:thinkingBudget.maxThinkingTokens")}
-
- setApiConfigurationField("modelMaxThinkingTokens", value)} - /> -
{customMaxThinkingTokens}
+ {isGeminiFlashThinking ? ( +
+ { + if (checked) { + setApiConfigurationField( + "modelMaxThinkingTokens", + DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS, + ) + } else { + setApiConfigurationField("modelMaxThinkingTokens", undefined) + } + }}> + {t("settings:thinkingBudget.manualThinkingBudget")} + + {!!apiConfiguration.modelMaxThinkingTokens && ( +
+ + setApiConfigurationField("modelMaxThinkingTokens", value) + } + /> +
{customMaxThinkingTokens}
+
+ )}
-
+ ) : ( +
+
{t("settings:thinkingBudget.maxThinkingTokens")}
+
+ + setApiConfigurationField("modelMaxThinkingTokens", value) + } + /> +
{customMaxThinkingTokens}
+
+
+ )} )} diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 71a7ff3c7b..0a5e47e137 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Tokens màxims", - "maxThinkingTokens": "Tokens de pensament màxims" + "maxThinkingTokens": "Tokens de pensament màxims", + "manualThinkingBudget": "Establir pressupost manual de pensament (si no, el model decideix automàticament)" }, "validation": { "apiKey": "Heu de proporcionar una clau API vàlida.", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index e34b4f1f9b..8172a5ccf3 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Maximale Tokens", - "maxThinkingTokens": "Maximale Thinking-Tokens" + "maxThinkingTokens": "Maximale Thinking-Tokens", + "manualThinkingBudget": "Manuelles Thinking-Budget festlegen (sonst entscheidet das Modell automatisch)" }, "validation": { "apiKey": "Du musst einen gültigen API-Schlüssel angeben.", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 4aaf873cb2..aa489c2344 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Max Tokens", - "maxThinkingTokens": "Max Thinking Tokens" + "maxThinkingTokens": "Max Thinking Tokens", + "manualThinkingBudget": "Set manual thinking budget (otherwise model decides automatically)" }, "validation": { "apiKey": "You must provide a valid API key.", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index e48d143393..d124cc9823 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Tokens máximos", - "maxThinkingTokens": "Tokens máximos de pensamiento" + "maxThinkingTokens": "Tokens máximos de pensamiento", + "manualThinkingBudget": "Establecer presupuesto manual de pensamientos (de lo contrario el modelo decide automáticamente)" }, "validation": { "apiKey": "Debe proporcionar una clave API válida.", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index f39a67b3a0..3007e83f49 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Tokens maximum", - "maxThinkingTokens": "Tokens de réflexion maximum" + "maxThinkingTokens": "Tokens de réflexion maximum", + "manualThinkingBudget": "Définir un budget manuel de réflexion (sinon le modèle décide automatiquement)" }, "validation": { "apiKey": "Vous devez fournir une clé API valide.", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 3057eb1723..5588b88d6c 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "अधिकतम tokens", - "maxThinkingTokens": "अधिकतम thinking tokens" + "maxThinkingTokens": "अधिकतम thinking tokens", + "manualThinkingBudget": "मैन्युअल सोच बजट सेट करें (अन्यथा मॉडल अपने आप निर्णय लेता है)" }, "validation": { "apiKey": "आपको एक मान्य API कुंजी प्रदान करनी होगी।", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 7149ca7a25..75fb113a0e 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Token massimi", - "maxThinkingTokens": "Token massimi di pensiero" + "maxThinkingTokens": "Token massimi di pensiero", + "manualThinkingBudget": "Imposta budget manuale di pensiero (altrimenti il modello decide automaticamente)" }, "validation": { "apiKey": "È necessario fornire una chiave API valida.", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 87be7c8ced..6c71632446 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "最大 tokens", - "maxThinkingTokens": "最大思考 tokens" + "maxThinkingTokens": "最大思考 tokens", + "manualThinkingBudget": "手動思考予算を設定(そうでなければモデルが自動的に決定)" }, "validation": { "apiKey": "有効なAPIキーを入力してください。", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 3c0077e1fb..5cff3c92d7 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "최대 tokens", - "maxThinkingTokens": "최대 사고 tokens" + "maxThinkingTokens": "최대 사고 tokens", + "manualThinkingBudget": "수동 사고 예산 설정 (그렇지 않으면 모델이 자동으로 결정)" }, "validation": { "apiKey": "유효한 API 키를 입력해야 합니다.", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 685eab40fe..69abba8a21 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Max tokens", - "maxThinkingTokens": "Max denk-tokens" + "maxThinkingTokens": "Max denk-tokens", + "manualThinkingBudget": "Handmatig denkbudget instellen (anders beslist het model automatisch)" }, "validation": { "apiKey": "Je moet een geldige API-sleutel opgeven.", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index f89e9330d6..a4538722b1 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Maksymalna liczba tokenów", - "maxThinkingTokens": "Maksymalna liczba tokenów myślenia" + "maxThinkingTokens": "Maksymalna liczba tokenów myślenia", + "manualThinkingBudget": "Ustaw manualny budżet myślenia (w przeciwnym razie model decyduje automatycznie)" }, "validation": { "apiKey": "Musisz podać prawidłowy klucz API.", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index ed2540b630..ba85481499 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Tokens máximos", - "maxThinkingTokens": "Tokens máximos de pensamento" + "maxThinkingTokens": "Tokens máximos de pensamento", + "manualThinkingBudget": "Definir orçamento manual de pensamento (caso contrário o modelo decide automaticamente)" }, "validation": { "apiKey": "Você deve fornecer uma chave de API válida.", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 385be646c9..3d26ea56dd 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Максимум токенов", - "maxThinkingTokens": "Максимум токенов на размышления" + "maxThinkingTokens": "Максимум токенов на размышления", + "manualThinkingBudget": "Установить ручной бюджет размышлений (иначе модель решает автоматически)" }, "validation": { "apiKey": "Вы должны указать действительный API-ключ.", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index e97c6d7505..fd8d906d32 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Maksimum token", - "maxThinkingTokens": "Maksimum düşünme tokeni" + "maxThinkingTokens": "Maksimum düşünme tokeni", + "manualThinkingBudget": "Manuel düşünme bütçesi ayarla (aksi takdirde model otomatik karar verir)" }, "validation": { "apiKey": "Geçerli bir API anahtarı sağlamalısınız.", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 28eabc8037..96c91fa914 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "Tokens tối đa", - "maxThinkingTokens": "Tokens suy nghĩ tối đa" + "maxThinkingTokens": "Tokens suy nghĩ tối đa", + "manualThinkingBudget": "Đặt ngân sách suy nghĩ thủ công (nếu không, mô hình sẽ quyết định tự động)" }, "validation": { "apiKey": "Bạn phải cung cấp khóa API hợp lệ.", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 0b979b5e2f..0644f7a1c6 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "最大Token数", - "maxThinkingTokens": "最大思考Token数" + "maxThinkingTokens": "最大思考Token数", + "manualThinkingBudget": "设置手动思考预算(否则模型自动决定)" }, "validation": { "apiKey": "您必须提供有效的 API 密钥。", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 1018d5934c..f913f195f2 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -547,7 +547,8 @@ }, "thinkingBudget": { "maxTokens": "最大 token 數", - "maxThinkingTokens": "最大思考 token 數" + "maxThinkingTokens": "最大思考 token 數", + "manualThinkingBudget": "設定手動思考預算(否則由模型自動決定)" }, "validation": { "apiKey": "請提供有效的 API 金鑰。",