Skip to content

Commit 7effefa

Browse files
committed
feat: add support for multiple LLM providers (Anthropic, Gemini, Bedrock, OpenRouter, Grok)
- Add new LLM provider implementations for Anthropic, Gemini, AWS Bedrock, OpenRouter, and xAI Grok - Refactor LLM provider interface to support provider-specific credential requirements - Add CredentialInput type for declarative credential collection - Update config collection to dynamically prompt for provider-specific credentials - Modify env file generation to work with any LLM provider - Replace hardcoded openaiApiKey with generic llmApiKey and llmAdditionalInputs fields - AWS Bedrock now prompts for Access Key ID, Secret Key, and Region as needed
1 parent ee739fb commit 7effefa

File tree

11 files changed

+213
-31
lines changed

11 files changed

+213
-31
lines changed

src/config-collection/collect-config.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
import { buildLanguageChoices } from "./choice-builders/language-choices.js";
1010
import { buildFrameworkChoices } from "./choice-builders/framework-choices.js";
1111
import { buildCodingAssistantChoices } from "./choice-builders/coding-assistant-choices.js";
12+
import { getAllLLMProviders } from "../providers/llm-providers/index.js";
1213
import { validateOpenAIKey } from "./validators/openai-key.js";
1314
import { validateLangWatchKey } from "./validators/langwatch-key.js";
1415
import { validateProjectGoal } from "./validators/project-goal.js";
@@ -46,17 +47,48 @@ export const collectConfig = async (): Promise<ProjectConfig> => {
4647
choices: await buildCodingAssistantChoices(),
4748
});
4849

50+
const allProviders = getAllLLMProviders();
4951
const llmProvider = await select<LLMProvider>({
5052
message: "What LLM provider do you want to use?",
51-
choices: [{ name: "OpenAI", value: "openai" }],
53+
choices: allProviders.map((p) => ({ name: p.displayName, value: p.id as LLMProvider })),
5254
});
5355

54-
const openaiApiKey = await password({
55-
message: "Enter your OpenAI API key:",
56+
const selectedProvider = allProviders.find((p) => p.id === llmProvider);
57+
const providerDisplayName = selectedProvider?.displayName || llmProvider;
58+
59+
const llmApiKey = await password({
60+
message: `Enter your ${providerDisplayName} API key:`,
5661
mask: "*",
57-
validate: validateOpenAIKey,
62+
validate: llmProvider === "openai" ? validateOpenAIKey : (value) => {
63+
if (!value || value.length < 5) {
64+
return "API key is required and must be at least 5 characters";
65+
}
66+
return true;
67+
},
5868
});
5969

70+
// Collect additional credentials if the provider needs them
71+
let llmAdditionalInputs: Record<string, string> | undefined;
72+
if (selectedProvider?.additionalCredentials && selectedProvider.additionalCredentials.length > 0) {
73+
llmAdditionalInputs = {};
74+
75+
for (const credential of selectedProvider.additionalCredentials) {
76+
if (credential.type === "password") {
77+
llmAdditionalInputs[credential.key] = await password({
78+
message: `Enter your ${credential.label}:`,
79+
mask: "*",
80+
validate: credential.validate,
81+
});
82+
} else {
83+
llmAdditionalInputs[credential.key] = await input({
84+
message: `Enter your ${credential.label}:`,
85+
default: credential.defaultValue,
86+
validate: credential.validate,
87+
});
88+
}
89+
}
90+
}
91+
6092
console.log(chalk.gray("\nTo get your LangWatch API key, visit:"));
6193
console.log(chalk.blue.underline("https://app.langwatch.ai/authorize\n"));
6294

@@ -76,7 +108,8 @@ export const collectConfig = async (): Promise<ProjectConfig> => {
76108
framework,
77109
codingAssistant,
78110
llmProvider,
79-
openaiApiKey,
111+
llmApiKey,
112+
llmAdditionalInputs,
80113
langwatchApiKey,
81114
projectGoal,
82115
};

src/project-scaffolding/create-project-structure.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ export const createProjectStructure = async ({
3131

3232
await generateEnvFiles({
3333
projectPath,
34-
openaiApiKey: config.openaiApiKey,
35-
langwatchApiKey: config.langwatchApiKey,
34+
config,
3635
});
3736

3837
await generateGitignore({ projectPath });
Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,55 @@
11
import * as fs from "fs/promises";
22
import * as path from "path";
3+
import type { ProjectConfig } from "../../types.js";
4+
import { getLLMProvider } from "../../providers/llm-providers/index.js";
35

46
/**
57
* Generates .env.example and .env files with API key placeholders.
68
*
79
* @param params - Parameters object
810
* @param params.projectPath - Absolute path to project root
9-
* @param params.openaiApiKey - OpenAI API key to include in .env
10-
* @param params.langwatchApiKey - LangWatch API key to include in .env
11+
* @param params.config - Project configuration with LLM provider details
1112
* @returns Promise that resolves when files are written
1213
*
1314
* @example
1415
* ```ts
15-
* await generateEnvFiles({ projectPath: '/path', openaiApiKey: 'sk-...', langwatchApiKey: 'sk-lw-...' });
16+
* await generateEnvFiles({ projectPath: '/path', config });
1617
* ```
1718
*/
1819
export const generateEnvFiles = async ({
1920
projectPath,
20-
openaiApiKey,
21-
langwatchApiKey,
21+
config,
2222
}: {
2323
projectPath: string;
24-
openaiApiKey: string;
25-
langwatchApiKey: string;
24+
config: ProjectConfig;
2625
}): Promise<void> => {
27-
const envExample = `# LLM Provider API Keys
28-
OPENAI_API_KEY=your_openai_api_key_here
26+
const provider = getLLMProvider({ provider: config.llmProvider });
27+
const envVars = provider.getEnvVariables({
28+
apiKey: config.llmApiKey,
29+
additionalInputs: config.llmAdditionalInputs,
30+
});
2931

30-
# LangWatch
31-
LANGWATCH_API_KEY=your_langwatch_api_key_here
32-
`;
32+
// Generate .env.example with placeholders
33+
const envExampleLines = [
34+
"# LLM Provider API Keys",
35+
...envVars.map((v) => `${v.key}=your_${v.key.toLowerCase()}_here`),
36+
"",
37+
"# LangWatch",
38+
"LANGWATCH_API_KEY=your_langwatch_api_key_here",
39+
];
40+
const envExample = envExampleLines.join("\n") + "\n";
3341

3442
await fs.writeFile(path.join(projectPath, ".env.example"), envExample);
3543

36-
const envContent = `# LLM Provider API Keys
37-
OPENAI_API_KEY=${openaiApiKey}
38-
39-
# LangWatch
40-
LANGWATCH_API_KEY=${langwatchApiKey}
41-
`;
44+
// Generate .env with actual values
45+
const envContentLines = [
46+
"# LLM Provider API Keys",
47+
...envVars.map((v) => `${v.key}=${v.value}`),
48+
"",
49+
"# LangWatch",
50+
`LANGWATCH_API_KEY=${config.langwatchApiKey}`,
51+
];
52+
const envContent = envContentLines.join("\n") + "\n";
4253

4354
await fs.writeFile(path.join(projectPath, ".env"), envContent);
4455
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { LLMProviderProvider } from "../index.js";
2+
3+
/**
4+
* Anthropic (Claude) LLM provider implementation.
5+
*/
6+
export const AnthropicProvider: LLMProviderProvider = {
7+
id: "anthropic",
8+
displayName: "Anthropic (Claude)",
9+
10+
getEnvVariables: ({ apiKey }) => [
11+
{ key: "ANTHROPIC_API_KEY", value: apiKey },
12+
],
13+
};
14+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { LLMProviderProvider } from "../index.js";
2+
3+
/**
4+
* AWS Bedrock LLM provider implementation.
5+
* Requires AWS credentials (access key, secret key, and region).
6+
*/
7+
export const BedrockProvider: LLMProviderProvider = {
8+
id: "bedrock",
9+
displayName: "AWS Bedrock",
10+
11+
additionalCredentials: [
12+
{
13+
key: "awsSecretKey",
14+
label: "AWS Secret Access Key",
15+
type: "password",
16+
validate: (value) => {
17+
if (!value || value.length < 10) {
18+
return "AWS Secret Access Key is required";
19+
}
20+
return true;
21+
},
22+
},
23+
{
24+
key: "awsRegion",
25+
label: "AWS Region (e.g., us-east-1)",
26+
type: "text",
27+
defaultValue: "us-east-1",
28+
validate: (value) => {
29+
if (!value || value.length < 5) {
30+
return "AWS Region is required";
31+
}
32+
return true;
33+
},
34+
},
35+
],
36+
37+
getEnvVariables: ({ apiKey, additionalInputs }) => {
38+
const envVars = [
39+
{ key: "AWS_ACCESS_KEY_ID", value: apiKey },
40+
];
41+
42+
if (additionalInputs?.awsSecretKey) {
43+
envVars.push({
44+
key: "AWS_SECRET_ACCESS_KEY",
45+
value: additionalInputs.awsSecretKey,
46+
});
47+
}
48+
49+
if (additionalInputs?.awsRegion) {
50+
envVars.push({
51+
key: "AWS_REGION",
52+
value: additionalInputs.awsRegion,
53+
});
54+
}
55+
56+
return envVars;
57+
},
58+
};
59+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { LLMProviderProvider } from "../index.js";
2+
3+
/**
4+
* Google Gemini LLM provider implementation.
5+
*/
6+
export const GeminiProvider: LLMProviderProvider = {
7+
id: "gemini",
8+
displayName: "Google Gemini",
9+
10+
getEnvVariables: ({ apiKey }) => [
11+
{ key: "GOOGLE_API_KEY", value: apiKey },
12+
{ key: "GEMINI_API_KEY", value: apiKey },
13+
],
14+
};
15+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { LLMProviderProvider } from "../index.js";
2+
3+
/**
4+
* xAI Grok LLM provider implementation.
5+
*/
6+
export const GrokProvider: LLMProviderProvider = {
7+
id: "grok",
8+
displayName: "xAI (Grok)",
9+
10+
getEnvVariables: ({ apiKey }) => [
11+
{ key: "XAI_API_KEY", value: apiKey },
12+
],
13+
};
14+

src/providers/llm-providers/index.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
import { OpenAIProvider } from "./openai/index.js";
2+
import { AnthropicProvider } from "./anthropic/index.js";
3+
import { GeminiProvider } from "./gemini/index.js";
4+
import { BedrockProvider } from "./bedrock/index.js";
5+
import { OpenRouterProvider } from "./openrouter/index.js";
6+
import { GrokProvider } from "./grok/index.js";
27

38
export type EnvVariable = {
49
key: string;
510
value: string;
611
};
712

13+
export type CredentialInput = {
14+
key: string;
15+
label: string;
16+
type: "password" | "text";
17+
defaultValue?: string;
18+
validate?: (value: string) => string | boolean;
19+
};
20+
821
/**
922
* Interface for LLM provider configuration.
1023
* Each provider returns environment variables needed for API access.
@@ -19,15 +32,26 @@ export interface LLMProviderProvider {
1932
readonly id: string;
2033
readonly displayName: string;
2134

35+
/** Optional additional credentials this provider needs (beyond the main API key) */
36+
readonly additionalCredentials?: CredentialInput[];
37+
2238
/** Returns environment variables needed for this provider */
23-
getEnvVariables(params: { apiKey: string }): EnvVariable[];
39+
getEnvVariables(params: {
40+
apiKey: string;
41+
additionalInputs?: Record<string, string>;
42+
}): EnvVariable[];
2443

2544
/** Validates API key format/connectivity */
2645
validateApiKey?(params: { apiKey: string }): Promise<boolean>;
2746
}
2847

2948
const PROVIDERS: Record<string, LLMProviderProvider> = {
3049
openai: OpenAIProvider,
50+
anthropic: AnthropicProvider,
51+
gemini: GeminiProvider,
52+
bedrock: BedrockProvider,
53+
openrouter: OpenRouterProvider,
54+
grok: GrokProvider,
3155
};
3256

3357
/**

src/providers/llm-providers/openai/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const OpenAIProvider: LLMProviderProvider = {
77
id: "openai",
88
displayName: "OpenAI",
99

10-
getEnvVariables: ({ apiKey }) => [{ key: "OPENAI_API_KEY", value: apiKey }],
10+
getEnvVariables: ({ apiKey }) => [
11+
{ key: "OPENAI_API_KEY", value: apiKey },
12+
],
1113
};
1214

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { LLMProviderProvider } from "../index.js";
2+
3+
/**
4+
* OpenRouter LLM provider implementation.
5+
*/
6+
export const OpenRouterProvider: LLMProviderProvider = {
7+
id: "openrouter",
8+
displayName: "OpenRouter",
9+
10+
getEnvVariables: ({ apiKey }) => [
11+
{ key: "OPENROUTER_API_KEY", value: apiKey },
12+
],
13+
};
14+

0 commit comments

Comments
 (0)