diff --git a/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs b/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs index c46c22f0..043670d5 100644 --- a/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs +++ b/shell/agents/Microsoft.Azure.Agent/AzureAgent.cs @@ -335,7 +335,10 @@ internal void ReplaceKnownPlaceholders(ResponseData data) { string script = command.Script; command.Script = script.Replace(entry.Key, entry.Value, StringComparison.OrdinalIgnoreCase); - command.Updated = !ReferenceEquals(script, command.Script); + if (!ReferenceEquals(script, command.Script)) + { + command.Updated = true; + } } } diff --git a/shell/agents/Microsoft.Azure.Agent/ChatSession.cs b/shell/agents/Microsoft.Azure.Agent/ChatSession.cs index 82165af9..0a84b82e 100644 --- a/shell/agents/Microsoft.Azure.Agent/ChatSession.cs +++ b/shell/agents/Microsoft.Azure.Agent/ChatSession.cs @@ -149,7 +149,7 @@ private async Task StartConversationAsync(IHost host, CancellationToken cancella int chatNumber = conversationState.DailyConversationNumber; int requestNumber = conversationState.TurnNumber; - host.WriteLine($"\n{activity.Text} This is chat #{chatNumber}, request #{requestNumber}.\n"); + host.WriteLine($"\n{activity.Text}\nThis is chat #{chatNumber}, request #{requestNumber}.\n"); return; } } diff --git a/shell/agents/Microsoft.Azure.Agent/Command.cs b/shell/agents/Microsoft.Azure.Agent/Command.cs index edfb2077..6236e2e1 100644 --- a/shell/agents/Microsoft.Azure.Agent/Command.cs +++ b/shell/agents/Microsoft.Azure.Agent/Command.cs @@ -61,7 +61,7 @@ private void ReplaceAction() string subText = items.Count > 1 ? $"all {items.Count} argument placeholders" : "the argument placeholder"; - host.WriteLine($"\nWe'll provide assistance in replacing {subText} and regenerating the result. You can press 'Enter' to skip to the next parameter or press 'Ctrl+c' to exit the assistance.\n"); + host.WriteLine($"\nWe'll provide assistance in replacing {subText} and regenerating the result.\nYou can press 'Enter' to skip to the next parameter or press 'Ctrl+c' to exit the assistance.\n"); host.RenderDivider("Input Values", DividerAlignment.Left); host.WriteLine(); @@ -72,13 +72,12 @@ private void ReplaceAction() var item = items[i]; var (command, parameter) = dataRetriever.GetMappedCommand(item.Name); - string desc = item.Desc.TrimEnd('.'); - string coloredCmd = parameter is null ? null : SyntaxHighlightAzCommand(command, parameter, item.Name); - string cmdPart = coloredCmd is null ? null : $" [{coloredCmd}]"; - - host.WriteLine(item.Type is "string" - ? $"{i+1}. {desc}{cmdPart}" - : $"{i+1}. {desc}{cmdPart}. Value type: {item.Type}"); + string caption = parameter is null ? item.Name : SyntaxHighlightAzCommand(command, parameter, item.Name); + host.WriteLine($"{i+1}. {caption}"); + if (item.Name != item.Desc) + { + host.WriteLine(item.Desc); + } // Get the task for creating the 'ArgumentInfo' object and show a spinner // if we have to wait for the task to complete. @@ -109,6 +108,13 @@ private void ReplaceAction() string value = host.PromptForArgument(argInfo, printCaption: false); if (!string.IsNullOrEmpty(value)) { + // Add quotes for the value if needed. + value = value.Trim(); + if (value.StartsWith('-') || value.Contains(' ')) + { + value = $"\"{value}\""; + } + _values.Add(item.Name, value); _agent.SaveUserValue(item.Name, value); diff --git a/shell/agents/Microsoft.Azure.Agent/DataRetriever.cs b/shell/agents/Microsoft.Azure.Agent/DataRetriever.cs index c3befa9b..2b8ec8c1 100644 --- a/shell/agents/Microsoft.Azure.Agent/DataRetriever.cs +++ b/shell/agents/Microsoft.Azure.Agent/DataRetriever.cs @@ -236,7 +236,7 @@ static DataRetriever() new("Container Registry", "cr", "The name only allows alphanumeric characters. Length: 5 to 50 chars.", - "cr[][]", + "cr[][]", ["crnavigatorprod001", "crhadoopdev001"], "az acr create --name", "New-AzContainerRegistry -Name"), @@ -244,7 +244,7 @@ static DataRetriever() new("Storage Account", "st", "The name can only contain lowercase letters and numbers. Length: 3 to 24 chars.", - "st[][]", + "st[][]", ["stsalesappdataqa", "sthadoopoutputtest"], "az storage account create --name", "New-AzStorageAccount -Name"), @@ -324,38 +324,50 @@ private void PairPlaceholders(ResponseData data) foreach (var cmd in data.CommandSet) { - string script = cmd.Script.Trim(); + bool placeholderFound = false; + // Az Copilot may return a code block that contains multiple commands. + string[] scripts = cmd.Script.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - // Handle AzCLI commands. - if (script.StartsWith("az ", StringComparison.OrdinalIgnoreCase)) + foreach (string script in scripts) { - if (!cmds.TryGetValue(script, out command)) + // Handle AzCLI commands. + if (script.StartsWith("az ", StringComparison.OrdinalIgnoreCase)) { - int firstParamIndex = script.IndexOf("--"); - command = script.AsSpan(0, firstParamIndex).Trim().ToString(); - cmds.Add(script, command); - } + if (!cmds.TryGetValue(script, out command)) + { + int firstParamIndex = script.IndexOf("--"); + command = script.AsSpan(0, firstParamIndex).Trim().ToString(); + cmds.Add(script, command); + } - int argIndex = script.IndexOf(item.Name, StringComparison.OrdinalIgnoreCase); - if (argIndex is -1) - { - continue; + int argIndex = script.IndexOf(item.Name, StringComparison.OrdinalIgnoreCase); + if (argIndex is -1) + { + continue; + } + + int paramIndex = script.LastIndexOf("--", argIndex); + parameter = script.AsSpan(paramIndex, argIndex - paramIndex).Trim().ToString(); + + placeholderFound = true; + break; } - int paramIndex = script.LastIndexOf("--", argIndex); - parameter = script.AsSpan(paramIndex, argIndex - paramIndex).Trim().ToString(); + // It's a non-AzCLI command, such as "ssh". + if (script.Contains(item.Name, StringComparison.OrdinalIgnoreCase)) + { + // Leave the parameter to be null for non-AzCLI commands, as there is + // no reliable way to parse an arbitrary command + command = script; + parameter = null; - break; + placeholderFound = true; + break; + } } - // It's a non-AzCLI command, such as "ssh". - if (script.Contains(item.Name, StringComparison.OrdinalIgnoreCase)) + if (placeholderFound) { - // Leave the parameter to be null for non-AzCLI commands, as there is - // no reliable way to parse an arbitrary command - command = script; - parameter = null; - break; } } @@ -421,12 +433,7 @@ private ArgumentInfo CreateArgInfo(ArgumentPair pair) string cmdAndParam = $"{pair.Command} {pair.Parameter}"; if (s_azNamingRules.TryGetValue(cmdAndParam, out NamingRule rule)) { - string restriction = rule.PatternText is null - ? rule.GeneralRule - : $""" - - {rule.GeneralRule} - - Recommended pattern: {rule.PatternText}, e.g. {string.Join(", ", rule.Example)}. - """; + string restriction = rule.PatternText is null ? null : $"Recommended pattern: {rule.PatternText}"; return new ArgumentInfoWithNamingRule(item.Name, item.Desc, restriction, rule); } @@ -439,14 +446,11 @@ private ArgumentInfo CreateArgInfo(ArgumentPair pair) if (_stop) { return null; } - List suggestions = GetArgValues(pair, out Option option); - // If the option's description is less than the placeholder's description in length, then it's - // unlikely to provide more information than the latter. In that case, we don't use it. - string optionDesc = option?.Description?.Length > item.Desc.Length ? option.Description : null; - return new ArgumentInfo(item.Name, item.Desc, optionDesc, dataType, suggestions); + List suggestions = GetArgValues(pair); + return new ArgumentInfo(item.Name, item.Desc, restriction: null, dataType, suggestions); } - private List GetArgValues(ArgumentPair pair, out Option option) + private List GetArgValues(ArgumentPair pair) { // First, try to get static argument values if they exist. string command = pair.Command; @@ -466,7 +470,7 @@ private List GetArgValues(ArgumentPair pair, out Option option) s_azStaticDataCache.TryAdd(command, commandData); } - option = commandData?.FindOption(pair.Parameter); + Option option = commandData?.FindOption(pair.Parameter); List staticValues = option?.Arguments; if (staticValues?.Count > 0) { @@ -646,7 +650,7 @@ internal NamingRule( if (abbreviation is not null) { - PatternText = $"-{abbreviation}[-][-]"; + PatternText = $"-{abbreviation}[-][-]"; PatternRegex = new Regex($"^(?[a-zA-Z0-9]+)-{abbreviation}(?:-(?[a-zA-Z0-9]+))?(?:-[a-zA-Z0-9]+)?$", RegexOptions.Compiled); string product = s_products[Random.Shared.Next(0, s_products.Length)];