diff --git a/lib/shared/src/configuration.ts b/lib/shared/src/configuration.ts index 58c94a5ccb41..d761a83bf520 100644 --- a/lib/shared/src/configuration.ts +++ b/lib/shared/src/configuration.ts @@ -466,12 +466,20 @@ export interface FireworksCodeCompletionParams { export interface AgentToolboxSettings { /** - * The agent ID that user has currently enabled. + * The agent that user has currently enabled. */ - agent?: string + agent?: { + /** + * The name of the agent that user has currently enabled. E.g. "deep-cody" + */ + name?: string + } /** * Whether the user has enabled terminal context. * Defaulted to undefined if shell context is not enabled by site admin via feature flag. */ - shell?: boolean + shell?: { + enabled: boolean + error?: string + } } diff --git a/vscode/src/chat/agentic/CodyTool.test.ts b/vscode/src/chat/agentic/CodyTool.test.ts index 8786cf7e348b..5aeb679db193 100644 --- a/vscode/src/chat/agentic/CodyTool.test.ts +++ b/vscode/src/chat/agentic/CodyTool.test.ts @@ -209,7 +209,7 @@ describe('CodyTool', () => { }) it('should register all default tools based on toolbox settings', () => { - const mockedToolboxSettings = { agent: 'mock-agent', shell: true } + const mockedToolboxSettings = { agent: { name: 'mock-agent' }, shell: { enabled: true } } vi.spyOn(toolboxManager, 'getSettings').mockReturnValue(mockedToolboxSettings) const localStorageData: { [key: string]: unknown } = {} mockLocalStorage({ @@ -226,7 +226,7 @@ describe('CodyTool', () => { expect(tools.some(t => t.config.title.includes('Terminal'))).toBeTruthy() // Disable shell and check if terminal tool is removed. - mockedToolboxSettings.shell = false + mockedToolboxSettings.shell.enabled = false vi.spyOn(toolboxManager, 'getSettings').mockReturnValue(mockedToolboxSettings) const newTools = CodyToolProvider.getTools() expect(newTools.some(t => t.config.title.includes('Terminal'))).toBeFalsy() diff --git a/vscode/src/chat/agentic/CodyToolProvider.test.ts b/vscode/src/chat/agentic/CodyToolProvider.test.ts index 419c8d167449..0152b3d9ee5c 100644 --- a/vscode/src/chat/agentic/CodyToolProvider.test.ts +++ b/vscode/src/chat/agentic/CodyToolProvider.test.ts @@ -89,13 +89,19 @@ describe('CodyToolProvider', () => { }) it('should not include CLI tool if shell is disabled', () => { - vi.spyOn(toolboxManager, 'getSettings').mockReturnValue({ agent: 'deep-cody', shell: false }) + vi.spyOn(toolboxManager, 'getSettings').mockReturnValue({ + agent: { name: 'deep-cody' }, + shell: { enabled: false }, + }) const tools = CodyToolProvider.getTools() expect(tools.some(tool => tool.config.title === 'Terminal')).toBe(false) }) it('should include CLI tool if shell is enabled', () => { - vi.spyOn(toolboxManager, 'getSettings').mockReturnValue({ agent: 'deep-cody', shell: true }) + vi.spyOn(toolboxManager, 'getSettings').mockReturnValue({ + agent: { name: 'deep-cody' }, + shell: { enabled: true }, + }) const tools = CodyToolProvider.getTools() expect(tools.some(tool => tool.config.title === 'Terminal')).toBe(true) }) diff --git a/vscode/src/chat/agentic/CodyToolProvider.ts b/vscode/src/chat/agentic/CodyToolProvider.ts index 167742236ad9..a4e26a4152a7 100644 --- a/vscode/src/chat/agentic/CodyToolProvider.ts +++ b/vscode/src/chat/agentic/CodyToolProvider.ts @@ -90,8 +90,16 @@ export class ToolFactory { private generateToolName(provider: ContextMentionProviderMetadata): string { const suffix = provider.id.includes('modelcontextprotocol') ? 'MCP' : '' - const title = provider.title.replace(' ', '').split('/').at(-1) - return 'TOOL' + title?.toUpperCase().replace(/[^a-zA-Z0-9]/g, '') + suffix + return ( + 'TOOL' + + provider.title + .split('/') + .pop() + ?.replace(/\s+/g, '') + ?.toUpperCase() + ?.replace(/[^A-Z0-9]/g, '') + + suffix + ) } private getToolConfig(provider: ContextMentionProviderMetadata): CodyToolConfig { @@ -151,7 +159,7 @@ export namespace CodyToolProvider { } export function setupOpenCtxProviderListener(): void { - if (!openCtx.controller) { + if (!openCtx.controller || !factory) { console.error('OpenCtx controller not available') } if (openCtxSubscription || !openCtx.controller) { @@ -161,7 +169,7 @@ export namespace CodyToolProvider { openCtxSubscription = openCtx.controller .metaChanges({}, {}) .pipe(map(providers => providers.filter(p => !!p.mentions).map(openCtxProviderMetadata))) - .subscribe(providerMeta => factory.createOpenCtxTools(providerMeta)) + .subscribe(providerMeta => factory?.createOpenCtxTools(providerMeta)) } function initializeRegistry(): void { diff --git a/vscode/src/chat/agentic/ToolboxManager.ts b/vscode/src/chat/agentic/ToolboxManager.ts index cb03acb1fcb3..4defa5506517 100644 --- a/vscode/src/chat/agentic/ToolboxManager.ts +++ b/vscode/src/chat/agentic/ToolboxManager.ts @@ -32,7 +32,7 @@ type StoredToolboxSettings = { * NOTE: This is a Singleton class. */ class ToolboxManager { - private static readonly STORAGE_KEY = 'CODY_CHATAGENTS_TOOLBOX_SETTINGS' + private static readonly STORAGE_KEY = 'CODYAGENT_TOOLBOX_SETTINGS' private static instance?: ToolboxManager private constructor() { @@ -51,8 +51,8 @@ class ToolboxManager { private getStoredUserSettings(): StoredToolboxSettings { return ( localStorage.get(ToolboxManager.STORAGE_KEY) ?? { - agent: undefined, - shell: this.shellConfig.user, + agent: this.isEnabled ? 'deep-cody' : undefined, + shell: false, } ) } @@ -62,20 +62,31 @@ class ToolboxManager { return null } const { agent, shell } = this.getStoredUserSettings() + const isShellEnabled = this.shellConfig.instance && this.shellConfig.client ? shell : undefined return { - agent, + agent: { name: agent }, // Only show shell option if it's supported by instance and client. - shell: this.shellConfig.instance && this.shellConfig.client ? shell : undefined, + shell: { enabled: isShellEnabled ?? false }, } } - public async updateToolboxSettings(settings: AgentToolboxSettings): Promise { + public async updateSettings(settings: AgentToolboxSettings): Promise { logDebug('ToolboxManager', 'Updating toolbox settings', { verbose: settings }) - await localStorage.set(ToolboxManager.STORAGE_KEY, settings) + await localStorage.set(ToolboxManager.STORAGE_KEY, { + agent: settings.agent?.name, + shell: settings.shell?.enabled ?? false, + }) this.changeNotifications.next() } - - public readonly settings: Observable = combineLatest( + /** + * Returns a real-time Observable stream of toolbox settings that updates when any of the following changes: + * - Feature flags + * - User subscription + * - Available models + * - Manual settings updates + * Use this when you need to react to settings changes over time. + */ + public readonly observable: Observable = combineLatest( featureFlagProvider.evaluatedFeatureFlag(FeatureFlag.DeepCody), featureFlagProvider.evaluatedFeatureFlag(FeatureFlag.ContextAgentDefaultChatModel), featureFlagProvider.evaluatedFeatureFlag(FeatureFlag.DeepCodyShellContext), diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 5ebbcd49f3de..0b344324444f 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -663,7 +663,7 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv editorState, intent: detectedIntent, manuallySelectedIntent: manuallySelectedIntent ? detectedIntent : undefined, - agent: toolboxManager.getSettings()?.agent, + agent: toolboxManager.getSettings()?.agent?.name, }) this.postViewTranscript({ speaker: 'assistant' }) @@ -1544,10 +1544,10 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv userProductSubscription.pipe( map(value => (value === pendingOperation ? null : value)) ), - toolboxSettings: () => toolboxManager.settings, + toolboxSettings: () => toolboxManager.observable, updateToolboxSettings: settings => { return promiseFactoryToObservable(async () => { - await toolboxManager.updateToolboxSettings(settings) + await toolboxManager.updateSettings(settings) }) }, } diff --git a/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/ToolboxButton.tsx b/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/ToolboxButton.tsx index 6329184a990a..f44bc5a25268 100644 --- a/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/ToolboxButton.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/ToolboxButton.tsx @@ -79,13 +79,15 @@ export const ToolboxButton: FC = memo(({ settings, api }) =>

Self-Reflection Context Agent

setSettingsForm({ ...settingsForm, - agent: settingsForm.agent - ? undefined - : 'deep-cody', + agent: { + name: settingsForm.agent?.name + ? undefined + : 'deep-cody', // TODO: update name when finalized. + }, }) } /> @@ -93,36 +95,32 @@ export const ToolboxButton: FC = memo(({ settings, api }) =>
{ToolboxOptionText.agentic}
- {settings.shell !== undefined && ( - <> -
-

- Terminal Context Agent -

- - setSettingsForm({ - ...settingsForm, - shell: - !!settingsForm.agent && - !settingsForm.shell, - }) - } - /> -
-
- Enable with caution as mistakes are possible. -
-
- {ToolboxOptionText.terminal} -
- - )} +
+

Terminal Context Agent

+ + setSettingsForm({ + ...settingsForm, + shell: { + enabled: + !!settingsForm.agent?.name && + !settingsForm.shell?.enabled, + }, + }) + } + /> +
+
+ Enable with caution as mistakes are possible. +
+
+ {ToolboxOptionText.terminal} +
@@ -153,7 +151,7 @@ export const ToolboxButton: FC = memo(({ settings, api }) => >