mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-22 21:31:08 -05:00
Chat modes
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { ExtensionContext } from 'vscode'
|
||||
import { ApiConfiguration } from '../../shared/api'
|
||||
import { Mode } from '../prompts/types'
|
||||
import { ApiConfigMeta } from '../../shared/ExtensionMessage'
|
||||
|
||||
export interface ApiConfigData {
|
||||
@@ -7,20 +8,29 @@ export interface ApiConfigData {
|
||||
apiConfigs: {
|
||||
[key: string]: ApiConfiguration
|
||||
}
|
||||
modeApiConfigs?: Partial<Record<Mode, string>>
|
||||
}
|
||||
|
||||
export class ConfigManager {
|
||||
private readonly defaultConfig: ApiConfigData = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {}
|
||||
default: {
|
||||
id: this.generateId()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly SCOPE_PREFIX = "roo_cline_config_"
|
||||
private readonly context: ExtensionContext
|
||||
|
||||
constructor(context: ExtensionContext) {
|
||||
this.context = context
|
||||
this.initConfig().catch(console.error)
|
||||
}
|
||||
|
||||
private generateId(): string {
|
||||
return Math.random().toString(36).substring(2, 15)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,6 +41,20 @@ export class ConfigManager {
|
||||
const config = await this.readConfig()
|
||||
if (!config) {
|
||||
await this.writeConfig(this.defaultConfig)
|
||||
return
|
||||
}
|
||||
|
||||
// Migrate: ensure all configs have IDs
|
||||
let needsMigration = false
|
||||
for (const [name, apiConfig] of Object.entries(config.apiConfigs)) {
|
||||
if (!apiConfig.id) {
|
||||
apiConfig.id = this.generateId()
|
||||
needsMigration = true
|
||||
}
|
||||
}
|
||||
|
||||
if (needsMigration) {
|
||||
await this.writeConfig(config)
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to initialize config: ${error}`)
|
||||
@@ -45,6 +69,7 @@ export class ConfigManager {
|
||||
const config = await this.readConfig()
|
||||
return Object.entries(config.apiConfigs).map(([name, apiConfig]) => ({
|
||||
name,
|
||||
id: apiConfig.id || '',
|
||||
apiProvider: apiConfig.apiProvider,
|
||||
}))
|
||||
} catch (error) {
|
||||
@@ -58,7 +83,11 @@ export class ConfigManager {
|
||||
async SaveConfig(name: string, config: ApiConfiguration): Promise<void> {
|
||||
try {
|
||||
const currentConfig = await this.readConfig()
|
||||
currentConfig.apiConfigs[name] = config
|
||||
const existingConfig = currentConfig.apiConfigs[name]
|
||||
currentConfig.apiConfigs[name] = {
|
||||
...config,
|
||||
id: existingConfig?.id || this.generateId()
|
||||
}
|
||||
await this.writeConfig(currentConfig)
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save config: ${error}`)
|
||||
@@ -137,6 +166,34 @@ export class ConfigManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the API config for a specific mode
|
||||
*/
|
||||
async SetModeConfig(mode: Mode, configId: string): Promise<void> {
|
||||
try {
|
||||
const currentConfig = await this.readConfig()
|
||||
if (!currentConfig.modeApiConfigs) {
|
||||
currentConfig.modeApiConfigs = {}
|
||||
}
|
||||
currentConfig.modeApiConfigs[mode] = configId
|
||||
await this.writeConfig(currentConfig)
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to set mode config: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API config ID for a specific mode
|
||||
*/
|
||||
async GetModeConfigId(mode: Mode): Promise<string | undefined> {
|
||||
try {
|
||||
const config = await this.readConfig()
|
||||
return config.modeApiConfigs?.[mode]
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get mode config: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async readConfig(): Promise<ApiConfigData> {
|
||||
try {
|
||||
const configKey = `${this.SCOPE_PREFIX}api_config`
|
||||
|
||||
@@ -36,7 +36,10 @@ describe('ConfigManager', () => {
|
||||
mockSecrets.get.mockResolvedValue(JSON.stringify({
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {}
|
||||
default: {
|
||||
config: {},
|
||||
id: 'default'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -45,6 +48,29 @@ describe('ConfigManager', () => {
|
||||
expect(mockSecrets.store).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should generate IDs for configs that lack them', async () => {
|
||||
// Mock a config with missing IDs
|
||||
mockSecrets.get.mockResolvedValue(JSON.stringify({
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {
|
||||
config: {}
|
||||
},
|
||||
test: {
|
||||
apiProvider: 'anthropic'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
await configManager.initConfig()
|
||||
|
||||
// Should have written the config with new IDs
|
||||
expect(mockSecrets.store).toHaveBeenCalled()
|
||||
const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1])
|
||||
expect(storedConfig.apiConfigs.default.id).toBeTruthy()
|
||||
expect(storedConfig.apiConfigs.test.id).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should throw error if secrets storage fails', async () => {
|
||||
mockSecrets.get.mockRejectedValue(new Error('Storage failed'))
|
||||
|
||||
@@ -59,10 +85,18 @@ describe('ConfigManager', () => {
|
||||
const existingConfig: ApiConfigData = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {},
|
||||
default: {
|
||||
id: 'default'
|
||||
},
|
||||
test: {
|
||||
apiProvider: 'anthropic'
|
||||
apiProvider: 'anthropic',
|
||||
id: 'test-id'
|
||||
}
|
||||
},
|
||||
modeApiConfigs: {
|
||||
code: 'default',
|
||||
architect: 'default',
|
||||
ask: 'default'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,15 +104,20 @@ describe('ConfigManager', () => {
|
||||
|
||||
const configs = await configManager.ListConfig()
|
||||
expect(configs).toEqual([
|
||||
{ name: 'default', apiProvider: undefined },
|
||||
{ name: 'test', apiProvider: 'anthropic' }
|
||||
{ name: 'default', id: 'default', apiProvider: undefined },
|
||||
{ name: 'test', id: 'test-id', apiProvider: 'anthropic' }
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle empty config file', async () => {
|
||||
const emptyConfig: ApiConfigData = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {}
|
||||
apiConfigs: {},
|
||||
modeApiConfigs: {
|
||||
code: 'default',
|
||||
architect: 'default',
|
||||
ask: 'default'
|
||||
}
|
||||
}
|
||||
|
||||
mockSecrets.get.mockResolvedValue(JSON.stringify(emptyConfig))
|
||||
@@ -102,6 +141,11 @@ describe('ConfigManager', () => {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {}
|
||||
},
|
||||
modeApiConfigs: {
|
||||
code: 'default',
|
||||
architect: 'default',
|
||||
ask: 'default'
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -112,11 +156,23 @@ describe('ConfigManager', () => {
|
||||
|
||||
await configManager.SaveConfig('test', newConfig)
|
||||
|
||||
// Get the actual stored config to check the generated ID
|
||||
const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1])
|
||||
const testConfigId = storedConfig.apiConfigs.test.id
|
||||
|
||||
const expectedConfig = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {},
|
||||
test: newConfig
|
||||
test: {
|
||||
...newConfig,
|
||||
id: testConfigId
|
||||
}
|
||||
},
|
||||
modeApiConfigs: {
|
||||
code: 'default',
|
||||
architect: 'default',
|
||||
ask: 'default'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +188,8 @@ describe('ConfigManager', () => {
|
||||
apiConfigs: {
|
||||
test: {
|
||||
apiProvider: 'anthropic',
|
||||
apiKey: 'old-key'
|
||||
apiKey: 'old-key',
|
||||
id: 'test-id'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +206,11 @@ describe('ConfigManager', () => {
|
||||
const expectedConfig = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
test: updatedConfig
|
||||
test: {
|
||||
apiProvider: 'anthropic',
|
||||
apiKey: 'new-key',
|
||||
id: 'test-id'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,9 +238,12 @@ describe('ConfigManager', () => {
|
||||
const existingConfig: ApiConfigData = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {},
|
||||
default: {
|
||||
id: 'default'
|
||||
},
|
||||
test: {
|
||||
apiProvider: 'anthropic'
|
||||
apiProvider: 'anthropic',
|
||||
id: 'test-id'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,17 +252,11 @@ describe('ConfigManager', () => {
|
||||
|
||||
await configManager.DeleteConfig('test')
|
||||
|
||||
const expectedConfig = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
|
||||
expect(mockSecrets.store).toHaveBeenCalledWith(
|
||||
'roo_cline_config_api_config',
|
||||
JSON.stringify(expectedConfig, null, 2)
|
||||
)
|
||||
// Get the stored config to check the ID
|
||||
const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1])
|
||||
expect(storedConfig.currentApiConfigName).toBe('default')
|
||||
expect(Object.keys(storedConfig.apiConfigs)).toEqual(['default'])
|
||||
expect(storedConfig.apiConfigs.default.id).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should throw error when trying to delete non-existent config', async () => {
|
||||
@@ -215,7 +273,11 @@ describe('ConfigManager', () => {
|
||||
it('should throw error when trying to delete last remaining config', async () => {
|
||||
mockSecrets.get.mockResolvedValue(JSON.stringify({
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: { default: {} }
|
||||
apiConfigs: {
|
||||
default: {
|
||||
id: 'default'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
await expect(configManager.DeleteConfig('default')).rejects.toThrow(
|
||||
@@ -231,7 +293,8 @@ describe('ConfigManager', () => {
|
||||
apiConfigs: {
|
||||
test: {
|
||||
apiProvider: 'anthropic',
|
||||
apiKey: 'test-key'
|
||||
apiKey: 'test-key',
|
||||
id: 'test-id'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,29 +305,29 @@ describe('ConfigManager', () => {
|
||||
|
||||
expect(config).toEqual({
|
||||
apiProvider: 'anthropic',
|
||||
apiKey: 'test-key'
|
||||
apiKey: 'test-key',
|
||||
id: 'test-id'
|
||||
})
|
||||
|
||||
const expectedConfig = {
|
||||
currentApiConfigName: 'test',
|
||||
apiConfigs: {
|
||||
test: {
|
||||
apiProvider: 'anthropic',
|
||||
apiKey: 'test-key'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(mockSecrets.store).toHaveBeenCalledWith(
|
||||
'roo_cline_config_api_config',
|
||||
JSON.stringify(expectedConfig, null, 2)
|
||||
)
|
||||
// Get the stored config to check the structure
|
||||
const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1])
|
||||
expect(storedConfig.currentApiConfigName).toBe('test')
|
||||
expect(storedConfig.apiConfigs.test).toEqual({
|
||||
apiProvider: 'anthropic',
|
||||
apiKey: 'test-key',
|
||||
id: 'test-id'
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw error when config does not exist', async () => {
|
||||
mockSecrets.get.mockResolvedValue(JSON.stringify({
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: { default: {} }
|
||||
apiConfigs: {
|
||||
default: {
|
||||
config: {},
|
||||
id: 'default'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
await expect(configManager.LoadConfig('nonexistent')).rejects.toThrow(
|
||||
@@ -276,7 +339,12 @@ describe('ConfigManager', () => {
|
||||
mockSecrets.get.mockResolvedValue(JSON.stringify({
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
test: { apiProvider: 'anthropic' }
|
||||
test: {
|
||||
config: {
|
||||
apiProvider: 'anthropic'
|
||||
},
|
||||
id: 'test-id'
|
||||
}
|
||||
}
|
||||
}))
|
||||
mockSecrets.store.mockRejectedValueOnce(new Error('Storage failed'))
|
||||
@@ -292,9 +360,12 @@ describe('ConfigManager', () => {
|
||||
const existingConfig: ApiConfigData = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {},
|
||||
default: {
|
||||
id: 'default'
|
||||
},
|
||||
test: {
|
||||
apiProvider: 'anthropic'
|
||||
apiProvider: 'anthropic',
|
||||
id: 'test-id'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,20 +374,14 @@ describe('ConfigManager', () => {
|
||||
|
||||
await configManager.SetCurrentConfig('test')
|
||||
|
||||
const expectedConfig = {
|
||||
currentApiConfigName: 'test',
|
||||
apiConfigs: {
|
||||
default: {},
|
||||
test: {
|
||||
apiProvider: 'anthropic'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(mockSecrets.store).toHaveBeenCalledWith(
|
||||
'roo_cline_config_api_config',
|
||||
JSON.stringify(expectedConfig, null, 2)
|
||||
)
|
||||
// Get the stored config to check the structure
|
||||
const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1])
|
||||
expect(storedConfig.currentApiConfigName).toBe('test')
|
||||
expect(storedConfig.apiConfigs.default.id).toBe('default')
|
||||
expect(storedConfig.apiConfigs.test).toEqual({
|
||||
apiProvider: 'anthropic',
|
||||
id: 'test-id'
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw error when config does not exist', async () => {
|
||||
@@ -350,9 +415,12 @@ describe('ConfigManager', () => {
|
||||
const existingConfig: ApiConfigData = {
|
||||
currentApiConfigName: 'default',
|
||||
apiConfigs: {
|
||||
default: {},
|
||||
default: {
|
||||
id: 'default'
|
||||
},
|
||||
test: {
|
||||
apiProvider: 'anthropic'
|
||||
apiProvider: 'anthropic',
|
||||
id: 'test-id'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user