From 6f83418dca7b578a2813561ba637345c4a937dc3 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 3 Dec 2025 18:49:12 -0800 Subject: [PATCH 1/5] Implement JSON output cleaning for Azure CLI commands Added a method to clean JSON output from Azure CLI commands to remove control characters and non-JSON content. --- .../Services/AzureCliService.cs | 69 +++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureCliService.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureCliService.cs index fa5e473..6828c6d 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureCliService.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureCliService.cs @@ -51,7 +51,16 @@ public async Task IsLoggedInAsync() return null; } - var accountJson = JsonSerializer.Deserialize(result.StandardOutput); + // Clean the output to remove any control characters or non-JSON content + var cleanedOutput = CleanJsonOutput(result.StandardOutput); + + if (string.IsNullOrWhiteSpace(cleanedOutput)) + { + _logger.LogError("Azure CLI returned empty output"); + return null; + } + + var accountJson = JsonSerializer.Deserialize(cleanedOutput); return new AzureAccountInfo { @@ -89,7 +98,13 @@ public async Task> ListResourceGroupsAsync() return new List(); } - var resourceGroupsJson = JsonSerializer.Deserialize(result.StandardOutput); + var cleanedOutput = CleanJsonOutput(result.StandardOutput); + if (string.IsNullOrWhiteSpace(cleanedOutput)) + { + return new List(); + } + + var resourceGroupsJson = JsonSerializer.Deserialize(cleanedOutput); return resourceGroupsJson?.Select(rg => new AzureResourceGroup { @@ -120,7 +135,13 @@ public async Task> ListAppServicePlansAsync() return new List(); } - var plansJson = JsonSerializer.Deserialize(result.StandardOutput); + var cleanedOutput = CleanJsonOutput(result.StandardOutput); + if (string.IsNullOrWhiteSpace(cleanedOutput)) + { + return new List(); + } + + var plansJson = JsonSerializer.Deserialize(cleanedOutput); return plansJson?.Select(plan => new AzureAppServicePlan { @@ -153,7 +174,13 @@ public async Task> ListLocationsAsync() return new List(); } - var locationsJson = JsonSerializer.Deserialize(result.StandardOutput); + var cleanedOutput = CleanJsonOutput(result.StandardOutput); + if (string.IsNullOrWhiteSpace(cleanedOutput)) + { + return new List(); + } + + var locationsJson = JsonSerializer.Deserialize(cleanedOutput); return locationsJson?.Select(loc => new AzureLocation { @@ -170,4 +197,36 @@ public async Task> ListLocationsAsync() return new List(); } } -} \ No newline at end of file + + /// + /// Cleans JSON output from Azure CLI by removing control characters and non-JSON content + /// + private string CleanJsonOutput(string output) + { + if (string.IsNullOrWhiteSpace(output)) + { + return string.Empty; + } + + // Remove control characters (0x00-0x1F except \r, \n, \t) + var cleaned = new System.Text.StringBuilder(output.Length); + foreach (char c in output) + { + if (c >= 32 || c == '\n' || c == '\r' || c == '\t') + { + cleaned.Append(c); + } + } + + var result = cleaned.ToString().Trim(); + + // Find the first { or [ to locate JSON start + int jsonStart = result.IndexOfAny(new[] { '{', '[' }); + if (jsonStart > 0) + { + result = result.Substring(jsonStart); + } + + return result; + } +} From 18bcd3dce37e70f076fc714eb2b5a43ace56fadf Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 3 Dec 2025 19:06:44 -0800 Subject: [PATCH 2/5] Implement JSON output cleaning in BotConfigurator Added JSON output cleaning functionality to improve parsing of Azure CLI responses. --- .../Services/BotConfigurator.cs | 91 +++++++++---------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs index 1989eff..f3c41cd 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using Microsoft.Agents.A365.DevTools.Cli.Constants; -using Microsoft.Agents.A365.DevTools.Cli.Exceptions; using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers; using Microsoft.Extensions.Logging; using System.Net.Http.Headers; @@ -63,7 +62,8 @@ public async Task CreateEndpointWithAgentBlueprintAsync( return false; } - var subscriptionInfo = JsonSerializer.Deserialize(subscriptionResult.StandardOutput); + var cleanedOutput = CleanJsonOutput(subscriptionResult.StandardOutput); + var subscriptionInfo = JsonSerializer.Deserialize(cleanedOutput); var tenantId = subscriptionInfo.GetProperty("tenantId").GetString(); if (string.IsNullOrEmpty(tenantId)) @@ -88,7 +88,7 @@ public async Task CreateEndpointWithAgentBlueprintAsync( // Determine the audience (App ID) based on the environment var audience = ConfigConstants.GetAgent365ToolsResourceAppId(config.Environment); - authToken = await _authService.GetAccessTokenAsync(audience, tenantId); + authToken = await _authService.GetAccessTokenAsync(audience); if (string.IsNullOrWhiteSpace(authToken)) { @@ -181,7 +181,8 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( return false; } - var subscriptionInfo = JsonSerializer.Deserialize(subscriptionResult.StandardOutput); + var cleanedOutput = CleanJsonOutput(subscriptionResult.StandardOutput); + var subscriptionInfo = JsonSerializer.Deserialize(cleanedOutput); var tenantId = subscriptionInfo.GetProperty("tenantId").GetString(); if (string.IsNullOrEmpty(tenantId)) @@ -211,7 +212,7 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( _logger.LogInformation("Environment: {Environment}, Audience: {Audience}", config.Environment, audience); - authToken = await _authService.GetAccessTokenAsync(audience, tenantId); + authToken = await _authService.GetAccessTokenAsync(audience); if (string.IsNullOrWhiteSpace(authToken)) { @@ -242,45 +243,10 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( if (!response.IsSuccessStatusCode) { + _logger.LogError("Failed to call delete endpoint. Status: {Status}", response.StatusCode); + var errorContent = await response.Content.ReadAsStringAsync(); - - // Parse the error response to provide cleaner user-facing messages - try - { - var errorJson = JsonSerializer.Deserialize(errorContent); - if (errorJson.TryGetProperty("error", out var errorMessage)) - { - var error = errorMessage.GetString(); - if (errorJson.TryGetProperty("details", out var detailsElement)) - { - var details = detailsElement.GetString(); - - // Check for common error scenarios and provide cleaner messages - if (details?.Contains("not found in any resource group") == true) - { - _logger.LogError("Failed to delete bot endpoint '{EndpointName}'. Status: {Status}", endpointName, response.StatusCode); - _logger.LogError("The bot service was not found. It may have already been deleted or may not exist."); - return false; - } - } - - // Generic error with cleaned up message - _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); - _logger.LogError("{Error}", error); - } - else - { - // Couldn't parse error, show raw response - _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); - _logger.LogError("Error response: {Error}", errorContent); - } - } - catch - { - // JSON parsing failed, show raw error - _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); - _logger.LogError("Error response: {Error}", errorContent); - } + _logger.LogError("Error response: {Error}", errorContent); return false; } @@ -288,14 +254,9 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( _logger.LogInformation("Successfully received response from delete endpoint"); return true; } - catch (AzureAuthenticationException ex) - { - _logger.LogError("Authentication failed: {Message}", ex.IssueDescription); - return false; - } catch (Exception ex) { - _logger.LogError("Failed to call delete endpoint: {Message}", ex.Message); + _logger.LogError(ex, "Failed to call delete endpoint directly"); return false; } } @@ -310,4 +271,36 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( return false; } } + + /// + /// Cleans JSON output from Azure CLI by removing control characters and non-JSON content + /// + private string CleanJsonOutput(string output) + { + if (string.IsNullOrWhiteSpace(output)) + { + return string.Empty; + } + + // Remove control characters (0x00-0x1F except \r, \n, \t) + var cleaned = new System.Text.StringBuilder(output.Length); + foreach (char c in output) + { + if (c >= 32 || c == '\n' || c == '\r' || c == '\t') + { + cleaned.Append(c); + } + } + + var result = cleaned.ToString().Trim(); + + // Find the first { or [ to locate JSON start + int jsonStart = result.IndexOfAny(new[] { '{', '[' }); + if (jsonStart > 0) + { + result = result.Substring(jsonStart); + } + + return result; + } } From dcba63df2bcd7f120612f05678b77baa26325f57 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 3 Dec 2025 19:20:51 -0800 Subject: [PATCH 3/5] Refactor: Use shared JsonDeserializationHelper for Azure CLI JSON cleaning - Added CleanAzureCliJsonOutput method to JsonDeserializationHelper - Updated AzureCliService, AzureAuthValidator, and BotConfigurator to use the shared helper - Removed duplicate CleanJsonOutput methods from individual services - Fixes JSON parsing errors when Azure CLI outputs control characters (0x0C) on Windows --- .../Services/AzureAuthValidator.cs | 6 ++- .../Services/AzureCliService.cs | 42 +++---------------- .../Services/BotConfigurator.cs | 36 +--------------- .../Helpers/JsonDeserializationHelper.cs | 38 +++++++++++++++++ 4 files changed, 49 insertions(+), 73 deletions(-) diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureAuthValidator.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureAuthValidator.cs index eef2e29..6c252b0 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureAuthValidator.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureAuthValidator.cs @@ -3,6 +3,7 @@ using System.Text.Json; using Microsoft.Extensions.Logging; +using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers; namespace Microsoft.Agents.A365.DevTools.Cli.Services; @@ -47,8 +48,9 @@ public async Task ValidateAuthenticationAsync(string? expectedSubscription return false; } - // Parse the account information - var accountJson = JsonDocument.Parse(result.StandardOutput); + // Clean and parse the account information + var cleanedOutput = JsonDeserializationHelper.CleanAzureCliJsonOutput(result.StandardOutput); + var accountJson = JsonDocument.Parse(cleanedOutput); var root = accountJson.RootElement; var subscriptionId = root.GetProperty("id").GetString() ?? string.Empty; diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureCliService.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureCliService.cs index 6828c6d..4651c16 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureCliService.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/AzureCliService.cs @@ -4,6 +4,7 @@ using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Agents.A365.DevTools.Cli.Models; +using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers; namespace Microsoft.Agents.A365.DevTools.Cli.Services; @@ -51,8 +52,7 @@ public async Task IsLoggedInAsync() return null; } - // Clean the output to remove any control characters or non-JSON content - var cleanedOutput = CleanJsonOutput(result.StandardOutput); + var cleanedOutput = JsonDeserializationHelper.CleanAzureCliJsonOutput(result.StandardOutput); if (string.IsNullOrWhiteSpace(cleanedOutput)) { @@ -98,7 +98,7 @@ public async Task> ListResourceGroupsAsync() return new List(); } - var cleanedOutput = CleanJsonOutput(result.StandardOutput); + var cleanedOutput = JsonDeserializationHelper.CleanAzureCliJsonOutput(result.StandardOutput); if (string.IsNullOrWhiteSpace(cleanedOutput)) { return new List(); @@ -135,7 +135,7 @@ public async Task> ListAppServicePlansAsync() return new List(); } - var cleanedOutput = CleanJsonOutput(result.StandardOutput); + var cleanedOutput = JsonDeserializationHelper.CleanAzureCliJsonOutput(result.StandardOutput); if (string.IsNullOrWhiteSpace(cleanedOutput)) { return new List(); @@ -174,7 +174,7 @@ public async Task> ListLocationsAsync() return new List(); } - var cleanedOutput = CleanJsonOutput(result.StandardOutput); + var cleanedOutput = JsonDeserializationHelper.CleanAzureCliJsonOutput(result.StandardOutput); if (string.IsNullOrWhiteSpace(cleanedOutput)) { return new List(); @@ -197,36 +197,4 @@ public async Task> ListLocationsAsync() return new List(); } } - - /// - /// Cleans JSON output from Azure CLI by removing control characters and non-JSON content - /// - private string CleanJsonOutput(string output) - { - if (string.IsNullOrWhiteSpace(output)) - { - return string.Empty; - } - - // Remove control characters (0x00-0x1F except \r, \n, \t) - var cleaned = new System.Text.StringBuilder(output.Length); - foreach (char c in output) - { - if (c >= 32 || c == '\n' || c == '\r' || c == '\t') - { - cleaned.Append(c); - } - } - - var result = cleaned.ToString().Trim(); - - // Find the first { or [ to locate JSON start - int jsonStart = result.IndexOfAny(new[] { '{', '[' }); - if (jsonStart > 0) - { - result = result.Substring(jsonStart); - } - - return result; - } } diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs index f3c41cd..670904a 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs @@ -62,7 +62,7 @@ public async Task CreateEndpointWithAgentBlueprintAsync( return false; } - var cleanedOutput = CleanJsonOutput(subscriptionResult.StandardOutput); + var cleanedOutput = JsonDeserializationHelper.CleanAzureCliJsonOutput(subscriptionResult.StandardOutput); var subscriptionInfo = JsonSerializer.Deserialize(cleanedOutput); var tenantId = subscriptionInfo.GetProperty("tenantId").GetString(); @@ -181,7 +181,7 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( return false; } - var cleanedOutput = CleanJsonOutput(subscriptionResult.StandardOutput); + var cleanedOutput = JsonDeserializationHelper.CleanAzureCliJsonOutput(subscriptionResult.StandardOutput); var subscriptionInfo = JsonSerializer.Deserialize(cleanedOutput); var tenantId = subscriptionInfo.GetProperty("tenantId").GetString(); @@ -271,36 +271,4 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( return false; } } - - /// - /// Cleans JSON output from Azure CLI by removing control characters and non-JSON content - /// - private string CleanJsonOutput(string output) - { - if (string.IsNullOrWhiteSpace(output)) - { - return string.Empty; - } - - // Remove control characters (0x00-0x1F except \r, \n, \t) - var cleaned = new System.Text.StringBuilder(output.Length); - foreach (char c in output) - { - if (c >= 32 || c == '\n' || c == '\r' || c == '\t') - { - cleaned.Append(c); - } - } - - var result = cleaned.ToString().Trim(); - - // Find the first { or [ to locate JSON start - int jsonStart = result.IndexOfAny(new[] { '{', '[' }); - if (jsonStart > 0) - { - result = result.Substring(jsonStart); - } - - return result; - } } diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/Helpers/JsonDeserializationHelper.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/Helpers/JsonDeserializationHelper.cs index e685799..fb712df 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/Helpers/JsonDeserializationHelper.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/Helpers/JsonDeserializationHelper.cs @@ -63,4 +63,42 @@ public static class JsonDeserializationHelper return null; } } + + /// + /// Cleans JSON output from Azure CLI by removing control characters and non-JSON content. + /// Azure CLI on Windows can output control characters (like 0x0C - form feed) and warning messages + /// that need to be stripped before JSON parsing. + /// + /// The raw output from Azure CLI + /// Cleaned JSON string ready for parsing + public static string CleanAzureCliJsonOutput(string output) + { + if (string.IsNullOrWhiteSpace(output)) + { + return string.Empty; + } + + // Remove control characters (0x00-0x1F except \r, \n, \t) + // These characters can appear in Azure CLI output on Windows + var cleaned = new System.Text.StringBuilder(output.Length); + foreach (char c in output) + { + if (c >= 32 || c == '\n' || c == '\r' || c == '\t') + { + cleaned.Append(c); + } + } + + var result = cleaned.ToString().Trim(); + + // Find the first { or [ to locate JSON start + // This handles cases where Azure CLI outputs warnings or other text before the JSON + int jsonStart = result.IndexOfAny(new[] { '{', '[' }); + if (jsonStart > 0) + { + result = result.Substring(jsonStart); + } + + return result; + } } From 51e925ac38c4cb8c0771be5eea6e9dcd9d3ca11c Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 3 Dec 2025 19:29:53 -0800 Subject: [PATCH 4/5] Fix: Restore missing tenantId parameter and improved error handling from main - Added tenantId parameter back to GetAccessTokenAsync calls - Restored comprehensive error handling for delete endpoint failures - Re-added Microsoft.Agents.A365.DevTools.Cli.Exceptions using statement - Maintains JSON cleaning functionality while aligning with main branch changes --- .../Services/BotConfigurator.cs | 53 ++- temp_main_botconfigurator.cs | 313 ++++++++++++++++++ 2 files changed, 360 insertions(+), 6 deletions(-) create mode 100644 temp_main_botconfigurator.cs diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs index 670904a..a2b098b 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/BotConfigurator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using Microsoft.Agents.A365.DevTools.Cli.Constants; +using Microsoft.Agents.A365.DevTools.Cli.Exceptions; using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers; using Microsoft.Extensions.Logging; using System.Net.Http.Headers; @@ -88,7 +89,7 @@ public async Task CreateEndpointWithAgentBlueprintAsync( // Determine the audience (App ID) based on the environment var audience = ConfigConstants.GetAgent365ToolsResourceAppId(config.Environment); - authToken = await _authService.GetAccessTokenAsync(audience); + authToken = await _authService.GetAccessTokenAsync(audience, tenantId); if (string.IsNullOrWhiteSpace(authToken)) { @@ -212,7 +213,7 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( _logger.LogInformation("Environment: {Environment}, Audience: {Audience}", config.Environment, audience); - authToken = await _authService.GetAccessTokenAsync(audience); + authToken = await _authService.GetAccessTokenAsync(audience, tenantId); if (string.IsNullOrWhiteSpace(authToken)) { @@ -243,10 +244,45 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( if (!response.IsSuccessStatusCode) { - _logger.LogError("Failed to call delete endpoint. Status: {Status}", response.StatusCode); - var errorContent = await response.Content.ReadAsStringAsync(); - _logger.LogError("Error response: {Error}", errorContent); + + // Parse the error response to provide cleaner user-facing messages + try + { + var errorJson = JsonSerializer.Deserialize(errorContent); + if (errorJson.TryGetProperty("error", out var errorMessage)) + { + var error = errorMessage.GetString(); + if (errorJson.TryGetProperty("details", out var detailsElement)) + { + var details = detailsElement.GetString(); + + // Check for common error scenarios and provide cleaner messages + if (details?.Contains("not found in any resource group") == true) + { + _logger.LogError("Failed to delete bot endpoint '{EndpointName}'. Status: {Status}", endpointName, response.StatusCode); + _logger.LogError("The bot service was not found. It may have already been deleted or may not exist."); + return false; + } + } + + // Generic error with cleaned up message + _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); + _logger.LogError("{Error}", error); + } + else + { + // Couldn't parse error, show raw response + _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); + _logger.LogError("Error response: {Error}", errorContent); + } + } + catch + { + // JSON parsing failed, show raw error + _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); + _logger.LogError("Error response: {Error}", errorContent); + } return false; } @@ -254,9 +290,14 @@ public async Task DeleteEndpointWithAgentBlueprintAsync( _logger.LogInformation("Successfully received response from delete endpoint"); return true; } + catch (AzureAuthenticationException ex) + { + _logger.LogError("Authentication failed: {Message}", ex.IssueDescription); + return false; + } catch (Exception ex) { - _logger.LogError(ex, "Failed to call delete endpoint directly"); + _logger.LogError("Failed to call delete endpoint: {Message}", ex.Message); return false; } } diff --git a/temp_main_botconfigurator.cs b/temp_main_botconfigurator.cs new file mode 100644 index 0000000..1989eff --- /dev/null +++ b/temp_main_botconfigurator.cs @@ -0,0 +1,313 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Agents.A365.DevTools.Cli.Constants; +using Microsoft.Agents.A365.DevTools.Cli.Exceptions; +using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers; +using Microsoft.Extensions.Logging; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace Microsoft.Agents.A365.DevTools.Cli.Services; + +/// +/// Service for configuring Azure Bot resources +/// +public class BotConfigurator : IBotConfigurator +{ + private readonly ILogger _logger; + private readonly CommandExecutor _executor; + + private readonly IConfigService _configService; + private readonly AuthenticationService _authService; + + public BotConfigurator(ILogger logger, CommandExecutor executor, IConfigService configService, AuthenticationService authService) + { + _logger = logger; + _executor = executor; + _configService = configService ?? throw new ArgumentNullException(nameof(configService)); + _authService = authService ?? throw new ArgumentNullException(nameof(authService)); + } + + /// + /// Create endpoint with Agent Blueprint Identity + /// + public async Task CreateEndpointWithAgentBlueprintAsync( + string endpointName, + string location, + string messagingEndpoint, + string agentDescription, + string agentBlueprintId) + { + _logger.LogInformation("Creating endpoint with Agent Blueprint Identity..."); + _logger.LogDebug(" Endpoint Name: {EndpointName}", endpointName); + _logger.LogDebug(" Messaging Endpoint: {Endpoint}", messagingEndpoint); + _logger.LogDebug(" Agent Blueprint ID: {AgentBlueprintId}", agentBlueprintId); + + try + { + // Get subscription info for tenant ID + var subscriptionResult = await _executor.ExecuteAsync("az", "account show", captureOutput: true); + if (subscriptionResult == null) + { + _logger.LogError("Failed to execute account show command - null result"); + return false; + } + + if (!subscriptionResult.Success) + { + _logger.LogError("Failed to get subscription information for endpoint creation"); + return false; + } + + var subscriptionInfo = JsonSerializer.Deserialize(subscriptionResult.StandardOutput); + var tenantId = subscriptionInfo.GetProperty("tenantId").GetString(); + + if (string.IsNullOrEmpty(tenantId)) + { + _logger.LogError("Could not determine tenant ID for endpoint creation"); + return false; + } + + // Create new endpoint with agent blueprint identity + _logger.LogInformation("Creating new endpoint with Agent Blueprint Identity..."); + + try + { + var config = await _configService.LoadAsync(); + var createEndpointUrl = EndpointHelper.GetCreateEndpointUrl(config.Environment); + + _logger.LogInformation("Calling create endpoint directly..."); + + // Get authentication token interactively (unless skip-auth is specified) + string? authToken = null; + _logger.LogInformation("Getting authentication token..."); + + // Determine the audience (App ID) based on the environment + var audience = ConfigConstants.GetAgent365ToolsResourceAppId(config.Environment); + authToken = await _authService.GetAccessTokenAsync(audience, tenantId); + + if (string.IsNullOrWhiteSpace(authToken)) + { + _logger.LogError("Failed to acquire authentication token"); + return false; + } + _logger.LogInformation("Successfully acquired access token"); + + var createEndpointBody = new JsonObject + { + ["AzureBotServiceInstanceName"] = endpointName, + ["AppId"] = agentBlueprintId, + ["TenantId"] = tenantId, + ["MessagingEndpoint"] = messagingEndpoint, + ["Description"] = agentDescription, + ["Location"] = location, + ["Environment"] = EndpointHelper.GetDeploymentEnvironment(config.Environment), + ["ClusterCategory"] = EndpointHelper.GetClusterCategory(config.Environment) + }; + // Use helper to create authenticated HTTP client + using var httpClient = Services.Internal.HttpClientFactory.CreateAuthenticatedClient(authToken); + + // Call the endpoint + _logger.LogInformation("Making request to create endpoint."); + + var response = await httpClient.PostAsync(createEndpointUrl, + new StringContent(createEndpointBody.ToJsonString(), System.Text.Encoding.UTF8, "application/json")); + + if (!response.IsSuccessStatusCode) + { + _logger.LogError("Failed to call create endpoint. Status: {Status}", response.StatusCode); + + var errorContent = await response.Content.ReadAsStringAsync(); + if (errorContent.Contains("Failed to provision bot resource via Azure Management API. Status: BadRequest", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogError("Please ensure that the Agent 365 CLI is supported in the selected region ('{Location}') and that your web app name ('{EndpointName}') is globally unique.", location, endpointName); + return false; + } + + _logger.LogError("Error response: {Error}", errorContent); + return false; + } + + _logger.LogInformation("Successfully received response from create endpoint"); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to call create endpoint directly"); + return false; + } + } + catch (JsonException ex) + { + _logger.LogError("Failed to parse tenant information: {Message}", ex.Message); + return false; + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error creating endpoint with agent blueprint: {Message}", ex.Message); + return false; + } + } + + /// + /// Delete endpoint with Agent Blueprint Identity + /// + public async Task DeleteEndpointWithAgentBlueprintAsync( + string endpointName, + string location, + string agentBlueprintId) + { + _logger.LogInformation("Deleting endpoint with Agent Blueprint Identity..."); + _logger.LogDebug(" Endpoint Name: {EndpointName}", endpointName); + _logger.LogDebug(" Agent Blueprint ID: {AgentBlueprintId}", agentBlueprintId); + + try + { + // Get subscription info for tenant ID + var subscriptionResult = await _executor.ExecuteAsync("az", "account show", captureOutput: true); + if (subscriptionResult == null) + { + _logger.LogError("Failed to execute account show command - null result"); + return false; + } + + if (!subscriptionResult.Success) + { + _logger.LogError("Failed to get subscription information for endpoint deletion"); + return false; + } + + var subscriptionInfo = JsonSerializer.Deserialize(subscriptionResult.StandardOutput); + var tenantId = subscriptionInfo.GetProperty("tenantId").GetString(); + + if (string.IsNullOrEmpty(tenantId)) + { + _logger.LogError("Could not determine tenant ID for endpoint deletion"); + return false; + } + + // Delete endpoint with agent blueprint identity + _logger.LogInformation("Deleting endpoint with Agent Blueprint Identity..."); + + try + { + var config = await _configService.LoadAsync(); + var deleteEndpointUrl = EndpointHelper.GetDeleteEndpointUrl(config.Environment); + + _logger.LogInformation("Calling delete endpoint directly..."); + _logger.LogInformation("Environment: {Env}", config.Environment); + _logger.LogInformation("Endpoint URL: {Url}", deleteEndpointUrl); + + // Get authentication token interactively (unless skip-auth is specified) + string? authToken = null; + _logger.LogInformation("Getting authentication token..."); + + // Determine the audience (App ID) based on the environment + var audience = ConfigConstants.GetAgent365ToolsResourceAppId(config.Environment); + + _logger.LogInformation("Environment: {Environment}, Audience: {Audience}", config.Environment, audience); + + authToken = await _authService.GetAccessTokenAsync(audience, tenantId); + + if (string.IsNullOrWhiteSpace(authToken)) + { + _logger.LogError("Failed to acquire authentication token"); + return false; + } + _logger.LogInformation("Successfully acquired access token"); + + var createEndpointBody = new JsonObject + { + ["AzureBotServiceInstanceName"] = endpointName, + ["AppId"] = agentBlueprintId, + ["TenantId"] = tenantId, + ["Location"] = location, + ["Environment"] = EndpointHelper.GetDeploymentEnvironment(config.Environment), + ["ClusterCategory"] = EndpointHelper.GetClusterCategory(config.Environment) + }; + // Use helper to create authenticated HTTP client + using var httpClient = Services.Internal.HttpClientFactory.CreateAuthenticatedClient(authToken); + + // Call the endpoint + _logger.LogInformation("Making request to delete endpoint."); + + using var request = new HttpRequestMessage(HttpMethod.Delete, deleteEndpointUrl); + request.Content = new StringContent(createEndpointBody.ToJsonString(), System.Text.Encoding.UTF8, "application/json"); + var response = await httpClient.SendAsync(request); + + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + + // Parse the error response to provide cleaner user-facing messages + try + { + var errorJson = JsonSerializer.Deserialize(errorContent); + if (errorJson.TryGetProperty("error", out var errorMessage)) + { + var error = errorMessage.GetString(); + if (errorJson.TryGetProperty("details", out var detailsElement)) + { + var details = detailsElement.GetString(); + + // Check for common error scenarios and provide cleaner messages + if (details?.Contains("not found in any resource group") == true) + { + _logger.LogError("Failed to delete bot endpoint '{EndpointName}'. Status: {Status}", endpointName, response.StatusCode); + _logger.LogError("The bot service was not found. It may have already been deleted or may not exist."); + return false; + } + } + + // Generic error with cleaned up message + _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); + _logger.LogError("{Error}", error); + } + else + { + // Couldn't parse error, show raw response + _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); + _logger.LogError("Error response: {Error}", errorContent); + } + } + catch + { + // JSON parsing failed, show raw error + _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); + _logger.LogError("Error response: {Error}", errorContent); + } + + return false; + } + + _logger.LogInformation("Successfully received response from delete endpoint"); + return true; + } + catch (AzureAuthenticationException ex) + { + _logger.LogError("Authentication failed: {Message}", ex.IssueDescription); + return false; + } + catch (Exception ex) + { + _logger.LogError("Failed to call delete endpoint: {Message}", ex.Message); + return false; + } + } + catch (JsonException ex) + { + _logger.LogError("Failed to parse tenant information: {Message}", ex.Message); + return false; + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error deleting endpoint with agent blueprint: {Message}", ex.Message); + return false; + } + } +} From 2c487a61a771647388adfba64f1e1b7d751bc580 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 3 Dec 2025 19:34:20 -0800 Subject: [PATCH 5/5] Remove temp file --- temp_main_botconfigurator.cs | 313 ----------------------------------- 1 file changed, 313 deletions(-) delete mode 100644 temp_main_botconfigurator.cs diff --git a/temp_main_botconfigurator.cs b/temp_main_botconfigurator.cs deleted file mode 100644 index 1989eff..0000000 --- a/temp_main_botconfigurator.cs +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Agents.A365.DevTools.Cli.Constants; -using Microsoft.Agents.A365.DevTools.Cli.Exceptions; -using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers; -using Microsoft.Extensions.Logging; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; - -namespace Microsoft.Agents.A365.DevTools.Cli.Services; - -/// -/// Service for configuring Azure Bot resources -/// -public class BotConfigurator : IBotConfigurator -{ - private readonly ILogger _logger; - private readonly CommandExecutor _executor; - - private readonly IConfigService _configService; - private readonly AuthenticationService _authService; - - public BotConfigurator(ILogger logger, CommandExecutor executor, IConfigService configService, AuthenticationService authService) - { - _logger = logger; - _executor = executor; - _configService = configService ?? throw new ArgumentNullException(nameof(configService)); - _authService = authService ?? throw new ArgumentNullException(nameof(authService)); - } - - /// - /// Create endpoint with Agent Blueprint Identity - /// - public async Task CreateEndpointWithAgentBlueprintAsync( - string endpointName, - string location, - string messagingEndpoint, - string agentDescription, - string agentBlueprintId) - { - _logger.LogInformation("Creating endpoint with Agent Blueprint Identity..."); - _logger.LogDebug(" Endpoint Name: {EndpointName}", endpointName); - _logger.LogDebug(" Messaging Endpoint: {Endpoint}", messagingEndpoint); - _logger.LogDebug(" Agent Blueprint ID: {AgentBlueprintId}", agentBlueprintId); - - try - { - // Get subscription info for tenant ID - var subscriptionResult = await _executor.ExecuteAsync("az", "account show", captureOutput: true); - if (subscriptionResult == null) - { - _logger.LogError("Failed to execute account show command - null result"); - return false; - } - - if (!subscriptionResult.Success) - { - _logger.LogError("Failed to get subscription information for endpoint creation"); - return false; - } - - var subscriptionInfo = JsonSerializer.Deserialize(subscriptionResult.StandardOutput); - var tenantId = subscriptionInfo.GetProperty("tenantId").GetString(); - - if (string.IsNullOrEmpty(tenantId)) - { - _logger.LogError("Could not determine tenant ID for endpoint creation"); - return false; - } - - // Create new endpoint with agent blueprint identity - _logger.LogInformation("Creating new endpoint with Agent Blueprint Identity..."); - - try - { - var config = await _configService.LoadAsync(); - var createEndpointUrl = EndpointHelper.GetCreateEndpointUrl(config.Environment); - - _logger.LogInformation("Calling create endpoint directly..."); - - // Get authentication token interactively (unless skip-auth is specified) - string? authToken = null; - _logger.LogInformation("Getting authentication token..."); - - // Determine the audience (App ID) based on the environment - var audience = ConfigConstants.GetAgent365ToolsResourceAppId(config.Environment); - authToken = await _authService.GetAccessTokenAsync(audience, tenantId); - - if (string.IsNullOrWhiteSpace(authToken)) - { - _logger.LogError("Failed to acquire authentication token"); - return false; - } - _logger.LogInformation("Successfully acquired access token"); - - var createEndpointBody = new JsonObject - { - ["AzureBotServiceInstanceName"] = endpointName, - ["AppId"] = agentBlueprintId, - ["TenantId"] = tenantId, - ["MessagingEndpoint"] = messagingEndpoint, - ["Description"] = agentDescription, - ["Location"] = location, - ["Environment"] = EndpointHelper.GetDeploymentEnvironment(config.Environment), - ["ClusterCategory"] = EndpointHelper.GetClusterCategory(config.Environment) - }; - // Use helper to create authenticated HTTP client - using var httpClient = Services.Internal.HttpClientFactory.CreateAuthenticatedClient(authToken); - - // Call the endpoint - _logger.LogInformation("Making request to create endpoint."); - - var response = await httpClient.PostAsync(createEndpointUrl, - new StringContent(createEndpointBody.ToJsonString(), System.Text.Encoding.UTF8, "application/json")); - - if (!response.IsSuccessStatusCode) - { - _logger.LogError("Failed to call create endpoint. Status: {Status}", response.StatusCode); - - var errorContent = await response.Content.ReadAsStringAsync(); - if (errorContent.Contains("Failed to provision bot resource via Azure Management API. Status: BadRequest", StringComparison.OrdinalIgnoreCase)) - { - _logger.LogError("Please ensure that the Agent 365 CLI is supported in the selected region ('{Location}') and that your web app name ('{EndpointName}') is globally unique.", location, endpointName); - return false; - } - - _logger.LogError("Error response: {Error}", errorContent); - return false; - } - - _logger.LogInformation("Successfully received response from create endpoint"); - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to call create endpoint directly"); - return false; - } - } - catch (JsonException ex) - { - _logger.LogError("Failed to parse tenant information: {Message}", ex.Message); - return false; - } - catch (Exception ex) - { - _logger.LogError(ex, "Unexpected error creating endpoint with agent blueprint: {Message}", ex.Message); - return false; - } - } - - /// - /// Delete endpoint with Agent Blueprint Identity - /// - public async Task DeleteEndpointWithAgentBlueprintAsync( - string endpointName, - string location, - string agentBlueprintId) - { - _logger.LogInformation("Deleting endpoint with Agent Blueprint Identity..."); - _logger.LogDebug(" Endpoint Name: {EndpointName}", endpointName); - _logger.LogDebug(" Agent Blueprint ID: {AgentBlueprintId}", agentBlueprintId); - - try - { - // Get subscription info for tenant ID - var subscriptionResult = await _executor.ExecuteAsync("az", "account show", captureOutput: true); - if (subscriptionResult == null) - { - _logger.LogError("Failed to execute account show command - null result"); - return false; - } - - if (!subscriptionResult.Success) - { - _logger.LogError("Failed to get subscription information for endpoint deletion"); - return false; - } - - var subscriptionInfo = JsonSerializer.Deserialize(subscriptionResult.StandardOutput); - var tenantId = subscriptionInfo.GetProperty("tenantId").GetString(); - - if (string.IsNullOrEmpty(tenantId)) - { - _logger.LogError("Could not determine tenant ID for endpoint deletion"); - return false; - } - - // Delete endpoint with agent blueprint identity - _logger.LogInformation("Deleting endpoint with Agent Blueprint Identity..."); - - try - { - var config = await _configService.LoadAsync(); - var deleteEndpointUrl = EndpointHelper.GetDeleteEndpointUrl(config.Environment); - - _logger.LogInformation("Calling delete endpoint directly..."); - _logger.LogInformation("Environment: {Env}", config.Environment); - _logger.LogInformation("Endpoint URL: {Url}", deleteEndpointUrl); - - // Get authentication token interactively (unless skip-auth is specified) - string? authToken = null; - _logger.LogInformation("Getting authentication token..."); - - // Determine the audience (App ID) based on the environment - var audience = ConfigConstants.GetAgent365ToolsResourceAppId(config.Environment); - - _logger.LogInformation("Environment: {Environment}, Audience: {Audience}", config.Environment, audience); - - authToken = await _authService.GetAccessTokenAsync(audience, tenantId); - - if (string.IsNullOrWhiteSpace(authToken)) - { - _logger.LogError("Failed to acquire authentication token"); - return false; - } - _logger.LogInformation("Successfully acquired access token"); - - var createEndpointBody = new JsonObject - { - ["AzureBotServiceInstanceName"] = endpointName, - ["AppId"] = agentBlueprintId, - ["TenantId"] = tenantId, - ["Location"] = location, - ["Environment"] = EndpointHelper.GetDeploymentEnvironment(config.Environment), - ["ClusterCategory"] = EndpointHelper.GetClusterCategory(config.Environment) - }; - // Use helper to create authenticated HTTP client - using var httpClient = Services.Internal.HttpClientFactory.CreateAuthenticatedClient(authToken); - - // Call the endpoint - _logger.LogInformation("Making request to delete endpoint."); - - using var request = new HttpRequestMessage(HttpMethod.Delete, deleteEndpointUrl); - request.Content = new StringContent(createEndpointBody.ToJsonString(), System.Text.Encoding.UTF8, "application/json"); - var response = await httpClient.SendAsync(request); - - - if (!response.IsSuccessStatusCode) - { - var errorContent = await response.Content.ReadAsStringAsync(); - - // Parse the error response to provide cleaner user-facing messages - try - { - var errorJson = JsonSerializer.Deserialize(errorContent); - if (errorJson.TryGetProperty("error", out var errorMessage)) - { - var error = errorMessage.GetString(); - if (errorJson.TryGetProperty("details", out var detailsElement)) - { - var details = detailsElement.GetString(); - - // Check for common error scenarios and provide cleaner messages - if (details?.Contains("not found in any resource group") == true) - { - _logger.LogError("Failed to delete bot endpoint '{EndpointName}'. Status: {Status}", endpointName, response.StatusCode); - _logger.LogError("The bot service was not found. It may have already been deleted or may not exist."); - return false; - } - } - - // Generic error with cleaned up message - _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); - _logger.LogError("{Error}", error); - } - else - { - // Couldn't parse error, show raw response - _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); - _logger.LogError("Error response: {Error}", errorContent); - } - } - catch - { - // JSON parsing failed, show raw error - _logger.LogError("Failed to delete bot endpoint. Status: {Status}", response.StatusCode); - _logger.LogError("Error response: {Error}", errorContent); - } - - return false; - } - - _logger.LogInformation("Successfully received response from delete endpoint"); - return true; - } - catch (AzureAuthenticationException ex) - { - _logger.LogError("Authentication failed: {Message}", ex.IssueDescription); - return false; - } - catch (Exception ex) - { - _logger.LogError("Failed to call delete endpoint: {Message}", ex.Message); - return false; - } - } - catch (JsonException ex) - { - _logger.LogError("Failed to parse tenant information: {Message}", ex.Message); - return false; - } - catch (Exception ex) - { - _logger.LogError(ex, "Unexpected error deleting endpoint with agent blueprint: {Message}", ex.Message); - return false; - } - } -}