Просмотр исходного кода

Welcome page OAuth (#1913)

* Add Requesty OAuth flow

* New 1-click onboarding flow

* Requesty: Use correct default model info

* When called from the onboard flow, created the default profile

Glama OAuth handler changed for consistency.

* Add router images

* Shuffle the routers

* Translate

* Appease knip

---------

Co-authored-by: Daniel Trugman <[email protected]>
Matt Rubens 9 месяцев назад
Родитель
Сommit
e810a886d2
31 измененных файлов с 502 добавлено и 141 удалено
  1. 5 0
      .changeset/late-chefs-remember.md
  2. 2 1
      .vscodeignore
  3. BIN
      assets/images/openrouter.png
  4. BIN
      assets/images/requesty.png
  5. 7 1
      src/activate/handleUri.ts
  6. 50 14
      src/api/providers/__tests__/requesty.test.ts
  7. 3 3
      src/api/providers/requesty.ts
  8. 81 48
      src/core/webview/ClineProvider.ts
  9. 0 9
      src/shared/api.ts
  10. 14 0
      webview-ui/package-lock.json
  11. 1 0
      webview-ui/package.json
  12. 0 15
      webview-ui/src/components/common/Alert.tsx
  13. 17 11
      webview-ui/src/components/settings/ApiOptions.tsx
  14. 81 14
      webview-ui/src/components/welcome/WelcomeView.tsx
  15. 14 1
      webview-ui/src/i18n/locales/ca/welcome.json
  16. 14 1
      webview-ui/src/i18n/locales/de/welcome.json
  17. 14 1
      webview-ui/src/i18n/locales/en/welcome.json
  18. 14 1
      webview-ui/src/i18n/locales/es/welcome.json
  19. 18 5
      webview-ui/src/i18n/locales/fr/welcome.json
  20. 14 1
      webview-ui/src/i18n/locales/hi/welcome.json
  21. 14 1
      webview-ui/src/i18n/locales/it/welcome.json
  22. 14 1
      webview-ui/src/i18n/locales/ja/welcome.json
  23. 14 1
      webview-ui/src/i18n/locales/ko/welcome.json
  24. 14 1
      webview-ui/src/i18n/locales/pl/welcome.json
  25. 14 1
      webview-ui/src/i18n/locales/pt-BR/welcome.json
  26. 18 5
      webview-ui/src/i18n/locales/tr/welcome.json
  27. 14 1
      webview-ui/src/i18n/locales/vi/welcome.json
  28. 14 1
      webview-ui/src/i18n/locales/zh-CN/welcome.json
  29. 16 3
      webview-ui/src/i18n/locales/zh-TW/welcome.json
  30. 16 0
      webview-ui/src/oauth/urls.ts
  31. 5 0
      webview-ui/src/types.d.ts

+ 5 - 0
.changeset/late-chefs-remember.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Update the welcome page to provide 1-click OAuth flows with LLM routers or more advanced configuration with custom providers.

+ 2 - 1
.vscodeignore

@@ -48,8 +48,9 @@ webview-ui/node_modules/**
 # Include default themes JSON files used in getTheme
 !src/integrations/theme/default-themes/**
 
-# Include icons
+# Include icons and images
 !assets/icons/**
+!assets/images/**
 
 # Include .env file for telemetry
 !.env

BIN
assets/images/openrouter.png


BIN
assets/images/requesty.png


+ 7 - 1
src/activate/handleUri.ts

@@ -6,7 +6,6 @@ export const handleUri = async (uri: vscode.Uri) => {
 	const path = uri.path
 	const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
 	const visibleProvider = ClineProvider.getVisibleInstance()
-
 	if (!visibleProvider) {
 		return
 	}
@@ -26,6 +25,13 @@ export const handleUri = async (uri: vscode.Uri) => {
 			}
 			break
 		}
+		case "/requesty": {
+			const code = query.get("code")
+			if (code) {
+				await visibleProvider.handleRequestyCallback(code)
+			}
+			break
+		}
 		default:
 			break
 	}

+ 50 - 14
src/api/providers/__tests__/requesty.test.ts

@@ -1,6 +1,6 @@
 import { Anthropic } from "@anthropic-ai/sdk"
 import OpenAI from "openai"
-import { ApiHandlerOptions, ModelInfo, requestyModelInfoSaneDefaults } from "../../../shared/api"
+import { ApiHandlerOptions, ModelInfo, requestyDefaultModelInfo } from "../../../shared/api"
 import { RequestyHandler } from "../requesty"
 import { convertToOpenAiMessages } from "../../transform/openai-format"
 import { convertToR1Format } from "../../transform/r1-format"
@@ -18,14 +18,17 @@ describe("RequestyHandler", () => {
 		requestyApiKey: "test-key",
 		requestyModelId: "test-model",
 		requestyModelInfo: {
-			maxTokens: 1000,
-			contextWindow: 4000,
-			supportsPromptCache: false,
+			maxTokens: 8192,
+			contextWindow: 200_000,
 			supportsImages: true,
-			inputPrice: 1,
-			outputPrice: 10,
-			cacheReadsPrice: 0.1,
-			cacheWritesPrice: 1.5,
+			supportsComputerUse: true,
+			supportsPromptCache: true,
+			inputPrice: 3.0,
+			outputPrice: 15.0,
+			cacheWritesPrice: 3.75,
+			cacheReadsPrice: 0.3,
+			description:
+				"Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. Claude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks. Read more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)",
 		},
 		openAiStreamingEnabled: true,
 		includeMaxTokens: true, // Add this to match the implementation
@@ -115,7 +118,7 @@ describe("RequestyHandler", () => {
 						outputTokens: 10,
 						cacheWriteTokens: 5,
 						cacheReadTokens: 15,
-						totalCost: 0.000119, // (10 * 1 / 1,000,000) + (5 * 1.5 / 1,000,000) + (15 * 0.1 / 1,000,000) + (10 * 10 / 1,000,000)
+						totalCost: 0.00020325000000000003, // (10 * 3 / 1,000,000) + (5 * 3.75 / 1,000,000) + (15 * 0.3 / 1,000,000) + (10 * 15 / 1,000,000) (the ...0 is a fp skew)
 					},
 				])
 
@@ -123,8 +126,30 @@ describe("RequestyHandler", () => {
 					model: defaultOptions.requestyModelId,
 					temperature: 0,
 					messages: [
-						{ role: "system", content: systemPrompt },
-						{ role: "user", content: "Hello" },
+						{
+							role: "system",
+							content: [
+								{
+									cache_control: {
+										type: "ephemeral",
+									},
+									text: systemPrompt,
+									type: "text",
+								},
+							],
+						},
+						{
+							role: "user",
+							content: [
+								{
+									cache_control: {
+										type: "ephemeral",
+									},
+									text: "Hello",
+									type: "text",
+								},
+							],
+						},
 					],
 					stream: true,
 					stream_options: { include_usage: true },
@@ -191,7 +216,7 @@ describe("RequestyHandler", () => {
 						outputTokens: 5,
 						cacheWriteTokens: 0,
 						cacheReadTokens: 0,
-						totalCost: 0.00006, // (10 * 1 / 1,000,000) + (5 * 10 / 1,000,000)
+						totalCost: 0.000105, // (10 * 3 / 1,000,000) + (5 * 15 / 1,000,000)
 					},
 				])
 
@@ -199,7 +224,18 @@ describe("RequestyHandler", () => {
 					model: defaultOptions.requestyModelId,
 					messages: [
 						{ role: "user", content: systemPrompt },
-						{ role: "user", content: "Hello" },
+						{
+							role: "user",
+							content: [
+								{
+									cache_control: {
+										type: "ephemeral",
+									},
+									text: "Hello",
+									type: "text",
+								},
+							],
+						},
 					],
 				})
 			})
@@ -224,7 +260,7 @@ describe("RequestyHandler", () => {
 			const result = handler.getModel()
 			expect(result).toEqual({
 				id: defaultOptions.requestyModelId,
-				info: requestyModelInfoSaneDefaults,
+				info: defaultOptions.requestyModelInfo,
 			})
 		})
 	})

+ 3 - 3
src/api/providers/requesty.ts

@@ -1,6 +1,6 @@
 import axios from "axios"
 
-import { ModelInfo, requestyModelInfoSaneDefaults, requestyDefaultModelId } from "../../shared/api"
+import { ModelInfo, requestyDefaultModelInfo, requestyDefaultModelId } from "../../shared/api"
 import { calculateApiCostOpenAI, parseApiPrice } from "../../utils/cost"
 import { ApiStreamUsageChunk } from "../transform/stream"
 import { OpenAiHandler, OpenAiHandlerOptions } from "./openai"
@@ -26,7 +26,7 @@ export class RequestyHandler extends OpenAiHandler {
 			openAiApiKey: options.requestyApiKey,
 			openAiModelId: options.requestyModelId ?? requestyDefaultModelId,
 			openAiBaseUrl: "https://router.requesty.ai/v1",
-			openAiCustomModelInfo: options.requestyModelInfo ?? requestyModelInfoSaneDefaults,
+			openAiCustomModelInfo: options.requestyModelInfo ?? requestyDefaultModelInfo,
 		})
 	}
 
@@ -34,7 +34,7 @@ export class RequestyHandler extends OpenAiHandler {
 		const modelId = this.options.requestyModelId ?? requestyDefaultModelId
 		return {
 			id: modelId,
-			info: this.options.requestyModelInfo ?? requestyModelInfoSaneDefaults,
+			info: this.options.requestyModelInfo ?? requestyDefaultModelInfo,
 		}
 	}
 

+ 81 - 48
src/core/webview/ClineProvider.ts

@@ -10,7 +10,18 @@ import * as vscode from "vscode"
 
 import { changeLanguage, t } from "../../i18n"
 import { setPanel } from "../../activate/registerCommands"
-import { ApiConfiguration, ApiProvider, ModelInfo, API_CONFIG_KEYS } from "../../shared/api"
+import {
+	ApiConfiguration,
+	ApiProvider,
+	ModelInfo,
+	API_CONFIG_KEYS,
+	requestyDefaultModelId,
+	requestyDefaultModelInfo,
+	openRouterDefaultModelId,
+	openRouterDefaultModelInfo,
+	glamaDefaultModelId,
+	glamaDefaultModelInfo,
+} from "../../shared/api"
 import { findLast } from "../../shared/array"
 import { supportPrompt } from "../../shared/support-prompt"
 import { GlobalFileNames } from "../../shared/globalFileNames"
@@ -593,6 +604,8 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 			"codicon.css",
 		])
 
+		const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
+
 		const file = "src/index.tsx"
 		const scriptUri = `http://${localServerUrl}/${file}`
 
@@ -611,7 +624,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 			`font-src ${webview.cspSource}`,
 			`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
 			`img-src ${webview.cspSource} data:`,
-			`script-src 'unsafe-eval' https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
+			`script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
 			`connect-src https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
 		]
 
@@ -624,6 +637,9 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 					<meta http-equiv="Content-Security-Policy" content="${csp.join("; ")}">
 					<link rel="stylesheet" type="text/css" href="${stylesUri}">
 					<link href="${codiconsUri}" rel="stylesheet" />
+					<script nonce="${nonce}">
+						window.IMAGES_BASE_URI = "${imagesUri}"
+					</script>
 					<title>Roo Code</title>
 				</head>
 				<body>
@@ -672,6 +688,8 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 			"codicon.css",
 		])
 
+		const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
+
 		// const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js"))
 
 		// const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "reset.css"))
@@ -704,6 +722,9 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
             <meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} data:; script-src 'nonce-${nonce}' https://us-assets.i.posthog.com; connect-src https://openrouter.ai https://us.i.posthog.com https://us-assets.i.posthog.com;">
             <link rel="stylesheet" type="text/css" href="${stylesUri}">
 			<link href="${codiconsUri}" rel="stylesheet" />
+			<script nonce="${nonce}">
+				window.IMAGES_BASE_URI = "${imagesUri}"
+			</script>
             <title>Roo Code</title>
           </head>
           <body>
@@ -1811,23 +1832,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 						break
 					case "upsertApiConfiguration":
 						if (message.text && message.apiConfiguration) {
-							try {
-								await this.configManager.saveConfig(message.text, message.apiConfiguration)
-								const listApiConfig = await this.configManager.listConfig()
-
-								await Promise.all([
-									this.updateGlobalState("listApiConfigMeta", listApiConfig),
-									this.updateApiConfiguration(message.apiConfiguration),
-									this.updateGlobalState("currentApiConfigName", message.text),
-								])
-
-								await this.postStateToWebview()
-							} catch (error) {
-								this.outputChannel.appendLine(
-									`Error create new api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
-								)
-								vscode.window.showErrorMessage(t("common:errors.create_api_config"))
-							}
+							await this.upsertApiConfiguration(message.text, message.apiConfiguration)
 						}
 						break
 					case "renameApiConfiguration":
@@ -2251,9 +2256,10 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 	// OpenRouter
 
 	async handleOpenRouterCallback(code: string) {
+		let { apiConfiguration, currentApiConfigName } = await this.getState()
+
 		let apiKey: string
 		try {
-			const { apiConfiguration } = await this.getState()
 			const baseUrl = apiConfiguration.openRouterBaseUrl || "https://openrouter.ai/api/v1"
 			// Extract the base domain for the auth endpoint
 			const baseUrlDomain = baseUrl.match(/^(https?:\/\/[^\/]+)/)?.[1] || "https://openrouter.ai"
@@ -2270,17 +2276,15 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 			throw error
 		}
 
-		const openrouter: ApiProvider = "openrouter"
-		await this.contextProxy.setValues({
-			apiProvider: openrouter,
+		const newConfiguration: ApiConfiguration = {
+			...apiConfiguration,
+			apiProvider: "openrouter",
 			openRouterApiKey: apiKey,
-		})
-
-		await this.postStateToWebview()
-		if (this.getCurrentCline()) {
-			this.getCurrentCline()!.api = buildApiHandler({ apiProvider: openrouter, openRouterApiKey: apiKey })
+			openRouterModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
+			openRouterModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo,
 		}
-		// await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome
+
+		await this.upsertApiConfiguration(currentApiConfigName, newConfiguration)
 	}
 
 	// Glama
@@ -2301,19 +2305,55 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 			throw error
 		}
 
-		const glama: ApiProvider = "glama"
-		await this.contextProxy.setValues({
-			apiProvider: glama,
+		const { apiConfiguration, currentApiConfigName } = await this.getState()
+
+		const newConfiguration: ApiConfiguration = {
+			...apiConfiguration,
+			apiProvider: "glama",
 			glamaApiKey: apiKey,
-		})
-		await this.postStateToWebview()
-		if (this.getCurrentCline()) {
-			this.getCurrentCline()!.api = buildApiHandler({
-				apiProvider: glama,
-				glamaApiKey: apiKey,
-			})
+			glamaModelId: apiConfiguration?.glamaModelId || glamaDefaultModelId,
+			glamaModelInfo: apiConfiguration?.glamaModelInfo || glamaDefaultModelInfo,
+		}
+
+		await this.upsertApiConfiguration(currentApiConfigName, newConfiguration)
+	}
+
+	// Requesty
+
+	async handleRequestyCallback(code: string) {
+		let { apiConfiguration, currentApiConfigName } = await this.getState()
+
+		const newConfiguration: ApiConfiguration = {
+			...apiConfiguration,
+			apiProvider: "requesty",
+			requestyApiKey: code,
+			requestyModelId: apiConfiguration?.requestyModelId || requestyDefaultModelId,
+			requestyModelInfo: apiConfiguration?.requestyModelInfo || requestyDefaultModelInfo,
+		}
+
+		await this.upsertApiConfiguration(currentApiConfigName, newConfiguration)
+	}
+
+	// Save configuration
+
+	async upsertApiConfiguration(configName: string, apiConfiguration: ApiConfiguration) {
+		try {
+			await this.configManager.saveConfig(configName, apiConfiguration)
+			const listApiConfig = await this.configManager.listConfig()
+
+			await Promise.all([
+				this.updateGlobalState("listApiConfigMeta", listApiConfig),
+				this.updateApiConfiguration(apiConfiguration),
+				this.updateGlobalState("currentApiConfigName", configName),
+			])
+
+			await this.postStateToWebview()
+		} catch (error) {
+			this.outputChannel.appendLine(
+				`Error create new api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
+			)
+			vscode.window.showErrorMessage(t("common:errors.create_api_config"))
 		}
-		// await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome
 	}
 
 	// Task history
@@ -2627,14 +2667,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 		if (stateValues.apiProvider) {
 			apiProvider = stateValues.apiProvider
 		} else {
-			// Either new user or legacy user that doesn't have the apiProvider stored in state
-			// (If they're using OpenRouter or Bedrock, then apiProvider state will exist)
-			if (secretValues.apiKey) {
-				apiProvider = "anthropic"
-			} else {
-				// New users should default to openrouter
-				apiProvider = "openrouter"
-			}
+			apiProvider = "anthropic"
 		}
 
 		// Build the apiConfiguration object combining state values and secrets

+ 0 - 9
src/shared/api.ts

@@ -740,15 +740,6 @@ export const openAiModelInfoSaneDefaults: ModelInfo = {
 	outputPrice: 0,
 }
 
-export const requestyModelInfoSaneDefaults: ModelInfo = {
-	maxTokens: -1,
-	contextWindow: 128_000,
-	supportsImages: true,
-	supportsPromptCache: false,
-	inputPrice: 0,
-	outputPrice: 0,
-}
-
 // Gemini
 // https://ai.google.dev/gemini-api/docs/models/gemini
 export type GeminiModelId = keyof typeof geminiModels

+ 14 - 0
webview-ui/package-lock.json

@@ -31,6 +31,7 @@
 				"fzf": "^0.5.2",
 				"i18next": "^24.2.2",
 				"i18next-http-backend": "^3.0.2",
+				"knuth-shuffle-seeded": "^1.0.6",
 				"lucide-react": "^0.475.0",
 				"mermaid": "^11.4.1",
 				"posthog-js": "^1.227.2",
@@ -15017,6 +15018,14 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/knuth-shuffle-seeded": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz",
+			"integrity": "sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==",
+			"dependencies": {
+				"seed-random": "~2.2.0"
+			}
+		},
 		"node_modules/kolorist": {
 			"version": "1.8.0",
 			"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
@@ -19786,6 +19795,11 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
+		"node_modules/seed-random": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz",
+			"integrity": "sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ=="
+		},
 		"node_modules/semver": {
 			"version": "6.3.1",
 			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",

+ 1 - 0
webview-ui/package.json

@@ -41,6 +41,7 @@
 		"fzf": "^0.5.2",
 		"i18next": "^24.2.2",
 		"i18next-http-backend": "^3.0.2",
+		"knuth-shuffle-seeded": "^1.0.6",
 		"lucide-react": "^0.475.0",
 		"mermaid": "^11.4.1",
 		"posthog-js": "^1.227.2",

+ 0 - 15
webview-ui/src/components/common/Alert.tsx

@@ -1,15 +0,0 @@
-import { cn } from "@/lib/utils"
-import { HTMLAttributes } from "react"
-
-type AlertProps = HTMLAttributes<HTMLDivElement>
-
-export const Alert = ({ className, children, ...props }: AlertProps) => (
-	<div
-		className={cn(
-			"text-vscode-inputValidation-infoForeground bg-vscode-inputValidation-infoBackground border border-vscode-inputValidation-infoBorder rounded-xs p-2",
-			className,
-		)}
-		{...props}>
-		{children}
-	</div>
-)

+ 17 - 11
webview-ui/src/components/settings/ApiOptions.tsx

@@ -1,6 +1,7 @@
 import React, { memo, useCallback, useEffect, useMemo, useState } from "react"
 import { useAppTranslation } from "@/i18n/TranslationContext"
 import { Trans } from "react-i18next"
+import { getRequestyAuthUrl, getOpenRouterAuthUrl, getGlamaAuthUrl } from "../../oauth/urls"
 import { useDebounce, useEvent } from "react-use"
 import { LanguageModelChatSelector } from "vscode"
 import { Checkbox } from "vscrui"
@@ -279,7 +280,10 @@ const ApiOptions = ({
 						{t("settings:providers.apiKeyStorageNotice")}
 					</div>
 					{!apiConfiguration?.openRouterApiKey && (
-						<VSCodeButtonLink href={getOpenRouterAuthUrl(uriScheme)} appearance="secondary">
+						<VSCodeButtonLink
+							href={getOpenRouterAuthUrl(uriScheme)}
+							style={{ width: "100%" }}
+							appearance="primary">
 							{t("settings:providers.getOpenRouterApiKey")}
 						</VSCodeButtonLink>
 					)}
@@ -380,7 +384,10 @@ const ApiOptions = ({
 						{t("settings:providers.apiKeyStorageNotice")}
 					</div>
 					{!apiConfiguration?.glamaApiKey && (
-						<VSCodeButtonLink href={getGlamaAuthUrl(uriScheme)} appearance="secondary">
+						<VSCodeButtonLink
+							href={getGlamaAuthUrl(uriScheme)}
+							style={{ width: "100%" }}
+							appearance="primary">
 							{t("settings:providers.getGlamaApiKey")}
 						</VSCodeButtonLink>
 					)}
@@ -400,6 +407,14 @@ const ApiOptions = ({
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
 					</div>
+					{!apiConfiguration?.requestyApiKey && (
+						<VSCodeButtonLink
+							href={getRequestyAuthUrl(uriScheme)}
+							style={{ width: "100%" }}
+							appearance="primary">
+							{t("settings:providers.getRequestyApiKey")}
+						</VSCodeButtonLink>
+					)}
 				</>
 			)}
 
@@ -1536,15 +1551,6 @@ const ApiOptions = ({
 	)
 }
 
-export function getGlamaAuthUrl(uriScheme?: string) {
-	const callbackUrl = `${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/glama`
-	return `https://glama.ai/oauth/authorize?callback_url=${encodeURIComponent(callbackUrl)}`
-}
-
-export function getOpenRouterAuthUrl(uriScheme?: string) {
-	return `https://openrouter.ai/auth?callback_url=${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/openrouter`
-}
-
 export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
 	const provider = apiConfiguration?.apiProvider || "anthropic"
 	const modelId = apiConfiguration?.apiModelId

+ 81 - 14
webview-ui/src/components/welcome/WelcomeView.tsx

@@ -1,18 +1,17 @@
 import { useCallback, useState } from "react"
 import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
-
 import { useExtensionState } from "../../context/ExtensionStateContext"
 import { validateApiConfiguration } from "../../utils/validate"
 import { vscode } from "../../utils/vscode"
 import ApiOptions from "../settings/ApiOptions"
 import { Tab, TabContent } from "../common/Tab"
-import { Alert } from "../common/Alert"
 import { useAppTranslation } from "../../i18n/TranslationContext"
+import { getRequestyAuthUrl, getOpenRouterAuthUrl } from "../../oauth/urls"
+import knuthShuffle from "knuth-shuffle-seeded"
 
 const WelcomeView = () => {
-	const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState()
+	const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme, machineId } = useExtensionState()
 	const { t } = useAppTranslation()
-
 	const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
 
 	const handleSubmit = useCallback(() => {
@@ -27,24 +26,92 @@ const WelcomeView = () => {
 		vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
 	}, [apiConfiguration, currentApiConfigName])
 
+	// Using a lazy initializer so it reads once at mount
+	const [imagesBaseUri] = useState(() => {
+		const w = window as any
+		return w.IMAGES_BASE_URI || ""
+	})
+
 	return (
 		<Tab>
 			<TabContent className="flex flex-col gap-5">
 				<h2 className="m-0 p-0">{t("welcome:greeting")}</h2>
 				<div>{t("welcome:introduction")}</div>
-				<Alert className="font-bold text-sm">{t("welcome:notice")}</Alert>
-				<ApiOptions
-					fromWelcomeView
-					apiConfiguration={apiConfiguration || {}}
-					uriScheme={uriScheme}
-					setApiConfigurationField={(field, value) => setApiConfiguration({ [field]: value })}
-					errorMessage={errorMessage}
-					setErrorMessage={setErrorMessage}
-				/>
+				<div>{t("welcome:chooseProvider")}</div>
+
+				<div className="mb-4">
+					<h4 className="mt-3 mb-2">{t("welcome:startRouter")}</h4>
+
+					<div className="flex gap-4">
+						{/* Define the providers */}
+						{(() => {
+							// Provider card configuration
+							const providers = [
+								{
+									slug: "requesty",
+									name: "Requesty",
+									description: t("welcome:routers.requesty.description"),
+									incentive: t("welcome:routers.requesty.incentive"),
+									authUrl: getRequestyAuthUrl(uriScheme),
+								},
+								{
+									slug: "openrouter",
+									name: "OpenRouter",
+									description: t("welcome:routers.openrouter.description"),
+									authUrl: getOpenRouterAuthUrl(uriScheme),
+								},
+							]
+
+							// Shuffle providers based on machine ID (will be consistent for the same machine)
+							const orderedProviders = [...providers]
+							knuthShuffle(orderedProviders, (machineId as any) || Date.now())
+
+							// Render the provider cards
+							return orderedProviders.map((provider, index) => (
+								<a
+									key={index}
+									href={provider.authUrl}
+									className="flex-1 border border-vscode-panel-border rounded p-4 flex flex-col items-center cursor-pointer transition-all hover:bg-vscode-button-hoverBackground hover:border-vscode-button-border no-underline text-inherit"
+									target="_blank"
+									rel="noopener noreferrer">
+									<div className="w-16 h-16 flex items-center justify-center rounded mb-2 overflow-hidden bg-white relative">
+										<img
+											src={`${imagesBaseUri}/${provider.slug}.png`}
+											alt={provider.name}
+											className="w-full h-full object-contain p-2"
+										/>
+									</div>
+									<div className="text-center">
+										<div className="font-bold">{provider.name}</div>
+										<div className="text-sm text-vscode-descriptionForeground">
+											{provider.description}
+										</div>
+										{provider.incentive && (
+											<div className="text-sm font-bold">{provider.incentive}</div>
+										)}
+									</div>
+								</a>
+							))
+						})()}
+					</div>
+
+					<div className="text-center my-4">{t("welcome:or")}</div>
+					<h4 className="mt-3 mb-2">{t("welcome:startCustom")}</h4>
+					<ApiOptions
+						fromWelcomeView
+						apiConfiguration={apiConfiguration || {}}
+						uriScheme={uriScheme}
+						setApiConfigurationField={(field, value) => setApiConfiguration({ [field]: value })}
+						errorMessage={errorMessage}
+						setErrorMessage={setErrorMessage}
+					/>
+				</div>
 			</TabContent>
 			<div className="sticky bottom-0 bg-vscode-sideBar-background p-5">
 				<div className="flex flex-col gap-1">
-					<VSCodeButton onClick={handleSubmit}>{t("welcome:start")}</VSCodeButton>
+					<VSCodeButton onClick={handleSubmit} appearance="primary">
+						{t("welcome:start")}
+					</VSCodeButton>
 					{errorMessage && <div className="text-vscode-errorForeground">{errorMessage}</div>}
 				</div>
 			</div>

+ 14 - 1
webview-ui/src/i18n/locales/ca/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "Puc fer tot tipus de tasques gràcies als últims avenços en capacitats de codificació agent i accés a eines que em permeten crear i editar fitxers, explorar projectes complexos, utilitzar el navegador i executar ordres de terminal (amb el teu permís, és clar). Fins i tot puc utilitzar MCP per crear noves eines i ampliar les meves pròpies capacitats.",
 	"notice": "Per començar, aquesta extensió necessita un proveïdor d'API.",
 	"start": "Som-hi!",
+	"chooseProvider": "Tria un proveïdor d'API per començar:",
+	"routers": {
+		"requesty": {
+			"description": "El teu router LLM optimitzat",
+			"incentive": "$1 de crèdit gratuït"
+		},
+		"openrouter": {
+			"description": "Una interfície unificada per a LLMs"
+		}
+	},
+	"startRouter": "Configuració ràpida a través d'un router",
+	"startCustom": "Utilitza la teva pròpia clau API",
 	"telemetry": {
 		"title": "Ajuda a millorar Roo Code",
 		"anonymousTelemetry": "Envia dades d'ús i errors anònims per ajudar-nos a corregir errors i millorar l'extensió. No s'envia mai cap codi, text o informació personal.",
@@ -10,5 +22,6 @@
 		"settings": "configuració",
 		"allow": "Permetre",
 		"deny": "Denegar"
-	}
+	},
+	"or": "o"
 }

+ 14 - 1
webview-ui/src/i18n/locales/de/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "Ich kann alle Arten von Aufgaben erledigen, dank der neuesten Durchbrüche in agentenbasierten Codierungsfähigkeiten und dem Zugang zu Tools, die es mir ermöglichen, Dateien zu erstellen und zu bearbeiten, komplexe Projekte zu erkunden, den Browser zu verwenden und Terminalbefehle auszuführen (natürlich mit deiner Erlaubnis). Ich kann sogar MCP verwenden, um neue Tools zu erstellen und meine eigenen Fähigkeiten zu erweitern.",
 	"notice": "Um loszulegen, benötigt diese Erweiterung einen API-Anbieter.",
 	"start": "Los geht's!",
+	"chooseProvider": "Wähle einen API-Anbieter, um zu beginnen:",
+	"routers": {
+		"requesty": {
+			"description": "Dein optimierter LLM-Router",
+			"incentive": "$1 Guthaben gratis"
+		},
+		"openrouter": {
+			"description": "Eine einheitliche Schnittstelle für LLMs"
+		}
+	},
+	"startRouter": "Express-Einrichtung über einen Router",
+	"startCustom": "Eigenen API-Schlüssel verwenden",
 	"telemetry": {
 		"title": "Hilf, Roo Code zu verbessern",
 		"anonymousTelemetry": "Sende anonyme Fehler- und Nutzungsdaten, um uns bei der Fehlerbehebung und Verbesserung der Erweiterung zu helfen. Es werden niemals Code, Texte oder persönliche Informationen gesendet.",
@@ -10,5 +22,6 @@
 		"settings": "Einstellungen",
 		"allow": "Erlauben",
 		"deny": "Ablehnen"
-	}
+	},
+	"or": "oder"
 }

+ 14 - 1
webview-ui/src/i18n/locales/en/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and access to tools that let me create & edit files, explore complex projects, use the browser, and execute terminal commands (with your permission, of course). I can even use MCP to create new tools and extend my own capabilities.",
 	"notice": "To get started, this extension needs an API provider.",
 	"start": "Let's go!",
+	"chooseProvider": "Choose an API provider to get started:",
+	"routers": {
+		"requesty": {
+			"description": "Your optimized LLM router",
+			"incentive": "$1 free credit"
+		},
+		"openrouter": {
+			"description": "A unified interface for LLMs"
+		}
+	},
+	"startRouter": "Express Setup Through a Router",
+	"startCustom": "Bring Your Own API Key",
 	"telemetry": {
 		"title": "Help Improve Roo Code",
 		"anonymousTelemetry": "Send anonymous error and usage data to help us fix bugs and improve the extension. No code, prompts, or personal information is ever sent.",
@@ -10,5 +22,6 @@
 		"settings": "settings",
 		"allow": "Allow",
 		"deny": "Deny"
-	}
+	},
+	"or": "or"
 }

+ 14 - 1
webview-ui/src/i18n/locales/es/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "Puedo realizar todo tipo de tareas gracias a los últimos avances en capacidades de codificación agentica y acceso a herramientas que me permiten crear y editar archivos, explorar proyectos complejos, usar el navegador y ejecutar comandos de terminal (con tu permiso, por supuesto). Incluso puedo usar MCP para crear nuevas herramientas y ampliar mis propias capacidades.",
 	"notice": "Para comenzar, esta extensión necesita un proveedor de API.",
 	"start": "¡Vamos!",
+	"chooseProvider": "Elige un proveedor de API para comenzar:",
+	"routers": {
+		"requesty": {
+			"description": "Tu router LLM optimizado",
+			"incentive": "$1 de crédito gratis"
+		},
+		"openrouter": {
+			"description": "Una interfaz unificada para LLMs"
+		}
+	},
+	"startRouter": "Configuración rápida a través de un router",
+	"startCustom": "Usa tu propia clave API",
 	"telemetry": {
 		"title": "Ayuda a mejorar Roo Code",
 		"anonymousTelemetry": "Envía datos de uso y errores anónimos para ayudarnos a corregir errores y mejorar la extensión. Nunca se envía código, texto o información personal.",
@@ -10,5 +22,6 @@
 		"settings": "configuración",
 		"allow": "Permitir",
 		"deny": "Denegar"
-	}
+	},
+	"or": "o"
 }

+ 18 - 5
webview-ui/src/i18n/locales/fr/welcome.json

@@ -1,14 +1,27 @@
 {
 	"greeting": "Salut, je suis Roo !",
-	"introduction": "Je peux effectuer toutes sortes de tâches grâce aux dernières avancées en matière de capacités de codage agentique et à l'accès à des outils qui me permettent de créer et de modifier des fichiers, d'explorer des projets complexes, d'utiliser le navigateur et d'exécuter des commandes terminal (avec votre permission, bien sûr). Je peux même utiliser MCP pour créer de nouveaux outils et étendre mes propres capacités.",
+	"introduction": "Je peux effectuer toutes sortes de tâches grâce aux dernières avancées en matière de capacités de codage agentique et à l'accès à des outils qui me permettent de créer et de modifier des fichiers, d'explorer des projets complexes, d'utiliser le navigateur et d'exécuter des commandes terminal (avec ta permission, bien sûr). Je peux même utiliser MCP pour créer de nouveaux outils et étendre mes propres capacités.",
 	"notice": "Pour commencer, cette extension a besoin d'un fournisseur d'API.",
 	"start": "C'est parti !",
+	"chooseProvider": "Choisis un fournisseur d'API pour commencer :",
+	"routers": {
+		"requesty": {
+			"description": "Ton routeur LLM optimisé",
+			"incentive": "1$ de crédit gratuit"
+		},
+		"openrouter": {
+			"description": "Une interface unifiée pour les LLMs"
+		}
+	},
+	"startRouter": "Configuration rapide via un routeur",
+	"startCustom": "Utiliser ta propre clé API",
 	"telemetry": {
-		"title": "Aidez à améliorer Roo Code",
-		"anonymousTelemetry": "Envoyez des données d'utilisation et d'erreurs anonymes pour nous aider à corriger les bugs et améliorer l'extension. Aucun code, texte ou information personnelle n'est jamais envoyé.",
-		"changeSettings": "Vous pouvez toujours modifier cela en bas des <settingsLink>paramètres</settingsLink>",
+		"title": "Aide à améliorer Roo Code",
+		"anonymousTelemetry": "Envoie des données d'utilisation et d'erreurs anonymes pour nous aider à corriger les bugs et améliorer l'extension. Aucun code, texte ou information personnelle n'est jamais envoyé.",
+		"changeSettings": "Tu peux toujours modifier cela en bas des <settingsLink>paramètres</settingsLink>",
 		"settings": "paramètres",
 		"allow": "Autoriser",
 		"deny": "Refuser"
-	}
+	},
+	"or": "ou"
 }

+ 14 - 1
webview-ui/src/i18n/locales/hi/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "मैं सभी प्रकार के कार्य कर सकता हूँ, एजेंटिक कोडिंग क्षमताओं में नवीनतम सफलताओं और उन टूल्स तक पहुंच के लिए धन्यवाद जो मुझे फाइलें बनाने और संपादित करने, जटिल परियोजनाओं का पता लगाने, ब्राउज़र का उपयोग करने और टर्मिनल कमांड निष्पादित करने की अनुमति देते हैं (आपकी अनुमति से, बिल्कुल)। मैं MCP का उपयोग करके नए टूल बना सकता हूँ और अपनी क्षमताओं का विस्तार कर सकता हूँ।",
 	"notice": "शुरू करने के लिए, इस एक्सटेंशन को एक API प्रदाता की आवश्यकता है।",
 	"start": "चलो शुरू करें!",
+	"chooseProvider": "शुरू करने के लिए एक API प्रदाता चुनें:",
+	"routers": {
+		"requesty": {
+			"description": "आपका अनुकूलित LLM राउटर",
+			"incentive": "$1 मुफ्त क्रेडिट"
+		},
+		"openrouter": {
+			"description": "LLMs के लिए एक एकीकृत इंटरफेस"
+		}
+	},
+	"startRouter": "राउटर के माध्यम से तेज़ सेटअप",
+	"startCustom": "अपनी खुद की API कुंजी का उपयोग करें",
 	"telemetry": {
 		"title": "Roo Code को बेहतर बनाने में मदद करें",
 		"anonymousTelemetry": "बग ठीक करने और एक्सटेंशन को बेहतर बनाने में हमारी मदद करने के लिए गुमनाम त्रुटि और उपयोग डेटा भेजें। कोड, संकेत या व्यक्तिगत जानकारी कभी नहीं भेजी जाती है।",
@@ -10,5 +22,6 @@
 		"settings": "सेटिंग्स",
 		"allow": "अनुमति दें",
 		"deny": "अस्वीकार करें"
-	}
+	},
+	"or": "या"
 }

+ 14 - 1
webview-ui/src/i18n/locales/it/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "Posso svolgere tutti i tipi di attività grazie ai più recenti progressi nelle capacità di codifica agentica e all'accesso a strumenti che mi permettono di creare e modificare file, esplorare progetti complessi, utilizzare il browser ed eseguire comandi terminal (con il tuo permesso, ovviamente). Posso persino utilizzare MCP per creare nuovi strumenti ed estendere le mie capacità.",
 	"notice": "Per iniziare, questa estensione necessita di un fornitore di API.",
 	"start": "Andiamo!",
+	"chooseProvider": "Scegli un fornitore di API per iniziare:",
+	"routers": {
+		"requesty": {
+			"description": "Il tuo router LLM ottimizzato",
+			"incentive": "$1 di credito gratuito"
+		},
+		"openrouter": {
+			"description": "Un'interfaccia unificata per LLMs"
+		}
+	},
+	"startRouter": "Configurazione rapida tramite router",
+	"startCustom": "Usa la tua chiave API",
 	"telemetry": {
 		"title": "Aiuta a migliorare Roo Code",
 		"anonymousTelemetry": "Invia dati di utilizzo ed errori anonimi per aiutarci a correggere bug e migliorare l'estensione. Non viene mai inviato codice, testo o informazioni personali.",
@@ -10,5 +22,6 @@
 		"settings": "impostazioni",
 		"allow": "Consenti",
 		"deny": "Nega"
-	}
+	},
+	"or": "o"
 }

+ 14 - 1
webview-ui/src/i18n/locales/ja/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "エージェント型コーディング能力の最新の進歩と、ファイルの作成・編集、複雑なプロジェクトの探索、ブラウザの使用、ターミナルコマンドの実行(もちろんあなたの許可を得て)を可能にするツールへのアクセスにより、あらゆる種類のタスクを実行できます。MCPを使用して新しいツールを作成し、自分の能力を拡張することもできます。",
 	"notice": "開始するには、この拡張機能にはAPIプロバイダーが必要です。",
 	"start": "さあ、始めましょう!",
+	"chooseProvider": "開始するにはAPIプロバイダーを選択してください:",
+	"routers": {
+		"requesty": {
+			"description": "最適化されたLLMルーター",
+			"incentive": "$1の無料クレジット"
+		},
+		"openrouter": {
+			"description": "LLMsのための統一インターフェース"
+		}
+	},
+	"startRouter": "ルーター経由の簡単セットアップ",
+	"startCustom": "自分のAPIキーを使用",
 	"telemetry": {
 		"title": "Roo Codeの改善にご協力ください",
 		"anonymousTelemetry": "バグの修正と拡張機能の改善のため、匿名のエラーと使用データを送信してください。コード、プロンプト、個人情報は一切送信されません。",
@@ -10,5 +22,6 @@
 		"settings": "設定",
 		"allow": "許可",
 		"deny": "拒否"
-	}
+	},
+	"or": "または"
 }

+ 14 - 1
webview-ui/src/i18n/locales/ko/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "에이전트 코딩 능력의 최신 발전과 파일 생성 및 편집, 복잡한 프로젝트 탐색, 브라우저 사용, 터미널 명령 실행(물론 사용자의 허락 하에)을 가능하게 하는 도구에 대한 접근 덕분에 모든 종류의 작업을 수행할 수 있습니다. MCP를 사용하여 새로운 도구를 만들고 제 능력을 확장할 수도 있습니다.",
 	"notice": "시작하려면 이 확장 프로그램에 API 공급자가 필요합니다.",
 	"start": "시작해 봅시다!",
+	"chooseProvider": "시작하려면 API 공급자를 선택하세요:",
+	"routers": {
+		"requesty": {
+			"description": "최적화된 LLM 라우터",
+			"incentive": "$1 무료 크레딧"
+		},
+		"openrouter": {
+			"description": "LLM을 위한 통합 인터페이스"
+		}
+	},
+	"startRouter": "라우터를 통한 빠른 설정",
+	"startCustom": "직접 API 키 사용하기",
 	"telemetry": {
 		"title": "Roo Code 개선에 도움 주세요",
 		"anonymousTelemetry": "버그 수정 및 확장 기능 개선을 위해 익명의 오류 및 사용 데이터를 보내주세요. 코드, 프롬프트 또는 개인 정보는 절대 전송되지 않습니다.",
@@ -10,5 +22,6 @@
 		"settings": "설정",
 		"allow": "허용",
 		"deny": "거부"
-	}
+	},
+	"or": "또는"
 }

+ 14 - 1
webview-ui/src/i18n/locales/pl/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "Mogę wykonywać wszelkiego rodzaju zadania dzięki najnowszym osiągnięciom w zakresie możliwości kodowania agentowego i dostępu do narzędzi, które pozwalają mi tworzyć i edytować pliki, eksplorować złożone projekty, korzystać z przeglądarki i wykonywać polecenia terminalowe (oczywiście za Twoją zgodą). Mogę nawet używać MCP do tworzenia nowych narzędzi i rozszerzania własnych możliwości.",
 	"notice": "Aby rozpocząć, to rozszerzenie potrzebuje dostawcy API.",
 	"start": "Zaczynajmy!",
+	"chooseProvider": "Wybierz dostawcę API, aby rozpocząć:",
+	"routers": {
+		"requesty": {
+			"description": "Twój zoptymalizowany router LLM",
+			"incentive": "$1 darmowego kredytu"
+		},
+		"openrouter": {
+			"description": "Ujednolicony interfejs dla LLMs"
+		}
+	},
+	"startRouter": "Szybka konfiguracja przez router",
+	"startCustom": "Użyj własnego klucza API",
 	"telemetry": {
 		"title": "Pomóż ulepszyć Roo Code",
 		"anonymousTelemetry": "Wyślij anonimowe dane o błędach i użyciu, aby pomóc nam w naprawianiu błędów i ulepszaniu rozszerzenia. Nigdy nie są wysyłane żadne kody, teksty ani informacje osobiste.",
@@ -10,5 +22,6 @@
 		"settings": "ustawienia",
 		"allow": "Zezwól",
 		"deny": "Odmów"
-	}
+	},
+	"or": "lub"
 }

+ 14 - 1
webview-ui/src/i18n/locales/pt-BR/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "Posso realizar todos os tipos de tarefas graças aos últimos avanços nas capacidades de codificação agentica e ao acesso a ferramentas que me permitem criar e editar arquivos, explorar projetos complexos, usar o navegador e executar comandos de terminal (com sua permissão, é claro). Posso até usar o MCP para criar novas ferramentas e expandir minhas próprias capacidades.",
 	"notice": "Para começar, esta extensão precisa de um provedor de API.",
 	"start": "Vamos lá!",
+	"chooseProvider": "Escolha um provedor de API para começar:",
+	"routers": {
+		"requesty": {
+			"description": "Seu roteador LLM otimizado",
+			"incentive": "$1 de crédito grátis"
+		},
+		"openrouter": {
+			"description": "Uma interface unificada para LLMs"
+		}
+	},
+	"startRouter": "Configuração rápida através de um roteador",
+	"startCustom": "Use sua própria chave API",
 	"telemetry": {
 		"title": "Ajude a melhorar o Roo Code",
 		"anonymousTelemetry": "Envie dados de uso e erros anônimos para nos ajudar a corrigir bugs e melhorar a extensão. Nenhum código, texto ou informação pessoal é enviado.",
@@ -10,5 +22,6 @@
 		"settings": "configurações",
 		"allow": "Permitir",
 		"deny": "Negar"
-	}
+	},
+	"or": "ou"
 }

+ 18 - 5
webview-ui/src/i18n/locales/tr/welcome.json

@@ -1,14 +1,27 @@
 {
 	"greeting": "Merhaba, ben Roo!",
-	"introduction": "Ajan tabanlı kodlama yeteneklerindeki son gelişmeler ve dosya oluşturma ve düzenleme, karmaşık projeleri keşfetme, tarayıcı kullanma ve terminal komutları çalıştırma (tabii ki sizin izninizle) gibi işlemleri yapmamı sağlayan araçlara erişim sayesinde her türlü görevi gerçekleştirebilirim. Hatta MCP'yi kullanarak yeni araçlar oluşturabilir ve kendi yeteneklerimi genişletebilirim.",
+	"introduction": "Ajan tabanlı kodlama yeteneklerindeki son gelişmeler ve dosya oluşturma ve düzenleme, karmaşık projeleri keşfetme, tarayıcı kullanma ve terminal komutları çalıştırma (tabii ki senin izninle) gibi işlemleri yapmamı sağlayan araçlara erişim sayesinde her türlü görevi gerçekleştirebilirim. Hatta MCP'yi kullanarak yeni araçlar oluşturabilir ve kendi yeteneklerimi genişletebilirim.",
 	"notice": "Başlamak için bu eklentinin bir API sağlayıcısına ihtiyacı var.",
 	"start": "Hadi başlayalım!",
+	"chooseProvider": "Başlamak için bir API sağlayıcısı seç:",
+	"routers": {
+		"requesty": {
+			"description": "Optimize edilmiş LLM yönlendiricin",
+			"incentive": "$1 ücretsiz kredi"
+		},
+		"openrouter": {
+			"description": "LLM'ler için birleşik bir arayüz"
+		}
+	},
+	"startRouter": "Yönlendirici Üzerinden Hızlı Kurulum",
+	"startCustom": "Kendi API Anahtarını Kullan",
 	"telemetry": {
-		"title": "Roo Code'u Geliştirmeye Yardım Edin",
-		"anonymousTelemetry": "Hataları düzeltmemize ve eklentiyi geliştirmemize yardımcı olmak için anonim hata ve kullanım verileri gönderin. Hiçbir zaman kod, metin veya kişisel bilgi gönderilmez.",
-		"changeSettings": "Bunu her zaman <settingsLink>ayarlar</settingsLink>ın altından değiştirebilirsiniz",
+		"title": "Roo Code'u Geliştirmeye Yardım Et",
+		"anonymousTelemetry": "Hataları düzeltmemize ve eklentiyi geliştirmemize yardımcı olmak için anonim hata ve kullanım verileri gönder. Hiçbir zaman kod, metin veya kişisel bilgi gönderilmez.",
+		"changeSettings": "Bunu her zaman <settingsLink>ayarlar</settingsLink>ın altından değiştirebilirsin",
 		"settings": "ayarlar",
 		"allow": "İzin Ver",
 		"deny": "Reddet"
-	}
+	},
+	"or": "veya"
 }

+ 14 - 1
webview-ui/src/i18n/locales/vi/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "Tôi có thể thực hiện nhiều loại nhiệm vụ nhờ vào những đột phá mới nhất trong khả năng lập trình dạng đại lý và quyền truy cập vào các công cụ cho phép tôi tạo & chỉnh sửa tệp, khám phá các dự án phức tạp, sử dụng trình duyệt và thực thi lệnh terminal (với sự cho phép của bạn, tất nhiên). Tôi thậm chí có thể sử dụng MCP để tạo công cụ mới và mở rộng khả năng của mình.",
 	"notice": "Để bắt đầu, tiện ích mở rộng này cần một nhà cung cấp API.",
 	"start": "Bắt đầu thôi!",
+	"chooseProvider": "Chọn một nhà cung cấp API để bắt đầu:",
+	"routers": {
+		"requesty": {
+			"description": "Bộ định tuyến LLM được tối ưu hóa của bạn",
+			"incentive": "$1 tín dụng miễn phí"
+		},
+		"openrouter": {
+			"description": "Giao diện thống nhất cho các LLM"
+		}
+	},
+	"startRouter": "Thiết lập nhanh qua bộ định tuyến",
+	"startCustom": "Sử dụng khóa API của riêng bạn",
 	"telemetry": {
 		"title": "Giúp cải thiện Roo Code",
 		"anonymousTelemetry": "Gửi dữ liệu lỗi và sử dụng ẩn danh để giúp chúng tôi sửa lỗi và cải thiện tiện ích mở rộng. Không bao giờ gửi mã, lời nhắc hoặc thông tin cá nhân.",
@@ -10,5 +22,6 @@
 		"settings": "cài đặt",
 		"allow": "Cho phép",
 		"deny": "Từ chối"
-	}
+	},
+	"or": "hoặc"
 }

+ 14 - 1
webview-ui/src/i18n/locales/zh-CN/welcome.json

@@ -3,6 +3,18 @@
 	"introduction": "得益于最新的代理编码能力突破和对各种工具的访问权限,我可以完成各种任务。我可以创建和编辑文件、探索复杂项目、使用浏览器,以及执行终端命令(当然,需要你的许可)。我甚至可以使用 MCP 创建新工具并扩展自己的能力。",
 	"notice": "首先,请配置一个大模型 API 服务商。",
 	"start": "开始吧!",
+	"chooseProvider": "选择一个 API 服务商开始:",
+	"routers": {
+		"requesty": {
+			"description": "你的优化 LLM 路由器",
+			"incentive": "$1 免费额度"
+		},
+		"openrouter": {
+			"description": "LLM 的统一接口"
+		}
+	},
+	"startRouter": "通过路由器快速设置",
+	"startCustom": "使用你自己的 API 密钥",
 	"telemetry": {
 		"title": "帮助改进 Roo 代码",
 		"changeSettings": "可以随时在<settingsLink>设置</settingsLink>页面底部更改此设置",
@@ -10,5 +22,6 @@
 		"anonymousTelemetry": "发送匿名的错误和使用数据,以帮助我们修复错误并改进扩展程序。不会发送任何代码、提示或个人信息。",
 		"allow": "允许",
 		"deny": "拒绝"
-	}
+	},
+	"or": "或"
 }

+ 16 - 3
webview-ui/src/i18n/locales/zh-TW/welcome.json

@@ -1,14 +1,27 @@
 {
 	"greeting": "嗨,我是 Roo!",
-	"introduction": "由於最新的代理編碼能力突破,以及能夠讓我創建和編輯文件、探索複雜項目、使用瀏覽器和執行終端命令的工具(當然是在的許可下),我可以完成各種任務。我甚至可以使用 MCP 創建新工具並擴展自己的能力。",
+	"introduction": "由於最新的代理編碼能力突破,以及能夠讓我創建和編輯文件、探索複雜項目、使用瀏覽器和執行終端命令的工具(當然是在的許可下),我可以完成各種任務。我甚至可以使用 MCP 創建新工具並擴展自己的能力。",
 	"notice": "要開始使用,此擴展需要一個 API 提供者。",
 	"start": "我們開始吧!",
+	"chooseProvider": "選擇一個 API 提供者開始:",
+	"routers": {
+		"requesty": {
+			"description": "你的優化 LLM 路由器",
+			"incentive": "$1 免費額度"
+		},
+		"openrouter": {
+			"description": "LLM 的統一接口"
+		}
+	},
+	"startRouter": "通過路由器快速設置",
+	"startCustom": "使用你自己的 API 密鑰",
 	"telemetry": {
 		"title": "幫助改進 Roo Code",
 		"anonymousTelemetry": "發送匿名的錯誤和使用數據,以幫助我們修復錯誤並改進擴展功能。不會發送任何代碼、提示或個人信息。",
-		"changeSettings": "您隨時可以在<settingsLink>設置</settingsLink>底部更改此選項",
+		"changeSettings": "隨時可以在<settingsLink>設置</settingsLink>底部更改此選項",
 		"settings": "設置",
 		"allow": "允許",
 		"deny": "拒絕"
-	}
+	},
+	"or": "或"
 }

+ 16 - 0
webview-ui/src/oauth/urls.ts

@@ -0,0 +1,16 @@
+export function getCallbackUrl(provider: string, uriScheme?: string) {
+	const callbackUrl = `${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/${provider}`
+	return encodeURIComponent(callbackUrl)
+}
+
+export function getGlamaAuthUrl(uriScheme?: string) {
+	return `https://glama.ai/oauth/authorize?callback_url=${getCallbackUrl("glama", uriScheme)}`
+}
+
+export function getOpenRouterAuthUrl(uriScheme?: string) {
+	return `https://openrouter.ai/auth?callback_url=${getCallbackUrl("openrouter", uriScheme)}`
+}
+
+export function getRequestyAuthUrl(uriScheme?: string) {
+	return `https://app.requesty.ai/oauth/authorize?callback_url=${getCallbackUrl("requesty", uriScheme)}`
+}

+ 5 - 0
webview-ui/src/types.d.ts

@@ -0,0 +1,5 @@
+// Type declarations for third-party modules
+
+declare module "knuth-shuffle-seeded" {
+	export default function knuthShuffle<T>(array: T[], seed: any): T[]
+}