Skip to content

Commit

Permalink
fix: properly initialize custom agents (#14354)
Browse files Browse the repository at this point in the history
Prompt templates of newly added custom agents were not stored within
the PromptService. As a consequence their prompt was ignored when
invoked in Chat.

This issue is now fixed by restructuring the prompt storing mechanism.
Prompts are now implicitly stored and removed based on all currently
registered agents.
  • Loading branch information
sdirix authored Oct 28, 2024
1 parent c1e4279 commit a5f26a3
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface TemplateSettingProps {

export const TemplateRenderer: React.FC<TemplateSettingProps> = ({ agentId, template, promptCustomizationService }) => {
const openTemplate = React.useCallback(async () => {
promptCustomizationService.editTemplate(template.id);
promptCustomizationService.editTemplate(template.id, template.template);
}, [template, promptCustomizationService]);
const resetTemplate = React.useCallback(async () => {
promptCustomizationService.resetTemplate(template.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,22 @@
// *****************************************************************************

import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { inject, injectable } from '@theia/core/shared/inversify';
import { PromptService } from '../common';
import { inject, injectable, named } from '@theia/core/shared/inversify';
import { Agent } from '../common';
import { AgentService } from '../common/agent-service';
import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';

@injectable()
export class AICoreFrontendApplicationContribution implements FrontendApplicationContribution {
@inject(AgentService)
private readonly agentService: AgentService;

@inject(PromptService)
private readonly promptService: PromptService;
@inject(ContributionProvider) @named(Agent)
protected readonly agentsProvider: ContributionProvider<Agent>;

onStart(): void {
this.agentService.getAllAgents().forEach(a => {
a.promptTemplates.forEach(t => {
this.promptService.storePrompt(t.id, t.template);
});
this.agentsProvider.getContributions().forEach(agent => {
this.agentService.registerAgent(agent);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@
import { DisposableCollection, URI, Event, Emitter } from '@theia/core';
import { OpenerService } from '@theia/core/lib/browser';
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { PromptCustomizationService, PromptTemplate, CustomAgentDescription } from '../common';
import { PromptCustomizationService, CustomAgentDescription } from '../common';
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { FileChangesEvent } from '@theia/filesystem/lib/common/files';
import { AICorePreferences, PREFERENCE_NAME_PROMPT_TEMPLATES } from './ai-core-preferences';
import { AgentService } from '../common/agent-service';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { load, dump } from 'js-yaml';

Expand All @@ -49,9 +48,6 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
@inject(OpenerService)
protected readonly openerService: OpenerService;

@inject(AgentService)
protected readonly agentService: AgentService;

protected readonly trackedTemplateURIs = new Set<string>();
protected templates = new Map<string, string>();

Expand Down Expand Up @@ -168,17 +164,10 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
return this.templates.get(id);
}

async editTemplate(id: string, content?: string): Promise<void> {
const template = this.getOriginalTemplate(id);
if (template === undefined) {
throw new Error(`Unable to edit template ${id}: template not found.`);
}
async editTemplate(id: string, defaultContent?: string): Promise<void> {
const editorUri = await this.getTemplateURI(id);
if (! await this.fileService.exists(editorUri)) {
await this.fileService.createFile(editorUri, BinaryBuffer.fromString(content ?? template.template));
} else if (content) {
// Write content to the file before opening it
await this.fileService.writeFile(editorUri, BinaryBuffer.fromString(content));
await this.fileService.createFile(editorUri, BinaryBuffer.fromString(defaultContent ?? ''));
}
const openHandler = await this.openerService.getOpener(editorUri);
openHandler.open(editorUri);
Expand All @@ -191,17 +180,6 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati
}
}

getOriginalTemplate(id: string): PromptTemplate | undefined {
for (const agent of this.agentService.getAllAgents()) {
for (const template of agent.promptTemplates) {
if (template.id === id) {
return template;
}
}
}
return undefined;
}

getTemplateIDFromURI(uri: URI): string | undefined {
const id = this.removePromptTemplateSuffix(uri.path.name);
if (this.templates.has(id)) {
Expand Down
28 changes: 15 additions & 13 deletions packages/ai-core/src/common/agent-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { inject, injectable, named, optional, postConstruct } from '@theia/core/shared/inversify';
import { ContributionProvider, Emitter, Event } from '@theia/core';
import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
import { Emitter, Event } from '@theia/core';
import { Agent } from './agent';
import { AISettingsService } from './settings-service';
import { PromptService } from './prompt-service';

export const AgentService = Symbol('AgentService');

Expand Down Expand Up @@ -71,12 +72,12 @@ export interface AgentService {
@injectable()
export class AgentServiceImpl implements AgentService {

@inject(ContributionProvider) @named(Agent)
protected readonly agentsProvider: ContributionProvider<Agent>;

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

@inject(PromptService)
protected readonly promptService: PromptService;

protected disabledAgents = new Set<string>();

protected _agents: Agent[] = [];
Expand All @@ -95,28 +96,29 @@ export class AgentServiceImpl implements AgentService {
});
}

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
return [...this.agentsProvider.getContributions(), ...this._agents];
}

registerAgent(agent: Agent): void {
this._agents.push(agent);
agent.promptTemplates.forEach(
template => this.promptService.storePrompt(template.id, template.template)
);
this.onDidChangeAgentsEmitter.fire();
}

unregisterAgent(agentId: string): void {
const agent = this._agents.find(a => a.id === agentId);
this._agents = this._agents.filter(a => a.id !== agentId);
this.onDidChangeAgentsEmitter.fire();
agent?.promptTemplates.forEach(
template => this.promptService.removePrompt(template.id)
);
}

getAgents(): Agent[] {
return this.agents.filter(agent => this.isEnabled(agent.id));
return this._agents.filter(agent => this.isEnabled(agent.id));
}

getAllAgents(): Agent[] {
return this.agents;
return this._agents;
}

enableAgent(agentId: string): void {
Expand Down
14 changes: 11 additions & 3 deletions packages/ai-core/src/common/prompt-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ export interface PromptService {
*/
getPrompt(id: string, args?: { [key: string]: unknown }): Promise<ResolvedPromptTemplate | undefined>;
/**
* Manually add a prompt to the list of prompts.
* Adds a prompt to the list of prompts.
* @param id the id of the prompt
* @param prompt the prompt template to store
*/
storePrompt(id: string, prompt: string): void;
/**
* Removes a prompt from the list of prompts.
* @param id the id of the prompt
*/
removePrompt(id: string): void;
/**
* Return all known prompts as a {@link PromptMap map}.
*/
Expand Down Expand Up @@ -113,9 +118,9 @@ export interface PromptCustomizationService {
* on the implementation. Implementation may for example decide to
* open an editor, or request more information from the user, ...
* @param id the template id.
* @param content optional content to customize the template.
* @param content optional default content to initialize the template
*/
editTemplate(id: string, content?: string): void;
editTemplate(id: string, defaultContent?: string): void;

/**
* Reset the template to its default value.
Expand Down Expand Up @@ -250,4 +255,7 @@ export class PromptServiceImpl implements PromptService {
storePrompt(id: string, prompt: string): void {
this._prompts[id] = { id, template: prompt };
}
removePrompt(id: string): void {
delete this._prompts[id];
}
}

0 comments on commit a5f26a3

Please sign in to comment.