From ff221b8c6909241748da301f938178c10a0e76ad Mon Sep 17 00:00:00 2001 From: Eugen Neufeld Date: Mon, 16 Sep 2024 17:01:37 +0200 Subject: [PATCH 1/2] feat: show variables and function on ai agent configuration The agent configuration now shows also the variables (global and agent specific) as well as the functions that the agents uses. If a variable or function is used in the prompt but is not declared by the agent then this is also marked in the view. All agents are updated to declare the variables and functions they use. fixes #14133 --- .../ai-chat/src/common/command-chat-agents.ts | 11 +- .../src/common/orchestrator-chat-agent.ts | 9 +- .../src/common/universal-chat-agent.ts | 5 + .../src/common/code-completion-agent.ts | 13 +- .../agent-configuration-widget.tsx | 160 ++++++++++++++++-- .../frontend-prompt-customization-service.ts | 7 +- packages/ai-core/src/browser/style/index.css | 6 +- packages/ai-core/src/common/agent.ts | 14 +- packages/ai-core/src/common/prompt-service.ts | 7 +- .../src/browser/ai-terminal-agent.ts | 7 + .../src/browser/workspace-agent.ts | 7 +- 11 files changed, 223 insertions(+), 23 deletions(-) diff --git a/packages/ai-chat/src/common/command-chat-agents.ts b/packages/ai-chat/src/common/command-chat-agents.ts index d291ef17eca1c..7ab6608f049de 100644 --- a/packages/ai-chat/src/common/command-chat-agents.ts +++ b/packages/ai-chat/src/common/command-chat-agents.ts @@ -18,6 +18,7 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import { AbstractTextToModelParsingChatAgent, ChatAgent, SystemMessageDescription } from './chat-agents'; import { PromptTemplate, + AgentSpecificVariables } from '@theia/ai-core'; import { ChatRequestModelImpl, @@ -252,11 +253,13 @@ export class CommandChatAgent extends AbstractTextToModelParsingChatAgent { diff --git a/packages/ai-chat/src/common/orchestrator-chat-agent.ts b/packages/ai-chat/src/common/orchestrator-chat-agent.ts index 139bdf9535cd4..9beec41a5cc30 100644 --- a/packages/ai-chat/src/common/orchestrator-chat-agent.ts +++ b/packages/ai-chat/src/common/orchestrator-chat-agent.ts @@ -14,7 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { getJsonOfResponse, LanguageModelResponse } from '@theia/ai-core'; +import { AgentSpecificVariables, getJsonOfResponse, LanguageModelResponse } from '@theia/ai-core'; import { PromptTemplate } from '@theia/ai-core/lib/common'; @@ -64,9 +64,12 @@ export const OrchestratorChatAgentId = 'Orchestrator'; export class OrchestratorChatAgent extends AbstractStreamParsingChatAgent implements ChatAgent { name: string; description: string; - variables: string[]; + readonly variables: string[]; promptTemplates: PromptTemplate[]; fallBackChatAgentId: string; + readonly functions: string[] = []; + readonly agentSpecificVariables: AgentSpecificVariables[] = []; + constructor() { super(OrchestratorChatAgentId, [{ purpose: 'agent-selection', @@ -78,6 +81,8 @@ export class OrchestratorChatAgent extends AbstractStreamParsingChatAgent implem this.variables = ['chatAgents']; this.promptTemplates = [orchestratorTemplate]; this.fallBackChatAgentId = 'Universal'; + this.functions = []; + this.agentSpecificVariables = []; } @inject(ChatAgentService) diff --git a/packages/ai-chat/src/common/universal-chat-agent.ts b/packages/ai-chat/src/common/universal-chat-agent.ts index 32706aedf802b..ab8838d7578e6 100644 --- a/packages/ai-chat/src/common/universal-chat-agent.ts +++ b/packages/ai-chat/src/common/universal-chat-agent.ts @@ -14,6 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** +import { AgentSpecificVariables } from '@theia/ai-core'; import { PromptTemplate } from '@theia/ai-core/lib/common'; @@ -81,6 +82,8 @@ export class UniversalChatAgent extends AbstractStreamParsingChatAgent implement description: string; variables: string[]; promptTemplates: PromptTemplate[]; + readonly functions: string[]; + readonly agentSpecificVariables: AgentSpecificVariables[]; constructor() { super('Universal', [{ @@ -94,6 +97,8 @@ export class UniversalChatAgent extends AbstractStreamParsingChatAgent implement + 'access the current user context or the workspace.'; this.variables = []; this.promptTemplates = [universalTemplate]; + this.functions = []; + this.agentSpecificVariables = []; } protected override async getSystemMessageDescription(): Promise { diff --git a/packages/ai-code-completion/src/common/code-completion-agent.ts b/packages/ai-code-completion/src/common/code-completion-agent.ts index 1487f37b914f5..15724190118f0 100644 --- a/packages/ai-code-completion/src/common/code-completion-agent.ts +++ b/packages/ai-code-completion/src/common/code-completion-agent.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { - Agent, CommunicationHistoryEntry, CommunicationRecordingService, getTextOfResponse, + Agent, AgentSpecificVariables, CommunicationHistoryEntry, CommunicationRecordingService, getTextOfResponse, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequirement, PromptService, PromptTemplate } from '@theia/ai-core/lib/common'; import { generateUuid, ILogger } from '@theia/core'; @@ -116,8 +116,6 @@ export class CodeCompletionAgentImpl implements CodeCompletionAgent { enableForwardStability: true, }; } - tags?: String[] | undefined; - variables: string[] = []; @inject(ILogger) @named('code-completion-agent') @@ -154,4 +152,13 @@ Only return the exact replacement for [[MARKER]] to complete the snippet.`, identifier: 'openai/gpt-4o', }, ]; + readonly variables: string[] = []; + readonly functions: string[] = []; + readonly agentSpecificVariables: AgentSpecificVariables[] = [ + { name: 'file', usedInPrompt: true, description: 'The uri of the file being edited.' }, + { name: 'language', usedInPrompt: true, description: 'The languageId of the file being edited.' }, + { name: 'textUntilCurrentPosition', usedInPrompt: true, description: 'The code before the current position of the cursor.' }, + { name: 'textAfterCurrentPosition', usedInPrompt: true, description: 'The code after the current position of the cursor.' } + ]; + readonly tags?: String[] | undefined; } 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 9dc5640a37adf..8d5b06c5ccc41 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 @@ -17,7 +17,16 @@ import { codicon, ReactWidget } from '@theia/core/lib/browser'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import * as React from '@theia/core/shared/react'; -import { Agent, LanguageModel, LanguageModelRegistry, PromptCustomizationService } from '../../common'; +import { + Agent, + AIVariableService, + LanguageModel, + LanguageModelRegistry, + PROMPT_FUNCTION_REGEX, + PROMPT_VARIABLE_REGEX, + PromptCustomizationService, + PromptService, +} from '../../common'; import { AISettingsService } from '../ai-settings-service'; import { LanguageModelRenderer } from './language-model-renderer'; import { TemplateRenderer } from './template-settings-renderer'; @@ -25,6 +34,12 @@ import { AIConfigurationSelectionService } from './ai-configuration-service'; import { AIVariableConfigurationWidget } from './variable-configuration-widget'; import { AgentService } from '../../common/agent-service'; +interface ParsedPrompt { + functions: string[]; + globalVariables: string[]; + agentSpecificVariables: string[]; +}; + @injectable() export class AIAgentConfigurationWidget extends ReactWidget { @@ -46,6 +61,12 @@ export class AIAgentConfigurationWidget extends ReactWidget { @inject(AIConfigurationSelectionService) protected readonly aiConfigurationSelectionService: AIConfigurationSelectionService; + @inject(AIVariableService) + protected readonly variableService: AIVariableService; + + @inject(PromptService) + protected promptService: PromptService; + protected languageModels: LanguageModel[] | undefined; @postConstruct() @@ -62,6 +83,7 @@ export class AIAgentConfigurationWidget extends ReactWidget { this.languageModels = models; this.update(); })); + this.toDispose.push(this.promptCustomizationService.onDidChangePrompt(() => this.update())); this.aiSettingsService.onDidChange(() => this.update()); this.aiConfigurationSelectionService.onDidAgentChange(() => this.update()); @@ -98,6 +120,10 @@ export class AIAgentConfigurationWidget extends ReactWidget { const enabled = this.agentService.isEnabled(agent.id); + const parsedPromptParts = this.parsePromptTemplatesForVariableAndFunction(agent); + const globalVariables = Array.from(new Set([...parsedPromptParts.globalVariables, ...agent.variables])); + const functions = Array.from(new Set([...parsedPromptParts.functions, ...agent.functions])); + return
{this.renderAgentName(agent)}
{agent.description}
@@ -107,16 +133,6 @@ export class AIAgentConfigurationWidget extends ReactWidget { Enable Agent
-
- Variables: -
    - {agent.variables.map(variableId =>
  • -
    { this.showVariableConfigurationTab(); }} className='variable-reference'> - {variableId} - -
  • )} -
-
{agent.promptTemplates?.map(template =>
+
+ Used Global Variables: +
    + +
+
+
+ Used agent-specific Variables: +
    + +
+
+
+ Used Functions: +
    + +
+
; } + private parsePromptTemplatesForVariableAndFunction(agent: Agent): ParsedPrompt { + const promptTemplates = agent.promptTemplates; + const result: ParsedPrompt = { functions: [], globalVariables: [], agentSpecificVariables: [] }; + promptTemplates.forEach(template => { + const storedPrompt = this.promptService.getRawPrompt(template.id); + const prompt = storedPrompt?.template ?? template.template; + const variableMatches = [...prompt.matchAll(PROMPT_VARIABLE_REGEX)]; + + variableMatches.forEach(match => { + const variableId = match[1]; + // if the variable is part of the variable service and not part of the agent specific variables then it is a global variable + if (this.variableService.hasVariable(variableId) && + agent.agentSpecificVariables.find(v => v.name === variableId) === undefined) { + result.globalVariables.push(variableId); + } else { + result.agentSpecificVariables.push(variableId); + } + }); + + const functionMatches = [...prompt.matchAll(PROMPT_FUNCTION_REGEX)]; + functionMatches.forEach(match => { + const functionId = match[1]; + result.functions.push(functionId); + }); + + }); + return result; + } + protected showVariableConfigurationTab(): void { this.aiConfigurationSelectionService.selectConfigurationTab(AIVariableConfigurationWidget.ID); } @@ -159,3 +225,75 @@ export class AIAgentConfigurationWidget extends ReactWidget { }; } +interface AgentGlobalVariablesProps { + variables: string[]; + showVariableConfigurationTab: () => void; +} +const AgentGlobalVariables = ({ variables: globalVariables, showVariableConfigurationTab }: AgentGlobalVariablesProps) => { + if (globalVariables.length === 0) { + return <>None; + } + return <> + {globalVariables.map(variableId =>
  • +
    { showVariableConfigurationTab(); }} className='variable-reference'> + {variableId} + +
  • )} + + ; +}; + +interface AgentFunctionsProps { + functions: string[]; +} +const AgentFunctions = ({ functions }: AgentFunctionsProps) => { + if (functions.length === 0) { + return <>None; + } + return <> + {functions.map(functionId =>
  • + {functionId} +
  • )} + ; +}; + +interface AgentSpecificVariablesProps { + promptVariables: string[]; + agent: Agent; +} +const AgentSpecificVariables = ({ promptVariables, agent }: AgentSpecificVariablesProps) => { + const agentDefinedVariablesName = agent.agentSpecificVariables.map(v => v.name); + const variables = Array.from(new Set([...promptVariables, ...agentDefinedVariablesName])); + if (variables.length === 0) { + return <>None; + } + return <> + {variables.map(variableId => + + + )} + ; +}; +interface AgentSpecifcVariableProps { + variableId: string; + agent: Agent; + promptVariables: string[]; +} +const AgentSpecifcVariable = ({ variableId, agent, promptVariables }: AgentSpecifcVariableProps) => { + const agentDefinedVariable = agent.agentSpecificVariables.find(v => v.name === variableId); + const undeclared = agentDefinedVariable === undefined; + const notUsed = !promptVariables.includes(variableId) && agentDefinedVariable?.usedInPrompt === true; + return
  • +
    Name: {variableId}
    + {undeclared ?
    Undeclared
    : + (<> +
    Description: {agentDefinedVariable.description}
    + {notUsed &&
    Not used in prompt
    } + )} +
    +
  • ; +}; diff --git a/packages/ai-core/src/browser/frontend-prompt-customization-service.ts b/packages/ai-core/src/browser/frontend-prompt-customization-service.ts index 2f3d5525f2bca..3e9f182b336ab 100644 --- a/packages/ai-core/src/browser/frontend-prompt-customization-service.ts +++ b/packages/ai-core/src/browser/frontend-prompt-customization-service.ts @@ -14,7 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { DisposableCollection, URI } from '@theia/core'; +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 } from '../common'; @@ -48,6 +48,9 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati protected toDispose = new DisposableCollection(); + private readonly onDidChangePromptEmitter = new Emitter(); + readonly onDidChangePrompt: Event = this.onDidChangePromptEmitter.event; + @postConstruct() protected init(): void { this.preferences.onPreferenceChanged(event => { @@ -85,6 +88,8 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati _templates.set(this.removePromptTemplateSuffix(updatedFile.resource.path.name), filecontent.value); } } + const id = this.removePromptTemplateSuffix(new URI(child).path.name); + this.onDidChangePromptEmitter.fire(id); } } diff --git a/packages/ai-core/src/browser/style/index.css b/packages/ai-core/src/browser/style/index.css index b17cac722d42c..b325058b20541 100644 --- a/packages/ai-core/src/browser/style/index.css +++ b/packages/ai-core/src/browser/style/index.css @@ -66,14 +66,16 @@ } #ai-variable-configuration-container-widget .variable-references, -#ai-agent-configuration-container-widget .variable-references { +#ai-agent-configuration-container-widget .variable-references, +#ai-agent-configuration-container-widget .function-references { margin-left: 0.5rem; padding: 0.5rem; border-left: solid 1px var(--theia-tree-indentGuidesStroke); } #ai-variable-configuration-container-widget .variable-reference, -#ai-agent-configuration-container-widget .variable-reference { +#ai-agent-configuration-container-widget .variable-reference, +#ai-agent-configuration-container-widget .function-reference { display: flex; flex-direction: row; align-items: center; diff --git a/packages/ai-core/src/common/agent.ts b/packages/ai-core/src/common/agent.ts index 652eea799142e..bd37c46f72cd8 100644 --- a/packages/ai-core/src/common/agent.ts +++ b/packages/ai-core/src/common/agent.ts @@ -17,6 +17,12 @@ import { LanguageModelRequirement } from './language-model'; import { PromptTemplate } from './prompt-service'; +export interface AgentSpecificVariables { + name: string; + description: string; + usedInPrompt: boolean; +} + export const Agent = Symbol('Agent'); /** * Agents represent the main functionality of the AI system. They are responsible for processing user input, collecting information from the environment, @@ -44,7 +50,7 @@ export interface Agent { /** A markdown description of its functionality and its privacy-relevant requirements, including function call handlers that access some data autonomously. */ readonly description: string; - /** The list of variable identifiers this agent needs to clarify its context requirements. See #39. */ + /** The list of global variable identifiers this agent needs to clarify its context requirements. See #39. */ readonly variables: string[]; /** The prompt templates introduced and used by this agent. */ @@ -55,4 +61,10 @@ export interface Agent { /** A list of tags to filter agents and to display capabilities in the UI */ readonly tags?: String[]; + + /** The list of local variable identifiers this agent needs to clarify its context requirements. */ + readonly agentSpecificVariables: AgentSpecificVariables[]; + + /** The list of global function identifiers this agent needs to clarify its context requirements. */ + readonly functions: string[]; } diff --git a/packages/ai-core/src/common/prompt-service.ts b/packages/ai-core/src/common/prompt-service.ts index 119373f0402a7..8bc55bedebd8b 100644 --- a/packages/ai-core/src/common/prompt-service.ts +++ b/packages/ai-core/src/common/prompt-service.ts @@ -14,7 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { URI } from '@theia/core'; +import { URI, Event } from '@theia/core'; import { inject, injectable, optional } from '@theia/core/shared/inversify'; import { AIVariableService } from './variable-service'; import { ToolInvocationRegistry } from './tool-invocation-registry'; @@ -104,6 +104,11 @@ export interface PromptCustomizationService { * @param uri the uri of the template file */ getTemplateIDFromURI(uri: URI): string | undefined; + + /** + * Event which is fired when the prompt template is changed. + */ + readonly onDidChangePrompt: Event; } @injectable() diff --git a/packages/ai-terminal/src/browser/ai-terminal-agent.ts b/packages/ai-terminal/src/browser/ai-terminal-agent.ts index fdc911c50a3ed..f68f73eeb3424 100644 --- a/packages/ai-terminal/src/browser/ai-terminal-agent.ts +++ b/packages/ai-terminal/src/browser/ai-terminal-agent.ts @@ -40,6 +40,13 @@ export class AiTerminalAgent implements Agent { Based on the user\'s request, it suggests commands and allows the user to directly paste and execute them in the terminal. \ It accesses the current directory, environment and the recent terminal output of the terminal session to provide context-aware assistance'; variables = []; + functions = []; + agentSpecificVariables = [ + { name: 'userRequest', usedInPrompt: true, description: 'The user\'s question or request.' }, + { name: 'shell', usedInPrompt: true, description: 'The shell being used, e.g., /usr/bin/zsh.' }, + { name: 'cwd', usedInPrompt: true, description: 'The current working directory.' }, + { name: 'recentTerminalContents', usedInPrompt: true, description: 'The last 0 to 50 recent lines visible in the terminal.' } + ]; promptTemplates = [ { id: 'terminal-system', diff --git a/packages/ai-workspace-agent/src/browser/workspace-agent.ts b/packages/ai-workspace-agent/src/browser/workspace-agent.ts index 2fd47fbd260e1..3d05487a16d9a 100644 --- a/packages/ai-workspace-agent/src/browser/workspace-agent.ts +++ b/packages/ai-workspace-agent/src/browser/workspace-agent.ts @@ -14,9 +14,10 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { AbstractStreamParsingChatAgent, ChatAgent, SystemMessageDescription } from '@theia/ai-chat/lib/common'; -import { PromptTemplate, ToolInvocationRegistry } from '@theia/ai-core'; +import { AgentSpecificVariables, PromptTemplate, ToolInvocationRegistry } from '@theia/ai-core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { workspaceTemplate } from '../common/template'; +import { FILE_CONTENT_FUNCTION_ID, GET_WORKSPACE_FILE_LIST_FUNCTION_ID } from '../common/functions'; @injectable() export class WorkspaceAgent extends AbstractStreamParsingChatAgent implements ChatAgent { @@ -24,6 +25,8 @@ export class WorkspaceAgent extends AbstractStreamParsingChatAgent implements Ch description: string; promptTemplates: PromptTemplate[]; variables: never[]; + readonly agentSpecificVariables: AgentSpecificVariables[]; + readonly functions: string[]; @inject(ToolInvocationRegistry) protected toolInvocationRegistry: ToolInvocationRegistry; @@ -39,6 +42,8 @@ export class WorkspaceAgent extends AbstractStreamParsingChatAgent implements Ch where to put source code, where to find specific code or configurations, etc.'; this.promptTemplates = [workspaceTemplate]; this.variables = []; + this.agentSpecificVariables = []; + this.functions = [GET_WORKSPACE_FILE_LIST_FUNCTION_ID, FILE_CONTENT_FUNCTION_ID]; } protected override async getSystemMessageDescription(): Promise { From 7830297ca47567ae90d8c026246343b26dff8ab7 Mon Sep 17 00:00:00 2001 From: Eugen Neufeld Date: Fri, 20 Sep 2024 10:05:49 +0200 Subject: [PATCH 2/2] fix missing emit on propmt template folder switch --- .../frontend-prompt-customization-service.ts | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/ai-core/src/browser/frontend-prompt-customization-service.ts b/packages/ai-core/src/browser/frontend-prompt-customization-service.ts index 3e9f182b336ab..aff47785cb315 100644 --- a/packages/ai-core/src/browser/frontend-prompt-customization-service.ts +++ b/packages/ai-core/src/browser/frontend-prompt-customization-service.ts @@ -72,33 +72,32 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati this.toDispose.push(this.fileService.watch(templateURI, { recursive: true, excludes: [] })); this.toDispose.push(this.fileService.onDidFilesChange(async (event: FileChangesEvent) => { - - for (const child of this.trackedTemplateURIs) { - // check deletion and updates - if (event.contains(new URI(child))) { - for (const deletedFile of event.getDeleted()) { - if (this.trackedTemplateURIs.has(deletedFile.resource.toString())) { - this.trackedTemplateURIs.delete(deletedFile.resource.toString()); - _templates.delete(deletedFile.resource.path.name); - } - } - for (const updatedFile of event.getUpdated()) { - if (this.trackedTemplateURIs.has(updatedFile.resource.toString())) { - const filecontent = await this.fileService.read(updatedFile.resource); - _templates.set(this.removePromptTemplateSuffix(updatedFile.resource.path.name), filecontent.value); - } - } - const id = this.removePromptTemplateSuffix(new URI(child).path.name); - this.onDidChangePromptEmitter.fire(id); + // check deleted templates + for (const deletedFile of event.getDeleted()) { + if (this.trackedTemplateURIs.has(deletedFile.resource.toString())) { + this.trackedTemplateURIs.delete(deletedFile.resource.toString()); + const templateId = this.removePromptTemplateSuffix(deletedFile.resource.path.name); + _templates.delete(templateId); + this.onDidChangePromptEmitter.fire(templateId); + } + } + // check updated templates + for (const updatedFile of event.getUpdated()) { + if (this.trackedTemplateURIs.has(updatedFile.resource.toString())) { + const filecontent = await this.fileService.read(updatedFile.resource); + const templateId = this.removePromptTemplateSuffix(updatedFile.resource.path.name); + _templates.set(templateId, filecontent.value); + this.onDidChangePromptEmitter.fire(templateId); } } - // check new templates for (const addedFile of event.getAdded()) { if (addedFile.resource.parent.toString() === templateURI.toString() && addedFile.resource.path.ext === '.prompttemplate') { this.trackedTemplateURIs.add(addedFile.resource.toString()); const filecontent = await this.fileService.read(addedFile.resource); - _templates.set(this.removePromptTemplateSuffix(addedFile.resource.path.name), filecontent.value); + const templateId = this.removePromptTemplateSuffix(addedFile.resource.path.name); + _templates.set(templateId, filecontent.value); + this.onDidChangePromptEmitter.fire(templateId); } } @@ -117,7 +116,9 @@ export class FrontendPromptCustomizationServiceImpl implements PromptCustomizati if (fileURI.path.ext === '.prompttemplate') { this.trackedTemplateURIs.add(fileURI.toString()); const filecontent = await this.fileService.read(fileURI); - _templates.set(this.removePromptTemplateSuffix(file.name), filecontent.value); + const templateId = this.removePromptTemplateSuffix(file.name); + _templates.set(templateId, filecontent.value); + this.onDidChangePromptEmitter.fire(templateId); } } }