diff --git a/packages/ai-core/src/browser/ai-configuration/agent-configuration-widget.tsx b/packages/ai-core/src/browser/ai-configuration/agent-configuration-widget.tsx index 8d5b06c5ccc41..43b23b8c9060a 100644 --- a/packages/ai-core/src/browser/ai-configuration/agent-configuration-widget.tsx +++ b/packages/ai-core/src/browser/ai-configuration/agent-configuration-widget.tsx @@ -19,6 +19,7 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify' import * as React from '@theia/core/shared/react'; import { Agent, + AISettingsService, AIVariableService, LanguageModel, LanguageModelRegistry, @@ -27,7 +28,6 @@ import { PromptCustomizationService, PromptService, } from '../../common'; -import { AISettingsService } from '../ai-settings-service'; import { LanguageModelRenderer } from './language-model-renderer'; import { TemplateRenderer } from './template-settings-renderer'; import { AIConfigurationSelectionService } from './ai-configuration-service'; diff --git a/packages/ai-core/src/browser/ai-configuration/language-model-renderer.tsx b/packages/ai-core/src/browser/ai-configuration/language-model-renderer.tsx index c3ba5b2e06b50..f41a8acfd84bd 100644 --- a/packages/ai-core/src/browser/ai-configuration/language-model-renderer.tsx +++ b/packages/ai-core/src/browser/ai-configuration/language-model-renderer.tsx @@ -16,7 +16,7 @@ import * as React from '@theia/core/shared/react'; import { Agent, LanguageModelRequirement } from '../../common'; import { LanguageModel, LanguageModelRegistry } from '../../common/language-model'; -import { AISettingsService } from '../ai-settings-service'; +import { AISettingsService } from '../../common/settings-service'; import { Mutable } from '@theia/core'; export interface LanguageModelSettingsProps { @@ -29,8 +29,8 @@ export interface LanguageModelSettingsProps { export const LanguageModelRenderer: React.FC = ( { agent, languageModels, aiSettingsService, languageModelRegistry }) => { - const findLanguageModelRequirement = (purpose: string): LanguageModelRequirement | undefined => { - const requirementSetting = aiSettingsService.getAgentSettings(agent.id); + const findLanguageModelRequirement = async (purpose: string): Promise => { + const requirementSetting = await aiSettingsService.getAgentSettings(agent.id); return requirementSetting?.languageModelRequirements.find(e => e.purpose === purpose); }; @@ -41,7 +41,7 @@ export const LanguageModelRenderer: React.FC = ( const map = await agent.languageModelRequirements.reduce(async (accPromise, curr) => { const acc = await accPromise; // take the agents requirements and override them with the user settings if present - const lmRequirement = findLanguageModelRequirement(curr.purpose) ?? curr; + const lmRequirement = await findLanguageModelRequirement(curr.purpose) ?? curr; // if no llm is selected through the identifier, see what would be the default if (!lmRequirement.identifier) { const llm = await languageModelRegistry.selectLanguageModel({ agent: agent.id, ...lmRequirement }); diff --git a/packages/ai-core/src/browser/ai-core-frontend-module.ts b/packages/ai-core/src/browser/ai-core-frontend-module.ts index 9e1404f8f782a..7f137f81ace05 100644 --- a/packages/ai-core/src/browser/ai-core-frontend-module.ts +++ b/packages/ai-core/src/browser/ai-core-frontend-module.ts @@ -52,7 +52,7 @@ import { AIConfigurationContainerWidget } from './ai-configuration/ai-configurat import { AIVariableConfigurationWidget } from './ai-configuration/variable-configuration-widget'; import { AICoreFrontendApplicationContribution } from './ai-core-frontend-application-contribution'; import { bindAICorePreferences } from './ai-core-preferences'; -import { AISettingsService } from './ai-settings-service'; +import { AISettingsServiceImpl } from './ai-settings-service'; import { FrontendPromptCustomizationServiceImpl } from './frontend-prompt-customization-service'; import { FrontendVariableService } from './frontend-variable-service'; import { PromptTemplateContribution } from './prompttemplate-contribution'; @@ -63,6 +63,7 @@ import { AgentsVariableContribution } from '../common/agents-variable-contributi import { AIActivationService } from './ai-activation-service'; import { AgentService, AgentServiceImpl } from '../common/agent-service'; import { AICommandHandlerFactory } from './ai-command-handler-factory'; +import { AISettingsService } from '../common/settings-service'; export default new ContainerModule(bind => { bindContributionProvider(bind, LanguageModelProvider); @@ -112,7 +113,7 @@ export default new ContainerModule(bind => { .inSingletonScope(); bindViewContribution(bind, AIAgentConfigurationViewContribution); - bind(AISettingsService).toSelf().inRequestScope(); + bind(AISettingsService).to(AISettingsServiceImpl).inRequestScope(); bindContributionProvider(bind, AIVariableContribution); bind(FrontendVariableService).toSelf().inSingletonScope(); bind(AIVariableService).toService(FrontendVariableService); diff --git a/packages/ai-core/src/browser/ai-settings-service.ts b/packages/ai-core/src/browser/ai-settings-service.ts index 5ea34f158791b..f4c2ea5124688 100644 --- a/packages/ai-core/src/browser/ai-settings-service.ts +++ b/packages/ai-core/src/browser/ai-settings-service.ts @@ -15,42 +15,36 @@ // ***************************************************************************** import { DisposableCollection, Emitter, Event } from '@theia/core'; import { PreferenceScope, PreferenceService } from '@theia/core/lib/browser'; -import { JSONObject } from '@theia/core/shared/@phosphor/coreutils'; import { inject, injectable } from '@theia/core/shared/inversify'; -import { LanguageModelRequirement } from '../common'; +import { JSONObject } from '@theia/core/shared/@phosphor/coreutils'; +import { AISettings, AISettingsService, AgentSettings } from '../common'; @injectable() -export class AISettingsService { +export class AISettingsServiceImpl implements AISettingsService { @inject(PreferenceService) protected preferenceService: PreferenceService; - static readonly PREFERENCE_NAME = 'ai.settings'; + static readonly PREFERENCE_NAME = 'ai-features.agentSettings'; protected toDispose = new DisposableCollection(); protected readonly onDidChangeEmitter = new Emitter(); onDidChange: Event = this.onDidChangeEmitter.event; - updateAgentSettings(agent: string, agentSettings: AgentSettings): void { - const settings = this.getSettings(); - settings.agents[agent] = agentSettings; - this.preferenceService.set(AISettingsService.PREFERENCE_NAME, settings, PreferenceScope.User); + async updateAgentSettings(agent: string, agentSettings: Partial): Promise { + const settings = await this.getSettings(); + const newAgentSettings = { ...settings[agent], ...agentSettings }; + settings[agent] = newAgentSettings; + this.preferenceService.set(AISettingsServiceImpl.PREFERENCE_NAME, settings, PreferenceScope.User); this.onDidChangeEmitter.fire(); } - getAgentSettings(agent: string): AgentSettings | undefined { - const settings = this.getSettings(); - return settings.agents[agent]; + async getAgentSettings(agent: string): Promise { + const settings = await this.getSettings(); + return settings[agent]; } - getSettings(): AISettings { - const pref = this.preferenceService.inspect(AISettingsService.PREFERENCE_NAME); - return pref?.value ? pref.value : { agents: {} }; + async getSettings(): Promise { + await this.preferenceService.ready; + const pref = this.preferenceService.inspect(AISettingsServiceImpl.PREFERENCE_NAME); + return pref?.value ? pref.value : {}; } - -} -export interface AISettings extends JSONObject { - agents: Record -} - -interface AgentSettings extends JSONObject { - languageModelRequirements: LanguageModelRequirement[]; } diff --git a/packages/ai-core/src/browser/frontend-language-model-registry.ts b/packages/ai-core/src/browser/frontend-language-model-registry.ts index baecabaa0e716..045d066b60970 100644 --- a/packages/ai-core/src/browser/frontend-language-model-registry.ts +++ b/packages/ai-core/src/browser/frontend-language-model-registry.ts @@ -25,6 +25,7 @@ import { OutputChannelSeverity, } from '@theia/output/lib/browser/output-channel'; import { + AISettingsService, DefaultLanguageModelRegistryImpl, isLanguageModelParsedResponse, isLanguageModelStreamResponse, @@ -42,7 +43,6 @@ import { LanguageModelSelector, LanguageModelStreamResponsePart, } from '../common'; -import { AISettingsService } from './ai-settings-service'; @injectable() export class LanguageModelDelegateClientImpl @@ -288,7 +288,7 @@ export class FrontendLanguageModelRegistryImpl override async selectLanguageModels(request: LanguageModelSelector): Promise { await this.initialized; - const userSettings = this.settingsService.getAgentSettings(request.agent)?.languageModelRequirements.find(req => req.purpose === request.purpose); + const userSettings = (await this.settingsService.getAgentSettings(request.agent))?.languageModelRequirements.find(req => req.purpose === request.purpose); if (userSettings?.identifier) { const model = await this.getLanguageModel(userSettings.identifier); if (model) { diff --git a/packages/ai-core/src/common/agent-service.ts b/packages/ai-core/src/common/agent-service.ts index 8a44777749ba1..1038864bf81e3 100644 --- a/packages/ai-core/src/common/agent-service.ts +++ b/packages/ai-core/src/common/agent-service.ts @@ -13,9 +13,10 @@ // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { inject, injectable, named } from '@theia/core/shared/inversify'; +import { inject, injectable, named, optional, postConstruct } from '@theia/core/shared/inversify'; import { ContributionProvider } from '@theia/core'; import { Agent } from './agent'; +import { AISettingsService } from './settings-service'; export const AgentService = Symbol('AgentService'); @@ -55,10 +56,24 @@ export class AgentServiceImpl implements AgentService { @inject(ContributionProvider) @named(Agent) protected readonly agentsProvider: ContributionProvider; + @inject(AISettingsService) @optional() + protected readonly aiSettingsService: AISettingsService | undefined; + protected disabledAgents = new Set(); protected _agents: Agent[] = []; + @postConstruct() + protected init(): void { + this.aiSettingsService?.getSettings().then(settings => { + Object.entries(settings).forEach(([agentId, agentSettings]) => { + if (agentSettings.enable === false) { + this.disabledAgents.add(agentId); + } + }); + }); + } + private get agents(): Agent[] { // We can't collect the contributions at @postConstruct because this will lead to a circular dependency // with agents reusing the chat agent service (e.g. orchestrator) which in turn injects the agent service @@ -79,10 +94,12 @@ export class AgentServiceImpl implements AgentService { enableAgent(agentId: string): void { this.disabledAgents.delete(agentId); + this.aiSettingsService?.updateAgentSettings(agentId, { enable: true }); } disableAgent(agentId: string): void { this.disabledAgents.add(agentId); + this.aiSettingsService?.updateAgentSettings(agentId, { enable: false }); } isEnabled(agentId: string): boolean { diff --git a/packages/ai-core/src/common/index.ts b/packages/ai-core/src/common/index.ts index 9ebeec5a068ed..48070b59007c9 100644 --- a/packages/ai-core/src/common/index.ts +++ b/packages/ai-core/src/common/index.ts @@ -27,3 +27,4 @@ export * from './protocol'; export * from './today-variable-contribution'; export * from './tomorrow-variable-contribution'; export * from './variable-service'; +export * from './settings-service'; diff --git a/packages/ai-core/src/common/settings-service.ts b/packages/ai-core/src/common/settings-service.ts new file mode 100644 index 0000000000000..2c8f204b96643 --- /dev/null +++ b/packages/ai-core/src/common/settings-service.ts @@ -0,0 +1,33 @@ +// ***************************************************************************** +// Copyright (C) 2024 EclipseSource GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** +import { Event } from '@theia/core'; +import { LanguageModelRequirement } from './language-model'; + +export const AISettingsService = Symbol('AISettingsService'); +/** + * Service to store and retrieve settings on a per-agent basis. + */ +export interface AISettingsService { + updateAgentSettings(agent: string, agentSettings: Partial): Promise; + getAgentSettings(agent: string): Promise; + getSettings(): Promise; + onDidChange: Event; +} +export type AISettings = Record; +export interface AgentSettings { + languageModelRequirements: LanguageModelRequirement[]; + enable: boolean; +}