diff --git a/.changeset/lazy-bottles-travel.md b/.changeset/lazy-bottles-travel.md new file mode 100644 index 0000000000..f2ecdd70b5 --- /dev/null +++ b/.changeset/lazy-bottles-travel.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Revert profile management changes until we track down a bug with defaults diff --git a/.gitignore b/.gitignore index 3a541f1b09..1277732969 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ out out-* node_modules coverage/ -mock/ .DS_Store diff --git a/e2e/package-lock.json b/e2e/package-lock.json index e7f75013fd..bcee0b2676 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -12,7 +12,7 @@ "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.0", "mocha": "^11.1.0", - "typescript": "5.8.3" + "typescript": "^5.4.5" } }, "node_modules/@bcoe/v8-coverage": { diff --git a/e2e/package.json b/e2e/package.json index 1acec6bba5..a8007b1c4b 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -16,6 +16,6 @@ "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.0", "mocha": "^11.1.0", - "typescript": "5.8.3" + "typescript": "^5.4.5" } } diff --git a/evals/config/eslint/package.json b/evals/config/eslint/package.json index b706de1d95..3b84b039de 100644 --- a/evals/config/eslint/package.json +++ b/evals/config/eslint/package.json @@ -16,6 +16,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-turbo": "^2.4.4", "globals": "^16.0.0", + "typescript": "^5", "typescript-eslint": "^8.26.0" } } diff --git a/evals/package.json b/evals/package.json index 92c4ab99cd..2e7c21d977 100644 --- a/evals/package.json +++ b/evals/package.json @@ -20,7 +20,7 @@ "prettier": "^3.5.3", "tsx": "^4.19.4", "turbo": "^2.5.2", - "typescript": "5.8.3", + "typescript": "^5.8.3", "typescript-eslint": "^8.31.1" } } diff --git a/evals/packages/types/src/roo-code.ts b/evals/packages/types/src/roo-code.ts index 32b54c4c81..312040cf09 100644 --- a/evals/packages/types/src/roo-code.ts +++ b/evals/packages/types/src/roo-code.ts @@ -128,6 +128,18 @@ export const modelInfoSchema = z.object({ export type ModelInfo = z.infer +/** + * ApiConfigMeta + */ + +export const apiConfigMetaSchema = z.object({ + id: z.string(), + name: z.string(), + apiProvider: providerNamesSchema.optional(), +}) + +export type ApiConfigMeta = z.infer + /** * HistoryItem */ @@ -317,56 +329,27 @@ export type Experiments = z.infer // eslint-disable-next-line @typescript-eslint/no-unused-vars type _AssertExperiments = AssertEqual>> -/** - * ProviderSettingsEntry - */ - -export const providerSettingsEntrySchema = z.object({ - id: z.string(), - name: z.string(), - apiProvider: providerNamesSchema.optional(), -}) - -export type ProviderSettingsEntry = z.infer - /** * ProviderSettings */ -const genericProviderSettingsSchema = z.object({ - includeMaxTokens: z.boolean().optional(), - reasoningEffort: reasoningEffortsSchema.optional(), - promptCachingDisabled: z.boolean().optional(), - diffEnabled: z.boolean().optional(), - fuzzyMatchThreshold: z.number().optional(), - modelTemperature: z.number().nullish(), - rateLimitSeconds: z.number().optional(), - // Claude 3.7 Sonnet Thinking - modelMaxTokens: z.number().optional(), - modelMaxThinkingTokens: z.number().optional(), -}) - -const anthropicSchema = z.object({ +export const providerSettingsSchema = z.object({ + apiProvider: providerNamesSchema.optional(), + // Anthropic apiModelId: z.string().optional(), apiKey: z.string().optional(), anthropicBaseUrl: z.string().optional(), anthropicUseAuthToken: z.boolean().optional(), -}) - -const glamaSchema = z.object({ + // Glama glamaModelId: z.string().optional(), glamaApiKey: z.string().optional(), -}) - -const openRouterSchema = z.object({ + // OpenRouter openRouterApiKey: z.string().optional(), openRouterModelId: z.string().optional(), openRouterBaseUrl: z.string().optional(), openRouterSpecificProvider: z.string().optional(), openRouterUseMiddleOutTransform: z.boolean().optional(), -}) - -const bedrockSchema = z.object({ + // Amazon Bedrock awsAccessKey: z.string().optional(), awsSecretKey: z.string().optional(), awsSessionToken: z.string().optional(), @@ -376,18 +359,15 @@ const bedrockSchema = z.object({ awsProfile: z.string().optional(), awsUseProfile: z.boolean().optional(), awsCustomArn: z.string().optional(), -}) - -const vertexSchema = z.object({ + // Google Vertex vertexKeyFile: z.string().optional(), vertexJsonCredentials: z.string().optional(), vertexProjectId: z.string().optional(), vertexRegion: z.string().optional(), -}) - -const openAiSchema = z.object({ + // OpenAI openAiBaseUrl: z.string().optional(), openAiApiKey: z.string().optional(), + openAiHostHeader: z.string().optional(), openAiLegacyFormat: z.boolean().optional(), openAiR1FormatEnabled: z.boolean().optional(), openAiModelId: z.string().optional(), @@ -396,16 +376,10 @@ const openAiSchema = z.object({ azureApiVersion: z.string().optional(), openAiStreamingEnabled: z.boolean().optional(), enableReasoningEffort: z.boolean().optional(), - openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration. - openAiHeaders: z.record(z.string(), z.string()).optional(), -}) - -const ollamaSchema = z.object({ + // Ollama ollamaModelId: z.string().optional(), ollamaBaseUrl: z.string().optional(), -}) - -const vsCodeLmSchema = z.object({ + // VS Code LM vsCodeLmModelSelector: z .object({ vendor: z.string().optional(), @@ -414,208 +388,44 @@ const vsCodeLmSchema = z.object({ id: z.string().optional(), }) .optional(), -}) - -const lmStudioSchema = z.object({ + // LM Studio lmStudioModelId: z.string().optional(), lmStudioBaseUrl: z.string().optional(), lmStudioDraftModelId: z.string().optional(), lmStudioSpeculativeDecodingEnabled: z.boolean().optional(), -}) - -const geminiSchema = z.object({ + // Gemini geminiApiKey: z.string().optional(), googleGeminiBaseUrl: z.string().optional(), -}) - -const openAiNativeSchema = z.object({ + // OpenAI Native openAiNativeApiKey: z.string().optional(), openAiNativeBaseUrl: z.string().optional(), -}) - -const mistralSchema = z.object({ + // Mistral mistralApiKey: z.string().optional(), mistralCodestralUrl: z.string().optional(), -}) - -const deepSeekSchema = z.object({ + // DeepSeek deepSeekBaseUrl: z.string().optional(), deepSeekApiKey: z.string().optional(), -}) - -const unboundSchema = z.object({ + // Unbound unboundApiKey: z.string().optional(), unboundModelId: z.string().optional(), -}) - -const requestySchema = z.object({ + // Requesty requestyApiKey: z.string().optional(), requestyModelId: z.string().optional(), -}) - -const humanRelaySchema = z.object({}) - -const fakeAiSchema = z.object({ - fakeAi: z.unknown().optional(), -}) - -const xaiSchema = z.object({ + // X.AI (Grok) xaiApiKey: z.string().optional(), -}) - -const groqSchema = z.object({ - groqApiKey: z.string().optional(), -}) - -const chutesSchema = z.object({ - chutesApiKey: z.string().optional(), -}) - -const litellmSchema = z.object({ - litellmBaseUrl: z.string().optional(), - litellmApiKey: z.string().optional(), - litellmModelId: z.string().optional(), -}) - -const defaultSchema = z.object({ - apiProvider: z.undefined(), -}) - -export const providerSettingsSchemaDiscriminated = z - .discriminatedUnion("apiProvider", [ - anthropicSchema.merge( - z.object({ - apiProvider: z.literal("anthropic"), - }), - ), - glamaSchema.merge( - z.object({ - apiProvider: z.literal("glama"), - }), - ), - openRouterSchema.merge( - z.object({ - apiProvider: z.literal("openrouter"), - }), - ), - bedrockSchema.merge( - z.object({ - apiProvider: z.literal("bedrock"), - }), - ), - vertexSchema.merge( - z.object({ - apiProvider: z.literal("vertex"), - }), - ), - openAiSchema.merge( - z.object({ - apiProvider: z.literal("openai"), - }), - ), - ollamaSchema.merge( - z.object({ - apiProvider: z.literal("ollama"), - }), - ), - vsCodeLmSchema.merge( - z.object({ - apiProvider: z.literal("vscode-lm"), - }), - ), - lmStudioSchema.merge( - z.object({ - apiProvider: z.literal("lmstudio"), - }), - ), - geminiSchema.merge( - z.object({ - apiProvider: z.literal("gemini"), - }), - ), - openAiNativeSchema.merge( - z.object({ - apiProvider: z.literal("openai-native"), - }), - ), - mistralSchema.merge( - z.object({ - apiProvider: z.literal("mistral"), - }), - ), - deepSeekSchema.merge( - z.object({ - apiProvider: z.literal("deepseek"), - }), - ), - unboundSchema.merge( - z.object({ - apiProvider: z.literal("unbound"), - }), - ), - requestySchema.merge( - z.object({ - apiProvider: z.literal("requesty"), - }), - ), - humanRelaySchema.merge( - z.object({ - apiProvider: z.literal("human-relay"), - }), - ), - fakeAiSchema.merge( - z.object({ - apiProvider: z.literal("fake-ai"), - }), - ), - xaiSchema.merge( - z.object({ - apiProvider: z.literal("xai"), - }), - ), - groqSchema.merge( - z.object({ - apiProvider: z.literal("groq"), - }), - ), - chutesSchema.merge( - z.object({ - apiProvider: z.literal("chutes"), - }), - ), - litellmSchema.merge( - z.object({ - apiProvider: z.literal("litellm"), - }), - ), - defaultSchema, - ]) - .and(genericProviderSettingsSchema) - -export const providerSettingsSchema = z.object({ - apiProvider: providerNamesSchema.optional(), - ...anthropicSchema.shape, - ...glamaSchema.shape, - ...openRouterSchema.shape, - ...bedrockSchema.shape, - ...vertexSchema.shape, - ...openAiSchema.shape, - ...ollamaSchema.shape, - ...vsCodeLmSchema.shape, - ...lmStudioSchema.shape, - ...geminiSchema.shape, - ...openAiNativeSchema.shape, - ...mistralSchema.shape, - ...deepSeekSchema.shape, - ...unboundSchema.shape, - ...requestySchema.shape, - ...humanRelaySchema.shape, - ...fakeAiSchema.shape, - ...xaiSchema.shape, - ...groqSchema.shape, - ...chutesSchema.shape, - ...litellmSchema.shape, - ...genericProviderSettingsSchema.shape, + // Claude 3.7 Sonnet Thinking + modelMaxTokens: z.number().optional(), + modelMaxThinkingTokens: z.number().optional(), + // Generic + includeMaxTokens: z.boolean().optional(), + reasoningEffort: reasoningEffortsSchema.optional(), + promptCachingDisabled: z.boolean().optional(), + diffEnabled: z.boolean().optional(), + fuzzyMatchThreshold: z.number().optional(), + modelTemperature: z.number().nullish(), + rateLimitSeconds: z.number().optional(), + // Fake AI + fakeAi: z.unknown().optional(), }) export type ProviderSettings = z.infer @@ -656,6 +466,7 @@ const providerSettingsRecord: ProviderSettingsRecord = { // OpenAI openAiBaseUrl: undefined, openAiApiKey: undefined, + openAiHostHeader: undefined, openAiLegacyFormat: undefined, openAiR1FormatEnabled: undefined, openAiModelId: undefined, @@ -664,8 +475,6 @@ const providerSettingsRecord: ProviderSettingsRecord = { azureApiVersion: undefined, openAiStreamingEnabled: undefined, enableReasoningEffort: undefined, - openAiHostHeader: undefined, // Keep temporarily for backward compatibility during migration - openAiHeaders: undefined, // Ollama ollamaModelId: undefined, ollamaBaseUrl: undefined, @@ -708,14 +517,6 @@ const providerSettingsRecord: ProviderSettingsRecord = { fakeAi: undefined, // X.AI (Grok) xaiApiKey: undefined, - // Groq - groqApiKey: undefined, - // Chutes AI - chutesApiKey: undefined, - // LiteLLM - litellmBaseUrl: undefined, - litellmApiKey: undefined, - litellmModelId: undefined, } export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsRecord) as Keys[] @@ -726,7 +527,7 @@ export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsRecord) as Key export const globalSettingsSchema = z.object({ currentApiConfigName: z.string().optional(), - listApiConfigMeta: z.array(providerSettingsEntrySchema).optional(), + listApiConfigMeta: z.array(apiConfigMetaSchema).optional(), pinnedApiConfigs: z.record(z.string(), z.boolean()).optional(), lastShownAnnouncementId: z.string().optional(), diff --git a/evals/pnpm-lock.yaml b/evals/pnpm-lock.yaml index 0f17a6b3df..4402fe6385 100644 --- a/evals/pnpm-lock.yaml +++ b/evals/pnpm-lock.yaml @@ -30,7 +30,7 @@ importers: specifier: ^2.5.2 version: 2.5.3 typescript: - specifier: 5.8.3 + specifier: ^5.8.3 version: 5.8.3 typescript-eslint: specifier: ^8.31.1 @@ -235,9 +235,12 @@ importers: globals: specifier: ^16.0.0 version: 16.0.0 + typescript: + specifier: ^5 + version: 5.8.2 typescript-eslint: specifier: ^8.26.0 - version: 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3) + version: 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) config/typescript: {} @@ -3367,7 +3370,6 @@ packages: libsql@0.5.8: resolution: {integrity: sha512-+OopMI1wM/NvAJTHf3O3+beHd1YfKLnSVsOGBl3/7UBDZ4ydVadkbBk5Hjjs9d3ALC5rBaftMY59AvwyC8MzPw==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lightningcss-darwin-arm64@1.29.2: @@ -4374,6 +4376,11 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -6029,20 +6036,20 @@ snapshots: dependencies: '@types/node': 20.17.24 - '@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) '@typescript-eslint/scope-manager': 8.26.1 - '@typescript-eslint/type-utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) '@typescript-eslint/visitor-keys': 8.26.1 eslint: 9.22.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 2.0.1(typescript@5.8.3) - typescript: 5.8.3 + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -6063,15 +6070,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: '@typescript-eslint/scope-manager': 8.26.1 '@typescript-eslint/types': 8.26.1 - '@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.2) '@typescript-eslint/visitor-keys': 8.26.1 debug: 4.4.0 eslint: 9.22.0(jiti@2.4.2) - typescript: 5.8.3 + typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -6097,14 +6104,14 @@ snapshots: '@typescript-eslint/types': 8.32.0 '@typescript-eslint/visitor-keys': 8.32.0 - '@typescript-eslint/type-utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) debug: 4.4.0 eslint: 9.22.0(jiti@2.4.2) - ts-api-utils: 2.0.1(typescript@5.8.3) - typescript: 5.8.3 + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -6123,7 +6130,7 @@ snapshots: '@typescript-eslint/types@8.32.0': {} - '@typescript-eslint/typescript-estree@8.26.1(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.26.1(typescript@5.8.2)': dependencies: '@typescript-eslint/types': 8.26.1 '@typescript-eslint/visitor-keys': 8.26.1 @@ -6132,8 +6139,8 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.1 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 + ts-api-utils: 2.1.0(typescript@5.8.2) + typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -6151,14 +6158,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: '@eslint-community/eslint-utils': 4.6.1(eslint@9.22.0(jiti@2.4.2)) '@typescript-eslint/scope-manager': 8.26.1 '@typescript-eslint/types': 8.26.1 - '@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.2) eslint: 9.22.0(jiti@2.4.2) - typescript: 5.8.3 + typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -8569,9 +8576,13 @@ snapshots: toidentifier@1.0.1: {} - ts-api-utils@2.0.1(typescript@5.8.3): + ts-api-utils@2.0.1(typescript@5.8.2): dependencies: - typescript: 5.8.3 + typescript: 5.8.2 + + ts-api-utils@2.1.0(typescript@5.8.2): + dependencies: + typescript: 5.8.2 ts-api-utils@2.1.0(typescript@5.8.3): dependencies: @@ -8658,13 +8669,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3): + typescript-eslint@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) + '@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) eslint: 9.22.0(jiti@2.4.2) - typescript: 5.8.3 + typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -8678,6 +8689,8 @@ snapshots: transitivePeerDependencies: - supports-color + typescript@5.8.2: {} + typescript@5.8.3: {} unbox-primitive@1.1.0: diff --git a/git b/git new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package-lock.json b/package-lock.json index 0905686197..b53c817271 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "vscode-material-icons": "^0.1.1", "web-tree-sitter": "^0.22.6", "workerpool": "^9.2.0", - "zod": "^3.24.2" + "zod": "^3.23.8" }, "devDependencies": { "@changesets/cli": "^2.27.10", @@ -103,7 +103,7 @@ "ts-jest": "^29.2.5", "tsup": "^8.4.0", "tsx": "^4.19.3", - "typescript": "5.8.3", + "typescript": "^5.4.5", "vitest": "^3.1.3", "zod-to-ts": "^1.2.0" }, diff --git a/package.json b/package.json index 11eb3468e2..bb0e41492e 100644 --- a/package.json +++ b/package.json @@ -421,7 +421,7 @@ "vscode-material-icons": "^0.1.1", "web-tree-sitter": "^0.22.6", "workerpool": "^9.2.0", - "zod": "^3.24.2" + "zod": "^3.23.8" }, "devDependencies": { "@changesets/cli": "^2.27.10", @@ -459,7 +459,7 @@ "ts-jest": "^29.2.5", "tsup": "^8.4.0", "tsx": "^4.19.3", - "typescript": "5.8.3", + "typescript": "^5.4.5", "vitest": "^3.1.3", "zod-to-ts": "^1.2.0" }, diff --git a/src/api/index.ts b/src/api/index.ts index 10cbe14429..c337dff768 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,7 +1,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta/messages/index.mjs" -import { ProviderSettings, ModelInfo, ApiHandlerOptions } from "../shared/api" +import { ApiConfiguration, ModelInfo, ApiHandlerOptions } from "../shared/api" import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "./providers/constants" import { GlamaHandler } from "./providers/glama" import { AnthropicHandler } from "./providers/anthropic" @@ -47,7 +47,7 @@ export interface ApiHandler { countTokens(content: Array): Promise } -export function buildApiHandler(configuration: ProviderSettings): ApiHandler { +export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { const { apiProvider, ...options } = configuration switch (apiProvider) { diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index d22a87e097..53ae585b3c 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -1,14 +1,11 @@ import { ExtensionContext } from "vscode" import { z, ZodError } from "zod" -import { providerSettingsSchema, ProviderSettingsEntry, providerSettingsSchemaDiscriminated } from "../../schemas" +import { providerSettingsSchema, ApiConfigMeta } from "../../schemas" import { Mode, modes } from "../../shared/modes" import { telemetryService } from "../../services/telemetry/TelemetryService" const providerSettingsWithIdSchema = providerSettingsSchema.extend({ id: z.string().optional() }) -const discriminatedProviderSettingsWithIdSchema = providerSettingsSchemaDiscriminated.and( - z.object({ id: z.string().optional() }), -) type ProviderSettingsWithId = z.infer @@ -226,7 +223,7 @@ export class ProviderSettingsManager { /** * List all available configs with metadata. */ - public async listConfig(): Promise { + public async listConfig(): Promise { try { return await this.lock(async () => { const providerProfiles = await this.load() @@ -247,81 +244,66 @@ export class ProviderSettingsManager { * Preserves the ID from the input 'config' object if it exists, * otherwise generates a new one (for creation scenarios). */ - public async saveConfig(name: string, config: ProviderSettingsWithId): Promise { + public async saveConfig(name: string, config: ProviderSettingsWithId) { try { return await this.lock(async () => { const providerProfiles = await this.load() // Preserve the existing ID if this is an update to an existing config. const existingId = providerProfiles.apiConfigs[name]?.id - const id = config.id || existingId || this.generateId() - - // Filter out settings from other providers. - const filteredConfig = providerSettingsSchemaDiscriminated.parse(config) - providerProfiles.apiConfigs[name] = { ...filteredConfig, id } + providerProfiles.apiConfigs[name] = { ...config, id: config.id || existingId || this.generateId() } await this.store(providerProfiles) - return id }) } catch (error) { throw new Error(`Failed to save config: ${error}`) } } - public async getProfile( - params: { name: string } | { id: string }, - ): Promise { + /** + * Load a config by name and set it as the current config. + */ + public async loadConfig(name: string) { try { return await this.lock(async () => { const providerProfiles = await this.load() - let name: string - let providerSettings: ProviderSettingsWithId + const providerSettings = providerProfiles.apiConfigs[name] - if ("name" in params) { - name = params.name - - if (!providerProfiles.apiConfigs[name]) { - throw new Error(`Config with name '${name}' not found`) - } - - providerSettings = providerProfiles.apiConfigs[name] - } else { - const id = params.id - - const entry = Object.entries(providerProfiles.apiConfigs).find( - ([_, apiConfig]) => apiConfig.id === id, - ) - - if (!entry) { - throw new Error(`Config with ID '${id}' not found`) - } - - name = entry[0] - providerSettings = entry[1] + if (!providerSettings) { + throw new Error(`Config '${name}' not found`) } - return { name, ...providerSettings } + providerProfiles.currentApiConfigName = name + await this.store(providerProfiles) + + return providerSettings }) } catch (error) { - throw new Error(`Failed to get profile: ${error instanceof Error ? error.message : error}`) + throw new Error(`Failed to load config: ${error}`) } } /** - * Activate a profile by name or ID. + * Load a config by ID and set it as the current config. */ - public async activateProfile( - params: { name: string } | { id: string }, - ): Promise { - const { name, ...providerSettings } = await this.getProfile(params) - + public async loadConfigById(id: string) { try { return await this.lock(async () => { const providerProfiles = await this.load() + const providerSettings = Object.entries(providerProfiles.apiConfigs).find( + ([_, apiConfig]) => apiConfig.id === id, + ) + + if (!providerSettings) { + throw new Error(`Config with ID '${id}' not found`) + } + + const [name, apiConfig] = providerSettings providerProfiles.currentApiConfigName = name await this.store(providerProfiles) - return { name, ...providerSettings } + + return { config: apiConfig, name } }) } catch (error) { - throw new Error(`Failed to activate profile: ${error instanceof Error ? error.message : error}`) + throw new Error(`Failed to load config by ID: ${error}`) } } @@ -399,15 +381,7 @@ export class ProviderSettingsManager { public async export() { try { - return await this.lock(async () => { - const profiles = providerProfilesSchema.parse(await this.load()) - const configs = profiles.apiConfigs - for (const name in configs) { - // Avoid leaking properties from other providers. - configs[name] = discriminatedProviderSettingsWithIdSchema.parse(configs[name]) - } - return profiles - }) + return await this.lock(async () => providerProfilesSchema.parse(await this.load())) } catch (error) { throw new Error(`Failed to export provider profiles: ${error}`) } diff --git a/src/core/config/__tests__/ProviderSettingsManager.test.ts b/src/core/config/__tests__/ProviderSettingsManager.test.ts index 573c5453a4..064157da02 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.test.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.test.ts @@ -247,58 +247,10 @@ describe("ProviderSettingsManager", () => { }, } - expect(mockSecrets.store.mock.calls[0][0]).toEqual("roo_cline_config_api_config") - expect(storedConfig).toEqual(expectedConfig) - }) - - it("should only save provider relevant settings", async () => { - mockSecrets.get.mockResolvedValue( - JSON.stringify({ - currentApiConfigName: "default", - apiConfigs: { - default: {}, - }, - modeApiConfigs: { - code: "default", - architect: "default", - ask: "default", - }, - }), + expect(mockSecrets.store).toHaveBeenCalledWith( + "roo_cline_config_api_config", + JSON.stringify(expectedConfig, null, 2), ) - - const newConfig: ProviderSettings = { - apiProvider: "anthropic", - apiKey: "test-key", - } - const newConfigWithExtra: ProviderSettings = { - ...newConfig, - openRouterApiKey: "another-key", - } - - await providerSettingsManager.saveConfig("test", newConfigWithExtra) - - // Get the actual stored config to check the generated ID - const storedConfig = JSON.parse(mockSecrets.store.mock.lastCall[1]) - const testConfigId = storedConfig.apiConfigs.test.id - - const expectedConfig = { - currentApiConfigName: "default", - apiConfigs: { - default: {}, - test: { - ...newConfig, - id: testConfigId, - }, - }, - modeApiConfigs: { - code: "default", - architect: "default", - ask: "default", - }, - } - - expect(mockSecrets.store.mock.calls[0][0]).toEqual("roo_cline_config_api_config") - expect(storedConfig).toEqual(expectedConfig) }) it("should update existing config", async () => { @@ -339,9 +291,10 @@ describe("ProviderSettingsManager", () => { }, } - const storedConfig = JSON.parse(mockSecrets.store.mock.lastCall[1]) - expect(mockSecrets.store.mock.lastCall[0]).toEqual("roo_cline_config_api_config") - expect(storedConfig).toEqual(expectedConfig) + expect(mockSecrets.store).toHaveBeenCalledWith( + "roo_cline_config_api_config", + JSON.stringify(expectedConfig, null, 2), + ) }) it("should throw error if secrets storage fails", async () => { @@ -438,15 +391,17 @@ describe("ProviderSettingsManager", () => { mockGlobalState.get.mockResolvedValue(42) mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig)) - const { name, ...providerSettings } = await providerSettingsManager.activateProfile({ name: "test" }) + const config = await providerSettingsManager.loadConfig("test") - expect(name).toBe("test") - expect(providerSettings).toEqual({ apiProvider: "anthropic", apiKey: "test-key", id: "test-id" }) + expect(config).toEqual({ + apiProvider: "anthropic", + apiKey: "test-key", + id: "test-id", + }) - // Get the stored config to check the structure. + // Get the stored config to check the structure const storedConfig = JSON.parse(mockSecrets.store.mock.calls[1][1]) expect(storedConfig.currentApiConfigName).toBe("test") - expect(storedConfig.apiConfigs.test).toEqual({ apiProvider: "anthropic", apiKey: "test-key", @@ -458,12 +413,17 @@ describe("ProviderSettingsManager", () => { mockSecrets.get.mockResolvedValue( JSON.stringify({ currentApiConfigName: "default", - apiConfigs: { default: { config: {}, id: "default" } }, + apiConfigs: { + default: { + config: {}, + id: "default", + }, + }, }), ) - await expect(providerSettingsManager.activateProfile({ name: "nonexistent" })).rejects.toThrow( - "Config with name 'nonexistent' not found", + await expect(providerSettingsManager.loadConfig("nonexistent")).rejects.toThrow( + "Config 'nonexistent' not found", ) }) @@ -471,13 +431,20 @@ describe("ProviderSettingsManager", () => { mockSecrets.get.mockResolvedValue( JSON.stringify({ currentApiConfigName: "default", - apiConfigs: { test: { config: { apiProvider: "anthropic" }, id: "test-id" } }, + apiConfigs: { + test: { + config: { + apiProvider: "anthropic", + }, + id: "test-id", + }, + }, }), ) mockSecrets.store.mockRejectedValueOnce(new Error("Storage failed")) - await expect(providerSettingsManager.activateProfile({ name: "test" })).rejects.toThrow( - "Failed to activate profile: Failed to write provider profiles to secrets: Error: Storage failed", + await expect(providerSettingsManager.loadConfig("test")).rejects.toThrow( + "Failed to load config: Error: Failed to write provider profiles to secrets: Error: Storage failed", ) }) @@ -527,7 +494,12 @@ describe("ProviderSettingsManager", () => { mockSecrets.get.mockResolvedValue( JSON.stringify({ currentApiConfigName: "test", - apiConfigs: { test: { apiProvider: "anthropic", id: "test-id" } }, + apiConfigs: { + test: { + apiProvider: "anthropic", + id: "test-id", + }, + }, }), ) @@ -542,8 +514,18 @@ describe("ProviderSettingsManager", () => { it("should return true for existing config", async () => { const existingConfig: ProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { id: "default" }, test: { apiProvider: "anthropic", id: "test-id" } }, - migrations: { rateLimitSecondsMigrated: false }, + apiConfigs: { + default: { + id: "default", + }, + test: { + apiProvider: "anthropic", + id: "test-id", + }, + }, + migrations: { + rateLimitSecondsMigrated: false, + }, } mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig)) @@ -554,7 +536,10 @@ describe("ProviderSettingsManager", () => { it("should return false for non-existent config", async () => { mockSecrets.get.mockResolvedValue( - JSON.stringify({ currentApiConfigName: "default", apiConfigs: { default: {} } }), + JSON.stringify({ + currentApiConfigName: "default", + apiConfigs: { default: {} }, + }), ) const hasConfig = await providerSettingsManager.hasConfig("nonexistent") diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 05c9785452..f90d692617 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -16,7 +16,7 @@ import { ApiHandler, buildApiHandler } from "../../api" import { ApiStream } from "../../api/transform/stream" // shared -import { ProviderSettings } from "../../shared/api" +import { ApiConfiguration } from "../../shared/api" import { findLastIndex } from "../../shared/array" import { combineApiRequests } from "../../shared/combineApiRequests" import { combineCommandSequences } from "../../shared/combineCommandSequences" @@ -92,7 +92,7 @@ export type ClineEvents = { export type TaskOptions = { provider: ClineProvider - apiConfiguration: ProviderSettings + apiConfiguration: ApiConfiguration customInstructions?: string enableDiff?: boolean enableCheckpoints?: boolean @@ -130,7 +130,7 @@ export class Task extends EventEmitter { customInstructions?: string // API - readonly apiConfiguration: ProviderSettings + readonly apiConfiguration: ApiConfiguration api: ApiHandler private promptCacheKey: string private lastApiRequestTime?: number diff --git a/src/core/task/__tests__/Task.test.ts b/src/core/task/__tests__/Task.test.ts index 0bffc4a179..f70edfdefa 100644 --- a/src/core/task/__tests__/Task.test.ts +++ b/src/core/task/__tests__/Task.test.ts @@ -9,7 +9,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import { GlobalState } from "../../../schemas" import { Task } from "../Task" import { ClineProvider } from "../../webview/ClineProvider" -import { ProviderSettings, ModelInfo } from "../../../shared/api" +import { ApiConfiguration, ModelInfo } from "../../../shared/api" import { ApiStreamChunk } from "../../../api/transform/stream" import { ContextProxy } from "../../config/ContextProxy" import { processUserContentMentions } from "../../mentions/processUserContentMentions" @@ -156,7 +156,7 @@ const mockMessages = [ describe("Cline", () => { let mockProvider: jest.Mocked - let mockApiConfig: ProviderSettings + let mockApiConfig: ApiConfiguration let mockOutputChannel: any let mockExtensionContext: vscode.ExtensionContext diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index b9167c1cbf..87598bd45d 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -9,10 +9,16 @@ import axios from "axios" import pWaitFor from "p-wait-for" import * as vscode from "vscode" -import type { GlobalState, ProviderName, ProviderSettings, RooCodeSettings, ProviderSettingsEntry } from "../../schemas" +import { GlobalState, ProviderSettings, RooCodeSettings } from "../../schemas" import { t } from "../../i18n" import { setPanel } from "../../activate/registerCommands" -import { requestyDefaultModelId, openRouterDefaultModelId, glamaDefaultModelId } from "../../shared/api" +import { + ProviderName, + ApiConfiguration, + requestyDefaultModelId, + openRouterDefaultModelId, + glamaDefaultModelId, +} from "../../shared/api" import { findLast } from "../../shared/array" import { supportPrompt } from "../../shared/support-prompt" import { GlobalFileNames } from "../../shared/globalFileNames" @@ -246,7 +252,7 @@ export class ClineProvider extends EventEmitter implements return false } - // Check if there is a cline instance in the stack (if this provider has an active task) + // check if there is a cline instance in the stack (if this provider has an active task) if (visibleProvider.getCurrentCline()) { return true } @@ -756,6 +762,7 @@ export class ClineProvider extends EventEmitter implements * @param newMode The mode to switch to */ public async handleModeSwitch(newMode: Mode) { + // Capture mode switch telemetry event const cline = this.getCurrentCline() if (cline) { @@ -772,19 +779,24 @@ export class ClineProvider extends EventEmitter implements // Update listApiConfigMeta first to ensure UI has latest data await this.updateGlobalState("listApiConfigMeta", listApiConfig) - // If this mode has a saved config, use it. + // If this mode has a saved config, use it if (savedConfigId) { - const profile = listApiConfig.find(({ id }) => id === savedConfigId) + const config = listApiConfig?.find((c) => c.id === savedConfigId) - if (profile?.name) { - await this.activateProviderProfile({ name: profile.name }) + if (config?.name) { + const apiConfig = await this.providerSettingsManager.loadConfig(config.name) + + await Promise.all([ + this.updateGlobalState("currentApiConfigName", config.name), + this.updateApiConfiguration(apiConfig), + ]) } } else { - // If no saved config for this mode, save current config as default. + // If no saved config for this mode, save current config as default const currentApiConfigName = this.getGlobalState("currentApiConfigName") if (currentApiConfigName) { - const config = listApiConfig.find((c) => c.name === currentApiConfigName) + const config = listApiConfig?.find((c) => c.name === currentApiConfigName) if (config?.id) { await this.providerSettingsManager.setModeConfig(newMode, config.id) @@ -795,127 +807,27 @@ export class ClineProvider extends EventEmitter implements await this.postStateToWebview() } - // Provider Profile Management - - getProviderProfileEntries(): ProviderSettingsEntry[] { - return this.contextProxy.getValues().listApiConfigMeta || [] - } - - getProviderProfileEntry(name: string): ProviderSettingsEntry | undefined { - return this.getProviderProfileEntries().find((profile) => profile.name === name) - } - - public hasProviderProfileEntry(name: string): boolean { - return !!this.getProviderProfileEntry(name) - } - - async upsertProviderProfile( - name: string, - providerSettings: ProviderSettings, - activate: boolean = true, - ): Promise { - try { - // TODO: Do we need to be calling `activateProfile`? It's not - // clear to me what the source of truth should be; in some cases - // we rely on the `ContextProxy`'s data store and in other cases - // we rely on the `ProviderSettingsManager`'s data store. It might - // be simpler to unify these two. - const id = await this.providerSettingsManager.saveConfig(name, providerSettings) - - if (activate) { - const { mode } = await this.getState() - - // These promises do the following: - // 1. Adds or updates the list of provider profiles. - // 2. Sets the current provider profile. - // 3. Sets the current mode's provider profile. - // 4. Copies the provider settings to the context. - // - // Note: 1, 2, and 4 can be done in one `ContextProxy` call: - // this.contextProxy.setValues({ ...providerSettings, listApiConfigMeta: ..., currentApiConfigName: ... }) - // We should probably switch to that and verify that it works. - // I left the original implementation in just to be safe. - await Promise.all([ - this.updateGlobalState("listApiConfigMeta", await this.providerSettingsManager.listConfig()), - this.updateGlobalState("currentApiConfigName", name), - this.providerSettingsManager.setModeConfig(mode, id), - this.contextProxy.setProviderSettings(providerSettings), - ]) - - // Change the provider for the current task. - // TODO: We should rename `buildApiHandler` for clarity (e.g. `getProviderClient`). - const task = this.getCurrentCline() - - if (task) { - task.api = buildApiHandler(providerSettings) - } - } else { - await this.updateGlobalState("listApiConfigMeta", await this.providerSettingsManager.listConfig()) - } - - await this.postStateToWebview() - return id - } catch (error) { - this.log( - `Error create new api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, - ) - - vscode.window.showErrorMessage(t("common:errors.create_api_config")) - return undefined - } - } - - async deleteProviderProfile(profileToDelete: ProviderSettingsEntry) { - const globalSettings = this.contextProxy.getValues() - let profileToActivate: string | undefined = globalSettings.currentApiConfigName - - if (profileToDelete.name === profileToActivate) { - profileToActivate = this.getProviderProfileEntries().find(({ name }) => name !== profileToDelete.name)?.name - } - - if (!profileToActivate) { - throw new Error("You cannot delete the last profile") - } - - const entries = this.getProviderProfileEntries().filter(({ name }) => name !== profileToDelete.name) - - await this.contextProxy.setValues({ - ...globalSettings, - currentApiConfigName: profileToActivate, - listApiConfigMeta: entries, - }) - - await this.postStateToWebview() - } - - async activateProviderProfile(args: { name: string } | { id: string }) { - const { name, id, ...providerSettings } = await this.providerSettingsManager.activateProfile(args) - - // See `upsertProviderProfile` for a description of what this is doing. - await Promise.all([ - this.contextProxy.setValue("listApiConfigMeta", await this.providerSettingsManager.listConfig()), - this.contextProxy.setValue("currentApiConfigName", name), - this.contextProxy.setProviderSettings(providerSettings), - ]) - + async updateApiConfiguration(providerSettings: ProviderSettings) { + // Update mode's default config. const { mode } = await this.getState() - if (id) { - await this.providerSettingsManager.setModeConfig(mode, id) + if (mode) { + const currentApiConfigName = this.getGlobalState("currentApiConfigName") + const listApiConfig = await this.providerSettingsManager.listConfig() + const config = listApiConfig?.find((c) => c.name === currentApiConfigName) + + if (config?.id) { + await this.providerSettingsManager.setModeConfig(mode, config.id) + } } - // Change the provider for the current task. - const task = this.getCurrentCline() + await this.contextProxy.setProviderSettings(providerSettings) - if (task) { - task.api = buildApiHandler(providerSettings) + if (this.getCurrentCline()) { + this.getCurrentCline()!.api = buildApiHandler(providerSettings) } - - await this.postStateToWebview() } - // Task Management - async cancelTask() { const cline = this.getCurrentCline() @@ -1024,14 +936,14 @@ export class ClineProvider extends EventEmitter implements throw error } - const newConfiguration: ProviderSettings = { + const newConfiguration: ApiConfiguration = { ...apiConfiguration, apiProvider: "openrouter", openRouterApiKey: apiKey, openRouterModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId, } - await this.upsertProviderProfile(currentApiConfigName, newConfiguration) + await this.upsertApiConfiguration(currentApiConfigName, newConfiguration) } // Glama @@ -1054,14 +966,14 @@ export class ClineProvider extends EventEmitter implements const { apiConfiguration, currentApiConfigName } = await this.getState() - const newConfiguration: ProviderSettings = { + const newConfiguration: ApiConfiguration = { ...apiConfiguration, apiProvider: "glama", glamaApiKey: apiKey, glamaModelId: apiConfiguration?.glamaModelId || glamaDefaultModelId, } - await this.upsertProviderProfile(currentApiConfigName, newConfiguration) + await this.upsertApiConfiguration(currentApiConfigName, newConfiguration) } // Requesty @@ -1069,14 +981,36 @@ export class ClineProvider extends EventEmitter implements async handleRequestyCallback(code: string) { let { apiConfiguration, currentApiConfigName } = await this.getState() - const newConfiguration: ProviderSettings = { + const newConfiguration: ApiConfiguration = { ...apiConfiguration, apiProvider: "requesty", requestyApiKey: code, requestyModelId: apiConfiguration?.requestyModelId || requestyDefaultModelId, } - await this.upsertProviderProfile(currentApiConfigName, newConfiguration) + await this.upsertApiConfiguration(currentApiConfigName, newConfiguration) + } + + // Save configuration + + async upsertApiConfiguration(configName: string, apiConfiguration: ApiConfiguration) { + try { + await this.providerSettingsManager.saveConfig(configName, apiConfiguration) + const listApiConfig = await this.providerSettingsManager.listConfig() + + await Promise.all([ + this.updateGlobalState("listApiConfigMeta", listApiConfig), + this.updateApiConfiguration(apiConfiguration), + this.updateGlobalState("currentApiConfigName", configName), + ]) + + await this.postStateToWebview() + } catch (error) { + this.log( + `Error create new api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + ) + vscode.window.showErrorMessage(t("common:errors.create_api_config")) + } } // Task history diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 3bd3a11db4..1897a7e7e8 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -5,7 +5,7 @@ import * as vscode from "vscode" import axios from "axios" import { ClineProvider } from "../ClineProvider" -import { ProviderSettingsEntry, ClineMessage, ExtensionMessage, ExtensionState } from "../../../shared/ExtensionMessage" +import { ClineMessage, ExtensionMessage, ExtensionState } from "../../../shared/ExtensionMessage" import { setSoundEnabled } from "../../../utils/sound" import { setTtsEnabled } from "../../../utils/tts" import { defaultModeSlug } from "../../../shared/modes" @@ -227,6 +227,12 @@ jest.mock("../../../integrations/misc/extract-text", () => ({ }), })) +// Spy on console.error and console.log to suppress expected messages +beforeAll(() => { + jest.spyOn(console, "error").mockImplementation(() => {}) + jest.spyOn(console, "log").mockImplementation(() => {}) +}) + afterAll(() => { jest.restoreAllMocks() }) @@ -590,16 +596,14 @@ describe("ClineProvider", () => { expect(state.alwaysApproveResubmit).toBe(false) }) - it("loads saved API config when switching modes", async () => { + test("loads saved API config when switching modes", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - const profile: ProviderSettingsEntry = { name: "test-config", id: "test-id", apiProvider: "anthropic" } - ;(provider as any).providerSettingsManager = { getModeConfigId: jest.fn().mockResolvedValue("test-id"), - listConfig: jest.fn().mockResolvedValue([profile]), - activateProfile: jest.fn().mockResolvedValue(profile), + listConfig: jest.fn().mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), + loadConfig: jest.fn().mockResolvedValue({ apiProvider: "anthropic" }), setModeConfig: jest.fn(), } as any @@ -608,11 +612,11 @@ describe("ClineProvider", () => { // Should load the saved config for architect mode expect(provider.providerSettingsManager.getModeConfigId).toHaveBeenCalledWith("architect") - expect(provider.providerSettingsManager.activateProfile).toHaveBeenCalledWith({ name: "test-config" }) + expect(provider.providerSettingsManager.loadConfig).toHaveBeenCalledWith("test-config") expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "test-config") }) - it("saves current config when switching to mode without config", async () => { + test("saves current config when switching to mode without config", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] @@ -633,15 +637,16 @@ describe("ClineProvider", () => { expect(provider.providerSettingsManager.setModeConfig).toHaveBeenCalledWith("architect", "current-id") }) - it("saves config as default for current mode when loading config", async () => { + test("saves config as default for current mode when loading config", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - const profile: ProviderSettingsEntry = { apiProvider: "anthropic", id: "new-id", name: "new-config" } - ;(provider as any).providerSettingsManager = { - activateProfile: jest.fn().mockResolvedValue(profile), - listConfig: jest.fn().mockResolvedValue([profile]), + loadConfig: jest.fn().mockResolvedValue({ apiProvider: "anthropic", id: "new-id" }), + loadConfigById: jest + .fn() + .mockResolvedValue({ config: { apiProvider: "anthropic", id: "new-id" }, name: "new-config" }), + listConfig: jest.fn().mockResolvedValue([{ name: "new-config", id: "new-id", apiProvider: "anthropic" }]), setModeConfig: jest.fn(), getModeConfigId: jest.fn().mockResolvedValue(undefined), } as any @@ -656,19 +661,18 @@ describe("ClineProvider", () => { expect(provider.providerSettingsManager.setModeConfig).toHaveBeenCalledWith("architect", "new-id") }) - it("load API configuration by ID works and updates mode config", async () => { + test("load API configuration by ID works and updates mode config", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - const profile: ProviderSettingsEntry = { - name: "config-by-id", - id: "config-id-123", - apiProvider: "anthropic", - } - ;(provider as any).providerSettingsManager = { - activateProfile: jest.fn().mockResolvedValue(profile), - listConfig: jest.fn().mockResolvedValue([profile]), + loadConfigById: jest.fn().mockResolvedValue({ + config: { apiProvider: "anthropic", id: "config-id-123" }, + name: "config-by-id", + }), + listConfig: jest + .fn() + .mockResolvedValue([{ name: "config-by-id", id: "config-id-123", apiProvider: "anthropic" }]), setModeConfig: jest.fn(), getModeConfigId: jest.fn().mockResolvedValue(undefined), } as any @@ -682,8 +686,8 @@ describe("ClineProvider", () => { // Should save new config as default for architect mode expect(provider.providerSettingsManager.setModeConfig).toHaveBeenCalledWith("architect", "config-id-123") - // Ensure the `activateProfile` method was called with the correct ID - expect(provider.providerSettingsManager.activateProfile).toHaveBeenCalledWith({ id: "config-id-123" }) + // Ensure the loadConfigById method was called with the correct ID + expect(provider.providerSettingsManager.loadConfigById).toHaveBeenCalledWith("config-id-123") }) test("handles browserToolEnabled setting", async () => { @@ -882,7 +886,7 @@ describe("ClineProvider", () => { }) }) - it("saves mode config when updating API configuration", async () => { + test("saves mode config when updating API configuration", async () => { // Setup mock context with mode and config name mockContext = { ...mockContext, @@ -908,14 +912,12 @@ describe("ClineProvider", () => { ;(provider as any).providerSettingsManager = { listConfig: jest.fn().mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), - saveConfig: jest.fn().mockResolvedValue("test-id"), setModeConfig: jest.fn(), } as any // Update API configuration await messageHandler({ - type: "upsertApiConfiguration", - text: "test-config", + type: "apiConfiguration", apiConfiguration: { apiProvider: "anthropic" }, }) @@ -1540,17 +1542,13 @@ describe("ClineProvider", () => { await provider.resolveWebviewView(mockWebviewView) }) - it("loads saved API config when switching modes", async () => { - const profile: ProviderSettingsEntry = { - name: "saved-config", - id: "saved-config-id", - apiProvider: "anthropic", - } - + test("loads saved API config when switching modes", async () => { ;(provider as any).providerSettingsManager = { getModeConfigId: jest.fn().mockResolvedValue("saved-config-id"), - listConfig: jest.fn().mockResolvedValue([profile]), - activateProfile: jest.fn().mockResolvedValue(profile), + listConfig: jest + .fn() + .mockResolvedValue([{ name: "saved-config", id: "saved-config-id", apiProvider: "anthropic" }]), + loadConfig: jest.fn().mockResolvedValue({ apiProvider: "anthropic" }), setModeConfig: jest.fn(), } as any @@ -1562,7 +1560,7 @@ describe("ClineProvider", () => { // Verify saved config was loaded expect(provider.providerSettingsManager.getModeConfigId).toHaveBeenCalledWith("architect") - expect(provider.providerSettingsManager.activateProfile).toHaveBeenCalledWith({ name: "saved-config" }) + expect(provider.providerSettingsManager.loadConfig).toHaveBeenCalledWith("saved-config") expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "saved-config") // Verify state was posted to webview @@ -1678,11 +1676,14 @@ describe("ClineProvider", () => { currentApiConfigName: "test-config", } as any) - // Trigger upsertApiConfiguration + // Trigger updateApiConfiguration await messageHandler({ type: "upsertApiConfiguration", text: "test-config", - apiConfiguration: { apiProvider: "anthropic", apiKey: "test-key" }, + apiConfiguration: { + apiProvider: "anthropic", + apiKey: "test-key", + }, }) // Verify error was logged and user was notified diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index cde453d4fc..6560a5cac6 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -4,9 +4,9 @@ import pWaitFor from "p-wait-for" import * as vscode from "vscode" import { ClineProvider } from "./ClineProvider" -import { Language, ProviderSettings } from "../../schemas" +import { Language, ApiConfigMeta } from "../../schemas" import { changeLanguage, t } from "../../i18n" -import { RouterName, toRouterName } from "../../shared/api" +import { ApiConfiguration, RouterName, toRouterName } from "../../shared/api" import { supportPrompt } from "../../shared/support-prompt" import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage" @@ -89,12 +89,19 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We if (currentConfigName) { if (!(await provider.providerSettingsManager.hasConfig(currentConfigName))) { - // Current config name not valid, get first config in list. - const name = listApiConfig[0]?.name - await updateGlobalState("currentApiConfigName", name) + // current config name not valid, get first config in list + await updateGlobalState("currentApiConfigName", listApiConfig?.[0]?.name) + if (listApiConfig?.[0]?.name) { + const apiConfig = await provider.providerSettingsManager.loadConfig( + listApiConfig?.[0]?.name, + ) - if (name) { - await provider.activateProviderProfile({ name }) + await Promise.all([ + updateGlobalState("listApiConfigMeta", listApiConfig), + provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }), + provider.updateApiConfiguration(apiConfig), + ]) + await provider.postStateToWebview() return } } @@ -126,6 +133,12 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We // task. This essentially creates a fresh slate for the new task. await provider.initClineWithTask(message.text, message.images) break + case "apiConfiguration": + if (message.apiConfiguration) { + await provider.updateApiConfiguration(message.apiConfiguration) + } + await provider.postStateToWebview() + break case "customInstructions": await provider.updateCustomInstructions(message.text) break @@ -926,36 +939,45 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We const { apiConfiguration, customSupportPrompts, listApiConfigMeta, enhancementApiConfigId } = await provider.getState() - // Try to get enhancement config first, fall back to current config. - let configToUse: ProviderSettings = apiConfiguration - - if (enhancementApiConfigId && !!listApiConfigMeta.find(({ id }) => id === enhancementApiConfigId)) { - const { name: _, ...providerSettings } = await provider.providerSettingsManager.getProfile({ - id: enhancementApiConfigId, - }) - - if (providerSettings.apiProvider) { - configToUse = providerSettings + // Try to get enhancement config first, fall back to current config + let configToUse: ApiConfiguration = apiConfiguration + if (enhancementApiConfigId) { + const config = listApiConfigMeta?.find((c: ApiConfigMeta) => c.id === enhancementApiConfigId) + if (config?.name) { + const loadedConfig = await provider.providerSettingsManager.loadConfig(config.name) + if (loadedConfig.apiProvider) { + configToUse = loadedConfig + } } } const enhancedPrompt = await singleCompletionHandler( configToUse, - supportPrompt.create("ENHANCE", { userInput: message.text }, customSupportPrompts), + supportPrompt.create( + "ENHANCE", + { + userInput: message.text, + }, + customSupportPrompts, + ), ) - // Capture telemetry for prompt enhancement. + // Capture telemetry for prompt enhancement const currentCline = provider.getCurrentCline() telemetryService.capturePromptEnhanced(currentCline?.taskId) - await provider.postMessageToWebview({ type: "enhancedPrompt", text: enhancedPrompt }) + await provider.postMessageToWebview({ + type: "enhancedPrompt", + text: enhancedPrompt, + }) } catch (error) { provider.log( `Error enhancing prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, ) - vscode.window.showErrorMessage(t("common:errors.enhance_prompt")) - await provider.postMessageToWebview({ type: "enhancedPrompt" }) + await provider.postMessageToWebview({ + type: "enhancedPrompt", + }) } } break @@ -1062,7 +1084,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We break case "upsertApiConfiguration": if (message.text && message.apiConfiguration) { - await provider.upsertProviderProfile(message.text, message.apiConfiguration) + await provider.upsertApiConfiguration(message.text, message.apiConfiguration) } break case "renameApiConfiguration": @@ -1074,23 +1096,30 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We break } - // Load the old configuration to get its ID. - const { id } = await provider.providerSettingsManager.getProfile({ name: oldName }) + // Load the old configuration to get its ID + const oldConfig = await provider.providerSettingsManager.loadConfig(oldName) - // Create a new configuration with the new name and old ID. - await provider.providerSettingsManager.saveConfig(newName, { ...message.apiConfiguration, id }) + // Create a new configuration with the same ID + const newConfig = { + ...message.apiConfiguration, + id: oldConfig.id, // Preserve the ID + } - // Delete the old configuration. + // Save with the new name but same ID + await provider.providerSettingsManager.saveConfig(newName, newConfig) await provider.providerSettingsManager.deleteConfig(oldName) - // Re-activate to update the global settings related to the - // currently activated provider profile. - await provider.activateProviderProfile({ name: newName }) + const listApiConfig = await provider.providerSettingsManager.listConfig() + + // Update listApiConfigMeta first to ensure UI has latest data + await updateGlobalState("listApiConfigMeta", listApiConfig) + await updateGlobalState("currentApiConfigName", newName) + + await provider.postStateToWebview() } catch (error) { provider.log( `Error rename api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, ) - vscode.window.showErrorMessage(t("common:errors.rename_api_config")) } } @@ -1098,7 +1127,16 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We case "loadApiConfiguration": if (message.text) { try { - await provider.activateProviderProfile({ name: message.text }) + const apiConfig = await provider.providerSettingsManager.loadConfig(message.text) + const listApiConfig = await provider.providerSettingsManager.listConfig() + + await Promise.all([ + updateGlobalState("listApiConfigMeta", listApiConfig), + updateGlobalState("currentApiConfigName", message.text), + provider.updateApiConfiguration(apiConfig), + ]) + + await provider.postStateToWebview() } catch (error) { provider.log( `Error load api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, @@ -1110,7 +1148,18 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We case "loadApiConfigurationById": if (message.text) { try { - await provider.activateProviderProfile({ id: message.text }) + const { config: apiConfig, name } = await provider.providerSettingsManager.loadConfigById( + message.text, + ) + const listApiConfig = await provider.providerSettingsManager.listConfig() + + await Promise.all([ + updateGlobalState("listApiConfigMeta", listApiConfig), + updateGlobalState("currentApiConfigName", name), + provider.updateApiConfiguration(apiConfig), + ]) + + await provider.postStateToWebview() } catch (error) { provider.log( `Error load api configuration by ID: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, @@ -1131,25 +1180,29 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We break } - const oldName = message.text - - const newName = (await provider.providerSettingsManager.listConfig()).filter( - (c) => c.name !== oldName, - )[0]?.name - - if (!newName) { - vscode.window.showErrorMessage(t("common:errors.delete_api_config")) - return - } - try { - await provider.providerSettingsManager.deleteConfig(oldName) - await provider.activateProviderProfile({ name: newName }) + await provider.providerSettingsManager.deleteConfig(message.text) + const listApiConfig = await provider.providerSettingsManager.listConfig() + + // Update listApiConfigMeta first to ensure UI has latest data + await updateGlobalState("listApiConfigMeta", listApiConfig) + + // If this was the current config, switch to first available + const currentApiConfigName = getGlobalState("currentApiConfigName") + + if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) { + const apiConfig = await provider.providerSettingsManager.loadConfig(listApiConfig[0].name) + await Promise.all([ + updateGlobalState("currentApiConfigName", listApiConfig[0].name), + provider.updateApiConfiguration(apiConfig), + ]) + } + + await provider.postStateToWebview() } catch (error) { provider.log( `Error delete api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, ) - vscode.window.showErrorMessage(t("common:errors.delete_api_config")) } } diff --git a/src/exports/api.ts b/src/exports/api.ts index ca30c0f3fb..0d70d7dc04 100644 --- a/src/exports/api.ts +++ b/src/exports/api.ts @@ -6,14 +6,7 @@ import * as path from "path" import { getWorkspacePath } from "../utils/path" import { ClineProvider } from "../core/webview/ClineProvider" import { openClineInNewTab } from "../activate/registerCommands" -import { - RooCodeSettings, - RooCodeEvents, - RooCodeEventName, - ProviderSettings, - ProviderSettingsEntry, - isSecretStateKey, -} from "../schemas" +import { RooCodeSettings, RooCodeEvents, RooCodeEventName } from "../schemas" import { IpcOrigin, IpcMessageType, TaskCommandName, TaskEvent } from "../schemas/ipc" import { RooCodeAPI } from "./interface" @@ -185,6 +178,91 @@ export class API extends EventEmitter implements RooCodeAPI { await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "secondaryButtonClick" }) } + public getConfiguration() { + return this.sidebarProvider.getValues() + } + + public async setConfiguration(values: RooCodeSettings) { + await this.sidebarProvider.setValues(values) + await this.sidebarProvider.providerSettingsManager.saveConfig(values.currentApiConfigName || "default", values) + await this.sidebarProvider.postStateToWebview() + } + + public async createProfile(name: string) { + if (!name || !name.trim()) { + throw new Error("Profile name cannot be empty") + } + + const currentSettings = this.getConfiguration() + const profiles = currentSettings.listApiConfigMeta || [] + + if (profiles.some((profile) => profile.name === name)) { + throw new Error(`A profile with the name "${name}" already exists`) + } + + const id = this.sidebarProvider.providerSettingsManager.generateId() + + await this.setConfiguration({ + ...currentSettings, + listApiConfigMeta: [ + ...profiles, + { + id, + name: name.trim(), + apiProvider: "openai" as const, + }, + ], + }) + + return id + } + + public getProfiles() { + return (this.getConfiguration().listApiConfigMeta || []).map((profile) => profile.name) + } + + public async setActiveProfile(name: string) { + const currentSettings = this.getConfiguration() + const profiles = currentSettings.listApiConfigMeta || [] + + const profile = profiles.find((p) => p.name === name) + + if (!profile) { + throw new Error(`Profile with name "${name}" does not exist`) + } + + await this.setConfiguration({ ...currentSettings, currentApiConfigName: profile.name }) + } + + public getActiveProfile() { + return this.getConfiguration().currentApiConfigName + } + + public async deleteProfile(name: string) { + const currentSettings = this.getConfiguration() + const profiles = currentSettings.listApiConfigMeta || [] + const targetIndex = profiles.findIndex((p) => p.name === name) + + if (targetIndex === -1) { + throw new Error(`Profile with name "${name}" does not exist`) + } + + const profileToDelete = profiles[targetIndex] + profiles.splice(targetIndex, 1) + + // If we're deleting the active profile, clear the currentApiConfigName. + const newSettings: RooCodeSettings = { + ...currentSettings, + listApiConfigMeta: profiles, + currentApiConfigName: + currentSettings.currentApiConfigName === profileToDelete.name + ? undefined + : currentSettings.currentApiConfigName, + } + + await this.setConfiguration(newSettings) + } + public isReady() { return this.sidebarProvider.viewLaunched } @@ -250,103 +328,4 @@ export class API extends EventEmitter implements RooCodeAPI { this.logfile = undefined } } - - // Global Settings Management - - public getConfiguration(): RooCodeSettings { - return Object.fromEntries( - Object.entries(this.sidebarProvider.getValues()).filter(([key]) => !isSecretStateKey(key)), - ) - } - - public async setConfiguration(values: RooCodeSettings) { - await this.sidebarProvider.contextProxy.setValues(values) - await this.sidebarProvider.providerSettingsManager.saveConfig(values.currentApiConfigName || "default", values) - await this.sidebarProvider.postStateToWebview() - } - - // Provider Profile Management - - public getProfiles(): string[] { - return this.sidebarProvider.getProviderProfileEntries().map(({ name }) => name) - } - - public getProfileEntry(name: string): ProviderSettingsEntry | undefined { - return this.sidebarProvider.getProviderProfileEntry(name) - } - - public async createProfile(name: string, profile?: ProviderSettings, activate: boolean = true) { - const entry = this.getProfileEntry(name) - - if (entry) { - throw new Error(`Profile with name "${name}" already exists`) - } - - const id = await this.sidebarProvider.upsertProviderProfile(name, profile ?? {}, activate) - - if (!id) { - throw new Error(`Failed to create profile with name "${name}"`) - } - - return id - } - - public async updateProfile( - name: string, - profile: ProviderSettings, - activate: boolean = true, - ): Promise { - const entry = this.getProfileEntry(name) - - if (!entry) { - throw new Error(`Profile with name "${name}" does not exist`) - } - - const id = await this.sidebarProvider.upsertProviderProfile(name, profile, activate) - - if (!id) { - throw new Error(`Failed to update profile with name "${name}"`) - } - - return id - } - - public async upsertProfile( - name: string, - profile: ProviderSettings, - activate: boolean = true, - ): Promise { - const id = await this.sidebarProvider.upsertProviderProfile(name, profile, activate) - - if (!id) { - throw new Error(`Failed to upsert profile with name "${name}"`) - } - - return id - } - - public async deleteProfile(name: string): Promise { - const entry = this.getProfileEntry(name) - - if (!entry) { - throw new Error(`Profile with name "${name}" does not exist`) - } - - await this.sidebarProvider.deleteProviderProfile(entry) - } - - public getActiveProfile(): string | undefined { - return this.getConfiguration().currentApiConfigName - } - - public async setActiveProfile(name: string): Promise { - const entry = this.getProfileEntry(name) - - if (!entry) { - throw new Error(`Profile with name "${name}" does not exist`) - } - - await this.sidebarProvider.activateProviderProfile({ name }) - return this.getActiveProfile() - } } diff --git a/src/exports/interface.ts b/src/exports/interface.ts index eb30ea1773..a0ef46a226 100644 --- a/src/exports/interface.ts +++ b/src/exports/interface.ts @@ -1,23 +1,7 @@ import { EventEmitter } from "events" -import type { - GlobalSettings, - ProviderSettings, - ProviderSettingsEntry, - ClineMessage, - TokenUsage, - RooCodeEvents, -} from "./types" - -export type { - RooCodeSettings, - GlobalSettings, - ProviderSettings, - ProviderSettingsEntry, - ClineMessage, - TokenUsage, - RooCodeEvents, -} +import type { ProviderSettings, GlobalSettings, ClineMessage, TokenUsage, RooCodeEvents } from "./types" +export type { RooCodeSettings, ProviderSettings, GlobalSettings, ClineMessage, TokenUsage, RooCodeEvents } import { RooCodeEventName } from "../schemas" export type { RooCodeEventName } @@ -90,11 +74,6 @@ export interface RooCodeAPI extends EventEmitter { */ pressSecondaryButton(): Promise - /** - * Returns true if the API is ready to use. - */ - isReady(): boolean - /** * Returns the current configuration. * @returns The current configuration. @@ -107,6 +86,13 @@ export interface RooCodeAPI extends EventEmitter { */ setConfiguration(values: RooCodeSettings): Promise + /** + * Creates a new API configuration profile + * @param name The name of the profile + * @returns The ID of the created profile + */ + createProfile(name: string): Promise + /** * Returns a list of all configured profile names * @returns Array of profile names @@ -114,40 +100,17 @@ export interface RooCodeAPI extends EventEmitter { getProfiles(): string[] /** - * Returns the profile entry for a given name - * @param name The name of the profile - * @returns The profile entry, or undefined if the profile does not exist - */ - getProfileEntry(name: string): ProviderSettingsEntry | undefined - - /** - * Creates a new API configuration profile - * @param name The name of the profile - * @param profile The profile to create; defaults to an empty object - * @param activate Whether to activate the profile after creation; defaults to true - * @returns The ID of the created profile - * @throws Error if the profile already exists - */ - createProfile(name: string, profile?: ProviderSettings, activate?: boolean): Promise - - /** - * Updates an existing API configuration profile - * @param name The name of the profile - * @param profile The profile to update - * @param activate Whether to activate the profile after update; defaults to true - * @returns The ID of the updated profile + * Changes the active API configuration profile + * @param name The name of the profile to activate * @throws Error if the profile does not exist */ - updateProfile(name: string, profile: ProviderSettings, activate?: boolean): Promise + setActiveProfile(name: string): Promise /** - * Creates a new API configuration profile or updates an existing one - * @param name The name of the profile - * @param profile The profile to create or update; defaults to an empty object - * @param activate Whether to activate the profile after upsert; defaults to true - * @returns The ID of the upserted profile + * Returns the name of the currently active profile + * @returns The profile name, or undefined if no profile is active */ - upsertProfile(name: string, profile: ProviderSettings, activate?: boolean): Promise + getActiveProfile(): string | undefined /** * Deletes a profile by name @@ -157,15 +120,7 @@ export interface RooCodeAPI extends EventEmitter { deleteProfile(name: string): Promise /** - * Returns the name of the currently active profile - * @returns The profile name, or undefined if no profile is active + * Returns true if the API is ready to use. */ - getActiveProfile(): string | undefined - - /** - * Changes the active API configuration profile - * @param name The name of the profile to activate - * @throws Error if the profile does not exist - */ - setActiveProfile(name: string): Promise + isReady(): boolean } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 4727a1af4d..d0d82fa39e 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -1,5 +1,144 @@ import { EventEmitter } from "events" +type ProviderSettings = { + apiProvider?: + | ( + | "anthropic" + | "glama" + | "openrouter" + | "bedrock" + | "vertex" + | "openai" + | "ollama" + | "vscode-lm" + | "lmstudio" + | "gemini" + | "openai-native" + | "mistral" + | "deepseek" + | "unbound" + | "requesty" + | "human-relay" + | "fake-ai" + | "xai" + | "groq" + | "chutes" + | "litellm" + ) + | undefined + apiModelId?: string | undefined + apiKey?: string | undefined + anthropicBaseUrl?: string | undefined + anthropicUseAuthToken?: boolean | undefined + glamaModelId?: string | undefined + glamaApiKey?: string | undefined + openRouterApiKey?: string | undefined + openRouterModelId?: string | undefined + openRouterBaseUrl?: string | undefined + openRouterSpecificProvider?: string | undefined + openRouterUseMiddleOutTransform?: boolean | undefined + awsAccessKey?: string | undefined + awsSecretKey?: string | undefined + awsSessionToken?: string | undefined + awsRegion?: string | undefined + awsUseCrossRegionInference?: boolean | undefined + awsUsePromptCache?: boolean | undefined + awsProfile?: string | undefined + awsUseProfile?: boolean | undefined + awsCustomArn?: string | undefined + vertexKeyFile?: string | undefined + vertexJsonCredentials?: string | undefined + vertexProjectId?: string | undefined + vertexRegion?: string | undefined + openAiBaseUrl?: string | undefined + openAiApiKey?: string | undefined + openAiLegacyFormat?: boolean | undefined + openAiR1FormatEnabled?: boolean | undefined + openAiModelId?: string | undefined + openAiCustomModelInfo?: + | ({ + maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined + contextWindow: number + supportsImages?: boolean | undefined + supportsComputerUse?: boolean | undefined + supportsPromptCache: boolean + isPromptCacheOptional?: boolean | undefined + inputPrice?: number | undefined + outputPrice?: number | undefined + cacheWritesPrice?: number | undefined + cacheReadsPrice?: number | undefined + description?: string | undefined + reasoningEffort?: ("low" | "medium" | "high") | undefined + thinking?: boolean | undefined + minTokensPerCachePoint?: number | undefined + maxCachePoints?: number | undefined + cachableFields?: string[] | undefined + tiers?: + | { + contextWindow: number + inputPrice?: number | undefined + outputPrice?: number | undefined + cacheWritesPrice?: number | undefined + cacheReadsPrice?: number | undefined + }[] + | undefined + } | null) + | undefined + openAiUseAzure?: boolean | undefined + azureApiVersion?: string | undefined + openAiStreamingEnabled?: boolean | undefined + enableReasoningEffort?: boolean | undefined + openAiHostHeader?: string | undefined + openAiHeaders?: + | { + [x: string]: string + } + | undefined + ollamaModelId?: string | undefined + ollamaBaseUrl?: string | undefined + vsCodeLmModelSelector?: + | { + vendor?: string | undefined + family?: string | undefined + version?: string | undefined + id?: string | undefined + } + | undefined + lmStudioModelId?: string | undefined + lmStudioBaseUrl?: string | undefined + lmStudioDraftModelId?: string | undefined + lmStudioSpeculativeDecodingEnabled?: boolean | undefined + geminiApiKey?: string | undefined + googleGeminiBaseUrl?: string | undefined + openAiNativeApiKey?: string | undefined + openAiNativeBaseUrl?: string | undefined + mistralApiKey?: string | undefined + mistralCodestralUrl?: string | undefined + deepSeekBaseUrl?: string | undefined + deepSeekApiKey?: string | undefined + unboundApiKey?: string | undefined + unboundModelId?: string | undefined + requestyApiKey?: string | undefined + requestyModelId?: string | undefined + xaiApiKey?: string | undefined + groqApiKey?: string | undefined + chutesApiKey?: string | undefined + litellmBaseUrl?: string | undefined + litellmApiKey?: string | undefined + litellmModelId?: string | undefined + modelMaxTokens?: number | undefined + modelMaxThinkingTokens?: number | undefined + includeMaxTokens?: boolean | undefined + reasoningEffort?: ("low" | "medium" | "high") | undefined + promptCachingDisabled?: boolean | undefined + diffEnabled?: boolean | undefined + fuzzyMatchThreshold?: number | undefined + modelTemperature?: (number | null) | undefined + rateLimitSeconds?: number | undefined + fakeAi?: unknown | undefined +} + type GlobalSettings = { currentApiConfigName?: string | undefined listApiConfigMeta?: @@ -170,175 +309,6 @@ type GlobalSettings = { historyPreviewCollapsed?: boolean | undefined } -type ProviderSettings = { - apiProvider?: - | ( - | "anthropic" - | "glama" - | "openrouter" - | "bedrock" - | "vertex" - | "openai" - | "ollama" - | "vscode-lm" - | "lmstudio" - | "gemini" - | "openai-native" - | "mistral" - | "deepseek" - | "unbound" - | "requesty" - | "human-relay" - | "fake-ai" - | "xai" - | "groq" - | "chutes" - | "litellm" - ) - | undefined - apiModelId?: string | undefined - apiKey?: string | undefined - anthropicBaseUrl?: string | undefined - anthropicUseAuthToken?: boolean | undefined - glamaModelId?: string | undefined - glamaApiKey?: string | undefined - openRouterApiKey?: string | undefined - openRouterModelId?: string | undefined - openRouterBaseUrl?: string | undefined - openRouterSpecificProvider?: string | undefined - openRouterUseMiddleOutTransform?: boolean | undefined - awsAccessKey?: string | undefined - awsSecretKey?: string | undefined - awsSessionToken?: string | undefined - awsRegion?: string | undefined - awsUseCrossRegionInference?: boolean | undefined - awsUsePromptCache?: boolean | undefined - awsProfile?: string | undefined - awsUseProfile?: boolean | undefined - awsCustomArn?: string | undefined - vertexKeyFile?: string | undefined - vertexJsonCredentials?: string | undefined - vertexProjectId?: string | undefined - vertexRegion?: string | undefined - openAiBaseUrl?: string | undefined - openAiApiKey?: string | undefined - openAiLegacyFormat?: boolean | undefined - openAiR1FormatEnabled?: boolean | undefined - openAiModelId?: string | undefined - openAiCustomModelInfo?: - | ({ - maxTokens?: (number | null) | undefined - maxThinkingTokens?: (number | null) | undefined - contextWindow: number - supportsImages?: boolean | undefined - supportsComputerUse?: boolean | undefined - supportsPromptCache: boolean - isPromptCacheOptional?: boolean | undefined - inputPrice?: number | undefined - outputPrice?: number | undefined - cacheWritesPrice?: number | undefined - cacheReadsPrice?: number | undefined - description?: string | undefined - reasoningEffort?: ("low" | "medium" | "high") | undefined - thinking?: boolean | undefined - minTokensPerCachePoint?: number | undefined - maxCachePoints?: number | undefined - cachableFields?: string[] | undefined - tiers?: - | { - contextWindow: number - inputPrice?: number | undefined - outputPrice?: number | undefined - cacheWritesPrice?: number | undefined - cacheReadsPrice?: number | undefined - }[] - | undefined - } | null) - | undefined - openAiUseAzure?: boolean | undefined - azureApiVersion?: string | undefined - openAiStreamingEnabled?: boolean | undefined - enableReasoningEffort?: boolean | undefined - openAiHostHeader?: string | undefined - openAiHeaders?: - | { - [x: string]: string - } - | undefined - ollamaModelId?: string | undefined - ollamaBaseUrl?: string | undefined - vsCodeLmModelSelector?: - | { - vendor?: string | undefined - family?: string | undefined - version?: string | undefined - id?: string | undefined - } - | undefined - lmStudioModelId?: string | undefined - lmStudioBaseUrl?: string | undefined - lmStudioDraftModelId?: string | undefined - lmStudioSpeculativeDecodingEnabled?: boolean | undefined - geminiApiKey?: string | undefined - googleGeminiBaseUrl?: string | undefined - openAiNativeApiKey?: string | undefined - openAiNativeBaseUrl?: string | undefined - mistralApiKey?: string | undefined - mistralCodestralUrl?: string | undefined - deepSeekBaseUrl?: string | undefined - deepSeekApiKey?: string | undefined - unboundApiKey?: string | undefined - unboundModelId?: string | undefined - requestyApiKey?: string | undefined - requestyModelId?: string | undefined - fakeAi?: unknown | undefined - xaiApiKey?: string | undefined - groqApiKey?: string | undefined - chutesApiKey?: string | undefined - litellmBaseUrl?: string | undefined - litellmApiKey?: string | undefined - litellmModelId?: string | undefined - includeMaxTokens?: boolean | undefined - reasoningEffort?: ("low" | "medium" | "high") | undefined - promptCachingDisabled?: boolean | undefined - diffEnabled?: boolean | undefined - fuzzyMatchThreshold?: number | undefined - modelTemperature?: (number | null) | undefined - rateLimitSeconds?: number | undefined - modelMaxTokens?: number | undefined - modelMaxThinkingTokens?: number | undefined -} - -type ProviderSettingsEntry = { - id: string - name: string - apiProvider?: - | ( - | "anthropic" - | "glama" - | "openrouter" - | "bedrock" - | "vertex" - | "openai" - | "ollama" - | "vscode-lm" - | "lmstudio" - | "gemini" - | "openai-native" - | "mistral" - | "deepseek" - | "unbound" - | "requesty" - | "human-relay" - | "fake-ai" - | "xai" - | "groq" - | "chutes" - | "litellm" - ) - | undefined -} - type ClineMessage = { ts: number type: "ask" | "say" @@ -613,10 +583,6 @@ interface RooCodeAPI extends EventEmitter { * Simulates pressing the secondary button in the chat interface. */ pressSecondaryButton(): Promise - /** - * Returns true if the API is ready to use. - */ - isReady(): boolean /** * Returns the current configuration. * @returns The current configuration. @@ -627,43 +593,28 @@ interface RooCodeAPI extends EventEmitter { * @param values An object containing key-value pairs to set. */ setConfiguration(values: RooCodeSettings): Promise + /** + * Creates a new API configuration profile + * @param name The name of the profile + * @returns The ID of the created profile + */ + createProfile(name: string): Promise /** * Returns a list of all configured profile names * @returns Array of profile names */ getProfiles(): string[] /** - * Returns the profile entry for a given name - * @param name The name of the profile - * @returns The profile entry, or undefined if the profile does not exist - */ - getProfileEntry(name: string): ProviderSettingsEntry | undefined - /** - * Creates a new API configuration profile - * @param name The name of the profile - * @param profile The profile to create; defaults to an empty object - * @param activate Whether to activate the profile after creation; defaults to true - * @returns The ID of the created profile - * @throws Error if the profile already exists - */ - createProfile(name: string, profile?: ProviderSettings, activate?: boolean): Promise - /** - * Updates an existing API configuration profile - * @param name The name of the profile - * @param profile The profile to update - * @param activate Whether to activate the profile after update; defaults to true - * @returns The ID of the updated profile + * Changes the active API configuration profile + * @param name The name of the profile to activate * @throws Error if the profile does not exist */ - updateProfile(name: string, profile: ProviderSettings, activate?: boolean): Promise + setActiveProfile(name: string): Promise /** - * Creates a new API configuration profile or updates an existing one - * @param name The name of the profile - * @param profile The profile to create or update; defaults to an empty object - * @param activate Whether to activate the profile after upsert; defaults to true - * @returns The ID of the upserted profile + * Returns the name of the currently active profile + * @returns The profile name, or undefined if no profile is active */ - upsertProfile(name: string, profile: ProviderSettings, activate?: boolean): Promise + getActiveProfile(): string | undefined /** * Deletes a profile by name * @param name The name of the profile to delete @@ -671,23 +622,15 @@ interface RooCodeAPI extends EventEmitter { */ deleteProfile(name: string): Promise /** - * Returns the name of the currently active profile - * @returns The profile name, or undefined if no profile is active + * Returns true if the API is ready to use. */ - getActiveProfile(): string | undefined - /** - * Changes the active API configuration profile - * @param name The name of the profile to activate - * @throws Error if the profile does not exist - */ - setActiveProfile(name: string): Promise + isReady(): boolean } export { type ClineMessage, type GlobalSettings, type ProviderSettings, - type ProviderSettingsEntry, type RooCodeAPI, RooCodeEventName, type RooCodeEvents, diff --git a/src/exports/types.ts b/src/exports/types.ts index b42d630a31..05d492b8cc 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -1,6 +1,147 @@ // This file is automatically generated by running `npm run generate-types` // Do not edit it directly. +type ProviderSettings = { + apiProvider?: + | ( + | "anthropic" + | "glama" + | "openrouter" + | "bedrock" + | "vertex" + | "openai" + | "ollama" + | "vscode-lm" + | "lmstudio" + | "gemini" + | "openai-native" + | "mistral" + | "deepseek" + | "unbound" + | "requesty" + | "human-relay" + | "fake-ai" + | "xai" + | "groq" + | "chutes" + | "litellm" + ) + | undefined + apiModelId?: string | undefined + apiKey?: string | undefined + anthropicBaseUrl?: string | undefined + anthropicUseAuthToken?: boolean | undefined + glamaModelId?: string | undefined + glamaApiKey?: string | undefined + openRouterApiKey?: string | undefined + openRouterModelId?: string | undefined + openRouterBaseUrl?: string | undefined + openRouterSpecificProvider?: string | undefined + openRouterUseMiddleOutTransform?: boolean | undefined + awsAccessKey?: string | undefined + awsSecretKey?: string | undefined + awsSessionToken?: string | undefined + awsRegion?: string | undefined + awsUseCrossRegionInference?: boolean | undefined + awsUsePromptCache?: boolean | undefined + awsProfile?: string | undefined + awsUseProfile?: boolean | undefined + awsCustomArn?: string | undefined + vertexKeyFile?: string | undefined + vertexJsonCredentials?: string | undefined + vertexProjectId?: string | undefined + vertexRegion?: string | undefined + openAiBaseUrl?: string | undefined + openAiApiKey?: string | undefined + openAiLegacyFormat?: boolean | undefined + openAiR1FormatEnabled?: boolean | undefined + openAiModelId?: string | undefined + openAiCustomModelInfo?: + | ({ + maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined + contextWindow: number + supportsImages?: boolean | undefined + supportsComputerUse?: boolean | undefined + supportsPromptCache: boolean + isPromptCacheOptional?: boolean | undefined + inputPrice?: number | undefined + outputPrice?: number | undefined + cacheWritesPrice?: number | undefined + cacheReadsPrice?: number | undefined + description?: string | undefined + reasoningEffort?: ("low" | "medium" | "high") | undefined + thinking?: boolean | undefined + minTokensPerCachePoint?: number | undefined + maxCachePoints?: number | undefined + cachableFields?: string[] | undefined + tiers?: + | { + contextWindow: number + inputPrice?: number | undefined + outputPrice?: number | undefined + cacheWritesPrice?: number | undefined + cacheReadsPrice?: number | undefined + }[] + | undefined + } | null) + | undefined + openAiUseAzure?: boolean | undefined + azureApiVersion?: string | undefined + openAiStreamingEnabled?: boolean | undefined + enableReasoningEffort?: boolean | undefined + openAiHostHeader?: string | undefined + openAiHeaders?: + | { + [x: string]: string + } + | undefined + ollamaModelId?: string | undefined + ollamaBaseUrl?: string | undefined + vsCodeLmModelSelector?: + | { + vendor?: string | undefined + family?: string | undefined + version?: string | undefined + id?: string | undefined + } + | undefined + lmStudioModelId?: string | undefined + lmStudioBaseUrl?: string | undefined + lmStudioDraftModelId?: string | undefined + lmStudioSpeculativeDecodingEnabled?: boolean | undefined + geminiApiKey?: string | undefined + googleGeminiBaseUrl?: string | undefined + openAiNativeApiKey?: string | undefined + openAiNativeBaseUrl?: string | undefined + mistralApiKey?: string | undefined + mistralCodestralUrl?: string | undefined + deepSeekBaseUrl?: string | undefined + deepSeekApiKey?: string | undefined + unboundApiKey?: string | undefined + unboundModelId?: string | undefined + requestyApiKey?: string | undefined + requestyModelId?: string | undefined + xaiApiKey?: string | undefined + groqApiKey?: string | undefined + chutesApiKey?: string | undefined + litellmBaseUrl?: string | undefined + litellmApiKey?: string | undefined + litellmModelId?: string | undefined + modelMaxTokens?: number | undefined + modelMaxThinkingTokens?: number | undefined + includeMaxTokens?: boolean | undefined + reasoningEffort?: ("low" | "medium" | "high") | undefined + promptCachingDisabled?: boolean | undefined + diffEnabled?: boolean | undefined + fuzzyMatchThreshold?: number | undefined + modelTemperature?: (number | null) | undefined + rateLimitSeconds?: number | undefined + fakeAi?: unknown | undefined +} + +export type { ProviderSettings } + type GlobalSettings = { currentApiConfigName?: string | undefined listApiConfigMeta?: @@ -173,179 +314,6 @@ type GlobalSettings = { export type { GlobalSettings } -type ProviderSettings = { - apiProvider?: - | ( - | "anthropic" - | "glama" - | "openrouter" - | "bedrock" - | "vertex" - | "openai" - | "ollama" - | "vscode-lm" - | "lmstudio" - | "gemini" - | "openai-native" - | "mistral" - | "deepseek" - | "unbound" - | "requesty" - | "human-relay" - | "fake-ai" - | "xai" - | "groq" - | "chutes" - | "litellm" - ) - | undefined - apiModelId?: string | undefined - apiKey?: string | undefined - anthropicBaseUrl?: string | undefined - anthropicUseAuthToken?: boolean | undefined - glamaModelId?: string | undefined - glamaApiKey?: string | undefined - openRouterApiKey?: string | undefined - openRouterModelId?: string | undefined - openRouterBaseUrl?: string | undefined - openRouterSpecificProvider?: string | undefined - openRouterUseMiddleOutTransform?: boolean | undefined - awsAccessKey?: string | undefined - awsSecretKey?: string | undefined - awsSessionToken?: string | undefined - awsRegion?: string | undefined - awsUseCrossRegionInference?: boolean | undefined - awsUsePromptCache?: boolean | undefined - awsProfile?: string | undefined - awsUseProfile?: boolean | undefined - awsCustomArn?: string | undefined - vertexKeyFile?: string | undefined - vertexJsonCredentials?: string | undefined - vertexProjectId?: string | undefined - vertexRegion?: string | undefined - openAiBaseUrl?: string | undefined - openAiApiKey?: string | undefined - openAiLegacyFormat?: boolean | undefined - openAiR1FormatEnabled?: boolean | undefined - openAiModelId?: string | undefined - openAiCustomModelInfo?: - | ({ - maxTokens?: (number | null) | undefined - maxThinkingTokens?: (number | null) | undefined - contextWindow: number - supportsImages?: boolean | undefined - supportsComputerUse?: boolean | undefined - supportsPromptCache: boolean - isPromptCacheOptional?: boolean | undefined - inputPrice?: number | undefined - outputPrice?: number | undefined - cacheWritesPrice?: number | undefined - cacheReadsPrice?: number | undefined - description?: string | undefined - reasoningEffort?: ("low" | "medium" | "high") | undefined - thinking?: boolean | undefined - minTokensPerCachePoint?: number | undefined - maxCachePoints?: number | undefined - cachableFields?: string[] | undefined - tiers?: - | { - contextWindow: number - inputPrice?: number | undefined - outputPrice?: number | undefined - cacheWritesPrice?: number | undefined - cacheReadsPrice?: number | undefined - }[] - | undefined - } | null) - | undefined - openAiUseAzure?: boolean | undefined - azureApiVersion?: string | undefined - openAiStreamingEnabled?: boolean | undefined - enableReasoningEffort?: boolean | undefined - openAiHostHeader?: string | undefined - openAiHeaders?: - | { - [x: string]: string - } - | undefined - ollamaModelId?: string | undefined - ollamaBaseUrl?: string | undefined - vsCodeLmModelSelector?: - | { - vendor?: string | undefined - family?: string | undefined - version?: string | undefined - id?: string | undefined - } - | undefined - lmStudioModelId?: string | undefined - lmStudioBaseUrl?: string | undefined - lmStudioDraftModelId?: string | undefined - lmStudioSpeculativeDecodingEnabled?: boolean | undefined - geminiApiKey?: string | undefined - googleGeminiBaseUrl?: string | undefined - openAiNativeApiKey?: string | undefined - openAiNativeBaseUrl?: string | undefined - mistralApiKey?: string | undefined - mistralCodestralUrl?: string | undefined - deepSeekBaseUrl?: string | undefined - deepSeekApiKey?: string | undefined - unboundApiKey?: string | undefined - unboundModelId?: string | undefined - requestyApiKey?: string | undefined - requestyModelId?: string | undefined - fakeAi?: unknown | undefined - xaiApiKey?: string | undefined - groqApiKey?: string | undefined - chutesApiKey?: string | undefined - litellmBaseUrl?: string | undefined - litellmApiKey?: string | undefined - litellmModelId?: string | undefined - includeMaxTokens?: boolean | undefined - reasoningEffort?: ("low" | "medium" | "high") | undefined - promptCachingDisabled?: boolean | undefined - diffEnabled?: boolean | undefined - fuzzyMatchThreshold?: number | undefined - modelTemperature?: (number | null) | undefined - rateLimitSeconds?: number | undefined - modelMaxTokens?: number | undefined - modelMaxThinkingTokens?: number | undefined -} - -export type { ProviderSettings } - -type ProviderSettingsEntry = { - id: string - name: string - apiProvider?: - | ( - | "anthropic" - | "glama" - | "openrouter" - | "bedrock" - | "vertex" - | "openai" - | "ollama" - | "vscode-lm" - | "lmstudio" - | "gemini" - | "openai-native" - | "mistral" - | "deepseek" - | "unbound" - | "requesty" - | "human-relay" - | "fake-ai" - | "xai" - | "groq" - | "chutes" - | "litellm" - ) - | undefined -} - -export type { ProviderSettingsEntry } - type ClineMessage = { ts: number type: "ask" | "say" diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 4d318f805a..094b11b10d 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -135,6 +135,18 @@ export const modelInfoSchema = z.object({ export type ModelInfo = z.infer +/** + * ApiConfigMeta + */ + +export const apiConfigMetaSchema = z.object({ + id: z.string(), + name: z.string(), + apiProvider: providerNamesSchema.optional(), +}) + +export type ApiConfigMeta = z.infer + /** * HistoryItem */ @@ -329,56 +341,27 @@ export type Experiments = z.infer type _AssertExperiments = AssertEqual>> -/** - * ProviderSettingsEntry - */ - -export const providerSettingsEntrySchema = z.object({ - id: z.string(), - name: z.string(), - apiProvider: providerNamesSchema.optional(), -}) - -export type ProviderSettingsEntry = z.infer - /** * ProviderSettings */ -const genericProviderSettingsSchema = z.object({ - includeMaxTokens: z.boolean().optional(), - reasoningEffort: reasoningEffortsSchema.optional(), - promptCachingDisabled: z.boolean().optional(), - diffEnabled: z.boolean().optional(), - fuzzyMatchThreshold: z.number().optional(), - modelTemperature: z.number().nullish(), - rateLimitSeconds: z.number().optional(), - // Claude 3.7 Sonnet Thinking - modelMaxTokens: z.number().optional(), - modelMaxThinkingTokens: z.number().optional(), -}) - -const anthropicSchema = z.object({ +export const providerSettingsSchema = z.object({ + apiProvider: providerNamesSchema.optional(), + // Anthropic apiModelId: z.string().optional(), apiKey: z.string().optional(), anthropicBaseUrl: z.string().optional(), anthropicUseAuthToken: z.boolean().optional(), -}) - -const glamaSchema = z.object({ + // Glama glamaModelId: z.string().optional(), glamaApiKey: z.string().optional(), -}) - -const openRouterSchema = z.object({ + // OpenRouter openRouterApiKey: z.string().optional(), openRouterModelId: z.string().optional(), openRouterBaseUrl: z.string().optional(), openRouterSpecificProvider: z.string().optional(), openRouterUseMiddleOutTransform: z.boolean().optional(), -}) - -const bedrockSchema = z.object({ + // Amazon Bedrock awsAccessKey: z.string().optional(), awsSecretKey: z.string().optional(), awsSessionToken: z.string().optional(), @@ -388,16 +371,12 @@ const bedrockSchema = z.object({ awsProfile: z.string().optional(), awsUseProfile: z.boolean().optional(), awsCustomArn: z.string().optional(), -}) - -const vertexSchema = z.object({ + // Google Vertex vertexKeyFile: z.string().optional(), vertexJsonCredentials: z.string().optional(), vertexProjectId: z.string().optional(), vertexRegion: z.string().optional(), -}) - -const openAiSchema = z.object({ + // OpenAI openAiBaseUrl: z.string().optional(), openAiApiKey: z.string().optional(), openAiLegacyFormat: z.boolean().optional(), @@ -408,16 +387,12 @@ const openAiSchema = z.object({ azureApiVersion: z.string().optional(), openAiStreamingEnabled: z.boolean().optional(), enableReasoningEffort: z.boolean().optional(), - openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration. + openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration openAiHeaders: z.record(z.string(), z.string()).optional(), -}) - -const ollamaSchema = z.object({ + // Ollama ollamaModelId: z.string().optional(), ollamaBaseUrl: z.string().optional(), -}) - -const vsCodeLmSchema = z.object({ + // VS Code LM vsCodeLmModelSelector: z .object({ vendor: z.string().optional(), @@ -426,127 +401,54 @@ const vsCodeLmSchema = z.object({ id: z.string().optional(), }) .optional(), -}) - -const lmStudioSchema = z.object({ + // LM Studio lmStudioModelId: z.string().optional(), lmStudioBaseUrl: z.string().optional(), lmStudioDraftModelId: z.string().optional(), lmStudioSpeculativeDecodingEnabled: z.boolean().optional(), -}) - -const geminiSchema = z.object({ + // Gemini geminiApiKey: z.string().optional(), googleGeminiBaseUrl: z.string().optional(), -}) - -const openAiNativeSchema = z.object({ + // OpenAI Native openAiNativeApiKey: z.string().optional(), openAiNativeBaseUrl: z.string().optional(), -}) - -const mistralSchema = z.object({ + // Mistral mistralApiKey: z.string().optional(), mistralCodestralUrl: z.string().optional(), -}) - -const deepSeekSchema = z.object({ + // DeepSeek deepSeekBaseUrl: z.string().optional(), deepSeekApiKey: z.string().optional(), -}) - -const unboundSchema = z.object({ + // Unbound unboundApiKey: z.string().optional(), unboundModelId: z.string().optional(), -}) - -const requestySchema = z.object({ + // Requesty requestyApiKey: z.string().optional(), requestyModelId: z.string().optional(), -}) - -const humanRelaySchema = z.object({}) - -const fakeAiSchema = z.object({ - fakeAi: z.unknown().optional(), -}) - -const xaiSchema = z.object({ + // X.AI (Grok) xaiApiKey: z.string().optional(), -}) - -const groqSchema = z.object({ + // Groq groqApiKey: z.string().optional(), -}) - -const chutesSchema = z.object({ + // Chutes AI chutesApiKey: z.string().optional(), -}) - -const litellmSchema = z.object({ + // LiteLLM litellmBaseUrl: z.string().optional(), litellmApiKey: z.string().optional(), litellmModelId: z.string().optional(), + // Claude 3.7 Sonnet Thinking + modelMaxTokens: z.number().optional(), + modelMaxThinkingTokens: z.number().optional(), + // Generic + includeMaxTokens: z.boolean().optional(), + reasoningEffort: reasoningEffortsSchema.optional(), + promptCachingDisabled: z.boolean().optional(), + diffEnabled: z.boolean().optional(), + fuzzyMatchThreshold: z.number().optional(), + modelTemperature: z.number().nullish(), + rateLimitSeconds: z.number().optional(), + // Fake AI + fakeAi: z.unknown().optional(), }) -const defaultSchema = z.object({ - apiProvider: z.undefined(), -}) - -export const providerSettingsSchemaDiscriminated = z - .discriminatedUnion("apiProvider", [ - anthropicSchema.merge(z.object({ apiProvider: z.literal("anthropic") })), - glamaSchema.merge(z.object({ apiProvider: z.literal("glama") })), - openRouterSchema.merge(z.object({ apiProvider: z.literal("openrouter") })), - bedrockSchema.merge(z.object({ apiProvider: z.literal("bedrock") })), - vertexSchema.merge(z.object({ apiProvider: z.literal("vertex") })), - openAiSchema.merge(z.object({ apiProvider: z.literal("openai") })), - ollamaSchema.merge(z.object({ apiProvider: z.literal("ollama") })), - vsCodeLmSchema.merge(z.object({ apiProvider: z.literal("vscode-lm") })), - lmStudioSchema.merge(z.object({ apiProvider: z.literal("lmstudio") })), - geminiSchema.merge(z.object({ apiProvider: z.literal("gemini") })), - openAiNativeSchema.merge(z.object({ apiProvider: z.literal("openai-native") })), - mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })), - deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })), - unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })), - requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })), - humanRelaySchema.merge(z.object({ apiProvider: z.literal("human-relay") })), - fakeAiSchema.merge(z.object({ apiProvider: z.literal("fake-ai") })), - xaiSchema.merge(z.object({ apiProvider: z.literal("xai") })), - groqSchema.merge(z.object({ apiProvider: z.literal("groq") })), - chutesSchema.merge(z.object({ apiProvider: z.literal("chutes") })), - litellmSchema.merge(z.object({ apiProvider: z.literal("litellm") })), - defaultSchema, - ]) - .and(genericProviderSettingsSchema) - -export const providerSettingsSchema = z - .object({ - apiProvider: providerNamesSchema.optional(), - }) - .merge(anthropicSchema) - .merge(glamaSchema) - .merge(openRouterSchema) - .merge(bedrockSchema) - .merge(vertexSchema) - .merge(openAiSchema) - .merge(ollamaSchema) - .merge(vsCodeLmSchema) - .merge(lmStudioSchema) - .merge(geminiSchema) - .merge(openAiNativeSchema) - .merge(mistralSchema) - .merge(deepSeekSchema) - .merge(unboundSchema) - .merge(requestySchema) - .merge(humanRelaySchema) - .merge(fakeAiSchema) - .merge(xaiSchema) - .merge(groqSchema) - .merge(chutesSchema) - .merge(litellmSchema) - .merge(genericProviderSettingsSchema) - export type ProviderSettings = z.infer type ProviderSettingsRecord = Record, undefined> @@ -655,7 +557,7 @@ export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsRecord) as Key export const globalSettingsSchema = z.object({ currentApiConfigName: z.string().optional(), - listApiConfigMeta: z.array(providerSettingsEntrySchema).optional(), + listApiConfigMeta: z.array(apiConfigMetaSchema).optional(), pinnedApiConfigs: z.record(z.string(), z.boolean()).optional(), lastShownAnnouncementId: z.string().optional(), @@ -1074,9 +976,8 @@ export type TypeDefinition = { } export const typeDefinitions: TypeDefinition[] = [ - { schema: globalSettingsSchema, identifier: "GlobalSettings" }, { schema: providerSettingsSchema, identifier: "ProviderSettings" }, - { schema: providerSettingsEntrySchema, identifier: "ProviderSettingsEntry" }, + { schema: globalSettingsSchema, identifier: "GlobalSettings" }, { schema: clineMessageSchema, identifier: "ClineMessage" }, { schema: tokenUsageSchema, identifier: "TokenUsage" }, { schema: rooCodeEventsSchema, identifier: "RooCodeEvents" }, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 6330556024..d118dd3989 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -2,8 +2,8 @@ import { GitCommit } from "../utils/git" import { GlobalSettings, - ProviderSettingsEntry, - ProviderSettings, + ApiConfigMeta, + ProviderSettings as ApiConfiguration, HistoryItem, ModeConfig, TelemetrySetting, @@ -17,7 +17,7 @@ import { McpServer } from "./mcp" import { Mode } from "./modes" import { RouterModels } from "./api" -export type { ProviderSettingsEntry, ToolProgressStatus } +export type { ApiConfigMeta, ToolProgressStatus } export interface LanguageModelChatSelector { vendor?: string @@ -95,7 +95,7 @@ export interface ExtensionMessage { vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[] mcpServers?: McpServer[] commits?: GitCommit[] - listApiConfig?: ProviderSettingsEntry[] + listApiConfig?: ApiConfigMeta[] mode?: Mode customMode?: ModeConfig slug?: string @@ -172,7 +172,7 @@ export type ExtensionState = Pick< version: string clineMessages: ClineMessage[] currentTaskItem?: HistoryItem - apiConfiguration?: ProviderSettings + apiConfiguration?: ApiConfiguration uriScheme?: string shouldShowAnnouncement: boolean diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 22fe5c7d3e..fe8dfff40c 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -1,6 +1,6 @@ import { z } from "zod" -import { ProviderSettings } from "./api" +import { ApiConfiguration } from "./api" import { Mode, PromptComponent, ModeConfig } from "./modes" export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse" @@ -11,6 +11,7 @@ export type AudioType = "notification" | "celebration" | "progress_loop" export interface WebviewMessage { type: + | "apiConfiguration" | "deleteMultipleTasksWithIds" | "currentApiConfigName" | "saveApiConfiguration" @@ -133,7 +134,7 @@ export interface WebviewMessage { text?: string disabled?: boolean askResponse?: ClineAskResponse - apiConfiguration?: ProviderSettings + apiConfiguration?: ApiConfiguration images?: string[] bool?: boolean value?: number diff --git a/src/shared/__tests__/checkExistApiConfig.test.ts b/src/shared/__tests__/checkExistApiConfig.test.ts index a8e603db8a..c99ddddbc4 100644 --- a/src/shared/__tests__/checkExistApiConfig.test.ts +++ b/src/shared/__tests__/checkExistApiConfig.test.ts @@ -1,5 +1,5 @@ import { checkExistKey } from "../checkExistApiConfig" -import { ProviderSettings } from "../api" +import { ApiConfiguration } from "../api" describe("checkExistKey", () => { it("should return false for undefined config", () => { @@ -7,19 +7,19 @@ describe("checkExistKey", () => { }) it("should return false for empty config", () => { - const config: ProviderSettings = {} + const config: ApiConfiguration = {} expect(checkExistKey(config)).toBe(false) }) it("should return true when one key is defined", () => { - const config: ProviderSettings = { + const config: ApiConfiguration = { apiKey: "test-key", } expect(checkExistKey(config)).toBe(true) }) it("should return true when multiple keys are defined", () => { - const config: ProviderSettings = { + const config: ApiConfiguration = { apiKey: "test-key", glamaApiKey: "glama-key", openRouterApiKey: "openrouter-key", @@ -28,7 +28,7 @@ describe("checkExistKey", () => { }) it("should return true when only non-key fields are undefined", () => { - const config: ProviderSettings = { + const config: ApiConfiguration = { apiKey: "test-key", apiProvider: undefined, anthropicBaseUrl: undefined, @@ -38,7 +38,7 @@ describe("checkExistKey", () => { }) it("should return false when all key fields are undefined", () => { - const config: ProviderSettings = { + const config: ApiConfiguration = { apiKey: undefined, glamaApiKey: undefined, openRouterApiKey: undefined, diff --git a/src/shared/api.ts b/src/shared/api.ts index 17d2b14dd4..9f64d7913f 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -1,9 +1,11 @@ import { ModelInfo, ProviderName, ProviderSettings } from "../schemas" -export type { ModelInfo, ProviderName, ProviderSettings } +export type { ModelInfo, ProviderName } export type ApiHandlerOptions = Omit +export type ApiConfiguration = ProviderSettings + // Anthropic // https://docs.anthropic.com/en/docs/about-claude/models export type AnthropicModelId = keyof typeof anthropicModels diff --git a/src/utils/__tests__/enhance-prompt.test.ts b/src/utils/__tests__/enhance-prompt.test.ts index 524f99c817..adda8860eb 100644 --- a/src/utils/__tests__/enhance-prompt.test.ts +++ b/src/utils/__tests__/enhance-prompt.test.ts @@ -1,5 +1,5 @@ import { singleCompletionHandler } from "../single-completion-handler" -import { ProviderSettings } from "../../shared/api" +import { ApiConfiguration } from "../../shared/api" import { buildApiHandler, SingleCompletionHandler } from "../../api" import { supportPrompt } from "../../shared/support-prompt" @@ -9,7 +9,7 @@ jest.mock("../../api", () => ({ })) describe("enhancePrompt", () => { - const mockApiConfig: ProviderSettings = { + const mockApiConfig: ApiConfiguration = { apiProvider: "openai", openAiApiKey: "test-key", openAiBaseUrl: "https://api.openai.com/v1", @@ -69,7 +69,7 @@ describe("enhancePrompt", () => { }) it("throws error for missing API configuration", async () => { - await expect(singleCompletionHandler({} as ProviderSettings, "Test prompt")).rejects.toThrow( + await expect(singleCompletionHandler({} as ApiConfiguration, "Test prompt")).rejects.toThrow( "No valid API configuration provided", ) }) @@ -94,7 +94,7 @@ describe("enhancePrompt", () => { }) it("uses appropriate model based on provider", async () => { - const openRouterConfig: ProviderSettings = { + const openRouterConfig: ApiConfiguration = { apiProvider: "openrouter", openRouterApiKey: "test-key", openRouterModelId: "test-model", diff --git a/src/utils/single-completion-handler.ts b/src/utils/single-completion-handler.ts index 7434d07e19..5e049d4726 100644 --- a/src/utils/single-completion-handler.ts +++ b/src/utils/single-completion-handler.ts @@ -1,11 +1,11 @@ -import { ProviderSettings } from "../shared/api" +import { ApiConfiguration } from "../shared/api" import { buildApiHandler, SingleCompletionHandler } from "../api" /** * Enhances a prompt using the configured API without creating a full Cline instance or task history. * This is a lightweight alternative that only uses the API's completion functionality. */ -export async function singleCompletionHandler(apiConfiguration: ProviderSettings, promptText: string): Promise { +export async function singleCompletionHandler(apiConfiguration: ApiConfiguration, promptText: string): Promise { if (!promptText) { throw new Error("No prompt text provided") } diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx index f9fb71b066..625bad1169 100644 --- a/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx @@ -4,7 +4,7 @@ import React from "react" import { render, screen } from "@testing-library/react" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import TaskHeader, { TaskHeaderProps } from "../TaskHeader" @@ -27,7 +27,7 @@ jest.mock("@src/context/ExtensionStateContext", () => ({ apiProvider: "anthropic", apiKey: "test-api-key", // Add relevant fields apiModelId: "claude-3-opus-20240229", // Add relevant fields - } as ProviderSettings, // Optional: Add type assertion if ProviderSettings is imported + } as ApiConfiguration, // Optional: Add type assertion if ApiConfiguration is imported currentTaskItem: null, }), })) diff --git a/webview-ui/src/components/settings/ApiConfigManager.tsx b/webview-ui/src/components/settings/ApiConfigManager.tsx index 05b14bc586..841de5d8a2 100644 --- a/webview-ui/src/components/settings/ApiConfigManager.tsx +++ b/webview-ui/src/components/settings/ApiConfigManager.tsx @@ -2,7 +2,7 @@ import { memo, useEffect, useRef, useState } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { ChevronsUpDown, Check, X } from "lucide-react" -import { ProviderSettingsEntry } from "@roo/shared/ExtensionMessage" +import { ApiConfigMeta } from "@roo/shared/ExtensionMessage" import { useAppTranslation } from "@/i18n/TranslationContext" import { cn } from "@/lib/utils" @@ -25,7 +25,7 @@ import { interface ApiConfigManagerProps { currentApiConfigName?: string - listApiConfigMeta?: ProviderSettingsEntry[] + listApiConfigMeta?: ApiConfigMeta[] onSelectConfig: (configName: string) => void onDeleteConfig: (configName: string) => void onRenameConfig: (oldName: string, newName: string) => void diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 52fd1c0d3b..33bbdce78f 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -5,7 +5,7 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { type ProviderName, - type ProviderSettings, + type ApiConfiguration, openRouterDefaultModelId, requestyDefaultModelId, glamaDefaultModelId, @@ -56,8 +56,8 @@ import { BedrockCustomArn } from "./providers/BedrockCustomArn" export interface ApiOptionsProps { uriScheme: string | undefined - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: K, value: ApiConfiguration[K]) => void fromWelcomeView?: boolean errorMessage: string | undefined setErrorMessage: React.Dispatch> @@ -107,9 +107,9 @@ const ApiOptions = ({ const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/PromptCachingControl.tsx b/webview-ui/src/components/settings/PromptCachingControl.tsx index db5687a57d..e7600915ec 100644 --- a/webview-ui/src/components/settings/PromptCachingControl.tsx +++ b/webview-ui/src/components/settings/PromptCachingControl.tsx @@ -1,12 +1,12 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" interface PromptCachingControlProps { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: K, value: ApiConfiguration[K]) => void } export const PromptCachingControl = ({ apiConfiguration, setApiConfigurationField }: PromptCachingControlProps) => { diff --git a/webview-ui/src/components/settings/ReasoningEffort.tsx b/webview-ui/src/components/settings/ReasoningEffort.tsx index 53d21a2c49..8e3c399ae9 100644 --- a/webview-ui/src/components/settings/ReasoningEffort.tsx +++ b/webview-ui/src/components/settings/ReasoningEffort.tsx @@ -2,12 +2,12 @@ import { useAppTranslation } from "@/i18n/TranslationContext" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { reasoningEfforts, ReasoningEffort as ReasoningEffortType } from "@roo/schemas" interface ReasoningEffortProps { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: K, value: ApiConfiguration[K]) => void } export const ReasoningEffort = ({ apiConfiguration, setApiConfigurationField }: ReasoningEffortProps) => { diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 735a5b67ba..eeb11ea6eb 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -27,7 +27,7 @@ import { import { ExperimentId } from "@roo/shared/experiments" import { TelemetrySetting } from "@roo/shared/TelemetrySetting" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { vscode } from "@/utils/vscode" import { ExtensionStateContextType, useExtensionState } from "@/context/ExtensionStateContext" @@ -195,7 +195,7 @@ const SettingsView = forwardRef(({ onDone, t }, []) const setApiConfigurationField = useCallback( - (field: K, value: ProviderSettings[K]) => { + (field: K, value: ApiConfiguration[K]) => { setCachedState((prevState) => { if (prevState.apiConfiguration?.[field] === value) { return prevState diff --git a/webview-ui/src/components/settings/ThinkingBudget.tsx b/webview-ui/src/components/settings/ThinkingBudget.tsx index 6c4352f22b..1ee2f310e0 100644 --- a/webview-ui/src/components/settings/ThinkingBudget.tsx +++ b/webview-ui/src/components/settings/ThinkingBudget.tsx @@ -3,14 +3,14 @@ import { useAppTranslation } from "@/i18n/TranslationContext" import { Slider } from "@/components/ui" -import { ProviderSettings, ModelInfo } from "@roo/shared/api" +import { ApiConfiguration, ModelInfo } from "@roo/shared/api" const DEFAULT_MAX_OUTPUT_TOKENS = 16_384 const DEFAULT_MAX_THINKING_TOKENS = 8_192 interface ThinkingBudgetProps { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: K, value: ApiConfiguration[K]) => void modelInfo?: ModelInfo } diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx index b9b1d1d21e..115c623a27 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx @@ -3,7 +3,7 @@ import { render, screen, fireEvent } from "@testing-library/react" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext" import { openAiModelInfoSaneDefaults } from "@roo/shared/api" @@ -148,7 +148,7 @@ jest.mock("../DiffSettingsControl", () => ({ })) jest.mock("@src/components/ui/hooks/useSelectedModel", () => ({ - useSelectedModel: jest.fn((apiConfiguration: ProviderSettings) => { + useSelectedModel: jest.fn((apiConfiguration: ApiConfiguration) => { if (apiConfiguration.apiModelId?.includes("thinking")) { return { provider: apiConfiguration.apiProvider, diff --git a/webview-ui/src/components/settings/providers/Anthropic.tsx b/webview-ui/src/components/settings/providers/Anthropic.tsx index 4d5957b562..bf310d1cb7 100644 --- a/webview-ui/src/components/settings/providers/Anthropic.tsx +++ b/webview-ui/src/components/settings/providers/Anthropic.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from "react" import { Checkbox } from "vscrui" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -10,8 +10,8 @@ import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" import { inputEventTransform, noTransform } from "../transforms" type AnthropicProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: AnthropicProps) => { @@ -20,9 +20,9 @@ export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: Anthro const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/Bedrock.tsx b/webview-ui/src/components/settings/providers/Bedrock.tsx index 2ddc5797ca..fc69e74dde 100644 --- a/webview-ui/src/components/settings/providers/Bedrock.tsx +++ b/webview-ui/src/components/settings/providers/Bedrock.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react" import { Checkbox } from "vscrui" import { VSCodeTextField, VSCodeRadio, VSCodeRadioGroup } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings, ModelInfo } from "@roo/shared/api" +import { ApiConfiguration, ModelInfo } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" @@ -11,8 +11,8 @@ import { AWS_REGIONS } from "../constants" import { inputEventTransform, noTransform } from "../transforms" type BedrockProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void selectedModelInfo?: ModelInfo } @@ -20,9 +20,9 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/BedrockCustomArn.tsx b/webview-ui/src/components/settings/providers/BedrockCustomArn.tsx index 1caf95b8c7..f24d9ef81c 100644 --- a/webview-ui/src/components/settings/providers/BedrockCustomArn.tsx +++ b/webview-ui/src/components/settings/providers/BedrockCustomArn.tsx @@ -1,14 +1,14 @@ import { useMemo } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { validateBedrockArn } from "@src/utils/validate" import { useAppTranslation } from "@src/i18n/TranslationContext" type BedrockCustomArnProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const BedrockCustomArn = ({ apiConfiguration, setApiConfigurationField }: BedrockCustomArnProps) => { diff --git a/webview-ui/src/components/settings/providers/Chutes.tsx b/webview-ui/src/components/settings/providers/Chutes.tsx index 10ca2c202f..f87b9650cf 100644 --- a/webview-ui/src/components/settings/providers/Chutes.tsx +++ b/webview-ui/src/components/settings/providers/Chutes.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -9,17 +9,17 @@ import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" import { inputEventTransform } from "../transforms" type ChutesProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const Chutes = ({ apiConfiguration, setApiConfigurationField }: ChutesProps) => { const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/DeepSeek.tsx b/webview-ui/src/components/settings/providers/DeepSeek.tsx index ffb48aec09..8b1e2e91ef 100644 --- a/webview-ui/src/components/settings/providers/DeepSeek.tsx +++ b/webview-ui/src/components/settings/providers/DeepSeek.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -9,17 +9,17 @@ import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" import { inputEventTransform } from "../transforms" type DeepSeekProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const DeepSeek = ({ apiConfiguration, setApiConfigurationField }: DeepSeekProps) => { const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/Gemini.tsx b/webview-ui/src/components/settings/providers/Gemini.tsx index 0a9af31568..490ab73a51 100644 --- a/webview-ui/src/components/settings/providers/Gemini.tsx +++ b/webview-ui/src/components/settings/providers/Gemini.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from "react" import { Checkbox } from "vscrui" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -10,8 +10,8 @@ import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" import { inputEventTransform } from "../transforms" type GeminiProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const Gemini = ({ apiConfiguration, setApiConfigurationField }: GeminiProps) => { @@ -22,9 +22,9 @@ export const Gemini = ({ apiConfiguration, setApiConfigurationField }: GeminiPro ) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/Glama.tsx b/webview-ui/src/components/settings/providers/Glama.tsx index 7a7f5dcc49..1150e14936 100644 --- a/webview-ui/src/components/settings/providers/Glama.tsx +++ b/webview-ui/src/components/settings/providers/Glama.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings, RouterModels, glamaDefaultModelId } from "@roo/shared/api" +import { ApiConfiguration, RouterModels, glamaDefaultModelId } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { getGlamaAuthUrl } from "@src/oauth/urls" @@ -11,8 +11,8 @@ import { inputEventTransform } from "../transforms" import { ModelPicker } from "../ModelPicker" type GlamaProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void routerModels?: RouterModels uriScheme?: string } @@ -21,9 +21,9 @@ export const Glama = ({ apiConfiguration, setApiConfigurationField, routerModels const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/Groq.tsx b/webview-ui/src/components/settings/providers/Groq.tsx index eaf29a4572..45f827df14 100644 --- a/webview-ui/src/components/settings/providers/Groq.tsx +++ b/webview-ui/src/components/settings/providers/Groq.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -9,17 +9,17 @@ import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" import { inputEventTransform } from "../transforms" type GroqProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const Groq = ({ apiConfiguration, setApiConfigurationField }: GroqProps) => { const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/LMStudio.tsx b/webview-ui/src/components/settings/providers/LMStudio.tsx index df58f903bb..0979ec9242 100644 --- a/webview-ui/src/components/settings/providers/LMStudio.tsx +++ b/webview-ui/src/components/settings/providers/LMStudio.tsx @@ -4,7 +4,7 @@ import { Trans } from "react-i18next" import { Checkbox } from "vscrui" import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { ExtensionMessage } from "@roo/shared/ExtensionMessage" @@ -12,8 +12,8 @@ import { ExtensionMessage } from "@roo/shared/ExtensionMessage" import { inputEventTransform } from "../transforms" type LMStudioProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const LMStudio = ({ apiConfiguration, setApiConfigurationField }: LMStudioProps) => { @@ -22,9 +22,9 @@ export const LMStudio = ({ apiConfiguration, setApiConfigurationField }: LMStudi const [lmStudioModels, setLmStudioModels] = useState([]) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/LiteLLM.tsx b/webview-ui/src/components/settings/providers/LiteLLM.tsx index 8f98d97e5d..5fc466116c 100644 --- a/webview-ui/src/components/settings/providers/LiteLLM.tsx +++ b/webview-ui/src/components/settings/providers/LiteLLM.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings, RouterModels, litellmDefaultModelId } from "@roo/shared/api" +import { ApiConfiguration, RouterModels, litellmDefaultModelId } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" @@ -9,8 +9,8 @@ import { inputEventTransform } from "../transforms" import { ModelPicker } from "../ModelPicker" type LiteLLMProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void routerModels?: RouterModels } @@ -18,9 +18,9 @@ export const LiteLLM = ({ apiConfiguration, setApiConfigurationField, routerMode const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) @@ -46,7 +46,6 @@ export const LiteLLM = ({ apiConfiguration, setApiConfigurationField, routerMode className="w-full"> -
{t("settings:providers.apiKeyStorageNotice")}
diff --git a/webview-ui/src/components/settings/providers/Mistral.tsx b/webview-ui/src/components/settings/providers/Mistral.tsx index 1669166043..c8c27c1d55 100644 --- a/webview-ui/src/components/settings/providers/Mistral.tsx +++ b/webview-ui/src/components/settings/providers/Mistral.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings, RouterModels, mistralDefaultModelId } from "@roo/shared/api" +import { ApiConfiguration, RouterModels, mistralDefaultModelId } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -9,8 +9,8 @@ import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" import { inputEventTransform } from "../transforms" type MistralProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void routerModels?: RouterModels } @@ -18,9 +18,9 @@ export const Mistral = ({ apiConfiguration, setApiConfigurationField }: MistralP const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/Ollama.tsx b/webview-ui/src/components/settings/providers/Ollama.tsx index bda3c441cc..5646fae18c 100644 --- a/webview-ui/src/components/settings/providers/Ollama.tsx +++ b/webview-ui/src/components/settings/providers/Ollama.tsx @@ -2,7 +2,7 @@ import { useState, useCallback } from "react" import { useEvent } from "react-use" import { VSCodeTextField, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { ExtensionMessage } from "@roo/shared/ExtensionMessage" import { useAppTranslation } from "@src/i18n/TranslationContext" @@ -10,8 +10,8 @@ import { useAppTranslation } from "@src/i18n/TranslationContext" import { inputEventTransform } from "../transforms" type OllamaProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaProps) => { @@ -20,9 +20,9 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro const [ollamaModels, setOllamaModels] = useState([]) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/OpenAI.tsx b/webview-ui/src/components/settings/providers/OpenAI.tsx index 771d77ef6a..501dc9df55 100644 --- a/webview-ui/src/components/settings/providers/OpenAI.tsx +++ b/webview-ui/src/components/settings/providers/OpenAI.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from "react" import { Checkbox } from "vscrui" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -10,8 +10,8 @@ import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" import { inputEventTransform } from "../transforms" type OpenAIProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const OpenAI = ({ apiConfiguration, setApiConfigurationField }: OpenAIProps) => { @@ -22,9 +22,9 @@ export const OpenAI = ({ apiConfiguration, setApiConfigurationField }: OpenAIPro ) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx index 774a5681ce..43bdc2d9bd 100644 --- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -5,7 +5,7 @@ import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { convertHeadersToObject } from "../utils/headers" import { ModelInfo, ReasoningEffort as ReasoningEffortType } from "@roo/schemas" -import { ProviderSettings, azureOpenAiDefaultApiVersion, openAiModelInfoSaneDefaults } from "@roo/shared/api" +import { ApiConfiguration, azureOpenAiDefaultApiVersion, openAiModelInfoSaneDefaults } from "@roo/shared/api" import { ExtensionMessage } from "@roo/shared/ExtensionMessage" import { useAppTranslation } from "@src/i18n/TranslationContext" @@ -17,8 +17,8 @@ import { R1FormatSetting } from "../R1FormatSetting" import { ReasoningEffort } from "../ReasoningEffort" type OpenAICompatibleProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }: OpenAICompatibleProps) => { @@ -81,9 +81,9 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }: }, [customHeaders, setApiConfigurationField]) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/OpenRouter.tsx b/webview-ui/src/components/settings/providers/OpenRouter.tsx index 7bb7c30095..696fcca331 100644 --- a/webview-ui/src/components/settings/providers/OpenRouter.tsx +++ b/webview-ui/src/components/settings/providers/OpenRouter.tsx @@ -4,7 +4,7 @@ import { Checkbox } from "vscrui" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { ExternalLinkIcon } from "@radix-ui/react-icons" -import { ProviderSettings, RouterModels, openRouterDefaultModelId } from "@roo/shared/api" +import { ApiConfiguration, RouterModels, openRouterDefaultModelId } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { getOpenRouterAuthUrl } from "@src/oauth/urls" @@ -21,8 +21,8 @@ import { ModelPicker } from "../ModelPicker" import { OpenRouterBalanceDisplay } from "./OpenRouterBalanceDisplay" type OpenRouterProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void routerModels?: RouterModels selectedModelId: string uriScheme: string | undefined @@ -42,9 +42,9 @@ export const OpenRouter = ({ const [openRouterBaseUrlSelected, setOpenRouterBaseUrlSelected] = useState(!!apiConfiguration?.openRouterBaseUrl) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/Requesty.tsx b/webview-ui/src/components/settings/providers/Requesty.tsx index 9c6ba2844e..a8c8fc8099 100644 --- a/webview-ui/src/components/settings/providers/Requesty.tsx +++ b/webview-ui/src/components/settings/providers/Requesty.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings, RouterModels, requestyDefaultModelId } from "@roo/shared/api" +import { ApiConfiguration, RouterModels, requestyDefaultModelId } from "@roo/shared/api" import { vscode } from "@src/utils/vscode" import { useAppTranslation } from "@src/i18n/TranslationContext" @@ -13,8 +13,8 @@ import { ModelPicker } from "../ModelPicker" import { RequestyBalanceDisplay } from "./RequestyBalanceDisplay" type RequestyProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void routerModels?: RouterModels refetchRouterModels: () => void } @@ -30,9 +30,9 @@ export const Requesty = ({ const [didRefetch, setDidRefetch] = useState() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/Unbound.tsx b/webview-ui/src/components/settings/providers/Unbound.tsx index 77b24bb7cc..7467d2b7c6 100644 --- a/webview-ui/src/components/settings/providers/Unbound.tsx +++ b/webview-ui/src/components/settings/providers/Unbound.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings, RouterModels, unboundDefaultModelId } from "@roo/shared/api" +import { ApiConfiguration, RouterModels, unboundDefaultModelId } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -10,8 +10,8 @@ import { inputEventTransform } from "../transforms" import { ModelPicker } from "../ModelPicker" type UnboundProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void routerModels?: RouterModels } @@ -19,9 +19,9 @@ export const Unbound = ({ apiConfiguration, setApiConfigurationField, routerMode const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/VSCodeLM.tsx b/webview-ui/src/components/settings/providers/VSCodeLM.tsx index 7f9426e5ce..ca701ce024 100644 --- a/webview-ui/src/components/settings/providers/VSCodeLM.tsx +++ b/webview-ui/src/components/settings/providers/VSCodeLM.tsx @@ -2,7 +2,7 @@ import { useState, useCallback } from "react" import { useEvent } from "react-use" import { LanguageModelChatSelector } from "vscode" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { ExtensionMessage } from "@roo/shared/ExtensionMessage" import { useAppTranslation } from "@src/i18n/TranslationContext" @@ -11,8 +11,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { inputEventTransform } from "../transforms" type VSCodeLMProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const VSCodeLM = ({ apiConfiguration, setApiConfigurationField }: VSCodeLMProps) => { @@ -21,9 +21,9 @@ export const VSCodeLM = ({ apiConfiguration, setApiConfigurationField }: VSCodeL const [vsCodeLmModels, setVsCodeLmModels] = useState([]) const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/Vertex.tsx b/webview-ui/src/components/settings/providers/Vertex.tsx index b43c793816..f0723f9d31 100644 --- a/webview-ui/src/components/settings/providers/Vertex.tsx +++ b/webview-ui/src/components/settings/providers/Vertex.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" @@ -10,17 +10,17 @@ import { inputEventTransform } from "../transforms" import { VERTEX_REGIONS } from "../constants" type VertexProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const Vertex = ({ apiConfiguration, setApiConfigurationField }: VertexProps) => { const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/settings/providers/XAI.tsx b/webview-ui/src/components/settings/providers/XAI.tsx index c42bde1b1e..39b0e53d34 100644 --- a/webview-ui/src/components/settings/providers/XAI.tsx +++ b/webview-ui/src/components/settings/providers/XAI.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" import { useAppTranslation } from "@src/i18n/TranslationContext" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" @@ -9,17 +9,17 @@ import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" import { inputEventTransform } from "../transforms" type XAIProps = { - apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void } export const XAI = ({ apiConfiguration, setApiConfigurationField }: XAIProps) => { const { t } = useAppTranslation() const handleInputChange = useCallback( - ( + ( field: K, - transform: (event: E) => ProviderSettings[K] = inputEventTransform, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, ) => (event: E | Event) => { setApiConfigurationField(field, transform(event as E)) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index fb1b277981..3b1d792c93 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -1,6 +1,6 @@ import { type ProviderName, - type ProviderSettings, + type ApiConfiguration, type RouterModels, type ModelInfo, anthropicDefaultModelId, @@ -35,7 +35,7 @@ import { import { useRouterModels } from "./useRouterModels" -export const useSelectedModel = (apiConfiguration?: ProviderSettings) => { +export const useSelectedModel = (apiConfiguration?: ApiConfiguration) => { const { data: routerModels, isLoading, isError } = useRouterModels() const provider = apiConfiguration?.apiProvider || "anthropic" @@ -53,7 +53,7 @@ function getSelectedModel({ routerModels, }: { provider: ProviderName - apiConfiguration: ProviderSettings + apiConfiguration: ApiConfiguration routerModels: RouterModels }): { id: string; info: ModelInfo } { switch (provider) { diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 63c895f034..f3a1f69969 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -1,8 +1,9 @@ import React, { createContext, useCallback, useContext, useEffect, useState } from "react" import { useEvent } from "react-use" - -import { ProviderSettingsEntry, ExtensionMessage, ExtensionState } from "@roo/shared/ExtensionMessage" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfigMeta, ExtensionMessage, ExtensionState } from "@roo/shared/ExtensionMessage" +import { ApiConfiguration } from "@roo/shared/api" +import { vscode } from "@src/utils/vscode" +import { convertTextMateToHljs } from "@src/utils/textMateToHljs" import { findLastIndex } from "@roo/shared/array" import { McpServer } from "@roo/shared/mcp" import { checkExistKey } from "@roo/shared/checkExistApiConfig" @@ -11,9 +12,6 @@ import { CustomSupportPrompts } from "@roo/shared/support-prompt" import { experimentDefault, ExperimentId } from "@roo/shared/experiments" import { TelemetrySetting } from "@roo/shared/TelemetrySetting" -import { vscode } from "@src/utils/vscode" -import { convertTextMateToHljs } from "@src/utils/textMateToHljs" - export interface ExtensionStateContextType extends ExtensionState { historyPreviewCollapsed?: boolean // Add the new state property didHydrateState: boolean @@ -24,7 +22,7 @@ export interface ExtensionStateContextType extends ExtensionState { currentCheckpoint?: string filePaths: string[] openedTabs: Array<{ label: string; isActive: boolean; path?: string }> - setApiConfiguration: (config: ProviderSettings) => void + setApiConfiguration: (config: ApiConfiguration) => void setCustomInstructions: (value?: string) => void setAlwaysAllowReadOnly: (value: boolean) => void setAlwaysAllowReadOnlyOutsideWorkspace: (value: boolean) => void @@ -67,7 +65,7 @@ export interface ExtensionStateContextType extends ExtensionState { requestDelaySeconds: number setRequestDelaySeconds: (value: number) => void setCurrentApiConfigName: (value: string) => void - setListApiConfigMeta: (value: ProviderSettingsEntry[]) => void + setListApiConfigMeta: (value: ApiConfigMeta[]) => void mode: Mode setMode: (value: Mode) => void setCustomModePrompts: (value: CustomModePrompts) => void @@ -184,7 +182,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode const [currentCheckpoint, setCurrentCheckpoint] = useState() const setListApiConfigMeta = useCallback( - (value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })), + (value: ApiConfigMeta[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })), [], ) diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx index 02e57b5e35..1ba2d87e9a 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx @@ -1,16 +1,16 @@ -// npx jest src/context/__tests__/ExtensionStateContext.test.tsx +// cd webview-ui && npx jest src/context/__tests__/ExtensionStateContext.test.tsx import { render, screen, act } from "@testing-library/react" import { ExtensionState } from "@roo/shared/ExtensionMessage" import { ExtensionStateContextProvider, useExtensionState, mergeExtensionState } from "../ExtensionStateContext" import { ExperimentId } from "@roo/shared/experiments" -import { ProviderSettings } from "@roo/shared/api" +import { ApiConfiguration } from "@roo/shared/api" +// Test component that consumes the context const TestComponent = () => { const { allowedCommands, setAllowedCommands, soundEnabled, showRooIgnoredFiles, setShowRooIgnoredFiles } = useExtensionState() - return (
{JSON.stringify(allowedCommands)}
@@ -26,9 +26,9 @@ const TestComponent = () => { ) } +// Test component for API configuration const ApiConfigTestComponent = () => { const { apiConfiguration, setApiConfiguration } = useExtensionState() - return (
{JSON.stringify(apiConfiguration)}
@@ -197,7 +197,7 @@ describe("mergeExtensionState", () => { customModes: [], maxOpenTabsContext: 20, maxWorkspaceFiles: 100, - apiConfiguration: { providerId: "openrouter" } as ProviderSettings, + apiConfiguration: { providerId: "openrouter" } as ApiConfiguration, telemetrySetting: "unset", showRooIgnoredFiles: true, renderContext: "sidebar", diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index 0765fffeda..89abe3dedd 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -1,8 +1,8 @@ import i18next from "i18next" -import { ProviderSettings, isRouterName, RouterModels } from "@roo/shared/api" +import { ApiConfiguration, isRouterName, RouterModels } from "@roo/shared/api" -export function validateApiConfiguration(apiConfiguration: ProviderSettings): string | undefined { +export function validateApiConfiguration(apiConfiguration: ApiConfiguration): string | undefined { switch (apiConfiguration.apiProvider) { case "openrouter": if (!apiConfiguration.openRouterApiKey) { @@ -118,7 +118,7 @@ export function validateBedrockArn(arn: string, region?: string) { return { isValid: true, arnRegion, errorMessage: undefined } } -export function validateModelId(apiConfiguration: ProviderSettings, routerModels?: RouterModels): string | undefined { +export function validateModelId(apiConfiguration: ApiConfiguration, routerModels?: RouterModels): string | undefined { const provider = apiConfiguration.apiProvider ?? "" if (!isRouterName(provider)) {