From 08b678d5b0886ad23151ee2aaf215811e002d8f4 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Mon, 3 Nov 2025 16:13:37 -0300 Subject: [PATCH] Allow extending agent with additional properties Any configuration key that's not a property of AIAgent is available via the IHasAdditionalProperties.AdditionalProperties implemented by the configurable agent. --- src/Agents/AgentExtensions.cs | 10 ++++++++++ src/Agents/ConfigurableAIAgent.cs | 19 ++++++++++++++++++- src/Agents/IHasAdditionalProperties.cs | 10 ++++++++++ src/Tests/ConfigurableAgentTests.cs | 5 +++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/Agents/IHasAdditionalProperties.cs diff --git a/src/Agents/AgentExtensions.cs b/src/Agents/AgentExtensions.cs index d7acf9a..997dc59 100644 --- a/src/Agents/AgentExtensions.cs +++ b/src/Agents/AgentExtensions.cs @@ -23,4 +23,14 @@ public static ChatResponse AsChatResponse(this AgentRunResponse response) return chatResponse; } + + extension(AIAgent agent) + { + /// Gets the emoji associated with the agent, if any. + public string? Emoji => agent is not IHasAdditionalProperties additional + ? null + : additional.AdditionalProperties is null + ? null + : additional.AdditionalProperties.TryGetValue("Emoji", out var value) ? value as string : null; + } } \ No newline at end of file diff --git a/src/Agents/ConfigurableAIAgent.cs b/src/Agents/ConfigurableAIAgent.cs index 9f3a92e..0a69189 100644 --- a/src/Agents/ConfigurableAIAgent.cs +++ b/src/Agents/ConfigurableAIAgent.cs @@ -15,7 +15,7 @@ namespace Devlooped.Agents.AI; /// A configuration-driven which monitors configuration changes and /// re-applies them to the inner agent automatically. /// -public sealed partial class ConfigurableAIAgent : AIAgent, IDisposable +public sealed partial class ConfigurableAIAgent : AIAgent, IHasAdditionalProperties, IDisposable { readonly IServiceProvider services; readonly IConfiguration configuration; @@ -57,6 +57,9 @@ Type t when typeof(AIAgentMetadata).IsAssignableFrom(t) => metadata, _ => agent.GetService(serviceType, serviceKey) }; + /// + public AdditionalPropertiesDictionary? AdditionalProperties { get; set; } + /// public override string Id => agent.Id; /// @@ -89,6 +92,20 @@ public override IAsyncEnumerable RunStreamingAsync(IEnum options?.Description = options?.Description?.Dedent(); options?.Instructions = options?.Instructions?.Dedent(); + var properties = configSection.Get(); + if (properties is not null) + { + properties?.Remove(nameof(AgentClientOptions.Name)); + properties?.Remove(nameof(AgentClientOptions.Description)); + properties?.Remove(nameof(AgentClientOptions.Instructions)); + properties?.Remove(nameof(AgentClientOptions.Client)); + properties?.Remove(nameof(AgentClientOptions.Model)); + properties?.Remove(nameof(AgentClientOptions.Use)); + properties?.Remove(nameof(AgentClientOptions.Tools)); + + AdditionalProperties = properties; + } + // If there was a custom id, we must validate it didn't change since that's not supported. if (configuration[$"{section}:name"] is { } newname && newname != name) throw new InvalidOperationException($"The name of a configured agent cannot be changed at runtime. Expected '{name}' but was '{newname}'."); diff --git a/src/Agents/IHasAdditionalProperties.cs b/src/Agents/IHasAdditionalProperties.cs new file mode 100644 index 0000000..4b30884 --- /dev/null +++ b/src/Agents/IHasAdditionalProperties.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.AI; + +namespace Devlooped.Agents.AI; + +/// Indicates that the instance can have additional properties associated with it. +public interface IHasAdditionalProperties +{ + /// Gets or sets any additional properties associated with the instance. + AdditionalPropertiesDictionary? AdditionalProperties { get; set; } +} diff --git a/src/Tests/ConfigurableAgentTests.cs b/src/Tests/ConfigurableAgentTests.cs index 42e62e4..ae31366 100644 --- a/src/Tests/ConfigurableAgentTests.cs +++ b/src/Tests/ConfigurableAgentTests.cs @@ -25,6 +25,7 @@ public void CanConfigureAgent() ["ai:agents:bot:description"] = "Helpful chat agent", ["ai:agents:bot:instructions"] = "You are a helpful chat agent.", ["ai:agents:bot:options:temperature"] = "0.5", + ["ai:agents:bot:emoji"] = "🤖", }); builder.AddAIAgents(); @@ -36,6 +37,10 @@ public void CanConfigureAgent() Assert.Equal("chat", agent.Name); Assert.Equal("chat", agent.DisplayName); Assert.Equal("Helpful chat agent", agent.Description); + + var additional = Assert.IsType(agent, exactMatch: false); + Assert.Equal("🤖", additional.AdditionalProperties?["emoji"]?.ToString()); + Assert.Equal("🤖", agent.Emoji); } [Fact]