Revert "Improve provider profile management in the external API (#3386)" (#3440)

This commit is contained in:
Matt Rubens 2025-05-10 08:27:37 -04:00 committed by GitHub
parent 1b37e30580
commit 730d037a12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 1005 additions and 1493 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Revert profile management changes until we track down a bug with defaults

1
.gitignore vendored
View file

@ -4,7 +4,6 @@ out
out-*
node_modules
coverage/
mock/
.DS_Store

2
e2e/package-lock.json generated
View file

@ -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": {

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -128,6 +128,18 @@ export const modelInfoSchema = z.object({
export type ModelInfo = z.infer<typeof modelInfoSchema>
/**
* ApiConfigMeta
*/
export const apiConfigMetaSchema = z.object({
id: z.string(),
name: z.string(),
apiProvider: providerNamesSchema.optional(),
})
export type ApiConfigMeta = z.infer<typeof apiConfigMetaSchema>
/**
* HistoryItem
*/
@ -317,56 +329,27 @@ export type Experiments = z.infer<typeof experimentsSchema>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type _AssertExperiments = AssertEqual<Equals<ExperimentId, Keys<Experiments>>>
/**
* ProviderSettingsEntry
*/
export const providerSettingsEntrySchema = z.object({
id: z.string(),
name: z.string(),
apiProvider: providerNamesSchema.optional(),
})
export type ProviderSettingsEntry = z.infer<typeof providerSettingsEntrySchema>
/**
* 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<typeof providerSettingsSchema>
@ -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<ProviderSettings>[]
@ -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(),

73
evals/pnpm-lock.yaml generated
View file

@ -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:

0
git Normal file
View file

4
package-lock.json generated
View file

@ -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"
},

View file

@ -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"
},

View file

@ -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<Anthropic.Messages.ContentBlockParam>): Promise<number>
}
export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
const { apiProvider, ...options } = configuration
switch (apiProvider) {

View file

@ -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<typeof providerSettingsWithIdSchema>
@ -226,7 +223,7 @@ export class ProviderSettingsManager {
/**
* List all available configs with metadata.
*/
public async listConfig(): Promise<ProviderSettingsEntry[]> {
public async listConfig(): Promise<ApiConfigMeta[]> {
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<string> {
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<ProviderSettingsWithId & { name: string }> {
/**
* 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<ProviderSettingsWithId & { name: string }> {
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}`)
}

View file

@ -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")

View file

@ -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<ClineEvents> {
customInstructions?: string
// API
readonly apiConfiguration: ProviderSettings
readonly apiConfiguration: ApiConfiguration
api: ApiHandler
private promptCacheKey: string
private lastApiRequestTime?: number

View file

@ -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<ClineProvider>
let mockApiConfig: ProviderSettings
let mockApiConfig: ApiConfiguration
let mockOutputChannel: any
let mockExtensionContext: vscode.ExtensionContext

View file

@ -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<ClineProviderEvents> 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<ClineProviderEvents> 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<ClineProviderEvents> 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<ClineProviderEvents> 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<string | undefined> {
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<ClineProviderEvents> 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<ClineProviderEvents> 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<ClineProviderEvents> 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

View file

@ -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

View file

@ -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"))
}
}

View file

@ -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<RooCodeEvents> 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<RooCodeEvents> 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<string | undefined> {
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<string | undefined> {
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<void> {
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<string | undefined> {
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()
}
}

View file

@ -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<RooCodeEvents> {
*/
pressSecondaryButton(): Promise<void>
/**
* 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<RooCodeEvents> {
*/
setConfiguration(values: RooCodeSettings): Promise<void>
/**
* Creates a new API configuration profile
* @param name The name of the profile
* @returns The ID of the created profile
*/
createProfile(name: string): Promise<string>
/**
* Returns a list of all configured profile names
* @returns Array of profile names
@ -114,40 +100,17 @@ export interface RooCodeAPI extends EventEmitter<RooCodeEvents> {
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<string>
/**
* 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<string | undefined>
setActiveProfile(name: string): Promise<void>
/**
* 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<string | undefined>
getActiveProfile(): string | undefined
/**
* Deletes a profile by name
@ -157,15 +120,7 @@ export interface RooCodeAPI extends EventEmitter<RooCodeEvents> {
deleteProfile(name: string): Promise<void>
/**
* 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<string | undefined>
isReady(): boolean
}

View file

@ -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<RooCodeEvents> {
* Simulates pressing the secondary button in the chat interface.
*/
pressSecondaryButton(): Promise<void>
/**
* 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<RooCodeEvents> {
* @param values An object containing key-value pairs to set.
*/
setConfiguration(values: RooCodeSettings): Promise<void>
/**
* Creates a new API configuration profile
* @param name The name of the profile
* @returns The ID of the created profile
*/
createProfile(name: string): Promise<string>
/**
* 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<string>
/**
* 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<string | undefined>
setActiveProfile(name: string): Promise<void>
/**
* 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<string | undefined>
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<RooCodeEvents> {
*/
deleteProfile(name: string): Promise<void>
/**
* 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<string | undefined>
isReady(): boolean
}
export {
type ClineMessage,
type GlobalSettings,
type ProviderSettings,
type ProviderSettingsEntry,
type RooCodeAPI,
RooCodeEventName,
type RooCodeEvents,

View file

@ -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"

View file

@ -135,6 +135,18 @@ export const modelInfoSchema = z.object({
export type ModelInfo = z.infer<typeof modelInfoSchema>
/**
* ApiConfigMeta
*/
export const apiConfigMetaSchema = z.object({
id: z.string(),
name: z.string(),
apiProvider: providerNamesSchema.optional(),
})
export type ApiConfigMeta = z.infer<typeof apiConfigMetaSchema>
/**
* HistoryItem
*/
@ -329,56 +341,27 @@ export type Experiments = z.infer<typeof experimentsSchema>
type _AssertExperiments = AssertEqual<Equals<ExperimentId, Keys<Experiments>>>
/**
* ProviderSettingsEntry
*/
export const providerSettingsEntrySchema = z.object({
id: z.string(),
name: z.string(),
apiProvider: providerNamesSchema.optional(),
})
export type ProviderSettingsEntry = z.infer<typeof providerSettingsEntrySchema>
/**
* 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<typeof providerSettingsSchema>
type ProviderSettingsRecord = Record<Keys<ProviderSettings>, 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" },

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -1,9 +1,11 @@
import { ModelInfo, ProviderName, ProviderSettings } from "../schemas"
export type { ModelInfo, ProviderName, ProviderSettings }
export type { ModelInfo, ProviderName }
export type ApiHandlerOptions = Omit<ProviderSettings, "apiProvider" | "id">
export type ApiConfiguration = ProviderSettings
// Anthropic
// https://docs.anthropic.com/en/docs/about-claude/models
export type AnthropicModelId = keyof typeof anthropicModels

View file

@ -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",

View file

@ -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<string> {
export async function singleCompletionHandler(apiConfiguration: ApiConfiguration, promptText: string): Promise<string> {
if (!promptText) {
throw new Error("No prompt text provided")
}

View file

@ -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,
}),
}))

View file

@ -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

View file

@ -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: <K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => void
apiConfiguration: ApiConfiguration
setApiConfigurationField: <K extends keyof ApiConfiguration>(field: K, value: ApiConfiguration[K]) => void
fromWelcomeView?: boolean
errorMessage: string | undefined
setErrorMessage: React.Dispatch<React.SetStateAction<string | undefined>>
@ -107,9 +107,9 @@ const ApiOptions = ({
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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: <K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => void
apiConfiguration: ApiConfiguration
setApiConfigurationField: <K extends keyof ApiConfiguration>(field: K, value: ApiConfiguration[K]) => void
}
export const PromptCachingControl = ({ apiConfiguration, setApiConfigurationField }: PromptCachingControlProps) => {

View file

@ -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: <K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => void
apiConfiguration: ApiConfiguration
setApiConfigurationField: <K extends keyof ApiConfiguration>(field: K, value: ApiConfiguration[K]) => void
}
export const ReasoningEffort = ({ apiConfiguration, setApiConfigurationField }: ReasoningEffortProps) => {

View file

@ -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<SettingsViewRef, SettingsViewProps>(({ onDone, t
}, [])
const setApiConfigurationField = useCallback(
<K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => {
<K extends keyof ApiConfiguration>(field: K, value: ApiConfiguration[K]) => {
setCachedState((prevState) => {
if (prevState.apiConfiguration?.[field] === value) {
return prevState

View file

@ -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: <K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => void
apiConfiguration: ApiConfiguration
setApiConfigurationField: <K extends keyof ApiConfiguration>(field: K, value: ApiConfiguration[K]) => void
modelInfo?: ModelInfo
}

View file

@ -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,

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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) => {

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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<string[]>([])
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
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">
<label className="block font-medium mb-1">{t("settings:providers.litellmApiKey")}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground -mt-2">
{t("settings:providers.apiKeyStorageNotice")}
</div>

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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<string[]>([])
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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<boolean>()
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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<LanguageModelChatSelector[]>([])
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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(
<K extends keyof ProviderSettings, E>(
<K extends keyof ApiConfiguration, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
transform: (event: E) => ApiConfiguration[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))

View file

@ -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) {

View file

@ -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<string>()
const setListApiConfigMeta = useCallback(
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
(value: ApiConfigMeta[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
[],
)

View file

@ -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 (
<div>
<div data-testid="allowed-commands">{JSON.stringify(allowedCommands)}</div>
@ -26,9 +26,9 @@ const TestComponent = () => {
)
}
// Test component for API configuration
const ApiConfigTestComponent = () => {
const { apiConfiguration, setApiConfiguration } = useExtensionState()
return (
<div>
<div data-testid="api-configuration">{JSON.stringify(apiConfiguration)}</div>
@ -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",

View file

@ -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)) {