File: PromptAgent\PromptAgentBuilderExtensions.cs
Web Access
Project: src\src\Aspire.Hosting.Foundry\Aspire.Hosting.Foundry.csproj (Aspire.Hosting.Foundry)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#pragma warning disable OPENAI001 // Responses API is experimental
 
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Aspire.Hosting.Foundry;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.Identity;
 
namespace Aspire.Hosting;
 
/// <summary>
/// Extension methods for adding Microsoft Foundry prompt agents and tools to the distributed application model.
/// </summary>
public static class PromptAgentBuilderExtensions
{
    /// <summary>
    /// Adds a prompt agent to a Microsoft Foundry project with the specified tools.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Prompt agents are always deployed to Azure Foundry, even during local development
    /// (<c>aspire run</c>). Local services communicate with the cloud-provisioned agent
    /// using the project endpoint and agent name injected as environment variables.
    /// </para>
    /// <para>
    /// Tools are project-level resources created with <c>Add*Tool</c> methods and can be
    /// reused across multiple agents in the same project.
    /// </para>
    /// </remarks>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the parent Microsoft Foundry project resource.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the prompt agent. This will be the agent name in Foundry.</param>
    /// <param name="model">The model deployment to use for this agent.</param>
    /// <param name="instructions">Optional system instructions for the agent.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the prompt agent resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    /// <example>
    /// <code>
    /// var foundry = builder.AddFoundry("aif");
    /// var project = foundry.AddProject("proj");
    /// var chat = project.AddModelDeployment("gpt41", FoundryModel.OpenAI.Gpt41);
    ///
    /// var bing = project.AddBingGroundingTool("bing").WithReference(bingConnection);
    /// var aiSearch = project.AddAISearchTool("search").WithReference(searchResource);
    /// var codeInterp = project.AddCodeInterpreterTool("code-interp");
    ///
    /// project.AddPromptAgent("joker-agent", chat,
    ///     instructions: "You are good at telling jokes.")
    ///     .WithTool(bing)
    ///     .WithTool(aiSearch)
    ///     .WithTool(codeInterp);
    /// </code>
    /// </example>
    [AspireExport]
    public static IResourceBuilder<AzurePromptAgentResource> AddPromptAgent(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        IResourceBuilder<FoundryDeploymentResource> model,
        string? instructions = null)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentNullException.ThrowIfNull(model);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var agent = new AzurePromptAgentResource(name, model.Resource.DeploymentName, project.Resource, instructions);
 
        var agentBuilder = project.ApplicationBuilder.AddResource(agent)
            .WithIconName("Agents")
            .WithReferenceRelationship(project)
            .WithReference(project);
 
        // Add "Send Message" command to the dashboard (like hosted agents)
        agentBuilder.WithCommand(
            name: "send-message",
            displayName: "Send Message",
            executeCommand: async ctx =>
            {
                var message = ctx.Arguments.GetString("message")!;
 
                var endpoint = await agent.Project.Endpoint.GetValueAsync(ctx.CancellationToken).ConfigureAwait(false);
                if (string.IsNullOrEmpty(endpoint))
                {
                    return CommandResults.Failure("Project endpoint is not available.");
                }
 
                try
                {
                    ctx.CancellationToken.ThrowIfCancellationRequested();
 
                    var projectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
                    var agentRef = new AgentReference(name: name);
                    var responseClient = projectClient.ProjectOpenAIClient.GetProjectResponsesClientForAgent(agentRef);
                    var response = await responseClient.CreateResponseAsync(message, cancellationToken: ctx.CancellationToken).ConfigureAwait(false);
 
                    return CommandResults.Success("Agent response received.", response.Value.GetOutputText(), CommandResultFormat.Markdown, displayImmediately: true);
                }
                catch (OperationCanceledException) when (ctx.CancellationToken.IsCancellationRequested)
                {
                    return CommandResults.Canceled();
                }
                catch (Exception ex)
                {
                    return CommandResults.Failure($"Failed to invoke agent: {ex.Message}");
                }
            },
            commandOptions: new()
            {
                Progress = new() { Message = "Sending message to agent..." },
                IconName = "ChatSparkle",
                IconVariant = IconVariant.Regular,
                IsHighlighted = true,
                Arguments =
                [
                    new InteractionInput
                    {
                        Name = "message",
                        InputType = InputType.Text,
                        Label = "Message",
                        Required = true,
                        Placeholder = "Hello, what can you do?",
                        Description = $"Enter a message to send to '{name}'."
                    }
                ]
            }
        );
 
        // Add Foundry portal URL for the agent (populated after deployment when subscription info is available)
        // The actual URL is set after provisioning completes.
 
        return agentBuilder;
    }
 
    /// <summary>
    /// Adds a tool to a prompt agent.
    /// </summary>
    /// <param name="agent">The prompt agent resource builder.</param>
    /// <param name="tool">The tool resource to attach to this agent.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for chaining.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<AzurePromptAgentResource> WithTool(
        this IResourceBuilder<AzurePromptAgentResource> agent,
        IResourceBuilder<FoundryToolResource> tool)
    {
        ArgumentNullException.ThrowIfNull(agent);
        ArgumentNullException.ThrowIfNull(tool);
 
        agent.Resource.AddTool(tool.Resource);
        agent.WithReferenceRelationship(tool);
 
        return agent;
    }
 
    // ──────────────────────────────────────────────────────────────
    // Built-in tools (no Azure provisioning required)
    // ──────────────────────────────────────────────────────────────
 
    /// <summary>
    /// Adds a Code Interpreter tool to a Microsoft Foundry project, enabling agents to write and
    /// run Python code in a sandboxed environment for data analysis, math, and chart generation.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<CodeInterpreterToolResource> AddCodeInterpreterTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new CodeInterpreterToolResource(name, project.Resource);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Adds a File Search tool to a Microsoft Foundry project, enabling agents to search
    /// uploaded files and proprietary documents using vector search.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="vectorStoreIds">Optional vector store IDs to search. If empty, the agent's default stores are used.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<FileSearchToolResource> AddFileSearchTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        params string[] vectorStoreIds)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new FileSearchToolResource(name, project.Resource)
        {
            VectorStoreIds = vectorStoreIds.ToList()
        };
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Adds a Web Search tool to a Microsoft Foundry project, enabling agents to retrieve
    /// real-time information from the public web and return answers with inline citations.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<WebSearchToolResource> AddWebSearchTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new WebSearchToolResource(name, project.Resource);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Adds an Image Generation tool to a Microsoft Foundry project, enabling agents to
    /// generate and edit images.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<ImageGenerationToolResource> AddImageGenerationTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new ImageGenerationToolResource(name, project.Resource);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Adds a Computer Use tool to a Microsoft Foundry project, enabling agents to interact
    /// with a computer desktop by taking screenshots, moving the mouse, clicking, and typing.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="displayWidth">The width of the display in pixels.</param>
    /// <param name="displayHeight">The height of the display in pixels.</param>
    /// <param name="environment">The environment identifier. Defaults to "browser".</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<ComputerToolResource> AddComputerUseTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        int displayWidth = 1024,
        int displayHeight = 768,
        string environment = "browser")
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new ComputerToolResource(name, project.Resource, displayWidth, displayHeight, environment);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    // ──────────────────────────────────────────────────────────────
    // Resource-backed tools (require Azure backing resource)
    // ──────────────────────────────────────────────────────────────
 
    /// <summary>
    /// Adds an Azure AI Search tool to a Microsoft Foundry project, enabling agents to
    /// ground their responses using data from an Azure AI Search index.
    /// </summary>
    /// <remarks>
    /// After creating the tool, call <see cref="WithReference(IResourceBuilder{Foundry.AzureAISearchToolResource}, IResourceBuilder{AzureSearchResource})"/>
    /// to link it to the backing Azure AI Search resource.
    /// </remarks>
    /// <ats-remarks />
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="indexName">Optional name of the search index to query. If not specified, the agent must be told which index to use.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<AzureAISearchToolResource> AddAISearchTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        string? indexName = null)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new AzureAISearchToolResource(name, project.Resource)
        {
            IndexName = indexName
        };
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Links an Azure AI Search tool to a backing <see cref="AzureSearchResource"/>,
    /// creating the necessary Foundry project connection and role assignments.
    /// </summary>
    /// <ats-summary>Links an Azure AI Search tool to a backing search resource.</ats-summary>
    /// <param name="tool">The Azure AI Search tool resource builder.</param>
    /// <param name="search">The Azure AI Search resource to use for grounding.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for chaining.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<AzureAISearchToolResource> WithReference(
        this IResourceBuilder<AzureAISearchToolResource> tool,
        IResourceBuilder<AzureSearchResource> search)
    {
        ArgumentNullException.ThrowIfNull(tool);
        ArgumentNullException.ThrowIfNull(search);
 
        if (tool.Resource.Connection is not null)
        {
            throw new InvalidOperationException(
                $"Azure AI Search tool '{tool.Resource.Name}' already has a backing resource configured.");
        }
 
        // Find the project builder to create the connection
        var projectBuilder = tool.ApplicationBuilder.CreateResourceBuilder(tool.Resource.Project);
 
        // AddConnection(IResourceBuilder<AzureSearchResource>) already handles role assignments
        var connection = projectBuilder.AddConnection(search);
 
        tool.Resource.Connection = connection.Resource;
        tool.Resource.SearchResource = search.Resource;
        return tool;
    }
 
    /// <summary>
    /// Adds a Bing Grounding tool to a Microsoft Foundry project, enabling agents to
    /// ground their responses using Bing Search results.
    /// </summary>
    /// <remarks>
    /// After creating the tool, call one of the <c>WithReference</c> overloads
    /// to link it to a Bing Search resource.
    /// </remarks>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<BingGroundingToolResource> AddBingGroundingTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new BingGroundingToolResource(name, project.Resource);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Links a Bing Grounding tool to a Foundry project connection for the Bing Search service.
    /// </summary>
    /// <param name="tool">The Bing Grounding tool resource builder.</param>
    /// <param name="bingConnection">
    /// The Bing grounding connection resource builder created by
    /// <see cref="AzureCognitiveServicesProjectConnectionsBuilderExtensions.AddBingGroundingConnection(IResourceBuilder{AzureCognitiveServicesProjectResource}, string, string)"/>
    /// or
    /// <see cref="AzureCognitiveServicesProjectConnectionsBuilderExtensions.AddBingGroundingConnection(IResourceBuilder{AzureCognitiveServicesProjectResource}, string, IResourceBuilder{ParameterResource})"/>.
    /// </param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for chaining.</returns>
    [AspireExportIgnore(Reason = "Covered by the internal AspireUnion overload.")]
    public static IResourceBuilder<BingGroundingToolResource> WithReference(
        this IResourceBuilder<BingGroundingToolResource> tool,
        IResourceBuilder<BingGroundingConnectionResource> bingConnection)
    {
        return WithReference(tool, (object)bingConnection);
    }
 
    /// <summary>
    /// Links a Bing Grounding tool to a Bing Search resource by using its Azure resource ID.
    /// </summary>
    /// <param name="tool">The Bing Grounding tool resource builder.</param>
    /// <param name="bingResourceId">
    /// The full Azure resource ID of the Bing Search resource
    /// (e.g., <c>/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.Bing/accounts/{name}</c>).
    /// </param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for chaining.</returns>
    [AspireExportIgnore(Reason = "Covered by the internal AspireUnion overload.")]
    public static IResourceBuilder<BingGroundingToolResource> WithReference(
        this IResourceBuilder<BingGroundingToolResource> tool,
        string bingResourceId)
    {
        return WithReference(tool, (object)bingResourceId);
    }
 
    /// <summary>
    /// Links a Bing Grounding tool to a Bing Search resource by using a parameter that contains
    /// its Azure resource ID.
    /// </summary>
    /// <param name="tool">The Bing Grounding tool resource builder.</param>
    /// <param name="bingResourceId">
    /// A parameter resource containing the full Azure resource ID of the Bing Search resource.
    /// </param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for chaining.</returns>
    [AspireExportIgnore(Reason = "Covered by the internal AspireUnion overload.")]
    public static IResourceBuilder<BingGroundingToolResource> WithReference(
        this IResourceBuilder<BingGroundingToolResource> tool,
        IResourceBuilder<ParameterResource> bingResourceId)
    {
        return WithReference(tool, (object)bingResourceId);
    }
 
    /// <summary>
    /// Links a Bing Grounding tool to a Bing Search resource or connection.
    /// </summary>
    [AspireExport("withBingReference", MethodName = "withReference")]
    internal static IResourceBuilder<BingGroundingToolResource> WithReference(
        this IResourceBuilder<BingGroundingToolResource> tool,
        [AspireUnion(
            typeof(IResourceBuilder<BingGroundingConnectionResource>),
            typeof(string),
            typeof(IResourceBuilder<ParameterResource>))]
        object bingReference)
    {
        ArgumentNullException.ThrowIfNull(tool);
        ArgumentNullException.ThrowIfNull(bingReference);
 
        if (tool.Resource.Connection is not null)
        {
            throw new InvalidOperationException(
                $"Bing Grounding tool '{tool.Resource.Name}' already has a connection configured.");
        }
 
        switch (bingReference)
        {
            case IResourceBuilder<BingGroundingConnectionResource> connectionBuilder:
                tool.Resource.Connection = connectionBuilder.Resource;
                break;
 
            case string bingResourceId:
                ArgumentException.ThrowIfNullOrEmpty(bingResourceId);
                var projectBuilder = tool.ApplicationBuilder.CreateResourceBuilder(tool.Resource.Project);
                var connection = projectBuilder.AddBingGroundingConnection($"{tool.Resource.Name}-conn", bingResourceId);
                tool.Resource.Connection = connection.Resource;
                break;
 
            case IResourceBuilder<ParameterResource> parameterBuilder:
                var paramProjectBuilder = tool.ApplicationBuilder.CreateResourceBuilder(tool.Resource.Project);
                var paramConnection = paramProjectBuilder.AddBingGroundingConnection($"{tool.Resource.Name}-conn", parameterBuilder);
                tool.Resource.Connection = paramConnection.Resource;
                break;
 
            default:
                throw new ArgumentException(
                    $"Unsupported Bing reference type '{bingReference.GetType().Name}'. " +
                    "Expected IResourceBuilder<BingGroundingConnectionResource>, string, or IResourceBuilder<ParameterResource>.",
                    nameof(bingReference));
        }
 
        return tool;
    }
 
    // ──────────────────────────────────────────────────────────────
    // Configuration-only tools
    // ──────────────────────────────────────────────────────────────
 
    /// <summary>
    /// Adds a SharePoint grounding tool to a Microsoft Foundry project, enabling agents to
    /// search data from SharePoint sites configured as Foundry project connections.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="projectConnectionIds">The Foundry project connection IDs for the SharePoint sites.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<SharePointToolResource> AddSharePointTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        params string[] projectConnectionIds)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new SharePointToolResource(name, project.Resource, projectConnectionIds);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Adds a Microsoft Fabric data agent tool to a Microsoft Foundry project, enabling
    /// agents to query data through Fabric data agents.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="projectConnectionIds">The Foundry project connection IDs for the Fabric data agents.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<FabricToolResource> AddFabricTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        params string[] projectConnectionIds)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new FabricToolResource(name, project.Resource, projectConnectionIds);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    // ──────────────────────────────────────────────────────────────
    // Function tools
    // ──────────────────────────────────────────────────────────────
 
    /// <summary>
    /// Adds an Azure Function tool to a Microsoft Foundry project, enabling agents to
    /// invoke a serverless Azure Function with queue-based input/output bindings.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="functionName">The name of the Azure Function.</param>
    /// <param name="description">A description of what the function does.</param>
    /// <param name="parameters">The JSON schema defining the function parameters.</param>
    /// <param name="inputQueueEndpoint">The Azure Storage Queue endpoint for input binding.</param>
    /// <param name="inputQueueName">The queue name for input binding.</param>
    /// <param name="outputQueueEndpoint">The Azure Storage Queue endpoint for output binding.</param>
    /// <param name="outputQueueName">The queue name for output binding.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    [AspireExportIgnore(Reason = "BinaryData parameter is not ATS-compatible. Use the string overload instead.")]
    public static IResourceBuilder<AzureFunctionToolResource> AddAzureFunctionTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        string functionName,
        string description,
        BinaryData parameters,
        string inputQueueEndpoint,
        string inputQueueName,
        string outputQueueEndpoint,
        string outputQueueName)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new AzureFunctionToolResource(
            name, project.Resource, functionName, description, parameters,
            inputQueueEndpoint, inputQueueName, outputQueueEndpoint, outputQueueName);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Adds an Azure Function tool to a Microsoft Foundry project, enabling agents to
    /// invoke a serverless Azure Function with queue-based input/output bindings.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="functionName">The name of the Azure Function.</param>
    /// <param name="description">A description of what the function does.</param>
    /// <param name="parametersJson">The JSON schema defining the function parameters as a JSON string.</param>
    /// <param name="inputQueueEndpoint">The Azure Storage Queue endpoint for input binding.</param>
    /// <param name="inputQueueName">The queue name for input binding.</param>
    /// <param name="outputQueueEndpoint">The Azure Storage Queue endpoint for output binding.</param>
    /// <param name="outputQueueName">The queue name for output binding.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    public static IResourceBuilder<AzureFunctionToolResource> AddAzureFunctionTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        string functionName,
        string description,
        string parametersJson,
        string inputQueueEndpoint,
        string inputQueueName,
        string outputQueueEndpoint,
        string outputQueueName)
    {
        return project.AddAzureFunctionTool(
            name, functionName, description, BinaryData.FromString(parametersJson),
            inputQueueEndpoint, inputQueueName, outputQueueEndpoint, outputQueueName);
    }
 
    /// <summary>
    /// Adds a function calling tool to a Microsoft Foundry project, enabling agents to
    /// call application-defined functions with structured parameters.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="functionName">The name of the function.</param>
    /// <param name="parameters">The JSON schema defining the function parameters.</param>
    /// <param name="description">A description of what the function does.</param>
    /// <param name="strictModeEnabled">Whether to enable strict mode for parameter validation.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    [AspireExportIgnore(Reason = "BinaryData parameter is not ATS-compatible. Use the string overload instead.")]
    public static IResourceBuilder<FunctionToolResource> AddFunctionTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        string functionName,
        BinaryData parameters,
        string? description = null,
        bool? strictModeEnabled = null)
    {
        ArgumentNullException.ThrowIfNull(project);
        ArgumentException.ThrowIfNullOrEmpty(name);
 
        var resource = new FunctionToolResource(
            name, project.Resource, functionName, parameters, description, strictModeEnabled);
        return project.ApplicationBuilder.AddResource(resource)
            .WithReferenceRelationship(project);
    }
 
    /// <summary>
    /// Adds a function calling tool to a Microsoft Foundry project, enabling agents to
    /// call application-defined functions with structured parameters.
    /// </summary>
    /// <param name="project">The <see cref="IResourceBuilder{T}"/> for the Microsoft Foundry project.</param>
    /// <ats-param name="project">The Microsoft Foundry project resource builder.</ats-param>
    /// <param name="name">The name of the tool resource.</param>
    /// <param name="functionName">The name of the function.</param>
    /// <param name="parametersJson">The JSON schema defining the function parameters as a JSON string.</param>
    /// <param name="description">A description of what the function does.</param>
    /// <param name="strictModeEnabled">Whether to enable strict mode for parameter validation.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the tool resource.</returns>
    /// <ats-returns>The resource builder.</ats-returns>
    [AspireExport]
    internal static IResourceBuilder<FunctionToolResource> AddFunctionTool(
        this IResourceBuilder<AzureCognitiveServicesProjectResource> project,
        [ResourceName] string name,
        string functionName,
        string parametersJson,
        string? description = null,
        bool? strictModeEnabled = null)
    {
        return project.AddFunctionTool(
            name, functionName, BinaryData.FromString(parametersJson), description, strictModeEnabled);
    }
 
    // ──────────────────────────────────────────────────────────────
    // Escape hatch: custom IFoundryTool
    // ──────────────────────────────────────────────────────────────
 
    /// <summary>
    /// Adds a custom tool implementation to a prompt agent using the <see cref="IFoundryTool"/> interface.
    /// </summary>
    /// <remarks>
    /// This is an advanced extensibility point for tools that don't fit the standard
    /// <see cref="FoundryToolResource"/> model. For most scenarios, use the project-level
    /// <c>Add*Tool</c> methods and pass tool resources to <see cref="AddPromptAgent"/>.
    /// </remarks>
    /// <param name="builder">The prompt agent resource builder.</param>
    /// <param name="tool">The custom tool implementation.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for chaining.</returns>
    [AspireExportIgnore(Reason = "IFoundryTool is not ATS-compatible.")]
    public static IResourceBuilder<AzurePromptAgentResource> WithCustomTool(
        this IResourceBuilder<AzurePromptAgentResource> builder,
        IFoundryTool tool)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(tool);
 
        builder.Resource.AddCustomTool(tool);
        return builder;
    }
}