Skip to content

Commit 742ca14

Browse files
committed
Add apiKeyUseEnvVar to all providers supporting api key
Signed-off-by: Geoff Wilson <[email protected]>
1 parent 91725fd commit 742ca14

File tree

15 files changed

+203
-231
lines changed

15 files changed

+203
-231
lines changed

packages/types/src/provider-settings.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,12 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
8181
const glamaSchema = baseProviderSettingsSchema.extend({
8282
glamaModelId: z.string().optional(),
8383
glamaApiKey: z.string().optional(),
84+
glamaApiKeyUseEnvVar: z.boolean().optional(),
8485
})
8586

8687
const openRouterSchema = baseProviderSettingsSchema.extend({
8788
openRouterApiKey: z.string().optional(),
89+
openRouterApiKeyUseEnvVar: z.boolean().optional(),
8890
openRouterModelId: z.string().optional(),
8991
openRouterBaseUrl: z.string().optional(),
9092
openRouterSpecificProvider: z.string().optional(),
@@ -150,31 +152,37 @@ const lmStudioSchema = baseProviderSettingsSchema.extend({
150152

151153
const geminiSchema = apiModelIdProviderModelSchema.extend({
152154
geminiApiKey: z.string().optional(),
155+
geminiApiKeyUseEnvVar: z.boolean().optional(),
153156
googleGeminiBaseUrl: z.string().optional(),
154157
})
155158

156159
const openAiNativeSchema = apiModelIdProviderModelSchema.extend({
157160
openAiNativeApiKey: z.string().optional(),
161+
openAiNativeApiKeyUseEnvVar: z.boolean().optional(),
158162
openAiNativeBaseUrl: z.string().optional(),
159163
})
160164

161165
const mistralSchema = apiModelIdProviderModelSchema.extend({
162166
mistralApiKey: z.string().optional(),
167+
mistralApiKeyUseEnvVar: z.boolean().optional(),
163168
mistralCodestralUrl: z.string().optional(),
164169
})
165170

166171
const deepSeekSchema = apiModelIdProviderModelSchema.extend({
167172
deepSeekBaseUrl: z.string().optional(),
168173
deepSeekApiKey: z.string().optional(),
174+
deepSeekApiKeyUseEnvVar: z.boolean().optional(),
169175
})
170176

171177
const unboundSchema = baseProviderSettingsSchema.extend({
172178
unboundApiKey: z.string().optional(),
179+
unboundApiKeyUseEnvVar: z.boolean().optional(),
173180
unboundModelId: z.string().optional(),
174181
})
175182

176183
const requestySchema = baseProviderSettingsSchema.extend({
177184
requestyApiKey: z.string().optional(),
185+
requestyApiKeyUseEnvVar: z.boolean().optional(),
178186
requestyModelId: z.string().optional(),
179187
})
180188

@@ -186,19 +194,23 @@ const fakeAiSchema = baseProviderSettingsSchema.extend({
186194

187195
const xaiSchema = apiModelIdProviderModelSchema.extend({
188196
xaiApiKey: z.string().optional(),
197+
xaiApiKeyUseEnvVar: z.boolean().optional(),
189198
})
190199

191200
const groqSchema = apiModelIdProviderModelSchema.extend({
192201
groqApiKey: z.string().optional(),
202+
groqApiKeyUseEnvVar: z.boolean().optional(),
193203
})
194204

195205
const chutesSchema = apiModelIdProviderModelSchema.extend({
196206
chutesApiKey: z.string().optional(),
207+
chutesApiKeyUseEnvVar: z.boolean().optional(),
197208
})
198209

199210
const litellmSchema = baseProviderSettingsSchema.extend({
200211
litellmBaseUrl: z.string().optional(),
201212
litellmApiKey: z.string().optional(),
213+
litellmApiKeyUseEnvVar: z.boolean().optional(),
202214
litellmModelId: z.string().optional(),
203215
})
204216

@@ -270,8 +282,10 @@ export const PROVIDER_SETTINGS_KEYS = keysOf<ProviderSettings>()([
270282
// Glama
271283
"glamaModelId",
272284
"glamaApiKey",
285+
"glamaApiKeyUseEnvVar",
273286
// OpenRouter
274287
"openRouterApiKey",
288+
"openRouterApiKeyUseEnvVar",
275289
"openRouterModelId",
276290
"openRouterBaseUrl",
277291
"openRouterSpecificProvider",
@@ -315,21 +329,27 @@ export const PROVIDER_SETTINGS_KEYS = keysOf<ProviderSettings>()([
315329
"lmStudioSpeculativeDecodingEnabled",
316330
// Gemini
317331
"geminiApiKey",
332+
"geminiApiKeyUseEnvVar",
318333
"googleGeminiBaseUrl",
319334
// OpenAI Native
320335
"openAiNativeApiKey",
336+
"openAiNativeApiKeyUseEnvVar",
321337
"openAiNativeBaseUrl",
322338
// Mistral
323339
"mistralApiKey",
340+
"mistralApiKeyUseEnvVar",
324341
"mistralCodestralUrl",
325342
// DeepSeek
326343
"deepSeekBaseUrl",
327344
"deepSeekApiKey",
345+
"deepSeekApiKeyUseEnvVar",
328346
// Unbound
329347
"unboundApiKey",
348+
"unboundApiKeyUseEnvVar",
330349
"unboundModelId",
331350
// Requesty
332351
"requestyApiKey",
352+
"requestyApiKeyUseEnvVar",
333353
"requestyModelId",
334354
// Code Index
335355
"codeIndexOpenAiKey",
@@ -349,12 +369,16 @@ export const PROVIDER_SETTINGS_KEYS = keysOf<ProviderSettings>()([
349369
"fakeAi",
350370
// X.AI (Grok)
351371
"xaiApiKey",
372+
"xaiApiKeyUseEnvVar",
352373
// Groq
353374
"groqApiKey",
375+
"groqApiKeyUseEnvVar",
354376
// Chutes AI
355377
"chutesApiKey",
378+
"chutesApiKeyUseEnvVar",
356379
// LiteLLM
357380
"litellmBaseUrl",
358381
"litellmApiKey",
382+
"litellmApiKeyUseEnvVar",
359383
"litellmModelId",
360384
])

src/api/index.ts

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ export interface ApiHandler {
5858
countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number>
5959
}
6060

61-
6261
/**
6362
* Read an environment variable value, returning a default value if not set. If neither key nor default is set,
6463
* returns undefined
@@ -83,10 +82,14 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
8382
}
8483
return new AnthropicHandler(options)
8584
case "glama":
86-
options.glamaApiKey = getEnvVar("GLAMA_API_KEY", options.glamaApiKey)
85+
if (options.glamaApiKeyUseEnvVar) {
86+
options.glamaApiKey = getEnvVar("GLAMA_API_KEY", options.glamaApiKey)
87+
}
8788
return new GlamaHandler(options)
8889
case "openrouter":
89-
options.openRouterApiKey = getEnvVar("OPEN_ROUTER_API_KEY", options.openRouterApiKey)
90+
if (options.openRouterApiKeyUseEnvVar) {
91+
options.openRouterApiKey = getEnvVar("OPEN_ROUTER_API_KEY", options.openRouterApiKey)
92+
}
9093
return new OpenRouterHandler(options)
9194
case "bedrock":
9295
return new AwsBedrockHandler(options)
@@ -104,46 +107,65 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
104107
case "lmstudio":
105108
return new LmStudioHandler(options)
106109
case "gemini":
107-
options.geminiApiKey = getEnvVar("GEMINI_API_KEY", options.geminiApiKey)
110+
if (options.geminiApiKeyUseEnvVar) {
111+
options.geminiApiKey = getEnvVar("GEMINI_API_KEY", options.geminiApiKey)
112+
}
108113
return new GeminiHandler(options)
109114
case "openai-native":
110-
options.openAiNativeApiKey = getEnvVar(
111-
"OPEN_AI_NATIVE_API_KEY",
112-
options.openAiNativeApiKey
113-
)
115+
if (options.openAiNativeApiKeyUseEnvVar) {
116+
options.openAiNativeApiKey = getEnvVar("OPEN_AI_NATIVE_API_KEY", options.openAiNativeApiKey)
117+
}
114118
return new OpenAiNativeHandler(options)
115119
case "deepseek":
116-
options.deepSeekApiKey = getEnvVar("DEEP_SEEK_API_KEY", options.deepSeekApiKey)
120+
if (options.deepSeekApiKeyUseEnvVar) {
121+
options.deepSeekApiKey = getEnvVar("DEEP_SEEK_API_KEY", options.deepSeekApiKey)
122+
}
117123
return new DeepSeekHandler(options)
118124
case "vscode-lm":
119125
return new VsCodeLmHandler(options)
120126
case "mistral":
121-
options.mistralApiKey = getEnvVar("MISTRAL_API_KEY", options.mistralApiKey)
127+
if (options.mistralApiKeyUseEnvVar) {
128+
options.mistralApiKey = getEnvVar("MISTRAL_API_KEY", options.mistralApiKey)
129+
}
122130
return new MistralHandler(options)
123131
case "unbound":
124-
options.unboundApiKey = getEnvVar("UNBOUND_API_KEY", options.unboundApiKey)
132+
if (options.unboundApiKeyUseEnvVar) {
133+
options.unboundApiKey = getEnvVar("UNBOUND_API_KEY", options.unboundApiKey)
134+
}
125135
return new UnboundHandler(options)
126136
case "requesty":
127-
options.requestyApiKey = getEnvVar("REQUESTY_API_KEY", options.requestyApiKey)
137+
if (options.requestyApiKeyUseEnvVar) {
138+
options.requestyApiKey = getEnvVar("REQUESTY_API_KEY", options.requestyApiKey)
139+
}
128140
return new RequestyHandler(options)
129141
case "human-relay":
130142
return new HumanRelayHandler()
131143
case "fake-ai":
132144
return new FakeAIHandler(options)
133145
case "xai":
134-
options.xaiApiKey = getEnvVar("XAI_API_KEY", options.xaiApiKey)
146+
if (options.xaiApiKeyUseEnvVar) {
147+
options.xaiApiKey = getEnvVar("XAI_API_KEY", options.xaiApiKey)
148+
}
135149
return new XAIHandler(options)
136150
case "groq":
137-
options.groqApiKey = getEnvVar("GROQ_API_KEY", options.groqApiKey)
151+
if (options.groqApiKeyUseEnvVar) {
152+
options.groqApiKey = getEnvVar("GROQ_API_KEY", options.groqApiKey)
153+
}
138154
return new GroqHandler(options)
139155
case "chutes":
140-
options.chutesApiKey = getEnvVar("CHUTES_API_KEY", options.chutesApiKey)
156+
if (options.chutesApiKeyUseEnvVar) {
157+
options.chutesApiKey = getEnvVar("CHUTES_API_KEY", options.chutesApiKey)
158+
}
141159
return new ChutesHandler(options)
142160
case "litellm":
143-
options.litellmApiKey = getEnvVar("LITELLM_API_KEY", options.litellmApiKey)
161+
if (options.litellmApiKeyUseEnvVar) {
162+
options.litellmApiKey = getEnvVar("LITELLM_API_KEY", options.litellmApiKey)
163+
}
144164
return new LiteLLMHandler(options)
145165
default:
146-
options.apiKey = getEnvVar("ANTROPIC_API_KEY", options.apiKey)
166+
if (options.anthropicApiKeyUseEnvVar) {
167+
options.apiKey = getEnvVar("ANTROPIC_API_KEY", options.apiKey)
168+
}
147169
return new AnthropicHandler(options)
148170
}
149171
}

webview-ui/src/components/settings/providers/Chutes.tsx

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useAppTranslation } from "@src/i18n/TranslationContext"
77
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
88

99
import { inputEventTransform } from "../transforms"
10+
import { ApiKey } from "../ApiKey"
1011

1112
type ChutesProps = {
1213
apiConfiguration: ProviderSettings
@@ -26,25 +27,18 @@ export const Chutes = ({ apiConfiguration, setApiConfigurationField }: ChutesPro
2627
},
2728
[setApiConfigurationField],
2829
)
29-
3030
return (
3131
<>
32-
<VSCodeTextField
33-
value={apiConfiguration?.chutesApiKey || ""}
34-
type="password"
35-
onInput={handleInputChange("chutesApiKey")}
36-
placeholder={t("settings:placeholders.apiKey")}
37-
className="w-full">
38-
<label className="block font-medium mb-1">{t("settings:providers.chutesApiKey")}</label>
39-
</VSCodeTextField>
40-
<div className="text-sm text-vscode-descriptionForeground -mt-2">
41-
{t("settings:providers.apiKeyStorageNotice")}
42-
</div>
43-
{!apiConfiguration?.chutesApiKey && (
44-
<VSCodeButtonLink href="https://chutes.ai/app/api" appearance="secondary">
45-
{t("settings:providers.getChutesApiKey")}
46-
</VSCodeButtonLink>
47-
)}
32+
<ApiKey
33+
apiKey={apiConfiguration?.chutesApiKey || ""}
34+
apiKeyEnvVar="CHUTES_API_KEY"
35+
apiKeyUseEnvVar={!!apiConfiguration?.chutesApiKeyUseEnvVar}
36+
setApiKey={(value: string) => setApiConfigurationField("apiKey", value)}
37+
setApiKeyUseEnvVar={(value: boolean) => setApiConfigurationField("chutesApiKeyUseEnvVar", value)}
38+
apiKeyLabel={t("settings:providers.chutesApiKey")}
39+
getApiKeyUrl="https://chutes.ai/app/api"
40+
getApiKeyLabel={t("settings:providers.getchutesApiKey")}
41+
/>
4842
</>
4943
)
5044
}

webview-ui/src/components/settings/providers/DeepSeek.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useAppTranslation } from "@src/i18n/TranslationContext"
77
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
88

99
import { inputEventTransform } from "../transforms"
10+
import { ApiKey } from "../ApiKey"
1011

1112
type DeepSeekProps = {
1213
apiConfiguration: ProviderSettings
@@ -29,22 +30,16 @@ export const DeepSeek = ({ apiConfiguration, setApiConfigurationField }: DeepSee
2930

3031
return (
3132
<>
32-
<VSCodeTextField
33-
value={apiConfiguration?.deepSeekApiKey || ""}
34-
type="password"
35-
onInput={handleInputChange("deepSeekApiKey")}
36-
placeholder={t("settings:placeholders.apiKey")}
37-
className="w-full">
38-
<label className="block font-medium mb-1">{t("settings:providers.deepSeekApiKey")}</label>
39-
</VSCodeTextField>
40-
<div className="text-sm text-vscode-descriptionForeground -mt-2">
41-
{t("settings:providers.apiKeyStorageNotice")}
42-
</div>
43-
{!apiConfiguration?.deepSeekApiKey && (
44-
<VSCodeButtonLink href="https://platform.deepseek.com/" appearance="secondary">
45-
{t("settings:providers.getDeepSeekApiKey")}
46-
</VSCodeButtonLink>
47-
)}
33+
<ApiKey
34+
apiKey={apiConfiguration?.deepSeekApiKey || ""}
35+
apiKeyEnvVar="DEEP_SEEK_API_KEY"
36+
apiKeyUseEnvVar={!!apiConfiguration?.deepSeekApiKeyUseEnvVar}
37+
setApiKey={(value: string) => setApiConfigurationField("deepSeekApiKey", value)}
38+
setApiKeyUseEnvVar={(value: boolean) => setApiConfigurationField("deepSeekApiKeyUseEnvVar", value)}
39+
apiKeyLabel={t("settings:providers.deepSeekApiKey")}
40+
getApiKeyUrl="https://platform.deepseek.com/"
41+
getApiKeyLabel={t("settings:providers.getDeepSeekApiKey")}
42+
/>
4843
</>
4944
)
5045
}

webview-ui/src/components/settings/providers/Gemini.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useAppTranslation } from "@src/i18n/TranslationContext"
88
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
99

1010
import { inputEventTransform } from "../transforms"
11+
import { ApiKey } from "../ApiKey"
1112

1213
type GeminiProps = {
1314
apiConfiguration: ProviderSettings
@@ -34,22 +35,16 @@ export const Gemini = ({ apiConfiguration, setApiConfigurationField }: GeminiPro
3435

3536
return (
3637
<>
37-
<VSCodeTextField
38-
value={apiConfiguration?.geminiApiKey || ""}
39-
type="password"
40-
onInput={handleInputChange("geminiApiKey")}
41-
placeholder={t("settings:placeholders.apiKey")}
42-
className="w-full">
43-
<label className="block font-medium mb-1">{t("settings:providers.geminiApiKey")}</label>
44-
</VSCodeTextField>
45-
<div className="text-sm text-vscode-descriptionForeground -mt-2">
46-
{t("settings:providers.apiKeyStorageNotice")}
47-
</div>
48-
{!apiConfiguration?.geminiApiKey && (
49-
<VSCodeButtonLink href="https://ai.google.dev/" appearance="secondary">
50-
{t("settings:providers.getGeminiApiKey")}
51-
</VSCodeButtonLink>
52-
)}
38+
<ApiKey
39+
apiKey={apiConfiguration?.geminiApiKey || ""}
40+
apiKeyEnvVar="GEMINI_API_KEY"
41+
apiKeyUseEnvVar={!!apiConfiguration?.geminiApiKeyUseEnvVar}
42+
setApiKey={(value: string) => setApiConfigurationField("geminiApiKey", value)}
43+
setApiKeyUseEnvVar={(value: boolean) => setApiConfigurationField("geminiApiKeyUseEnvVar", value)}
44+
apiKeyLabel={t("settings:providers.geminiApiKey")}
45+
getApiKeyUrl="https://ai.google.dev/"
46+
getApiKeyLabel={t("settings:providers.getGeminiApiKey")}
47+
/>
5348
<div>
5449
<Checkbox
5550
checked={googleGeminiBaseUrlSelected}

0 commit comments

Comments
 (0)