// Copyright (c) Microsoft. All rights reserved. using Microsoft.Agents.AI.DurableTask; using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; using Microsoft.Extensions.Logging; namespace Microsoft.Agents.AI.Hosting.AzureFunctions; /// /// Transforms function metadata by registering durable agent functions for each configured agent. /// /// This transformer adds both entity trigger and HTTP trigger functions for every agent registered in the application. internal sealed class DurableAgentFunctionMetadataTransformer : IFunctionMetadataTransformer { private readonly ILogger _logger; private readonly IReadOnlyDictionary> _agents; private readonly IServiceProvider _serviceProvider; private readonly IFunctionsAgentOptionsProvider _functionsAgentOptionsProvider; #pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file - Azure Functions does not use single-file publishing private static readonly string s_builtInFunctionsScriptFile = Path.GetFileName(typeof(BuiltInFunctions).Assembly.Location); #pragma warning restore IL3000 public DurableAgentFunctionMetadataTransformer( IReadOnlyDictionary> agents, ILogger logger, IServiceProvider serviceProvider, IFunctionsAgentOptionsProvider functionsAgentOptionsProvider) { this._agents = agents ?? throw new ArgumentNullException(nameof(agents)); this._logger = logger ?? throw new ArgumentNullException(nameof(logger)); this._serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); this._functionsAgentOptionsProvider = functionsAgentOptionsProvider ?? throw new ArgumentNullException(nameof(functionsAgentOptionsProvider)); } public string Name => nameof(DurableAgentFunctionMetadataTransformer); public void Transform(IList original) { this._logger.LogTransformingFunctionMetadata(original.Count); foreach (KeyValuePair> kvp in this._agents) { string agentName = kvp.Key; this._logger.LogRegisteringTriggerForAgent(agentName, "entity"); original.Add(CreateAgentTrigger(agentName)); if (this._functionsAgentOptionsProvider.TryGet(agentName, out FunctionsAgentOptions? agentTriggerOptions)) { if (agentTriggerOptions.HttpTrigger.IsEnabled) { this._logger.LogRegisteringTriggerForAgent(agentName, "http"); original.Add(CreateHttpTrigger(agentName, $"agents/{agentName}/run")); } if (agentTriggerOptions.McpToolTrigger.IsEnabled) { AIAgent agent = kvp.Value(this._serviceProvider); this._logger.LogRegisteringTriggerForAgent(agentName, "mcpTool"); original.Add(CreateMcpToolTrigger(agentName, agent.Description)); } } } } private static DefaultFunctionMetadata CreateAgentTrigger(string name) { return new DefaultFunctionMetadata() { Name = AgentSessionId.ToEntityName(name), Language = "dotnet-isolated", RawBindings = [ """{"name":"encodedEntityRequest","type":"entityTrigger","direction":"In"}""", """{"name":"client","type":"durableClient","direction":"In"}""" ], EntryPoint = BuiltInFunctions.RunAgentEntityFunctionEntryPoint, ScriptFile = s_builtInFunctionsScriptFile, }; } private static DefaultFunctionMetadata CreateHttpTrigger(string name, string route) { return new DefaultFunctionMetadata() { Name = $"{BuiltInFunctions.HttpPrefix}{name}", Language = "dotnet-isolated", RawBindings = [ $"{{\"name\":\"req\",\"type\":\"httpTrigger\",\"direction\":\"In\",\"authLevel\":\"function\",\"methods\": [\"post\"],\"route\":\"{route}\"}}", "{\"name\":\"$return\",\"type\":\"http\",\"direction\":\"Out\"}", "{\"name\":\"client\",\"type\":\"durableClient\",\"direction\":\"In\"}" ], EntryPoint = BuiltInFunctions.RunAgentHttpFunctionEntryPoint, ScriptFile = s_builtInFunctionsScriptFile, }; } private static DefaultFunctionMetadata CreateMcpToolTrigger(string agentName, string? description) { return new DefaultFunctionMetadata { Name = $"{BuiltInFunctions.McpToolPrefix}{agentName}", Language = "dotnet-isolated", RawBindings = [ $$"""{"name":"context","type":"mcpToolTrigger","direction":"In","toolName":"{{agentName}}","description":"{{description}}","toolProperties":"[{\"propertyName\":\"query\",\"propertyType\":\"string\",\"description\":\"The query to send to the agent.\",\"isRequired\":true,\"isArray\":false},{\"propertyName\":\"threadId\",\"propertyType\":\"string\",\"description\":\"Optional thread identifier.\",\"isRequired\":false,\"isArray\":false}]"}""", """{"name":"query","type":"mcpToolProperty","direction":"In","propertyName":"query","description":"The query to send to the agent","isRequired":true,"dataType":"String","propertyType":"string"}""", """{"name":"threadId","type":"mcpToolProperty","direction":"In","propertyName":"threadId","description":"The thread identifier.","isRequired":false,"dataType":"String","propertyType":"string"}""", """{"name":"client","type":"durableClient","direction":"In"}""" ], EntryPoint = BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, ScriptFile = s_builtInFunctionsScriptFile, }; } }