diff --git a/shell/AIShell.Abstraction/ILLMAgent.cs b/shell/AIShell.Abstraction/ILLMAgent.cs index a4e3fb2f..1119db31 100644 --- a/shell/AIShell.Abstraction/ILLMAgent.cs +++ b/shell/AIShell.Abstraction/ILLMAgent.cs @@ -138,7 +138,7 @@ public interface ILLMAgent : IDisposable /// /// Refresh the current chat or force starting a new chat session. - /// This method allows an agent to reset chat states, interact with user for authentication, print welcome message, and more. + /// This method allows an agent to reset chat states, refresh token or settings, interact with user for authentication, print welcome message, and more. /// /// The interface for interacting with the shell. /// Whether or not to force creating a new chat session. diff --git a/shell/AIShell.Kernel/LLMAgent.cs b/shell/AIShell.Kernel/LLMAgent.cs index 20df609f..1b653d76 100644 --- a/shell/AIShell.Kernel/LLMAgent.cs +++ b/shell/AIShell.Kernel/LLMAgent.cs @@ -13,7 +13,7 @@ internal LLMAgent(ILLMAgent agent, AgentAssemblyLoadContext loadContext) { Impl = agent; LoadContext = loadContext; - Prompt = agent.Name; + Prompt = $"@{agent.Name}"; } internal void Display(Host host, string description = null) diff --git a/shell/AIShell.Kernel/Shell.cs b/shell/AIShell.Kernel/Shell.cs index dd99b309..c646d2e5 100644 --- a/shell/AIShell.Kernel/Shell.cs +++ b/shell/AIShell.Kernel/Shell.cs @@ -285,7 +285,7 @@ private void LoadAvailableAgents() chosenAgent ??= _agents.Count is 1 ? _agents[0] : Host.PromptForSelectionAsync( - title: "[orange1]Please select an [Blue]agent[/] to use[/]:", + title: "[orange1]Please select an [Blue]agent[/] to use[/]:\n[grey](You can switch to another agent later by typing [Blue]@[/])[/]", choices: _agents, converter: static a => a.Impl.Name) .GetAwaiter().GetResult(); @@ -538,6 +538,18 @@ private async Task ReadUserInput(string prompt, CancellationToken cancel return input; } + /// + /// Give an agent the opportunity to refresh its chat session, in the unforced way. + /// + private async Task RefreshChatAsNeeded(LLMAgent agent) + { + if (_shouldRefresh) + { + _shouldRefresh = false; + await agent?.Impl.RefreshChatAsync(this, force: false); + } + } + /// /// Run a chat REPL. /// @@ -552,11 +564,7 @@ internal async Task RunREPLAsync() try { - if (_shouldRefresh) - { - _shouldRefresh = false; - await agent?.Impl.RefreshChatAsync(this, force: false); - } + await RefreshChatAsNeeded(agent); if (Regenerate) { @@ -595,6 +603,12 @@ internal async Task RunREPLAsync() { continue; } + else + { + // We may be switching to an agent that hasn't setup its chat session yet. + // So, give it a chance to do so before calling its 'Chat' method. + await RefreshChatAsNeeded(agent); + } } string copiedText = await GetClipboardContent(input); diff --git a/shell/agents/AIShell.Interpreter.Agent/Agent.cs b/shell/agents/AIShell.Interpreter.Agent/Agent.cs index 62b0da85..1a3c6ee3 100644 --- a/shell/agents/AIShell.Interpreter.Agent/Agent.cs +++ b/shell/agents/AIShell.Interpreter.Agent/Agent.cs @@ -74,12 +74,15 @@ public void Initialize(AgentConfig config) /// public Task RefreshChatAsync(IShell shell, bool force) { - // Reload the setting file if needed. - ReloadSettings(); - // Reset the history so the subsequent chat can start fresh. - _chatService.RefreshChat(); - // Shut down the execution service to start fresh. - _executionService.Terminate(); + if (force) + { + // Reload the setting file if needed. + ReloadSettings(); + // Reset the history so the subsequent chat can start fresh. + _chatService.RefreshChat(); + // Shut down the execution service to start fresh. + _executionService.Terminate(); + } return Task.CompletedTask; } diff --git a/shell/agents/AIShell.OpenAI.Agent/Agent.cs b/shell/agents/AIShell.OpenAI.Agent/Agent.cs index 4da54ccc..16a86d4e 100644 --- a/shell/agents/AIShell.OpenAI.Agent/Agent.cs +++ b/shell/agents/AIShell.OpenAI.Agent/Agent.cs @@ -79,10 +79,13 @@ public void OnUserAction(UserActionPayload actionPayload) {} /// public Task RefreshChatAsync(IShell shell, bool force) { - // Reload the setting file if needed. - ReloadSettings(); - // Reset the history so the subsequent chat can start fresh. - _chatService.ChatHistory.Clear(); + if (force) + { + // Reload the setting file if needed. + ReloadSettings(); + // Reset the history so the subsequent chat can start fresh. + _chatService.ChatHistory.Clear(); + } return Task.CompletedTask; } diff --git a/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs b/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs index 034e84df..2236b95b 100644 --- a/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs +++ b/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs @@ -77,7 +77,7 @@ public AzureAgent() public void Dispose() { - ArgPlaceholder?.DataRetriever?.Dispose(); + ResetArgumentPlaceholder(); _chatSession.Dispose(); _httpClient.Dispose(); @@ -120,6 +120,7 @@ public async Task RefreshChatAsync(IShell shell, bool force) { IHost host = shell.Host; CancellationToken cancellationToken = shell.CancellationToken; + ResetArgumentPlaceholder(); try { @@ -179,8 +180,7 @@ public async Task ChatAsync(string input, IShell shell) if (_copilotResponse.ChunkReader is null) { - ArgPlaceholder?.DataRetriever?.Dispose(); - ArgPlaceholder = null; + ResetArgumentPlaceholder(); // Process CLI handler response specially to support parameter injection. ResponseData data = null; @@ -360,6 +360,12 @@ private ResponseData ParseCLIHandlerResponse(IShell shell) return data; } + internal void ResetArgumentPlaceholder() + { + ArgPlaceholder?.DataRetriever?.Dispose(); + ArgPlaceholder = null; + } + internal void SaveUserValue(string phName, string value) { ArgumentException.ThrowIfNullOrEmpty(phName); @@ -465,7 +471,9 @@ internal string GenerateAnswer(ResponseData data) _buffer.Append($"- `{phItem.Name}`: {phItem.Desc}\n"); } - _buffer.Append("\nRun `/replace` to get assistance in placeholder replacement.\n"); + // Use green (0,195,0) on grey (48,48,48) for rendering the command '/replace'. + // TODO: the color formatting should be exposed by the shell as utility method. + _buffer.Append("\nRun \x1b[38;2;0;195;0;48;2;48;48;48m /replace \x1b[0m to get assistance in placeholder replacement.\n"); } } diff --git a/shell/agents/Microsoft.Azure.Agent/ChatSession.cs b/shell/agents/Microsoft.Azure.Agent/ChatSession.cs index 17a60723..888fd55a 100644 --- a/shell/agents/Microsoft.Azure.Agent/ChatSession.cs +++ b/shell/agents/Microsoft.Azure.Agent/ChatSession.cs @@ -70,7 +70,7 @@ internal async Task RefreshAsync(IStatusContext context, bool force, Can if (force) { // End the existing conversation. - context.Status("End current chat ..."); + context.Status("Ending current chat ..."); EndConversation(); Reset(); } @@ -78,7 +78,7 @@ internal async Task RefreshAsync(IStatusContext context, bool force, Can { try { - context.Status("Refresh DirectLine token ..."); + context.Status("Refreshing token ..."); await RenewTokenAsync(cancellationToken); return null; } diff --git a/shell/agents/Microsoft.Azure.Agent/Command.cs b/shell/agents/Microsoft.Azure.Agent/Command.cs index f79800f2..e664417a 100644 --- a/shell/agents/Microsoft.Azure.Agent/Command.cs +++ b/shell/agents/Microsoft.Azure.Agent/Command.cs @@ -238,8 +238,7 @@ private async Task RegenerateAsync() if (data.PlaceholderSet is null) { - _agent.ArgPlaceholder.DataRetriever.Dispose(); - _agent.ArgPlaceholder = null; + _agent.ResetArgumentPlaceholder(); } return _agent.GenerateAnswer(data);