From 1dd424a441749b03a8dca1a32f03b234ec72f298 Mon Sep 17 00:00:00 2001 From: uinstinct <61635505+uinstinct@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:40:40 +0530 Subject: [PATCH 1/4] add openai api key option in onboarding --- extensions/cli/spec/onboarding.md | 18 ++++- extensions/cli/src/onboarding.ts | 66 ++++++++++++++---- extensions/cli/src/services/index.ts | 3 +- extensions/cli/src/util/apiKeyValidation.ts | 37 ++++++++-- extensions/cli/src/util/yamlConfigUpdater.ts | 72 +++++++++++++++++++- 5 files changed, 172 insertions(+), 24 deletions(-) diff --git a/extensions/cli/spec/onboarding.md b/extensions/cli/spec/onboarding.md index 58c65132292..6344ce497be 100644 --- a/extensions/cli/spec/onboarding.md +++ b/extensions/cli/spec/onboarding.md @@ -10,7 +10,6 @@ When a user first runs `cn` in interactive mode, they will be taken through "onb 2. If the CONTINUE_USE_BEDROCK environment variable is set to "1", automatically use AWS Bedrock configuration and skip interactive prompts 3. Present the user with available options: - - Log in with Continue: log them in, which will automatically create their assistant and then we can load the first assistant from the first org - Enter your Anthropic API key: let them enter the key, and then either create a ~/.continue/config.yaml with the following contents OR update the existing config.yaml to add the model ```yaml @@ -19,11 +18,26 @@ When a user first runs `cn` in interactive mode, they will be taken through "onb schema: v1 models: - - uses: anthropic/claude-4-sonnet + - uses: anthropic/claude-sonnet-4-6 with: ANTHROPIC_API_KEY: ``` + - Enter your OpenAI API key: let them enter the key, and then either create a ~/.continue/config.yaml with the following contents OR update the existing config.yaml to add the model + + ```yaml + name: Local Config + version: 1.0.0 + schema: v1 + + models: + - uses: openai/gpt-4.1 + with: + OPENAI_API_KEY: + ``` + + - Log in with Continue: log them in, which will automatically create their assistant and then we can load the first assistant from the first org + When CONTINUE_USE_BEDROCK=1 is detected, it will use AWS Bedrock configuration. The user must have AWS credentials configured through the standard AWS credential chain (AWS CLI, environment variables, IAM roles, etc.). When something in the onboarding flow is done automatically, we should tell the user what happened. For example, when CONTINUE_USE_BEDROCK=1 is detected, the CLI displays: "✓ Using AWS Bedrock (CONTINUE_USE_BEDROCK detected)" diff --git a/extensions/cli/src/onboarding.ts b/extensions/cli/src/onboarding.ts index 2a8162284de..43b8ba08553 100644 --- a/extensions/cli/src/onboarding.ts +++ b/extensions/cli/src/onboarding.ts @@ -11,9 +11,13 @@ import { env } from "./env.js"; import { getApiKeyValidationError, isValidAnthropicApiKey, + isValidOpenAIApiKey, } from "./util/apiKeyValidation.js"; import { question, questionWithChoices } from "./util/prompt.js"; -import { updateAnthropicModelInYaml } from "./util/yamlConfigUpdater.js"; +import { + updateAnthropicModelInYaml, + updateOpenAIModelInYaml, +} from "./util/yamlConfigUpdater.js"; const CONFIG_PATH = path.join(env.continueHome, "config.yaml"); @@ -32,7 +36,9 @@ export async function checkHasAcceptableModel( } } -export async function createOrUpdateConfig(apiKey: string): Promise { +export async function createOrUpdateConfigAnthropic( + apiKey: string, +): Promise { const configDir = path.dirname(CONFIG_PATH); if (!fs.existsSync(configDir)) { @@ -48,6 +54,24 @@ export async function createOrUpdateConfig(apiKey: string): Promise { setConfigFilePermissions(CONFIG_PATH); } +export async function createOrUpdateConfigOpenAI( + apiKey: string, +): Promise { + const configDir = path.dirname(CONFIG_PATH); + + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); + } + + const existingContent = fs.existsSync(CONFIG_PATH) + ? fs.readFileSync(CONFIG_PATH, "utf8") + : ""; + + const updatedContent = updateOpenAIModelInYaml(existingContent, apiKey); + fs.writeFileSync(CONFIG_PATH, updatedContent); + setConfigFilePermissions(CONFIG_PATH); +} + export async function runOnboardingFlow( configPath: string | undefined, ): Promise { @@ -76,7 +100,7 @@ export async function runOnboardingFlow( // In test/CI environment, check for ANTHROPIC_API_KEY first if (process.env.ANTHROPIC_API_KEY) { console.log(chalk.blue("✓ Using ANTHROPIC_API_KEY from environment")); - await createOrUpdateConfig(process.env.ANTHROPIC_API_KEY); + await createOrUpdateConfigAnthropic(process.env.ANTHROPIC_API_KEY); console.log(chalk.gray(` Config saved to: ${CONFIG_PATH}`)); return false; } @@ -85,38 +109,52 @@ export async function runOnboardingFlow( return false; } - // Step 4: Present user with two options + // Step 4: Present user with three options console.log(chalk.yellow("How do you want to get started?")); - console.log(chalk.white("1. ⏩ Log in with Continue")); - console.log(chalk.white("2. 🔑 Enter your Anthropic API key")); + console.log(chalk.white("1. 🔑 Enter your Anthropic API key")); + console.log(chalk.white("2. 🔑 Enter your OpenAI API key")); + console.log(chalk.white("3. ⏩ Log in with Continue")); const choice = await questionWithChoices( chalk.yellow("\nEnter choice (1): "), - ["1", "2", ""], + ["1", "2", "3", ""], "1", - chalk.dim("Please enter 1 or 2"), + chalk.dim("Please enter 1, 2, or 3"), ); if (choice === "1" || choice === "") { - await login(); - return true; - } else if (choice === "2") { const apiKey = await question( chalk.white("\nEnter your Anthropic API key: "), ); if (!isValidAnthropicApiKey(apiKey)) { - throw new Error(getApiKeyValidationError(apiKey)); + throw new Error(getApiKeyValidationError(apiKey, "anthropic")); + } + + await createOrUpdateConfigAnthropic(apiKey); + console.log( + chalk.green(`✓ Config file updated successfully at ${CONFIG_PATH}`), + ); + + return true; + } else if (choice === "2") { + const apiKey = await question(chalk.white("\nEnter your OpenAI API key: ")); + + if (!isValidOpenAIApiKey(apiKey)) { + throw new Error(getApiKeyValidationError(apiKey, "openai")); } - await createOrUpdateConfig(apiKey); + await createOrUpdateConfigOpenAI(apiKey); console.log( chalk.green(`✓ Config file updated successfully at ${CONFIG_PATH}`), ); + return true; + } else if (choice === "3") { + await login(); return true; } else { - throw new Error(`Invalid choice. Please select "1" or "2"`); + throw new Error(`Invalid choice. Please select "1", "2", or "3"`); } } diff --git a/extensions/cli/src/services/index.ts b/extensions/cli/src/services/index.ts index bebe44c9dfc..6f1e7134fad 100644 --- a/extensions/cli/src/services/index.ts +++ b/extensions/cli/src/services/index.ts @@ -84,7 +84,8 @@ export async function initializeServices(initOptions: ServiceInitOptions = {}) { !commandOptions.config && process.env.ANTHROPIC_API_KEY ) { - const { createOrUpdateConfig } = await import("../onboarding.js"); + const { createOrUpdateConfigAnthropic: createOrUpdateConfig } = + await import("../onboarding.js"); const { env } = await import("../env.js"); const path = await import("path"); diff --git a/extensions/cli/src/util/apiKeyValidation.ts b/extensions/cli/src/util/apiKeyValidation.ts index f6fbd896c96..41af819b516 100644 --- a/extensions/cli/src/util/apiKeyValidation.ts +++ b/extensions/cli/src/util/apiKeyValidation.ts @@ -14,24 +14,51 @@ export function isValidAnthropicApiKey( return apiKey.startsWith("sk-ant-") && apiKey.length > "sk-ant-".length; } +/** + * Validates an OpenAI API key format + * @param apiKey The API key to validate + * @returns true if the API key is valid, false otherwise + */ +export function isValidOpenAIApiKey( + apiKey: string | null | undefined, +): boolean { + if (!apiKey || typeof apiKey !== "string") { + return false; + } + + return apiKey.startsWith("sk-") && apiKey.length > "sk-".length; +} + /** * Gets a user-friendly error message for invalid API keys * @param apiKey The API key that failed validation + * @param provider The provider name (e.g., "Anthropic", "OpenAI") * @returns A descriptive error message */ export function getApiKeyValidationError( apiKey: string | null | undefined, + provider: "anthropic" | "openai" = "anthropic", ): string { if (!apiKey || typeof apiKey !== "string") { return "API key is required"; } - if (!apiKey.startsWith("sk-ant-")) { - return 'API key must start with "sk-ant-"'; - } + if (provider === "anthropic") { + if (!apiKey.startsWith("sk-ant-")) { + return 'API key must start with "sk-ant-"'; + } + + if (apiKey.length <= "sk-ant-".length) { + return "API key is too short"; + } + } else if (provider === "openai") { + if (!apiKey.startsWith("sk-")) { + return 'API key must start with "sk-"'; + } - if (apiKey.length <= "sk-ant-".length) { - return "API key is too short"; + if (apiKey.length <= "sk-".length) { + return "API key is too short"; + } } return "Invalid API key format"; diff --git a/extensions/cli/src/util/yamlConfigUpdater.ts b/extensions/cli/src/util/yamlConfigUpdater.ts index 08b1c27fb2a..06277a8eb62 100644 --- a/extensions/cli/src/util/yamlConfigUpdater.ts +++ b/extensions/cli/src/util/yamlConfigUpdater.ts @@ -3,7 +3,8 @@ import { parseDocument } from "yaml"; export interface ModelConfig { uses: string; with: { - ANTHROPIC_API_KEY: string; + ANTHROPIC_API_KEY?: string; + OPENAI_API_KEY?: string; }; } @@ -65,7 +66,74 @@ export function updateAnthropicModelInYaml( (model: any) => !model || model.uses !== "anthropic/claude-sonnet-4-6", ); - // Add the new anthropic model + config.models.push(newModel); + + doc.set("models", config.models); + + return doc.toString(); + } catch { + const defaultConfig: ConfigStructure = { + name: "Local Config", + version: "1.0.0", + schema: "v1", + models: [newModel], + }; + + const doc = parseDocument(""); + Object.keys(defaultConfig).forEach((key) => + doc.set(key, (defaultConfig as any)[key]), + ); + return doc.toString(); + } +} + +/** + * Updates or adds an OpenAI model configuration in a YAML string while preserving comments and formatting. + * This is a pure function that takes a YAML string and returns a modified YAML string. + * + * @param yamlContent - The original YAML content as a string (can be empty) + * @param apiKey - The OpenAI API key to set + * @returns The updated YAML content as a string with comments preserved + */ +export function updateOpenAIModelInYaml( + yamlContent: string, + apiKey: string, +): string { + const newModel: ModelConfig = { + uses: "openai/gpt-5.3", + with: { + OPENAI_API_KEY: apiKey, + }, + }; + + try { + const doc = parseDocument(yamlContent); + + if (!doc.contents || doc.contents === null) { + const defaultConfig: ConfigStructure = { + name: "Local Config", + version: "1.0.0", + schema: "v1", + models: [newModel], + }; + + const newDoc = parseDocument(""); + Object.keys(defaultConfig).forEach((key) => + newDoc.set(key, (defaultConfig as any)[key]), + ); + return newDoc.toString(); + } + + const config = doc.toJS() as any; + + if (!config.models) { + config.models = []; + } + + config.models = config.models.filter( + (model: any) => !model || model.uses !== "openai/gpt-5.3", + ); + config.models.push(newModel); // Update the models array while preserving comments and structure From c28af72402590ce3f7f701c8636c17457c84e71c Mon Sep 17 00:00:00 2001 From: uinstinct <61635505+uinstinct@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:43:52 +0530 Subject: [PATCH 2/4] remove login with continue option --- extensions/cli/spec/onboarding.md | 4 +--- extensions/cli/src/onboarding.ts | 14 +++++--------- extensions/cli/src/util/yamlConfigUpdater.ts | 4 ++-- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/extensions/cli/spec/onboarding.md b/extensions/cli/spec/onboarding.md index 6344ce497be..bbbc4bb4662 100644 --- a/extensions/cli/spec/onboarding.md +++ b/extensions/cli/spec/onboarding.md @@ -31,13 +31,11 @@ When a user first runs `cn` in interactive mode, they will be taken through "onb schema: v1 models: - - uses: openai/gpt-4.1 + - uses: openai/gpt-5.4 with: OPENAI_API_KEY: ``` - - Log in with Continue: log them in, which will automatically create their assistant and then we can load the first assistant from the first org - When CONTINUE_USE_BEDROCK=1 is detected, it will use AWS Bedrock configuration. The user must have AWS credentials configured through the standard AWS credential chain (AWS CLI, environment variables, IAM roles, etc.). When something in the onboarding flow is done automatically, we should tell the user what happened. For example, when CONTINUE_USE_BEDROCK=1 is detected, the CLI displays: "✓ Using AWS Bedrock (CONTINUE_USE_BEDROCK detected)" diff --git a/extensions/cli/src/onboarding.ts b/extensions/cli/src/onboarding.ts index 43b8ba08553..b902cbf79ce 100644 --- a/extensions/cli/src/onboarding.ts +++ b/extensions/cli/src/onboarding.ts @@ -4,7 +4,7 @@ import * as path from "path"; import chalk from "chalk"; import { setConfigFilePermissions } from "core/util/paths.js"; -import { AuthConfig, login } from "./auth/workos.js"; +import { AuthConfig } from "./auth/workos.js"; import { getApiClient } from "./config.js"; import { loadConfiguration } from "./configLoader.js"; import { env } from "./env.js"; @@ -109,17 +109,16 @@ export async function runOnboardingFlow( return false; } - // Step 4: Present user with three options + // Step 4: Present user with two options console.log(chalk.yellow("How do you want to get started?")); console.log(chalk.white("1. 🔑 Enter your Anthropic API key")); console.log(chalk.white("2. 🔑 Enter your OpenAI API key")); - console.log(chalk.white("3. ⏩ Log in with Continue")); const choice = await questionWithChoices( chalk.yellow("\nEnter choice (1): "), - ["1", "2", "3", ""], + ["1", "2", ""], "1", - chalk.dim("Please enter 1, 2, or 3"), + chalk.dim("Please enter 1 or 2"), ); if (choice === "1" || choice === "") { @@ -149,12 +148,9 @@ export async function runOnboardingFlow( chalk.green(`✓ Config file updated successfully at ${CONFIG_PATH}`), ); - return true; - } else if (choice === "3") { - await login(); return true; } else { - throw new Error(`Invalid choice. Please select "1", "2", or "3"`); + throw new Error(`Invalid choice. Please select "1" or "2"`); } } diff --git a/extensions/cli/src/util/yamlConfigUpdater.ts b/extensions/cli/src/util/yamlConfigUpdater.ts index 06277a8eb62..566b65e6b6c 100644 --- a/extensions/cli/src/util/yamlConfigUpdater.ts +++ b/extensions/cli/src/util/yamlConfigUpdater.ts @@ -100,7 +100,7 @@ export function updateOpenAIModelInYaml( apiKey: string, ): string { const newModel: ModelConfig = { - uses: "openai/gpt-5.3", + uses: "openai/gpt-5.4", with: { OPENAI_API_KEY: apiKey, }, @@ -131,7 +131,7 @@ export function updateOpenAIModelInYaml( } config.models = config.models.filter( - (model: any) => !model || model.uses !== "openai/gpt-5.3", + (model: any) => !model || model.uses !== "openai/gpt-5.4", ); config.models.push(newModel); From 38f09cded36b68b4347e4a8597c81c5e83faeb86 Mon Sep 17 00:00:00 2001 From: uinstinct <61635505+uinstinct@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:51:49 +0530 Subject: [PATCH 3/4] put models locally instead of using hub --- extensions/cli/src/util/yamlConfigUpdater.ts | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/extensions/cli/src/util/yamlConfigUpdater.ts b/extensions/cli/src/util/yamlConfigUpdater.ts index 566b65e6b6c..49c42991a90 100644 --- a/extensions/cli/src/util/yamlConfigUpdater.ts +++ b/extensions/cli/src/util/yamlConfigUpdater.ts @@ -1,11 +1,10 @@ import { parseDocument } from "yaml"; export interface ModelConfig { - uses: string; - with: { - ANTHROPIC_API_KEY?: string; - OPENAI_API_KEY?: string; - }; + name: string; + provider: string; + model: string; + apiKey: string; } export interface ConfigStructure { @@ -28,10 +27,10 @@ export function updateAnthropicModelInYaml( apiKey: string, ): string { const newModel: ModelConfig = { - uses: "anthropic/claude-sonnet-4-6", - with: { - ANTHROPIC_API_KEY: apiKey, - }, + name: "claude sonnet 4.6", + provider: "anthropic", + model: "claude-sonnet-4-6", + apiKey: apiKey, }; try { @@ -100,10 +99,10 @@ export function updateOpenAIModelInYaml( apiKey: string, ): string { const newModel: ModelConfig = { - uses: "openai/gpt-5.4", - with: { - OPENAI_API_KEY: apiKey, - }, + name: "GPT 5.4", + provider: "openai", + model: "gpt-5.4", + apiKey: apiKey, }; try { From a48d9ec246d78faa304367d1e189ad2d7c21cfe5 Mon Sep 17 00:00:00 2001 From: uinstinct <61635505+uinstinct@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:23:24 +0530 Subject: [PATCH 4/4] update `uses` with local model in docs --- extensions/cli/spec/onboarding.md | 14 +- .../cli/src/util/yamlConfigUpdater.test.ts | 143 ++++++++++++------ extensions/cli/src/util/yamlConfigUpdater.ts | 9 +- 3 files changed, 111 insertions(+), 55 deletions(-) diff --git a/extensions/cli/spec/onboarding.md b/extensions/cli/spec/onboarding.md index bbbc4bb4662..1a38ef5c0ea 100644 --- a/extensions/cli/spec/onboarding.md +++ b/extensions/cli/spec/onboarding.md @@ -18,9 +18,10 @@ When a user first runs `cn` in interactive mode, they will be taken through "onb schema: v1 models: - - uses: anthropic/claude-sonnet-4-6 - with: - ANTHROPIC_API_KEY: + - name: claude sonnet 4.6 + provider: anthropic + model: claude-sonnet-4-6 + apiKey: ``` - Enter your OpenAI API key: let them enter the key, and then either create a ~/.continue/config.yaml with the following contents OR update the existing config.yaml to add the model @@ -31,9 +32,10 @@ When a user first runs `cn` in interactive mode, they will be taken through "onb schema: v1 models: - - uses: openai/gpt-5.4 - with: - OPENAI_API_KEY: + - name: GPT 5.4 + provider: openai + model: gpt-5.4 + apiKey: ``` When CONTINUE_USE_BEDROCK=1 is detected, it will use AWS Bedrock configuration. The user must have AWS credentials configured through the standard AWS credential chain (AWS CLI, environment variables, IAM roles, etc.). diff --git a/extensions/cli/src/util/yamlConfigUpdater.test.ts b/extensions/cli/src/util/yamlConfigUpdater.test.ts index e0524e6028c..f2b3e996cd3 100644 --- a/extensions/cli/src/util/yamlConfigUpdater.test.ts +++ b/extensions/cli/src/util/yamlConfigUpdater.test.ts @@ -1,6 +1,9 @@ import { parse } from "yaml"; -import { updateAnthropicModelInYaml } from "./yamlConfigUpdater.js"; +import { + updateAnthropicModelInYaml, + updateOpenAIModelInYaml, +} from "./yamlConfigUpdater.js"; describe("updateAnthropicModelInYaml", () => { const testApiKey = "sk-ant-test123456789"; @@ -12,8 +15,9 @@ describe("updateAnthropicModelInYaml", () => { expect(result).toContain("name: Local Config"); expect(result).toContain("version: 1.0.0"); expect(result).toContain("schema: v1"); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("model: claude-sonnet-4-6"); + expect(result).toContain("apiKey: sk-ant-test123456789"); }); it("should create new config from invalid YAML", () => { @@ -21,8 +25,9 @@ describe("updateAnthropicModelInYaml", () => { const result = updateAnthropicModelInYaml(invalidYaml, testApiKey); expect(result).toContain("name: Local Config"); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("model: claude-sonnet-4-6"); + expect(result).toContain("apiKey: sk-ant-test123456789"); }); }); @@ -34,18 +39,19 @@ version: 1.0.0 schema: v1 # List of available models models: - - uses: openai/gpt-4 - with: - OPENAI_API_KEY: TEST-openai-test + - name: GPT 4 + provider: openai + model: gpt-4 + apiKey: TEST-openai-test `; const result = updateAnthropicModelInYaml(yamlWithComments, testApiKey); expect(result).toContain("# My Continue config"); expect(result).toContain("# List of available models"); - expect(result).toContain("uses: openai/gpt-4"); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); + expect(result).toContain("provider: openai"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("apiKey: sk-ant-test123456789"); }); it("should preserve comments when updating existing model", () => { @@ -55,17 +61,19 @@ version: 1.0.0 schema: v1 # List of available models models: - - uses: anthropic/claude-sonnet-4-6 - with: - ANTHROPIC_API_KEY: old-key + - name: claude sonnet 4.6 + provider: anthropic + model: claude-sonnet-4-6 + apiKey: old-key `; const result = updateAnthropicModelInYaml(yamlWithComments, testApiKey); expect(result).toContain("# My Continue config"); expect(result).toContain("# List of available models"); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("model: claude-sonnet-4-6"); + expect(result).toContain("apiKey: sk-ant-test123456789"); expect(result).not.toContain("old-key"); }); }); @@ -76,17 +84,19 @@ models: version: 1.0.0 schema: v1 models: - - uses: openai/gpt-4 - with: - OPENAI_API_KEY: TEST-openai-test + - name: GPT 4 + provider: openai + model: gpt-4 + apiKey: TEST-openai-test `; const result = updateAnthropicModelInYaml(existingConfig, testApiKey); - expect(result).toContain("uses: openai/gpt-4"); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); - expect(result).toContain("OPENAI_API_KEY: TEST-openai-test"); + expect(result).toContain("provider: openai"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("model: claude-sonnet-4-6"); + expect(result).toContain("apiKey: sk-ant-test123456789"); + expect(result).toContain("apiKey: TEST-openai-test"); }); it("should update existing anthropic model", () => { @@ -94,26 +104,26 @@ models: version: 1.0.0 schema: v1 models: - - uses: anthropic/claude-sonnet-4-6 - with: - ANTHROPIC_API_KEY: old-anthropic-key - - uses: openai/gpt-4 - with: - OPENAI_API_KEY: TEST-openai-test + - name: claude sonnet 4.6 + provider: anthropic + model: claude-sonnet-4-6 + apiKey: old-anthropic-key + - name: GPT 4 + provider: openai + model: gpt-4 + apiKey: TEST-openai-test `; const result = updateAnthropicModelInYaml(existingConfig, testApiKey); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("uses: openai/gpt-4"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); - expect(result).toContain("OPENAI_API_KEY: TEST-openai-test"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("provider: openai"); + expect(result).toContain("apiKey: sk-ant-test123456789"); + expect(result).toContain("apiKey: TEST-openai-test"); expect(result).not.toContain("old-anthropic-key"); // Should only have one anthropic model - const anthropicMatches = result.match( - /uses: anthropic\/claude-sonnet-4-6/g, - ); + const anthropicMatches = result.match(/model: claude-sonnet-4-6/g); expect(anthropicMatches).toHaveLength(1); }); @@ -130,8 +140,9 @@ schema: v1 expect(result).toContain("name: Local Config"); expect(result).toContain("models:"); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("model: claude-sonnet-4-6"); + expect(result).toContain("apiKey: sk-ant-test123456789"); }); it("should handle config with empty models array", () => { @@ -147,8 +158,9 @@ models: [] ); expect(result).toContain("name: Local Config"); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("model: claude-sonnet-4-6"); + expect(result).toContain("apiKey: sk-ant-test123456789"); }); }); @@ -157,7 +169,10 @@ models: [] const input = `# Test config name: Test models: - - uses: existing/model + - name: existing + provider: test + model: test-model + apiKey: test-key `; const result = updateAnthropicModelInYaml(input, testApiKey); @@ -175,9 +190,10 @@ models: expect(result).toMatch(/^version: /m); expect(result).toMatch(/^schema: /m); expect(result).toMatch(/^models:/m); - expect(result).toMatch(/^\s+- uses: /m); - expect(result).toMatch(/^\s+with:/m); - expect(result).toMatch(/^\s+ANTHROPIC_API_KEY: /m); + expect(result).toMatch(/^\s+- name: /m); + expect(result).toMatch(/^\s+provider: /m); + expect(result).toMatch(/^\s+model: /m); + expect(result).toMatch(/^\s+apiKey: /m); }); }); @@ -189,8 +205,9 @@ models: "not an array" const result = updateAnthropicModelInYaml(malformedConfig, testApiKey); - expect(result).toContain("uses: anthropic/claude-sonnet-4-6"); - expect(result).toContain("ANTHROPIC_API_KEY: sk-ant-test123456789"); + expect(result).toContain("provider: anthropic"); + expect(result).toContain("model: claude-sonnet-4-6"); + expect(result).toContain("apiKey: sk-ant-test123456789"); }); it("should handle different API key formats", () => { @@ -202,8 +219,40 @@ models: "not an array" differentKeys.forEach((key) => { const result = updateAnthropicModelInYaml("", key); - expect(result).toContain(`ANTHROPIC_API_KEY: ${key}`); + expect(result).toContain(`apiKey: ${key}`); }); }); }); }); + +describe("updateOpenAIModelInYaml", () => { + const testApiKey = "sk-openai-test123456789"; + + it("should create new config from empty string", () => { + const result = updateOpenAIModelInYaml("", testApiKey); + + expect(result).toContain("name: Local Config"); + expect(result).toContain("provider: openai"); + expect(result).toContain("model: gpt-5.4"); + expect(result).toContain("apiKey: sk-openai-test123456789"); + }); + + it("should update existing openai model", () => { + const existingConfig = `name: Local Config +version: 1.0.0 +schema: v1 +models: + - name: GPT 5.4 + provider: openai + model: gpt-5.4 + apiKey: old-key +`; + + const result = updateOpenAIModelInYaml(existingConfig, testApiKey); + + expect(result).toContain("provider: openai"); + expect(result).toContain("model: gpt-5.4"); + expect(result).toContain("apiKey: sk-openai-test123456789"); + expect(result).not.toContain("old-key"); + }); +}); diff --git a/extensions/cli/src/util/yamlConfigUpdater.ts b/extensions/cli/src/util/yamlConfigUpdater.ts index 49c42991a90..fed256f2990 100644 --- a/extensions/cli/src/util/yamlConfigUpdater.ts +++ b/extensions/cli/src/util/yamlConfigUpdater.ts @@ -62,7 +62,11 @@ export function updateAnthropicModelInYaml( // Filter out existing anthropic models config.models = config.models.filter( - (model: any) => !model || model.uses !== "anthropic/claude-sonnet-4-6", + (model: any) => + !model || + !( + model.provider === "anthropic" && model.model === "claude-sonnet-4-6" + ), ); config.models.push(newModel); @@ -130,7 +134,8 @@ export function updateOpenAIModelInYaml( } config.models = config.models.filter( - (model: any) => !model || model.uses !== "openai/gpt-5.4", + (model: any) => + !model || !(model.provider === "openai" && model.model === "gpt-5.4"), ); config.models.push(newModel);