Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
8d5d4f8
First cut on different UI
suprjinx May 27, 2025
9391ede
WIP for passing process environment to webview components
suprjinx May 28, 2025
fa0504f
Set process env in react html as script, instead of trying to pass me…
suprjinx May 30, 2025
39a1edb
Fix logic for activating apiKeyEnvVar
suprjinx May 31, 2025
91725fd
Add new ApiKey component and use with Anthropic
suprjinx Jun 2, 2025
742ca14
Add apiKeyUseEnvVar to all providers supporting api key
suprjinx Jul 18, 2025
4fb7ed7
Merge branch 'main' into add_api_key_env_vars
suprjinx Jul 24, 2025
891af6d
Fix merge errors
suprjinx Jul 24, 2025
488ac44
Add unit tests for components/settings/ApiKey.tsx and api/index.ts
suprjinx Jul 24, 2025
910028d
Fix lint issues
suprjinx Jul 25, 2025
436025f
Merge branch 'main' into add_api_key_env_vars
suprjinx Jul 25, 2025
e17c2e6
Add support for "balance display" used by OpenRouter and Requesty pro…
suprjinx Jul 25, 2025
7e518b1
Fix additional unused vars
suprjinx Jul 25, 2025
78598db
Fix new lint complaints after removing unused func
suprjinx Jul 25, 2025
2f5e00f
Add missing translation to non-EN locales
suprjinx Jul 25, 2025
60002d8
Fix last lint complaint
suprjinx Jul 25, 2025
2b6b2ea
Fix missing imports
suprjinx Jul 25, 2025
f154eb2
One more lint fix
suprjinx Jul 25, 2025
674055c
Fix code scanner complaint re: potential prototype-pollution
suprjinx Jul 25, 2025
88d587f
Fix OpenAINative api key translation keys
suprjinx Jul 25, 2025
1ea534c
Merge branch 'main' into add_api_key_env_vars
suprjinx Jul 27, 2025
5706600
PR suggestions to make "use env var" property more general; use
suprjinx Jul 28, 2025
6f8b0d9
Fix tsc compile issue
suprjinx Jul 28, 2025
f9c6a51
Switch to "ENV_VAR_EXISTS" map of known API keys, instead of writing
suprjinx Aug 11, 2025
2ff6dec
Place a map of know api keys -> bool in webview for checking existence
suprjinx Aug 11, 2025
895b505
Merge branch 'main' into add_api_key_env_vars
suprjinx Aug 12, 2025
11fe352
fix typo with chutes provider; remove redundant `balanceDisplay` and
suprjinx Aug 12, 2025
08e7d73
Merge branch 'main' into add_api_key_env_vars
suprjinx Aug 12, 2025
0879bcd
Remove unused `env` property for ApiOptions
suprjinx Aug 12, 2025
0231f6f
Merge branch 'main' into add_api_key_env_vars
suprjinx Aug 13, 2025
0707083
Merge branch 'main' into add_api_key_env_vars
suprjinx Aug 13, 2025
e711534
Fix merge conflict
suprjinx Aug 13, 2025
6f7cace
Remove unused import from merge
suprjinx Aug 13, 2025
ce8186d
Remove translations help function
suprjinx Aug 22, 2025
010300c
Merge branch 'main' into add_api_key_env_vars
suprjinx Aug 22, 2025
91206b1
Fix formatting in test file; fix OllamaHandler reference
suprjinx Aug 22, 2025
1076c2a
Merge branch 'main' into add_api_key_env_vars
suprjinx Aug 25, 2025
8dd02bc
Fix merge error
suprjinx Aug 25, 2025
6b82ada
Fix merge error
suprjinx Aug 25, 2025
c0f82d8
Fix merge error
suprjinx Aug 25, 2025
8868930
Fix merge error
suprjinx Aug 25, 2025
86fd178
Merge branch 'main' into add_api_key_env_vars
suprjinx Aug 27, 2025
b39fdb9
Remove unneeded but failing test case
suprjinx Aug 27, 2025
bfe5486
Merge branch 'main' into add_api_key_env_vars
suprjinx Sep 3, 2025
2fd04c5
Fix merge conflict
suprjinx Sep 3, 2025
b2ef23c
Merge branch 'main' into add_api_key_env_vars
suprjinx Sep 26, 2025
6b32481
Change DEEP_SEEK_API_KEY -> DEEPSEEK_API_KEY
suprjinx Sep 26, 2025
09dad33
Change OPEN_ROUTER_API_KEY -> OPENROUTER_API_KEY
suprjinx Sep 26, 2025
2391628
Fix index.spec.ts header
suprjinx Sep 26, 2025
95fbe97
Update validate function for ConfigUseEnvVar on existing providers
suprjinx Sep 26, 2025
5ec88ba
Additional providers having apikey
suprjinx Sep 26, 2025
4736894
Remove unused imports and defs since using ApiKey component
suprjinx Sep 26, 2025
cc85602
Use constants instead of hard-coded strings in buildApiHandler; fix
suprjinx Sep 27, 2025
d6f301f
Fix Chutes translation key
suprjinx Sep 27, 2025
5048312
Merge branch 'main' into add_api_key_env_vars
suprjinx Oct 27, 2025
4597469
Fixing MOONSHOOT->MOONSHOT plus remove extra space
suprjinx Oct 27, 2025
142412c
Merge branch 'RooCodeInc:main' into add_api_key_env_vars
suprjinx Oct 27, 2025
564cc9a
Fix formatting
suprjinx Oct 27, 2025
3823e1b
Update existing api key validations and add missing ones
suprjinx Oct 29, 2025
7b24b89
trigger GitHub actions
suprjinx Oct 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const apiModelIdProviderModelSchema = baseProviderSettingsSchema.extend({

const anthropicSchema = apiModelIdProviderModelSchema.extend({
apiKey: z.string().optional(),
anthropicApiKeyUseEnvVar: z.boolean().optional(),
anthropicBaseUrl: z.string().optional(),
anthropicUseAuthToken: z.boolean().optional(),
})
Expand All @@ -95,10 +96,12 @@ const claudeCodeSchema = apiModelIdProviderModelSchema.extend({
const glamaSchema = baseProviderSettingsSchema.extend({
glamaModelId: z.string().optional(),
glamaApiKey: z.string().optional(),
glamaApiKeyUseEnvVar: z.boolean().optional(),
})

const openRouterSchema = baseProviderSettingsSchema.extend({
openRouterApiKey: z.string().optional(),
openRouterApiKeyUseEnvVar: z.boolean().optional(),
openRouterModelId: z.string().optional(),
openRouterBaseUrl: z.string().optional(),
openRouterSpecificProvider: z.string().optional(),
Expand Down Expand Up @@ -130,6 +133,7 @@ const vertexSchema = apiModelIdProviderModelSchema.extend({
const openAiSchema = baseProviderSettingsSchema.extend({
openAiBaseUrl: z.string().optional(),
openAiApiKey: z.string().optional(),
openAiApiKeyUseEnvVar: z.boolean().optional(),
openAiLegacyFormat: z.boolean().optional(),
openAiR1FormatEnabled: z.boolean().optional(),
openAiModelId: z.string().optional(),
Expand Down Expand Up @@ -166,6 +170,7 @@ const lmStudioSchema = baseProviderSettingsSchema.extend({

const geminiSchema = apiModelIdProviderModelSchema.extend({
geminiApiKey: z.string().optional(),
geminiApiKeyUseEnvVar: z.boolean().optional(),
googleGeminiBaseUrl: z.string().optional(),
})

Expand All @@ -176,17 +181,20 @@ const geminiCliSchema = apiModelIdProviderModelSchema.extend({

const openAiNativeSchema = apiModelIdProviderModelSchema.extend({
openAiNativeApiKey: z.string().optional(),
openAiNativeApiKeyUseEnvVar: z.boolean().optional(),
openAiNativeBaseUrl: z.string().optional(),
})

const mistralSchema = apiModelIdProviderModelSchema.extend({
mistralApiKey: z.string().optional(),
mistralApiKeyUseEnvVar: z.boolean().optional(),
mistralCodestralUrl: z.string().optional(),
})

const deepSeekSchema = apiModelIdProviderModelSchema.extend({
deepSeekBaseUrl: z.string().optional(),
deepSeekApiKey: z.string().optional(),
deepSeekApiKeyUseEnvVar: z.boolean().optional(),
})

const moonshotSchema = apiModelIdProviderModelSchema.extend({
Expand All @@ -198,11 +206,13 @@ const moonshotSchema = apiModelIdProviderModelSchema.extend({

const unboundSchema = baseProviderSettingsSchema.extend({
unboundApiKey: z.string().optional(),
unboundApiKeyUseEnvVar: z.boolean().optional(),
unboundModelId: z.string().optional(),
})

const requestySchema = baseProviderSettingsSchema.extend({
requestyApiKey: z.string().optional(),
requestyApiKeyUseEnvVar: z.boolean().optional(),
requestyModelId: z.string().optional(),
})

Expand All @@ -214,10 +224,12 @@ const fakeAiSchema = baseProviderSettingsSchema.extend({

const xaiSchema = apiModelIdProviderModelSchema.extend({
xaiApiKey: z.string().optional(),
xaiApiKeyUseEnvVar: z.boolean().optional(),
})

const groqSchema = apiModelIdProviderModelSchema.extend({
groqApiKey: z.string().optional(),
groqApiKeyUseEnvVar: z.boolean().optional(),
})

const huggingFaceSchema = baseProviderSettingsSchema.extend({
Expand All @@ -228,11 +240,13 @@ const huggingFaceSchema = baseProviderSettingsSchema.extend({

const chutesSchema = apiModelIdProviderModelSchema.extend({
chutesApiKey: z.string().optional(),
chutesApiKeyUseEnvVar: z.boolean().optional(),
})

const litellmSchema = baseProviderSettingsSchema.extend({
litellmBaseUrl: z.string().optional(),
litellmApiKey: z.string().optional(),
litellmApiKeyUseEnvVar: z.boolean().optional(),
litellmModelId: z.string().optional(),
})

Expand Down
33 changes: 33 additions & 0 deletions scripts/find-missing-translations.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like these changes are outside the scope of the original issue, if you wish to add these I recommend creating a new issue and PR to add this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daniel-lxs let me know if we can merge -- everyday there are new merge conflicts 😢

Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
* --locale=<locale> Only check a specific locale (e.g. --locale=fr)
* --file=<file> Only check a specific file (e.g. --file=chat.json)
* --area=<area> Only check a specific area (core, webview, or both)
* --create-missing Creates missing keys in the other locales with the EN value
* --help Show this help message
*/

const { set } = require("@dotenvx/dotenvx")
const fs = require("fs")
const path = require("path")
let createMissing = false

// Process command line arguments
const args = process.argv.slice(2).reduce(
Expand All @@ -30,6 +33,8 @@
console.error(`Error: Invalid area '${acc.area}'. Must be 'core', 'webview', or 'both'.`)
process.exit(1)
}
} else if (arg.startsWith("--create-missing")) {
createMissing = true
}
return acc
},
Expand All @@ -54,6 +59,7 @@
'core' = Backend (src/i18n/locales)
'webview' = Frontend UI (webview-ui/src/i18n/locales)
'both' = Check both areas (default)
--create-missing Creates missing keys in the other locales with the EN value
--help Show this help message
Output:
Expand Down Expand Up @@ -105,6 +111,27 @@
return current
}

// Set value at a dotted path in an object
function setValueAtPath(obj, path, value) {
const parts = path.split(".")
let current = obj

for (let i = 0; i < parts.length; i++) {
const part = parts[i]

// If it's the last part, set the value
if (i === parts.length - 1) {
current[part] = value
} else {
// If the key doesn't exist or isn't an object, create an empty object
if (current[part] === undefined || typeof current[part] !== "object") {
current[part] = {}
}
current = current[part]
}
}
}

// Function to check translations for a specific area
function checkAreaTranslations(area) {
const LOCALES_DIR = LOCALES_DIRS[area]
Expand Down Expand Up @@ -198,11 +225,17 @@
key,
englishValue,
})
if (createMissing === true) {
setValueAtPath(localeContent, key, englishValue) // Set the missing key in the locale content
}
}
}

if (missingKeys.length > 0) {
missingTranslations[locale][name] = missingKeys
if (createMissing === true) {
fs.writeFileSync(localeFilePath, JSON.stringify(localeContent, null, "\t"))
}
}
}
}
Expand Down
57 changes: 56 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,40 @@ export interface ApiHandler {
countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number>
}

/**
* Read an environment variable value, returning a default value if not set. If neither key nor default is set,
* returns undefined
* @param key The environment variable key
* @param defaultValue The default value to return if the variable is not set
* @returns The value of the environment variable or the default value
*/
export function getEnvVar(key: string | undefined, defaultValue?: string | undefined): string | undefined {
if (key === undefined) {
return defaultValue
}
return process.env[key as string] ?? defaultValue
}

export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
const { apiProvider, ...options } = configuration

switch (apiProvider) {
case "anthropic":
if (options.anthropicApiKeyUseEnvVar) {
options.apiKey = getEnvVar("ANTHROPIC_API_KEY", options.apiKey)
}
return new AnthropicHandler(options)
case "claude-code":
return new ClaudeCodeHandler(options)
case "glama":
if (options.glamaApiKeyUseEnvVar) {
options.glamaApiKey = getEnvVar("GLAMA_API_KEY", options.glamaApiKey)
}
return new GlamaHandler(options)
case "openrouter":
if (options.openRouterApiKeyUseEnvVar) {
options.openRouterApiKey = getEnvVar("OPEN_ROUTER_API_KEY", options.openRouterApiKey)
}
return new OpenRouterHandler(options)
case "bedrock":
return new AwsBedrockHandler(options)
Expand All @@ -80,43 +103,75 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
? new AnthropicVertexHandler(options)
: new VertexHandler(options)
case "openai":
if (options.openAiApiKeyUseEnvVar) {
options.openAiApiKey = getEnvVar("OPEN_AI_API_KEY", options.openAiApiKey)
}
return new OpenAiHandler(options)
case "ollama":
return new OllamaHandler(options)
case "lmstudio":
return new LmStudioHandler(options)
case "gemini":
if (options.geminiApiKeyUseEnvVar) {
options.geminiApiKey = getEnvVar("GEMINI_API_KEY", options.geminiApiKey)
}
return new GeminiHandler(options)
case "openai-native":
if (options.openAiNativeApiKeyUseEnvVar) {
options.openAiNativeApiKey = getEnvVar("OPEN_AI_NATIVE_API_KEY", options.openAiNativeApiKey)
}
return new OpenAiNativeHandler(options)
case "deepseek":
if (options.deepSeekApiKeyUseEnvVar) {
options.deepSeekApiKey = getEnvVar("DEEP_SEEK_API_KEY", options.deepSeekApiKey)
}
return new DeepSeekHandler(options)
case "moonshot":
return new MoonshotHandler(options)
case "vscode-lm":
return new VsCodeLmHandler(options)
case "mistral":
if (options.mistralApiKeyUseEnvVar) {
options.mistralApiKey = getEnvVar("MISTRAL_API_KEY", options.mistralApiKey)
}
return new MistralHandler(options)
case "unbound":
if (options.unboundApiKeyUseEnvVar) {
options.unboundApiKey = getEnvVar("UNBOUND_API_KEY", options.unboundApiKey)
}
return new UnboundHandler(options)
case "requesty":
if (options.requestyApiKeyUseEnvVar) {
options.requestyApiKey = getEnvVar("REQUESTY_API_KEY", options.requestyApiKey)
}
return new RequestyHandler(options)
case "human-relay":
return new HumanRelayHandler()
case "fake-ai":
return new FakeAIHandler(options)
case "xai":
if (options.xaiApiKeyUseEnvVar) {
options.xaiApiKey = getEnvVar("XAI_API_KEY", options.xaiApiKey)
}
return new XAIHandler(options)
case "groq":
if (options.groqApiKeyUseEnvVar) {
options.groqApiKey = getEnvVar("GROQ_API_KEY", options.groqApiKey)
}
return new GroqHandler(options)
case "huggingface":
return new HuggingFaceHandler(options)
case "chutes":
if (options.chutesApiKeyUseEnvVar) {
options.chutesApiKey = getEnvVar("CHUTES_API_KEY", options.chutesApiKey)
}
return new ChutesHandler(options)
case "litellm":
if (options.litellmApiKeyUseEnvVar) {
options.litellmApiKey = getEnvVar("LITELLM_API_KEY", options.litellmApiKey)
}
return new LiteLLMHandler(options)
default:
apiProvider satisfies "gemini-cli" | undefined
return new AnthropicHandler(options)
}
}
1 change: 1 addition & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ export class ClineProvider
window.IMAGES_BASE_URI = "${imagesUri}"
window.AUDIO_BASE_URI = "${audioUri}"
window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
window.PROCESS_ENV = ${JSON.stringify(process.env)}
</script>
<title>Roo Code</title>
</head>
Expand Down
68 changes: 68 additions & 0 deletions webview-ui/src/components/settings/ApiKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useState } from "react"
import { Checkbox } from "vscrui"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
import { t } from "i18next"

type ApiKeyProps = {
apiKey: string
apiKeyEnvVar: string
setApiKey: (value: string) => void
setApiKeyUseEnvVar: (value: boolean) => void
apiKeyLabel: string
apiKeyUseEnvVar: boolean
getApiKeyUrl?: string
getApiKeyLabel?: string
disabled?: boolean
}

export const ApiKey = ({
apiKey,
apiKeyEnvVar,
setApiKey,
setApiKeyUseEnvVar,
apiKeyLabel,
apiKeyUseEnvVar,
getApiKeyUrl,
getApiKeyLabel,
disabled = false,
}: ApiKeyProps) => {

const env = (window as any).PROCESS_ENV || {}
const apiKeyEnvVarExists = !!env[apiKeyEnvVar]
const [useEnvVar, setUseEnvVar] = useState(apiKeyUseEnvVar && apiKeyEnvVarExists)

const handleUseEnvVarChange = (checked: boolean) => {
setUseEnvVar(checked)
setApiKeyUseEnvVar(checked)
}

return (
<>
<VSCodeTextField
value={apiKey || ""}
type="password"
onInput={e => setApiKey((e.target as HTMLInputElement).value)}
placeholder={t("settings:placeholders.apiKey")}
className="w-full"
disabled={useEnvVar || disabled}>
<label className="block font-medium mb-1">{apiKeyLabel}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground -mt-2">
{t("settings:providers.apiKeyStorageNotice")}
</div>
<Checkbox
checked={useEnvVar}
onChange={handleUseEnvVarChange}
className="mb-2"
disabled={!apiKeyEnvVarExists}>
{t("settings:providers.apiKeyUseEnvVar", { name: apiKeyEnvVar })}
</Checkbox>
{!(apiKey || useEnvVar) && getApiKeyUrl && getApiKeyLabel && (
<VSCodeButtonLink href={getApiKeyUrl} appearance="secondary">
{getApiKeyLabel}
</VSCodeButtonLink>
)}
</>
)
}
4 changes: 3 additions & 1 deletion webview-ui/src/components/settings/ApiOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export interface ApiOptionsProps {
fromWelcomeView?: boolean
errorMessage: string | undefined
setErrorMessage: React.Dispatch<React.SetStateAction<string | undefined>>
env?: Record<string, string | undefined>
}

const ApiOptions = ({
Expand All @@ -104,6 +105,7 @@ const ApiOptions = ({
fromWelcomeView,
errorMessage,
setErrorMessage,
env = {}
}: ApiOptionsProps) => {
const { t } = useAppTranslation()
const { organizationAllowList } = useExtensionState()
Expand Down Expand Up @@ -420,7 +422,7 @@ const ApiOptions = ({
)}

{selectedProvider === "anthropic" && (
<Anthropic apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
<Anthropic apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField}/>
)}

{selectedProvider === "claude-code" && (
Expand Down
Loading
Loading