Skip to content

Commit

Permalink
Add enable state of agent to preferences (#14206)
Browse files Browse the repository at this point in the history
* Add enable state of agent to preferences

The selected model was already persisted in the preferences,
but the enablement state was not.
Here the settings were extended to include the enablement state.
The new setting is consumed and updated from the AgentService.

Fixes #14205

* chore: align with ai-features preferences
  • Loading branch information
eneufeld authored Sep 20, 2024
1 parent 3a339a7 commit 31c776e
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -29,8 +29,8 @@ export interface LanguageModelSettingsProps {
export const LanguageModelRenderer: React.FC<LanguageModelSettingsProps> = (
{ agent, languageModels, aiSettingsService, languageModelRegistry }) => {

const findLanguageModelRequirement = (purpose: string): LanguageModelRequirement | undefined => {
const requirementSetting = aiSettingsService.getAgentSettings(agent.id);
const findLanguageModelRequirement = async (purpose: string): Promise<LanguageModelRequirement | undefined> => {
const requirementSetting = await aiSettingsService.getAgentSettings(agent.id);
return requirementSetting?.languageModelRequirements.find(e => e.purpose === purpose);
};

Expand All @@ -41,7 +41,7 @@ export const LanguageModelRenderer: React.FC<LanguageModelSettingsProps> = (
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 });
Expand Down
5 changes: 3 additions & 2 deletions packages/ai-core/src/browser/ai-core-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
38 changes: 16 additions & 22 deletions packages/ai-core/src/browser/ai-settings-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>();
onDidChange: Event<void> = 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<AgentSettings>): Promise<void> {
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<AgentSettings | undefined> {
const settings = await this.getSettings();
return settings[agent];
}

getSettings(): AISettings {
const pref = this.preferenceService.inspect<AISettings>(AISettingsService.PREFERENCE_NAME);
return pref?.value ? pref.value : { agents: {} };
async getSettings(): Promise<AISettings> {
await this.preferenceService.ready;
const pref = this.preferenceService.inspect<AISettings & JSONObject>(AISettingsServiceImpl.PREFERENCE_NAME);
return pref?.value ? pref.value : {};
}

}
export interface AISettings extends JSONObject {
agents: Record<string, AgentSettings>
}

interface AgentSettings extends JSONObject {
languageModelRequirements: LanguageModelRequirement[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
OutputChannelSeverity,
} from '@theia/output/lib/browser/output-channel';
import {
AISettingsService,
DefaultLanguageModelRegistryImpl,
isLanguageModelParsedResponse,
isLanguageModelStreamResponse,
Expand All @@ -42,7 +43,6 @@ import {
LanguageModelSelector,
LanguageModelStreamResponsePart,
} from '../common';
import { AISettingsService } from './ai-settings-service';

@injectable()
export class LanguageModelDelegateClientImpl
Expand Down Expand Up @@ -288,7 +288,7 @@ export class FrontendLanguageModelRegistryImpl

override async selectLanguageModels(request: LanguageModelSelector): Promise<LanguageModel[]> {
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) {
Expand Down
19 changes: 18 additions & 1 deletion packages/ai-core/src/common/agent-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -55,10 +56,24 @@ export class AgentServiceImpl implements AgentService {
@inject(ContributionProvider) @named(Agent)
protected readonly agentsProvider: ContributionProvider<Agent>;

@inject(AISettingsService) @optional()
protected readonly aiSettingsService: AISettingsService | undefined;

protected disabledAgents = new Set<string>();

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
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions packages/ai-core/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
33 changes: 33 additions & 0 deletions packages/ai-core/src/common/settings-service.ts
Original file line number Diff line number Diff line change
@@ -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<AgentSettings>): Promise<void>;
getAgentSettings(agent: string): Promise<AgentSettings | undefined>;
getSettings(): Promise<AISettings>;
onDidChange: Event<void>;
}
export type AISettings = Record<string, AgentSettings>;
export interface AgentSettings {
languageModelRequirements: LanguageModelRequirement[];
enable: boolean;
}

0 comments on commit 31c776e

Please sign in to comment.