diff --git a/src/Observability/Runtime/AgentSettings/AgentSettingTemplate.cs b/src/Observability/Runtime/AgentSettings/AgentSettingTemplate.cs
new file mode 100644
index 00000000..39892d6c
--- /dev/null
+++ b/src/Observability/Runtime/AgentSettings/AgentSettingTemplate.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Agents.A365.Observability.Runtime.AgentSettings
+{
+ ///
+ /// Represents an agent setting template for a specific agent type.
+ ///
+ public class AgentSettingTemplate
+ {
+ ///
+ /// Gets or sets the agent type identifier.
+ ///
+ public string AgentType { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the settings as key-value pairs.
+ ///
+ public Dictionary Settings { get; set; } = new Dictionary();
+
+ ///
+ /// Gets or sets optional metadata for the template.
+ ///
+ public Dictionary? Metadata { get; set; }
+ }
+}
diff --git a/src/Observability/Runtime/AgentSettings/AgentSettings.cs b/src/Observability/Runtime/AgentSettings/AgentSettings.cs
new file mode 100644
index 00000000..ca918106
--- /dev/null
+++ b/src/Observability/Runtime/AgentSettings/AgentSettings.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Agents.A365.Observability.Runtime.AgentSettings
+{
+ ///
+ /// Represents agent settings for a specific agent instance.
+ ///
+ public class AgentSettings
+ {
+ ///
+ /// Gets or sets the agent instance identifier.
+ ///
+ public string AgentInstanceId { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the agent type identifier.
+ ///
+ public string AgentType { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the settings as key-value pairs.
+ ///
+ public Dictionary Settings { get; set; } = new Dictionary();
+
+ ///
+ /// Gets or sets optional metadata for the settings.
+ ///
+ public Dictionary? Metadata { get; set; }
+ }
+}
diff --git a/src/Observability/Runtime/AgentSettings/AgentSettingsService.cs b/src/Observability/Runtime/AgentSettings/AgentSettingsService.cs
new file mode 100644
index 00000000..45a46d1e
--- /dev/null
+++ b/src/Observability/Runtime/AgentSettings/AgentSettingsService.cs
@@ -0,0 +1,201 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Microsoft.Agents.A365.Observability.Runtime.Common;
+
+namespace Microsoft.Agents.A365.Observability.Runtime.AgentSettings
+{
+ ///
+ /// Service for managing agent configuration templates and instance-specific settings.
+ ///
+ public class AgentSettingsService
+ {
+ private readonly PowerPlatformApiDiscovery _apiDiscovery;
+ private readonly string _tenantId;
+ private readonly HttpClient _httpClient;
+ private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ PropertyNameCaseInsensitive = true
+ };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Power Platform API discovery service.
+ /// The tenant identifier.
+ /// Optional HttpClient instance. If not provided, a new instance will be created.
+ /// For production use, it is recommended to provide an HttpClient instance managed by an IHttpClientFactory
+ /// to avoid socket exhaustion issues.
+ public AgentSettingsService(
+ PowerPlatformApiDiscovery apiDiscovery,
+ string tenantId,
+ HttpClient? httpClient = null)
+ {
+ _apiDiscovery = apiDiscovery ?? throw new ArgumentNullException(nameof(apiDiscovery));
+ _tenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId));
+ _httpClient = httpClient ?? new HttpClient();
+ }
+
+ ///
+ /// Gets the agent setting template for a specific agent type.
+ ///
+ /// The agent type identifier.
+ /// The authentication token.
+ /// The agent setting template.
+ public async Task GetAgentSettingTemplateAsync(string agentType, string token)
+ {
+ if (string.IsNullOrEmpty(agentType))
+ {
+ throw new ArgumentNullException(nameof(agentType));
+ }
+
+ if (string.IsNullOrEmpty(token))
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ var endpoint = _apiDiscovery.GetTenantEndpoint(_tenantId);
+ var url = $"https://{endpoint}/agents/v1.0/settings/templates/{Uri.EscapeDataString(agentType)}";
+
+ using (var request = new HttpRequestMessage(HttpMethod.Get, url))
+ {
+ request.Headers.Add("Authorization", $"Bearer {token}");
+ request.Headers.Add("Accept", "application/json");
+
+ var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
+
+ if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
+ {
+ return null;
+ }
+
+ response.EnsureSuccessStatusCode();
+
+ var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ return JsonSerializer.Deserialize(content, JsonOptions);
+ }
+ }
+
+ ///
+ /// Sets the agent setting template for a specific agent type.
+ ///
+ /// The agent setting template to set.
+ /// The authentication token.
+ /// A task representing the asynchronous operation.
+ public async Task SetAgentSettingTemplateAsync(AgentSettingTemplate template, string token)
+ {
+ if (template == null)
+ {
+ throw new ArgumentNullException(nameof(template));
+ }
+
+ if (string.IsNullOrEmpty(template.AgentType))
+ {
+ throw new ArgumentException("AgentType cannot be null or empty.", nameof(template));
+ }
+
+ if (string.IsNullOrEmpty(token))
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ var endpoint = _apiDiscovery.GetTenantEndpoint(_tenantId);
+ var url = $"https://{endpoint}/agents/v1.0/settings/templates/{Uri.EscapeDataString(template.AgentType)}";
+
+ var json = JsonSerializer.Serialize(template);
+ using (var request = new HttpRequestMessage(HttpMethod.Put, url))
+ {
+ request.Headers.Add("Authorization", $"Bearer {token}");
+ request.Content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+ }
+ }
+
+ ///
+ /// Gets the agent settings for a specific agent instance.
+ ///
+ /// The agent instance identifier.
+ /// The authentication token.
+ /// The agent settings.
+ public async Task GetAgentSettingsAsync(string agentInstanceId, string token)
+ {
+ if (string.IsNullOrEmpty(agentInstanceId))
+ {
+ throw new ArgumentNullException(nameof(agentInstanceId));
+ }
+
+ if (string.IsNullOrEmpty(token))
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ var endpoint = _apiDiscovery.GetTenantEndpoint(_tenantId);
+ var url = $"https://{endpoint}/agents/v1.0/settings/instances/{Uri.EscapeDataString(agentInstanceId)}";
+
+ using (var request = new HttpRequestMessage(HttpMethod.Get, url))
+ {
+ request.Headers.Add("Authorization", $"Bearer {token}");
+ request.Headers.Add("Accept", "application/json");
+
+ var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
+
+ if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
+ {
+ return null;
+ }
+
+ response.EnsureSuccessStatusCode();
+
+ var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ return JsonSerializer.Deserialize(content, JsonOptions);
+ }
+ }
+
+ ///
+ /// Sets the agent settings for a specific agent instance.
+ ///
+ /// The agent settings to set.
+ /// The authentication token.
+ /// A task representing the asynchronous operation.
+ public async Task SetAgentSettingsAsync(AgentSettings settings, string token)
+ {
+ if (settings == null)
+ {
+ throw new ArgumentNullException(nameof(settings));
+ }
+
+ if (string.IsNullOrEmpty(settings.AgentInstanceId))
+ {
+ throw new ArgumentException("AgentInstanceId cannot be null or empty.", nameof(settings));
+ }
+
+ if (string.IsNullOrEmpty(token))
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ var endpoint = _apiDiscovery.GetTenantEndpoint(_tenantId);
+ var url = $"https://{endpoint}/agents/v1.0/settings/instances/{Uri.EscapeDataString(settings.AgentInstanceId)}";
+
+ var json = JsonSerializer.Serialize(settings);
+ using (var request = new HttpRequestMessage(HttpMethod.Put, url))
+ {
+ request.Headers.Add("Authorization", $"Bearer {token}");
+ request.Content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+ }
+ }
+ }
+}
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/AgentSettings/AgentSettingsServiceTests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/AgentSettings/AgentSettingsServiceTests.cs
new file mode 100644
index 00000000..14e7a035
--- /dev/null
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/AgentSettings/AgentSettingsServiceTests.cs
@@ -0,0 +1,361 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Agents.A365.Observability.Runtime.AgentSettings;
+using Microsoft.Agents.A365.Observability.Runtime.Common;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Moq.Protected;
+
+namespace Microsoft.Agents.A365.Observability.Runtime.Tests.AgentSettings.Tests
+{
+ [TestClass]
+ public class AgentSettingsServiceTests
+ {
+ private const string TestTenantId = "e3064512-cc6d-4703-be71-a2ecaecaa98a";
+ private const string TestAgentType = "test-agent";
+ private const string TestAgentInstanceId = "test-instance-123";
+ private const string TestToken = "test-token";
+
+ [TestMethod]
+ public void Constructor_WithValidParameters_Succeeds()
+ {
+ // Arrange
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+
+ // Act
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId);
+
+ // Assert
+ Assert.IsNotNull(service);
+ }
+
+ [TestMethod]
+ public void Constructor_WithNullApiDiscovery_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Assert.ThrowsException(() =>
+ new AgentSettingsService(null!, TestTenantId));
+ }
+
+ [TestMethod]
+ public void Constructor_WithNullTenantId_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+
+ // Act & Assert
+ Assert.ThrowsException(() =>
+ new AgentSettingsService(apiDiscovery, null!));
+ }
+
+ [TestMethod]
+ public async Task GetAgentSettingTemplateAsync_WithValidResponse_ReturnsTemplate()
+ {
+ // Arrange
+ var expectedTemplate = new AgentSettingTemplate
+ {
+ AgentType = TestAgentType,
+ Settings = new Dictionary { ["key1"] = "value1" }
+ };
+
+ var responseContent = JsonSerializer.Serialize(expectedTemplate);
+ var httpClient = CreateMockHttpClient(HttpStatusCode.OK, responseContent);
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId, httpClient);
+
+ // Act
+ var result = await service.GetAgentSettingTemplateAsync(TestAgentType, TestToken);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.AreEqual(TestAgentType, result.AgentType);
+ Assert.IsTrue(result.Settings.ContainsKey("key1"));
+ }
+
+ [TestMethod]
+ public async Task GetAgentSettingTemplateAsync_WithNotFoundResponse_ReturnsNull()
+ {
+ // Arrange
+ var httpClient = CreateMockHttpClient(HttpStatusCode.NotFound, string.Empty);
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId, httpClient);
+
+ // Act
+ var result = await service.GetAgentSettingTemplateAsync(TestAgentType, TestToken);
+
+ // Assert
+ Assert.IsNull(result);
+ }
+
+ [TestMethod]
+ public async Task GetAgentSettingTemplateAsync_WithNullAgentType_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId);
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(() =>
+ service.GetAgentSettingTemplateAsync(null!, TestToken));
+ }
+
+ [TestMethod]
+ public async Task GetAgentSettingTemplateAsync_WithNullToken_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId);
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(() =>
+ service.GetAgentSettingTemplateAsync(TestAgentType, null!));
+ }
+
+ [TestMethod]
+ public async Task SetAgentSettingTemplateAsync_WithValidTemplate_Succeeds()
+ {
+ // Arrange
+ var template = new AgentSettingTemplate
+ {
+ AgentType = TestAgentType,
+ Settings = new Dictionary { ["key1"] = "value1" }
+ };
+
+ var httpClient = CreateMockHttpClient(HttpStatusCode.OK, string.Empty);
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId, httpClient);
+
+ // Act
+ await service.SetAgentSettingTemplateAsync(template, TestToken);
+
+ // Assert - no exception thrown
+ }
+
+ [TestMethod]
+ public async Task SetAgentSettingTemplateAsync_WithNullTemplate_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId);
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(() =>
+ service.SetAgentSettingTemplateAsync(null!, TestToken));
+ }
+
+ [TestMethod]
+ public async Task SetAgentSettingTemplateAsync_WithEmptyAgentType_ThrowsArgumentException()
+ {
+ // Arrange
+ var template = new AgentSettingTemplate
+ {
+ AgentType = string.Empty,
+ Settings = new Dictionary { ["key1"] = "value1" }
+ };
+
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId);
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(() =>
+ service.SetAgentSettingTemplateAsync(template, TestToken));
+ }
+
+ [TestMethod]
+ public async Task GetAgentSettingsAsync_WithValidResponse_ReturnsSettings()
+ {
+ // Arrange
+ var expectedSettings = new Runtime.AgentSettings.AgentSettings
+ {
+ AgentInstanceId = TestAgentInstanceId,
+ AgentType = TestAgentType,
+ Settings = new Dictionary { ["key1"] = "value1" }
+ };
+
+ var responseContent = JsonSerializer.Serialize(expectedSettings);
+ var httpClient = CreateMockHttpClient(HttpStatusCode.OK, responseContent);
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId, httpClient);
+
+ // Act
+ var result = await service.GetAgentSettingsAsync(TestAgentInstanceId, TestToken);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.AreEqual(TestAgentInstanceId, result.AgentInstanceId);
+ Assert.AreEqual(TestAgentType, result.AgentType);
+ Assert.IsTrue(result.Settings.ContainsKey("key1"));
+ }
+
+ [TestMethod]
+ public async Task GetAgentSettingsAsync_WithNotFoundResponse_ReturnsNull()
+ {
+ // Arrange
+ var httpClient = CreateMockHttpClient(HttpStatusCode.NotFound, string.Empty);
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId, httpClient);
+
+ // Act
+ var result = await service.GetAgentSettingsAsync(TestAgentInstanceId, TestToken);
+
+ // Assert
+ Assert.IsNull(result);
+ }
+
+ [TestMethod]
+ public async Task SetAgentSettingsAsync_WithValidSettings_Succeeds()
+ {
+ // Arrange
+ var settings = new Runtime.AgentSettings.AgentSettings
+ {
+ AgentInstanceId = TestAgentInstanceId,
+ AgentType = TestAgentType,
+ Settings = new Dictionary { ["key1"] = "value1" }
+ };
+
+ var httpClient = CreateMockHttpClient(HttpStatusCode.OK, string.Empty);
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId, httpClient);
+
+ // Act
+ await service.SetAgentSettingsAsync(settings, TestToken);
+
+ // Assert - no exception thrown
+ }
+
+ [TestMethod]
+ public async Task SetAgentSettingsAsync_WithNullSettings_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId);
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(() =>
+ service.SetAgentSettingsAsync(null!, TestToken));
+ }
+
+ [TestMethod]
+ public async Task SetAgentSettingsAsync_WithEmptyAgentInstanceId_ThrowsArgumentException()
+ {
+ // Arrange
+ var settings = new Runtime.AgentSettings.AgentSettings
+ {
+ AgentInstanceId = string.Empty,
+ AgentType = TestAgentType,
+ Settings = new Dictionary { ["key1"] = "value1" }
+ };
+
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId);
+
+ // Act & Assert
+ await Assert.ThrowsExceptionAsync(() =>
+ service.SetAgentSettingsAsync(settings, TestToken));
+ }
+
+ [TestMethod]
+ public async Task GetAgentSettingTemplateAsync_ConstructsCorrectUrl()
+ {
+ // Arrange
+ HttpRequestMessage? capturedRequest = null;
+ var mockHandler = new Mock();
+ mockHandler
+ .Protected()
+ .Setup>(
+ "SendAsync",
+ ItExpr.IsAny(),
+ ItExpr.IsAny())
+ .Callback((req, ct) => capturedRequest = req)
+ .ReturnsAsync(new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(JsonSerializer.Serialize(new AgentSettingTemplate
+ {
+ AgentType = TestAgentType,
+ Settings = new Dictionary()
+ }))
+ });
+
+ var httpClient = new HttpClient(mockHandler.Object);
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId, httpClient);
+
+ // Act
+ await service.GetAgentSettingTemplateAsync(TestAgentType, TestToken);
+
+ // Assert
+ Assert.IsNotNull(capturedRequest);
+ var expectedEndpoint = apiDiscovery.GetTenantEndpoint(TestTenantId);
+ var expectedUrl = $"https://{expectedEndpoint}/agents/v1.0/settings/templates/{TestAgentType}";
+ Assert.AreEqual(expectedUrl, capturedRequest.RequestUri?.ToString());
+ Assert.AreEqual(HttpMethod.Get, capturedRequest.Method);
+ }
+
+ [TestMethod]
+ public async Task GetAgentSettingsAsync_ConstructsCorrectUrl()
+ {
+ // Arrange
+ HttpRequestMessage? capturedRequest = null;
+ var mockHandler = new Mock();
+ mockHandler
+ .Protected()
+ .Setup>(
+ "SendAsync",
+ ItExpr.IsAny(),
+ ItExpr.IsAny())
+ .Callback((req, ct) => capturedRequest = req)
+ .ReturnsAsync(new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(JsonSerializer.Serialize(new Runtime.AgentSettings.AgentSettings
+ {
+ AgentInstanceId = TestAgentInstanceId,
+ AgentType = TestAgentType,
+ Settings = new Dictionary()
+ }))
+ });
+
+ var httpClient = new HttpClient(mockHandler.Object);
+ var apiDiscovery = new PowerPlatformApiDiscovery("prod");
+ var service = new AgentSettingsService(apiDiscovery, TestTenantId, httpClient);
+
+ // Act
+ await service.GetAgentSettingsAsync(TestAgentInstanceId, TestToken);
+
+ // Assert
+ Assert.IsNotNull(capturedRequest);
+ var expectedEndpoint = apiDiscovery.GetTenantEndpoint(TestTenantId);
+ var expectedUrl = $"https://{expectedEndpoint}/agents/v1.0/settings/instances/{TestAgentInstanceId}";
+ Assert.AreEqual(expectedUrl, capturedRequest.RequestUri?.ToString());
+ Assert.AreEqual(HttpMethod.Get, capturedRequest.Method);
+ }
+
+ private static HttpClient CreateMockHttpClient(HttpStatusCode statusCode, string content)
+ {
+ var mockHandler = new Mock();
+ mockHandler
+ .Protected()
+ .Setup>(
+ "SendAsync",
+ ItExpr.IsAny(),
+ ItExpr.IsAny())
+ .ReturnsAsync(new HttpResponseMessage
+ {
+ StatusCode = statusCode,
+ Content = new StringContent(content, Encoding.UTF8, "application/json")
+ });
+
+ return new HttpClient(mockHandler.Object);
+ }
+ }
+}