diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 08efd09f05..52ce78a455 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -186,12 +186,6 @@ # ServiceLabel: %tools-FileShares # ServiceOwners: @ankushbindlish2 @kszobi -# PRLabel: %tools-Foundry -/tools/Azure.Mcp.Tools.Foundry/ @jayzzh @xiangyan99 @microsoft/azure-mcp - -# ServiceLabel: %tools-Foundry -# ServiceOwners: @jayzzh @xiangyan99 - # PRLabel: %tools-FunctionApp /tools/Azure.Mcp.Tools.FunctionApp/ @jongio @microsoft/azure-mcp diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs index d379802d7e..0ce67d8f56 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs @@ -19,7 +19,6 @@ using Azure.Mcp.Tools.Deploy; using Azure.Mcp.Tools.EventGrid; using Azure.Mcp.Tools.Extension; -using Azure.Mcp.Tools.Foundry; using Azure.Mcp.Tools.FunctionApp; using Azure.Mcp.Tools.Grafana; using Azure.Mcp.Tools.KeyVault; @@ -73,7 +72,6 @@ public static ICommandFactory CreateCommandFactory(IServiceProvider? serviceProv new DeploySetup(), new EventGridSetup(), new ExtensionSetup(), - new FoundrySetup(), new FunctionAppSetup(), new GrafanaSetup(), new KeyVaultSetup(), @@ -137,7 +135,6 @@ public static IServiceCollection SetupCommonServices() new DeploySetup(), new EventGridSetup(), new ExtensionSetup(), - new FoundrySetup(), new FunctionAppSetup(), new GrafanaSetup(), new KeyVaultSetup(), diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/RegistryDiscoveryStrategyTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/RegistryDiscoveryStrategyTests.cs index 579a8e84ca..9b4008b7b0 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/RegistryDiscoveryStrategyTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/RegistryDiscoveryStrategyTests.cs @@ -431,4 +431,71 @@ public async Task DiscoverServersAsync_NamespaceFilteringIsCaseInsensitive() var serverIds = providers.Select(p => p.CreateMetadata().Id).ToList(); Assert.Contains("documentation", serverIds); } + + [Fact] + public async Task DiscoverServersAsync_FoundryServerIsDiscovered() + { + // Arrange + var strategy = RegistryDiscoveryStrategyHelper.CreateStrategy(); + + // Act + var result = await strategy.DiscoverServersAsync(TestContext.Current.CancellationToken); + var foundryProvider = result.FirstOrDefault(p => p.CreateMetadata().Name == "foundry"); + + // Assert + Assert.NotNull(foundryProvider); + + var metadata = foundryProvider.CreateMetadata(); + Assert.Equal("foundry", metadata.Id); + Assert.Equal("foundry", metadata.Name); + Assert.NotEmpty(metadata.Description); + + // Verify description contains key terms + var description = metadata.Description.ToLowerInvariant(); + Assert.Contains("foundry", description); + Assert.Contains("mcp", description); + } + + [Fact] + public async Task DiscoverServersAsync_FoundryServerHasExpectedProperties() + { + // Arrange + var strategy = RegistryDiscoveryStrategyHelper.CreateStrategy(); + + // Act + var result = await strategy.DiscoverServersAsync(TestContext.Current.CancellationToken); + var foundryProvider = result.FirstOrDefault(p => p.CreateMetadata().Name == "foundry"); + + // Assert + Assert.NotNull(foundryProvider); + + var metadata = foundryProvider.CreateMetadata(); + Assert.Equal("foundry", metadata.Id); + Assert.Equal("foundry", metadata.Name); + + // Description should mention models, agents, and evaluation workflows + var description = metadata.Description.ToLowerInvariant(); + Assert.Contains("models", description); + Assert.Contains("agents", description); + } + + [Fact] + public async Task DiscoverServersAsync_AllExpectedServersArePresent() + { + // Arrange + var strategy = RegistryDiscoveryStrategyHelper.CreateStrategy(); + + // Act + var result = await strategy.DiscoverServersAsync(TestContext.Current.CancellationToken); + var serverIds = result.Select(p => p.CreateMetadata().Id).ToList(); + + // Assert + // Verify all expected registry servers are discovered + Assert.Contains("documentation", serverIds); + Assert.Contains("azd", serverIds); + Assert.Contains("foundry", serverIds); + + // Should have exactly 3 servers in registry + Assert.Equal(3, serverIds.Count); + } } diff --git a/core/Microsoft.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json b/core/Microsoft.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json index cf9bcb5cb1..78e4a8ed6e 100644 --- a/core/Microsoft.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json +++ b/core/Microsoft.Mcp.Core/src/Areas/Server/Resources/consolidated-tools.json @@ -404,39 +404,6 @@ "deploy_plan_get" ] }, - { - "name": "deploy_azure_ai_models", - "description": "Deploy AI models to Microsoft Foundry for machine learning inference and production use.", - "toolMetadata": { - "destructive": { - "value": true, - "description": "This tool may delete or modify existing resources in its environment." - }, - "idempotent": { - "value": false, - "description": "Running this operation multiple times with the same arguments may have additional effects or produce different results." - }, - "openWorld": { - "value": false, - "description": "This tool's domain of interaction is closed and well-defined, limited to a specific set of entities." - }, - "readOnly": { - "value": false, - "description": "This tool may modify its environment by creating, updating, or deleting data." - }, - "secret": { - "value": false, - "description": "This tool does not handle sensitive or secret information." - }, - "localRequired": { - "value": false, - "description": "This tool is available in both local and remote server modes." - } - }, - "mappedToolList": [ - "foundry_models_deploy" - ] - }, { "name": "get_azure_app_config_settings", "description": "Get details about Azure App Configuration settings", @@ -1040,7 +1007,7 @@ }, { "name": "get_azure_best_practices", - "description": "Retrieve Azure best practices and infrastructure schema for code generation, deployment, and operations. Covers general Azure practices, Azure Functions best practices, AI app development best practices, Terraform configurations, Bicep template schemas, deployment best practices and Microsoft Foundry sdk code samples.", + "description": "Retrieve Azure best practices and infrastructure schema for code generation, deployment, and operations. Covers general Azure practices, Azure Functions best practices, AI app development best practices, Terraform configurations, Bicep template schemas, and deployment best practices.", "toolMetadata": { "destructive": { "value": false, @@ -1071,8 +1038,7 @@ "azureterraformbestpractices_get", "bicepschema_get", "get_azure_bestpractices_ai_app", - "get_azure_bestpractices_get", - "foundry_agents_get-sdk-sample" + "get_azure_bestpractices_get" ] }, { @@ -1181,7 +1147,7 @@ }, { "name": "get_azure_ai_resources_details", - "description": "Get details about Azure AI resources including listing and querying AI Search services, listing models available to be deployed and models deployed already, knowledge index schema by Microsoft Foundry, knowledge base and source information, and listing Microsoft Foundry , threads, messages and resources.", + "description": "Get details about Azure AI resources including listing and querying AI Search services, knowledge base and source information.", "toolMetadata": { "destructive": { "value": false, @@ -1213,16 +1179,7 @@ "search_index_get", "search_index_query", "search_knowledge_source_get", - "search_knowledge_base_get", - "foundry_models_deployments_list", - "foundry_models_list", - "foundry_knowledge_index_list", - "foundry_knowledge_index_schema", - "foundry_openai_models-list", - "foundry_agents_list", - "foundry_resource_get", - "foundry_threads_list", - "foundry_threads_get-messages" + "search_knowledge_base_get" ] }, { diff --git a/core/Microsoft.Mcp.Core/src/Areas/Server/Resources/registry.json b/core/Microsoft.Mcp.Core/src/Areas/Server/Resources/registry.json index 27f63ab727..31f98bc8fc 100644 --- a/core/Microsoft.Mcp.Core/src/Areas/Server/Resources/registry.json +++ b/core/Microsoft.Mcp.Core/src/Areas/Server/Resources/registry.json @@ -12,6 +12,12 @@ "title": "Azure Developer CLI", "description": "Azure Developer CLI (azd) includes a suite of tools to help build, modernize, and manage applications on Azure. It simplifies the process of developing cloud applications by providing commands for project initialization, resource provisioning, deployment, and monitoring. Use this tool to streamline your Azure development workflow and manage your cloud resources efficiently.", "installInstructions": "The Azure Developer CLI (azd) is either not installed or requires an update. The minimum required version that works with MCP tools is 1.20.0.\n\nTo install or upgrade, follow the instructions at https://aka.ms/azd/install\n\nAfter installation you may need to restart the Azure MCP server and your IDE." + }, + "foundry": { + "url": "https://mcp.ai.azure.com", + "title": "Microsoft Foundry MCP", + "description": "MCP server with tools to read, create, modify resources such as Models, Agents and Evaluation workflows in Microsoft Foundry platform.", + "oauthScopes": ["https://mcp.ai.azure.com/Foundry.Mcp.Tools"] } } } diff --git a/servers/Azure.Mcp.Server/docs/azmcp-commands.md b/servers/Azure.Mcp.Server/docs/azmcp-commands.md index 3acc4e9234..520b0295e6 100644 --- a/servers/Azure.Mcp.Server/docs/azmcp-commands.md +++ b/servers/Azure.Mcp.Server/docs/azmcp-commands.md @@ -1512,14 +1512,13 @@ azmcp get azure bestpractices get --resource --action # deployment - Best practices for deployment (for general and azurefunctions) # Get best practices for building AI applications, workflows and agents in Azure -# Call this before generating code for any AI application, building with Microsoft Foundry models, -# working with Microsoft Agent Framework, or implementing AI solutions in Azure. +# Call this before generating code for any AI application, working with Microsoft Agent Framework, +# or implementing AI solutions in Azure. azmcp get azure bestpractices ai_app # AI App Development: # ai_app - Comprehensive guidance for AI applications including: # • Microsoft Agent Framework usage and patterns -# • Microsoft Foundry model selection # • Best practices for AI app/agent development in Azure ``` @@ -2715,152 +2714,6 @@ azmcp cloudarchitect design --question "What type of application are you buildin --confidence-score 0.1 ``` -### Microsoft Foundry Operations - -```bash -# Create an agent in a Microsoft Foundry project -# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry agents create --endpoint \ - --model-deployment \ - --agent-name \ - --systemInstruction - -# Connect to an agent in a Microsoft Foundry project and query it -# ❌ Destructive | ❌ Idempotent | ✅ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry agents connect --agent-id \ - --query \ - --endpoint - -# Evaluate a response from an agent by passing query and response inline -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry agents evaluate --agent-id \ - --query \ - --response \ - --evaluator \ - --azure-openai-endpoint \ - --azure-openai-deployment \ - [--tool-definitions ] - -# Get SDK samples for interacting with a Microsoft Foundry agent -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry agents get-sdk-sample --programming-language - -# List all Azure AI Agents available in the configured project -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry agents list --endpoint - -# Query and evaluate an agent in one command -# ❌ Destructive | ❌ Idempotent | ✅ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry agents query-and-evaluate --agent-id \ - --query \ - --endpoint \ - --azure-openai-endpoint \ - --azure-openai-deployment \ - [--evaluators ] - -# List knowledge indexes in a Microsoft Foundry project -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry knowledge index list --endpoint - -# Get knowledge index schema information -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry knowledge index schema --endpoint \ - --index - -# Deploy a Microsoft Foundry model -# ✅ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry models deploy --subscription \ - --resource-group \ - --deployment \ - --model-name \ - --model-format \ - --azure-ai-services \ - [--model-version ] \ - [--model-source ] \ - [--sku ] \ - [--sku-capacity ] \ - [--scale-type ] \ - [--scale-capacity ] - -# List Microsoft Foundry model deployments -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry models deployments list --endpoint - -# List Microsoft Foundry models -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry models list [--search-for-free-playground ] \ - [--publisher ] \ - [--license ] \ - [--model-name ] - -# Create interactive chat completions using Azure OpenAI chat models -# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry openai chat-completions-create --subscription \ - --resource-group \ - --resource-name \ - --deployment \ - --message-array \ - [--max-tokens ] \ - [--temperature ] \ - [--top-p ] \ - [--frequency-penalty ] \ - [--presence-penalty ] \ - [--stop ] \ - [--stream ] \ - [--seed ] \ - [--user ] \ - [--auth-method ] - -# Generate text completions using deployed Azure OpenAI models in Microsoft Foundry -# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry openai create-completion --subscription \ - --resource-group \ - --resource-name \ - --deployment \ - --prompt-text \ - [--max-tokens ] \ - [--temperature ] - -# Generate vector embeddings for text using Azure OpenAI embedding models -# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry openai embeddings-create --subscription \ - --resource-group \ - --resource-name \ - --deployment \ - --input-text \ - [--user ] \ - [--encoding-format ] \ - [--dimensions ] \ - [--auth-method ] - -# List all available OpenAI models and deployments in an Azure resource -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry openai models-list --subscription \ - --resource-group \ - --resource-name \ - [--auth-method ] - -# Get Microsoft Foundry (Cognitive Services) resource details -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry resource get --subscription \ - [--resource-group ] \ - [--resource-name ] - -# Create a Microsoft Foundry agent thread -# ❌ Destructive | ❌ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry threads create --endpoint - --user-message - -# Get messages of a Microsoft Foundry agent thread -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry threads get-messages --endpoint - --thread-id - -# List Microsoft Foundry agent threads in a Foundry project -# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp foundry threads list --endpoint -``` - ## Response Format All responses follow a consistent JSON format: diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs index b4019d1410..3b6a41eb3e 100644 --- a/servers/Azure.Mcp.Server/src/Program.cs +++ b/servers/Azure.Mcp.Server/src/Program.cs @@ -109,7 +109,6 @@ private static IAreaSetup[] RegisterAreas() new Azure.Mcp.Tools.ConfidentialLedger.ConfidentialLedgerSetup(), new Azure.Mcp.Tools.EventHubs.EventHubsSetup(), new Azure.Mcp.Tools.FileShares.FileSharesSetup(), - new Azure.Mcp.Tools.Foundry.FoundrySetup(), new Azure.Mcp.Tools.FunctionApp.FunctionAppSetup(), new Azure.Mcp.Tools.Grafana.GrafanaSetup(), new Azure.Mcp.Tools.KeyVault.KeyVaultSetup(), diff --git a/tools/Azure.Mcp.Tools.Foundry/src/AssemblyInfo.cs b/tools/Azure.Mcp.Tools.Foundry/src/AssemblyInfo.cs deleted file mode 100644 index 467f5d96b1..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Azure.Mcp.Tools.Foundry.UnitTests")] -[assembly: InternalsVisibleTo("Azure.Mcp.Tools.Foundry.LiveTests")] diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Azure.Mcp.Tools.Foundry.csproj b/tools/Azure.Mcp.Tools.Foundry/src/Azure.Mcp.Tools.Foundry.csproj deleted file mode 100644 index 1f76af65b6..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Azure.Mcp.Tools.Foundry.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsConnectCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsConnectCommand.cs deleted file mode 100644 index 0fbfaa5485..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsConnectCommand.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class AgentsConnectCommand : GlobalCommand -{ - private const string CommandTitle = "Connect to AI Agent and Run a Query"; - public override string Id => "2f0184c9-a19e-41a5-87cc-f96b08fafccb"; - - public override string Name => "connect"; - - public override string Description => - """ - Query a Microsoft Foundry agent and get the response as is (no query and evaluate). Use for one-off interaction or to capture run/thread IDs before calling evaluation tools. Do not use this tool for combined answer-plus-score workflows — instead, use agents_query-and-evaluate or agents_evaluate. - """; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = false, - OpenWorld = true, - ReadOnly = false, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.AgentIdOption); - command.Options.Add(FoundryOptionDefinitions.QueryOption); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - } - - protected override AgentsConnectOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.AgentId = parseResult.GetValueOrDefault(FoundryOptionDefinitions.AgentIdOption); - options.Query = parseResult.GetValueOrDefault(FoundryOptionDefinitions.QueryOption); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption); - return options; - } - - public override string Title => CommandTitle; - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - var response = await service.ConnectAgent( - options.AgentId!, - options.Query!, - options.Endpoint!, - options.Tenant, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new AgentsConnectCommandResult(response), - FoundryJsonContext.Default.AgentsConnectCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record AgentsConnectCommandResult(AgentsConnectResult Response); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsCreateCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsCreateCommand.cs deleted file mode 100644 index 58e6a6a97d..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsCreateCommand.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Agents; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public class AgentsCreateCommand : GlobalCommand -{ - private const string CommandTitle = "Create a Microsoft Foundry Agent"; - public override string Id => "6de93935-7df0-4161-8618-8daf624dbb23"; - public override string Name => "create"; - - - public override string Description => - """ - Creates a Microsoft Foundry Agent that processes messages according to a given system instruction using an existing Microsoft Foundry model deployment. - """; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = false, - OpenWorld = false, - ReadOnly = false, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - command.Options.Add(FoundryOptionDefinitions.ModelDeploymentNameOption); - command.Options.Add(FoundryOptionDefinitions.AgentNameOption); - command.Options.Add(FoundryOptionDefinitions.SystemInstructionOption); - } - - protected override AgentsCreateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption); - options.ModelDeploymentName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ModelDeploymentNameOption); - options.AgentName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.AgentNameOption); - options.SystemInstruction = parseResult.GetValueOrDefault(FoundryOptionDefinitions.SystemInstructionOption); - return options; - } - - public override string Title => CommandTitle; - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - AgentsCreateResult result = await service.CreateAgent( - options.Endpoint!, - options.ModelDeploymentName!, - options.AgentName!, - options.SystemInstruction!, - options.Tenant, - cancellationToken: cancellationToken - ); - - context.Response.Results = ResponseResult.Create( - result, - FoundryJsonContext.Default.AgentsCreateResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } -} - diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsEvaluateCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsEvaluateCommand.cs deleted file mode 100644 index 801db2bc03..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsEvaluateCommand.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class AgentsEvaluateCommand : GlobalCommand -{ - private const string CommandTitle = "Evaluate Agent"; - - public override string Id => "a614ec89-1fce-474f-9cb2-87537b287cbf"; - - public override string Name => "evaluate"; - - public override string Description => - """ - Run agent evaluation on agent data. Requires JSON strings for query, response, and tool definitions. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.QueryOption); - command.Options.Add(FoundryOptionDefinitions.EvaluatorNameOption); - command.Options.Add(FoundryOptionDefinitions.ResponseOption); - command.Options.Add(FoundryOptionDefinitions.ToolDefinitionsOption); - command.Options.Add(FoundryOptionDefinitions.AzureOpenAIEndpointOption); - command.Options.Add(FoundryOptionDefinitions.AzureOpenAIDeploymentOption); - } - - protected override AgentsEvaluateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Query = parseResult.GetValueOrDefault(FoundryOptionDefinitions.QueryOption); - options.EvaluatorName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EvaluatorNameOption); - options.Response = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ResponseOption); - options.ToolDefinitions = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ToolDefinitionsOption); - options.AzureOpenAIEndpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.AzureOpenAIEndpointOption); - options.AzureOpenAIDeployment = parseResult.GetValueOrDefault(FoundryOptionDefinitions.AzureOpenAIDeploymentOption); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - var result = await service.EvaluateAgent( - options.EvaluatorName!, - options.Query!, - options.Response!, - options.AzureOpenAIEndpoint!, - options.AzureOpenAIDeployment!, - options.ToolDefinitions, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new AgentsEvaluateCommandResult(result), - FoundryJsonContext.Default.AgentsEvaluateCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record AgentsEvaluateCommandResult(AgentsEvaluateResult Response); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsGetSdkSampleCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsGetSdkSampleCommand.cs deleted file mode 100644 index a42ff42d4e..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsGetSdkSampleCommand.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Agents; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public class AgentsGetSdkSampleCommand : BaseCommand -{ - private const string CommandTitle = "Get code samples to interact with a Foundry Agent using Microsoft Foundry SDK and programming language of your choice"; - public override string Id => "38cc4070-6704-49be-af1b-be55aec8d6d6"; - public override string Name => "get-sdk-sample"; - - public override string Description => - """ - Get code samples to interact with a Foundry Agent using Microsoft Foundry SDK and programming language of your choice. - """; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.ProgrammingLanguageOption); - } - - protected override AgentsGetSdkSampleOptions BindOptions(ParseResult parseResult) - { - var options = new AgentsGetSdkSampleOptions - { - ProgrammingLanguage = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ProgrammingLanguageOption) - }; - return options; - } - - public override string Title => CommandTitle; - - public override Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return Task.FromResult(context.Response); - } - - var options = BindOptions(parseResult); - - try - { - var programmingLanguageLowercase = options.ProgrammingLanguage!.ToLowerInvariant(); - - var service = context.GetService(); - var result = service.GetSdkCodeSample( - programmingLanguageLowercase); - - // If we can get a code sample, the programming language must be within one of our expected values so it's safe to log it. - context.Activity?.AddTag("programmingLanguage", programmingLanguageLowercase); - - context.Response.Results = ResponseResult.Create( - result, - FoundryJsonContext.Default.AgentsGetSdkCodeSampleResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return Task.FromResult(context.Response); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsListCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsListCommand.cs deleted file mode 100644 index c062f78de7..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsListCommand.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.AI.Agents.Persistent; -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class AgentsListCommand : GlobalCommand -{ - private const string CommandTitle = "List Evaluation Agents"; - - public override string Id => "8238b073-a302-49e6-8a27-8aab04c848fe"; - - public override string Name => "list"; - - public override string Description => - """ - List all Azure AI Agents in a Microsoft Foundry project. Shows agents that can be used for AI workflows, - evaluations, and interactive tasks. Requires the project endpoint URL (format: https://.services.ai.azure.com/api/projects/). - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - } - - protected override AgentsListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - var agents = await service.ListAgents( - options.Endpoint!, - options.Tenant, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = agents?.Count > 0 ? - ResponseResult.Create( - new AgentsListCommandResult(agents), - FoundryJsonContext.Default.AgentsListCommandResult) : - null; - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record AgentsListCommandResult(IEnumerable Agents); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsQueryAndEvaluate.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsQueryAndEvaluate.cs deleted file mode 100644 index 746ccb281e..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/AgentsQueryAndEvaluate.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class AgentsQueryAndEvaluateCommand : GlobalCommand -{ - private const string CommandTitle = "Query and Evaluate Agent"; - - public override string Name => "query-and-evaluate"; - - public override string Description => - """ - Query an agent and evaluate its response in a single operation. - Returns both the agent response and evaluation results - """; - - public override string Id => "0d8b60ac-4567-4420-bf9f-8d4ed27cc780"; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = false, - OpenWorld = true, - ReadOnly = false, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.AgentIdOption); - command.Options.Add(FoundryOptionDefinitions.QueryOption); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - command.Options.Add(FoundryOptionDefinitions.EvaluatorsOption); - command.Options.Add(FoundryOptionDefinitions.AzureOpenAIEndpointOption); - command.Options.Add(FoundryOptionDefinitions.AzureOpenAIDeploymentOption); - } - - protected override AgentsQueryAndEvaluateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption); - options.AgentId = parseResult.GetValueOrDefault(FoundryOptionDefinitions.AgentIdOption); - options.Query = parseResult.GetValueOrDefault(FoundryOptionDefinitions.QueryOption); - options.Evaluators = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EvaluatorsOption); - options.AzureOpenAIEndpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.AzureOpenAIEndpointOption); - options.AzureOpenAIDeployment = parseResult.GetValueOrDefault(FoundryOptionDefinitions.AzureOpenAIDeploymentOption); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - var result = await service.QueryAndEvaluateAgent( - options.AgentId!, - options.Query!, - options.Endpoint!, - options.AzureOpenAIEndpoint!, - options.AzureOpenAIDeployment!, - options.Tenant, - options.Evaluators?.Split(',').Select(e => e.Trim()).ToList(), - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new AgentsQueryAndEvaluateCommandResult(result), - FoundryJsonContext.Default.AgentsQueryAndEvaluateCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record AgentsQueryAndEvaluateCommandResult(AgentsQueryAndEvaluateResult Response); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/DeploymentsListCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/DeploymentsListCommand.cs deleted file mode 100644 index 30917fa208..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/DeploymentsListCommand.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.AI.Projects; -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class DeploymentsListCommand : GlobalCommand -{ - private const string CommandTitle = "List Deployments from Azure AI Services"; - - public override string Id => "6739126a-1f3c-468d-af2b-7b0c111c1679"; - - public override string Name => "list"; - - public override string Description => - """ - List model deployments in a Microsoft Foundry (Cognitive Services) project. Shows currently deployed AI models at the project level. - Use this to audit what models are deployed before invoking or creating new deployments. Requires the project - endpoint URL (format: https://.services.ai.azure.com/api/projects/). Note: This lists - deployed models only - use models_list to discover available catalog/base models. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - } - - protected override DeploymentsListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption.Name); - - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - - var service = context.GetService(); - var deployments = await service.ListDeployments( - options.Endpoint!, - options.Tenant, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create(new(deployments ?? []), FoundryJsonContext.Default.DeploymentsListCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record DeploymentsListCommandResult(IEnumerable Deployments); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs deleted file mode 100644 index b0c519a704..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options.Thread; -using Azure.Mcp.Tools.Foundry.Services; -using Azure.Mcp.Tools.Foundry.Services.Models; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.AI.Evaluation; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -[JsonSerializable(typeof(ModelsListCommand.ModelsListCommandResult))] -[JsonSerializable(typeof(DeploymentsListCommand.DeploymentsListCommandResult))] -[JsonSerializable(typeof(ModelDeploymentCommand.ModelDeploymentCommandResult))] -[JsonSerializable(typeof(AgentsListCommand.AgentsListCommandResult))] -[JsonSerializable(typeof(AgentsConnectCommand.AgentsConnectCommandResult))] -[JsonSerializable(typeof(KnowledgeIndexListCommand.KnowledgeIndexListCommandResult))] -[JsonSerializable(typeof(KnowledgeIndexSchemaCommand.KnowledgeIndexSchemaCommandResult))] -[JsonSerializable(typeof(OpenAiCompletionsCreateCommand.OpenAiCompletionsCreateCommandResult))] -[JsonSerializable(typeof(OpenAiEmbeddingsCreateCommand.OpenAiEmbeddingsCreateCommandResult))] -[JsonSerializable(typeof(OpenAiModelsListCommand.OpenAiModelsListCommandResult))] -[JsonSerializable(typeof(OpenAiChatCompletionsCreateCommand.OpenAiChatCompletionsCreateCommandResult))] -[JsonSerializable(typeof(ResourceGetCommand.ResourceGetCommandResult))] -[JsonSerializable(typeof(AiResourceInformation))] -[JsonSerializable(typeof(DeploymentInformation))] -[JsonSerializable(typeof(ChatCompletionCreateResult))] -[JsonSerializable(typeof(ChatCompletionResult))] -[JsonSerializable(typeof(ChatCompletionChoice))] -[JsonSerializable(typeof(ChatCompletionMessage))] -[JsonSerializable(typeof(ChatCompletionUsageInfo))] -[JsonSerializable(typeof(JsonElement))] -[JsonSerializable(typeof(ModelCatalogFilter))] -[JsonSerializable(typeof(ModelCatalogRequest))] -[JsonSerializable(typeof(ModelCatalogResponse))] -[JsonSerializable(typeof(ModelDeploymentInformation))] -[JsonSerializable(typeof(ModelInformation))] -[JsonSerializable(typeof(ModelDeploymentResult))] -[JsonSerializable(typeof(KnowledgeIndexInformation))] -[JsonSerializable(typeof(KnowledgeIndexSchema))] -[JsonSerializable(typeof(CompletionResult))] -[JsonSerializable(typeof(CompletionUsageInfo))] -[JsonSerializable(typeof(EmbeddingResult))] -[JsonSerializable(typeof(EmbeddingData))] -[JsonSerializable(typeof(EmbeddingUsageInfo))] -[JsonSerializable(typeof(OpenAiModelsListResult))] -[JsonSerializable(typeof(OpenAiModelDeployment))] -[JsonSerializable(typeof(OpenAiModelCapabilities))] -[JsonSerializable(typeof(CognitiveServicesSku))] -[JsonSerializable(typeof(CognitiveServicesAccountDeploymentProperties))] -[JsonSerializable(typeof(AgentsQueryAndEvaluateCommand.AgentsQueryAndEvaluateCommandResult))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(EvaluationResult))] -[JsonSerializable(typeof(AgentsEvaluateCommand.AgentsEvaluateCommandResult))] -[JsonSerializable(typeof(AgentsConnectResult))] -[JsonSerializable(typeof(AgentsQueryAndEvaluateResult))] -[JsonSerializable(typeof(AgentsEvaluateResult))] -[JsonSerializable(typeof(AgentsGetSdkCodeSampleResult))] -[JsonSerializable(typeof(CognitiveServicesAccountDeploymentData))] -[JsonSerializable(typeof(FoundryService.ToolDefinitionAIFunction))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(AgentsCreateResult))] -[JsonSerializable(typeof(ThreadCreateResult))] -[JsonSerializable(typeof(ThreadListResult))] -[JsonSerializable(typeof(ThreadGetMessagesResult))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(AgentFileSearchResult))] -[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault)] -internal sealed partial class FoundryJsonContext : JsonSerializerContext; diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/KnowledgeIndexListCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/KnowledgeIndexListCommand.cs deleted file mode 100644 index 5067daace5..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/KnowledgeIndexListCommand.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class KnowledgeIndexListCommand : GlobalCommand -{ - private const string CommandTitle = "List Knowledge Indexes in Microsoft Foundry"; - - public override string Id => "346847c5-61b7-4f14-b484-e28ee3e7c0f0"; - - public override string Name => "list"; - - public override string Description => - """ - Retrieves a list of knowledge indexes from Microsoft Foundry. - - This function is used when a user requests information about the available knowledge indexes in Microsoft Foundry. It provides an overview of the knowledge bases and search indexes that are currently deployed and available for use with AI agents and applications. - Requires the project endpoint URL (format: https://.services.ai.azure.com/api/projects/). - Usage: - Use this function when a user wants to explore the available knowledge indexes in Microsoft Foundry. This can help users understand what knowledge bases are currently operational and how they can be utilized for retrieval-augmented generation (RAG) scenarios. - - Notes: - - The indexes listed are knowledge indexes specifically created within Microsoft Foundry projects. - - These indexes can be used with AI agents for knowledge retrieval and RAG applications. - - The list may change as new indexes are created or existing ones are updated. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - } - - protected override KnowledgeIndexListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption.Name); - - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - var indexes = await service.ListKnowledgeIndexes( - options.Endpoint!, - options.Tenant, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create(new(indexes ?? []), FoundryJsonContext.Default.KnowledgeIndexListCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record KnowledgeIndexListCommandResult(IEnumerable Indexes); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/KnowledgeIndexSchemaCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/KnowledgeIndexSchemaCommand.cs deleted file mode 100644 index 426d4355a2..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/KnowledgeIndexSchemaCommand.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class KnowledgeIndexSchemaCommand : GlobalCommand -{ - private const string CommandTitle = "Get Knowledge Index Schema in Microsoft Foundry"; - - public override string Id => "7a7453e2-c021-4554-bf71-f95eb3e8c874"; - - public override string Name => "schema"; - - public override string Description => - """ - Retrieves the detailed schema configuration of a specific knowledge index from Microsoft Foundry. - - This function provides comprehensive information about the structure and configuration of a knowledge index, including field definitions, data types, searchable attributes, and other schema properties. The schema information is essential for understanding how the index is structured and how data is indexed and searchable. - - Usage: - Use this function when you need to examine the detailed configuration of a specific knowledge index. This is helpful for troubleshooting search issues, understanding index capabilities, planning data mapping, or when integrating with the index programmatically. - - Notes: - - Returns the index schema. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - command.Options.Add(FoundryOptionDefinitions.IndexNameOption); - } - - protected override KnowledgeIndexSchemaOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption.Name); - options.IndexName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.IndexNameOption.Name); - - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - var indexSchema = await service.GetKnowledgeIndexSchema( - options.Endpoint!, - options.IndexName!, - options.Tenant, - options.RetryPolicy, - cancellationToken: cancellationToken); - - if (indexSchema == null) - { - throw new Exception("Failed to retrieve knowledge index schema - no data returned."); - } - - context.Response.Results = ResponseResult.Create(new(indexSchema), FoundryJsonContext.Default.KnowledgeIndexSchemaCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record KnowledgeIndexSchemaCommandResult(KnowledgeIndexSchema Schema); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ModelDeploymentCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/ModelDeploymentCommand.cs deleted file mode 100644 index 064aa2d014..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ModelDeploymentCommand.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands.Subscription; -using Azure.Mcp.Core.Models.Option; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class ModelDeploymentCommand : SubscriptionCommand -{ - private const string CommandTitle = "Deploy Model to Azure AI Services"; - - public override string Id => "67ef0a4a-3d5b-4d9e-8f07-f2fdc0f60fba"; - - public override string Name => "deploy"; - - public override string Description => - "Deploys (create) a model instance (e.g. GPT4o / gpt-4o, OpenAI, OSS, proprietary) in Microsoft Foundry as a named inference deployment, which's bound to a specified Azure AI Services resource, resource group, and subscription. Do not use this tool for for Azure OpenAI Services to deploy OpenAI models."; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = true, - Idempotent = false, - OpenWorld = false, - ReadOnly = false, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired()); - command.Options.Add(FoundryOptionDefinitions.DeploymentNameOption); - command.Options.Add(FoundryOptionDefinitions.ModelNameOption); - command.Options.Add(FoundryOptionDefinitions.ModelFormatOption); - command.Options.Add(FoundryOptionDefinitions.AzureAiServicesNameOption); - command.Options.Add(FoundryOptionDefinitions.ModelVersionOption); - command.Options.Add(FoundryOptionDefinitions.ModelSourceOption); - command.Options.Add(FoundryOptionDefinitions.SkuNameOption); - command.Options.Add(FoundryOptionDefinitions.SkuCapacityOption); - command.Options.Add(FoundryOptionDefinitions.ScaleTypeOption); - command.Options.Add(FoundryOptionDefinitions.ScaleCapacityOption); - } - - protected override ModelDeploymentOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.DeploymentName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.DeploymentNameOption.Name); - options.ModelName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ModelNameOption.Name); - options.ModelFormat = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ModelFormatOption.Name); - options.AzureAiServicesName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.AzureAiServicesNameOption.Name); - options.ModelVersion = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ModelVersionOption.Name); - options.ModelSource = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ModelSourceOption.Name); - options.SkuName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.SkuNameOption.Name); - options.SkuCapacity = parseResult.GetValueOrDefault(FoundryOptionDefinitions.SkuCapacityOption.Name); - options.ScaleType = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ScaleTypeOption.Name); - options.ScaleCapacity = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ScaleCapacityOption.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - - var service = context.GetService(); - var deploymentResource = await service.DeployModel( - options.DeploymentName!, - options.ModelName!, - options.ModelFormat!, - options.AzureAiServicesName!, - options.ResourceGroup!, - options.Subscription!, - options.ModelVersion, - options.ModelSource, - options.SkuName, - options.SkuCapacity, - options.ScaleType, - options.ScaleCapacity, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create(new(deploymentResource), FoundryJsonContext.Default.ModelDeploymentCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record ModelDeploymentCommandResult(ModelDeploymentResult DeploymentData); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ModelsListCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/ModelsListCommand.cs deleted file mode 100644 index 3fa5b4346a..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ModelsListCommand.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class ModelsListCommand : GlobalCommand -{ - private const string CommandTitle = "List Models from Model Catalog"; - - public override string Id => "cd46271e-f665-480d-ac48-abd949beec45"; - - public override string Name => "list"; - - public override string Description => - """ - Retrieves a list of supported models from the Microsoft Foundry catalog. - This function is useful when a user requests a list of available Foundry models or Foundry Labs projects. - It fetches models based on optional filters like whether the model supports free playground usage, - the publisher name, and the license type. The function will return the list of models with useful fields. - Usage: - Use this function when users inquire about available models from the Microsoft Foundry catalog. - It can also be used when filtering models by free playground usage, publisher name, or license type. - If user didn't specify free playground or ask for models that support GitHub token, always explain that by default it will show the all the models but some of them would support free playground. - Explain to the user that if they want to find models suitable for prototyping and free to use with support for free playground, they can look for models that supports free playground, or look for models that they can use with GitHub token. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.SearchForFreePlaygroundOption); - command.Options.Add(FoundryOptionDefinitions.PublisherNameOption); - command.Options.Add(FoundryOptionDefinitions.LicenseNameOption); - command.Options.Add(FoundryOptionDefinitions.ModelNameOption.AsOptional()); - } - - protected override ModelsListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.SearchForFreePlayground = parseResult.GetValueOrDefault(FoundryOptionDefinitions.SearchForFreePlaygroundOption.Name); - options.PublisherName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.PublisherNameOption.Name); - options.LicenseName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.LicenseNameOption.Name); - options.ModelName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ModelNameOption.Name); - - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - var models = await service.ListModels( - options.SearchForFreePlayground ?? false, - options.PublisherName ?? "", - options.LicenseName ?? "", - options.ModelName ?? "", - 3, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create(new(models ?? []), FoundryJsonContext.Default.ModelsListCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record ModelsListCommandResult(IEnumerable Models); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiChatCompletionsCreateCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiChatCompletionsCreateCommand.cs deleted file mode 100644 index a7d332333e..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiChatCompletionsCreateCommand.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Nodes; -using Azure.Mcp.Core.Commands.Subscription; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Models.Option; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class OpenAiChatCompletionsCreateCommand : SubscriptionCommand -{ - private const string CommandTitle = "Create OpenAI Chat Completions"; - - public override string Id => "aa674825-1463-464d-9b2c-f8315f778d0d"; - - public override string Name => "chat-completions-create"; - - public override string Description => - $""" - Create chat completions using Azure OpenAI in Microsoft Foundry. Send messages to Azure OpenAI chat models deployed - in your Microsoft Foundry resource and receive AI-generated conversational responses. Supports multi-turn conversations - with message history, system instructions, and response customization. Use this when you need to create chat - completions, have AI conversations, get conversational responses, or build interactive dialogues with Azure OpenAI. - Requires resource-name, deployment-name, and message-array. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = false, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired()); - command.Options.Add(FoundryOptionDefinitions.ResourceNameOption); - command.Options.Add(FoundryOptionDefinitions.DeploymentNameOption); - command.Options.Add(FoundryOptionDefinitions.MessageArrayOption); - command.Options.Add(FoundryOptionDefinitions.MaxTokensOption); - command.Options.Add(FoundryOptionDefinitions.TemperatureOption); - command.Options.Add(FoundryOptionDefinitions.TopPOption); - command.Options.Add(FoundryOptionDefinitions.FrequencyPenaltyOption); - command.Options.Add(FoundryOptionDefinitions.PresencePenaltyOption); - command.Options.Add(FoundryOptionDefinitions.StopOption); - command.Options.Add(FoundryOptionDefinitions.StreamOption); - command.Options.Add(FoundryOptionDefinitions.SeedOption); - command.Options.Add(FoundryOptionDefinitions.UserOption); - } - - protected override OpenAiChatCompletionsCreateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.ResourceName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ResourceNameOption.Name); - options.DeploymentName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.DeploymentNameOption.Name); - options.MessageArray = parseResult.GetValueOrDefault(FoundryOptionDefinitions.MessageArrayOption.Name); - options.MaxTokens = parseResult.GetValueOrDefault(FoundryOptionDefinitions.MaxTokensOption.Name); - options.Temperature = parseResult.GetValueOrDefault(FoundryOptionDefinitions.TemperatureOption.Name); - options.TopP = parseResult.GetValueOrDefault(FoundryOptionDefinitions.TopPOption.Name); - options.FrequencyPenalty = parseResult.GetValueOrDefault(FoundryOptionDefinitions.FrequencyPenaltyOption.Name); - options.PresencePenalty = parseResult.GetValueOrDefault(FoundryOptionDefinitions.PresencePenaltyOption.Name); - options.Stop = parseResult.GetValueOrDefault(FoundryOptionDefinitions.StopOption.Name); - options.Stream = parseResult.GetValueOrDefault(FoundryOptionDefinitions.StreamOption.Name); - options.Seed = parseResult.GetValueOrDefault(FoundryOptionDefinitions.SeedOption.Name); - options.User = parseResult.GetValueOrDefault(FoundryOptionDefinitions.UserOption.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - try - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - var foundryService = context.GetService(); - - // Parse the message array - var messages = new List(); - if (!string.IsNullOrEmpty(options.MessageArray)) - { - using var jsonDocument = JsonDocument.Parse(options.MessageArray); - foreach (var element in jsonDocument.RootElement.EnumerateArray()) - { - // Convert JsonElement to JsonObject for proper type matching in service - var jsonNode = JsonNode.Parse(element.GetRawText()); - if (jsonNode is JsonObject jsonObj) - { - messages.Add(jsonObj); - } - } - } - - var result = await foundryService.CreateChatCompletionsAsync( - options.ResourceName!, - options.DeploymentName!, - options.Subscription!, - options.ResourceGroup!, - messages, - options.MaxTokens, - options.Temperature, - options.TopP, - options.FrequencyPenalty, - options.PresencePenalty, - options.Stop, - options.Stream, - options.Seed, - options.User, - options.Tenant, - options.AuthMethod ?? AuthMethod.Credential, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new OpenAiChatCompletionsCreateCommandResult(result, options.ResourceName!, options.DeploymentName!), - FoundryJsonContext.Default.OpenAiChatCompletionsCreateCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record OpenAiChatCompletionsCreateCommandResult( - ChatCompletionResult Result, - string ResourceName, - string DeploymentName); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiCompletionsCreateCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiCompletionsCreateCommand.cs deleted file mode 100644 index ac94183b06..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiCompletionsCreateCommand.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands.Subscription; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Models.Option; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class OpenAiCompletionsCreateCommand : SubscriptionCommand -{ - private const string CommandTitle = "Create OpenAI Completion"; - - public override string Id => "c42f95c1-7744-48da-852d-c50444c3c35c"; - - public override string Name => "create-completion"; - - public override string Description => - $""" - Create text completions using Azure OpenAI in Microsoft Foundry. Send a prompt or question to Azure OpenAI models - deployed in your Microsoft Foundry resource and receive generated text answers. Use this when you need to create - completions, get AI-generated content, generate answers to questions, or produce text completions from Azure - OpenAI based on any input prompt. Supports customization with temperature and max tokens. - Requires resource-name, deployment-name, and prompt-text. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = false, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired()); - command.Options.Add(FoundryOptionDefinitions.ResourceNameOption); - command.Options.Add(FoundryOptionDefinitions.DeploymentNameOption); - command.Options.Add(FoundryOptionDefinitions.PromptTextOption); - command.Options.Add(FoundryOptionDefinitions.MaxTokensOption); - command.Options.Add(FoundryOptionDefinitions.TemperatureOption); - } - - protected override OpenAiCompletionsCreateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.ResourceName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ResourceNameOption.Name); - options.DeploymentName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.DeploymentNameOption.Name); - options.PromptText = parseResult.GetValueOrDefault(FoundryOptionDefinitions.PromptTextOption.Name); - options.MaxTokens = parseResult.GetValueOrDefault(FoundryOptionDefinitions.MaxTokensOption.Name); - options.Temperature = parseResult.GetValueOrDefault(FoundryOptionDefinitions.TemperatureOption.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - try - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - var foundryService = context.GetService(); - var result = await foundryService.CreateCompletionAsync( - options.ResourceName!, - options.DeploymentName!, - options.PromptText!, - options.Subscription!, - options.ResourceGroup!, - options.MaxTokens, - options.Temperature, - options.Tenant, - options.AuthMethod ?? AuthMethod.Credential, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new OpenAiCompletionsCreateCommandResult(result.CompletionText, result.UsageInfo), - FoundryJsonContext.Default.OpenAiCompletionsCreateCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record OpenAiCompletionsCreateCommandResult(string CompletionText, CompletionUsageInfo UsageInfo); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiEmbeddingsCreateCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiEmbeddingsCreateCommand.cs deleted file mode 100644 index 197602eccd..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiEmbeddingsCreateCommand.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands.Subscription; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Models.Option; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class OpenAiEmbeddingsCreateCommand : SubscriptionCommand -{ - private const string CommandTitle = "Create OpenAI Embeddings"; - - public override string Id => "8b10845b-bb51-4cc4-b5dd-aacc874d4a0a"; - - public override string Name => "embeddings-create"; - - public override string Description => - $""" - Create embeddings using Azure OpenAI in Microsoft Foundry. Generate vector embeddings from text using Azure OpenAI - deployments in your Microsoft Foundry resource for semantic search, similarity comparisons, clustering, or machine - learning. Use this when you need to create foundry embeddings, generate vectors from text, or convert text to - numerical representations using Azure OpenAI. Requires resource-name, deployment-name, and input-text. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = false, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired()); - command.Options.Add(FoundryOptionDefinitions.ResourceNameOption); - command.Options.Add(FoundryOptionDefinitions.DeploymentNameOption); - command.Options.Add(FoundryOptionDefinitions.InputTextOption); - command.Options.Add(FoundryOptionDefinitions.UserOption); - command.Options.Add(FoundryOptionDefinitions.EncodingFormatOption); - command.Options.Add(FoundryOptionDefinitions.DimensionsOption); - } - - protected override OpenAiEmbeddingsCreateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.ResourceName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ResourceNameOption.Name); - options.DeploymentName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.DeploymentNameOption.Name); - options.InputText = parseResult.GetValueOrDefault(FoundryOptionDefinitions.InputTextOption.Name); - options.User = parseResult.GetValueOrDefault(FoundryOptionDefinitions.UserOption.Name); - options.EncodingFormat = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EncodingFormatOption.Name); - options.Dimensions = parseResult.GetValueOrDefault(FoundryOptionDefinitions.DimensionsOption.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - try - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - var foundryService = context.GetService(); - var result = await foundryService.CreateEmbeddingsAsync( - options.ResourceName!, - options.DeploymentName!, - options.InputText!, - options.Subscription!, - options.ResourceGroup!, - options.User, - options.EncodingFormat!, - options.Dimensions, - options.Tenant, - options.AuthMethod ?? AuthMethod.Credential, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new OpenAiEmbeddingsCreateCommandResult(result, options.ResourceName!, options.DeploymentName!, options.InputText!), - FoundryJsonContext.Default.OpenAiEmbeddingsCreateCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record OpenAiEmbeddingsCreateCommandResult( - EmbeddingResult EmbeddingResult, - string ResourceName, - string DeploymentName, - string InputText); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiModelsListCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiModelsListCommand.cs deleted file mode 100644 index be8d9a6743..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/OpenAiModelsListCommand.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands.Subscription; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Models.Option; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class OpenAiModelsListCommand : SubscriptionCommand -{ - private const string CommandTitle = "List OpenAI Models"; - - public override string Id => "7453dac1-9767-4613-a8f2-80c996af6da2"; - - public override string Name => "models-list"; - - public override string Description => - $""" - List all available Azure OpenAI models and deployments in a Microsoft Foundry resource. This tool retrieves information - about Azure OpenAI models deployed in your Microsoft Foundry resource including model names, versions, capabilities, - and deployment status. Use this when you need to see what OpenAI models are available, check model deployments, - or list Azure OpenAI models in your foundry resource. Returns model information as JSON array. Requires resource-name. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired()); - command.Options.Add(FoundryOptionDefinitions.ResourceNameOption); - } - - protected override OpenAiModelsListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.ResourceName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ResourceNameOption.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - try - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - var foundryService = context.GetService(); - var result = await foundryService.ListOpenAiModelsAsync( - options.ResourceName!, - options.Subscription!, - options.ResourceGroup!, - options.Tenant, - options.AuthMethod ?? AuthMethod.Credential, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new OpenAiModelsListCommandResult(result, options.ResourceName!), - FoundryJsonContext.Default.OpenAiModelsListCommandResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } - - internal record OpenAiModelsListCommandResult( - OpenAiModelsListResult ModelsListResult, - string ResourceName); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ResourceGetCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/ResourceGetCommand.cs deleted file mode 100644 index 3b7e9bf591..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ResourceGetCommand.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Commands.Subscription; -using Azure.Mcp.Core.Models.Option; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public sealed class ResourceGetCommand(ILogger logger) : SubscriptionCommand() -{ - private const string CommandTitle = "Get Microsoft Foundry Resource Details"; - private readonly ILogger _logger = logger; - - public override string Id => "422dd6ec-d36e-4ff3-8e0d-b57e892c325e"; - - public override string Name => "get"; - - public override string Description => - """ - Gets detailed information about Microsoft Foundry (Cognitive Services) resources, including endpoint URL, - location, SKU, and all deployed models with their configuration. If a specific resource name is provided, - returns details for that resource only. If no resource name is provided, lists all Microsoft Foundry resources - in the subscription or resource group. Use this tool when users need endpoint information, want to discover - available AI resources, or need to see all models deployed on AI resources. - """; - - public override string Title => CommandTitle; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsOptional()); - command.Options.Add(FoundryOptionDefinitions.ResourceNameOption.AsOptional()); - } - - protected override ResourceGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup = parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.ResourceName = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ResourceNameOption.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - - // If resource name and resource group are provided, get specific resource - if (!string.IsNullOrEmpty(options.ResourceName) && !string.IsNullOrEmpty(options.ResourceGroup)) - { - var resource = await service.GetAiResourceAsync( - options.Subscription!, - options.ResourceGroup!, - options.ResourceName!, - options.Tenant, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new ResourceGetCommandResult([resource]), - FoundryJsonContext.Default.ResourceGetCommandResult); - } - // Otherwise, list all resources in subscription/resource group - else - { - var resources = await service.ListAiResourcesAsync( - options.Subscription!, - options.ResourceGroup, - options.Tenant, - options.RetryPolicy, - cancellationToken: cancellationToken); - - context.Response.Results = ResponseResult.Create( - new ResourceGetCommandResult(resources ?? []), - FoundryJsonContext.Default.ResourceGetCommandResult); - } - } - catch (Exception ex) - { - if (string.IsNullOrEmpty(options.ResourceName)) - { - _logger.LogError(ex, "Error listing AI resources. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, Options: {@Options}", - options.Subscription, options.ResourceGroup, options); - } - else - { - _logger.LogError(ex, "Error getting AI resource. ResourceName: {ResourceName}, ResourceGroup: {ResourceGroup}, Subscription: {Subscription}, Options: {@Options}", - options.ResourceName, options.ResourceGroup, options.Subscription, options); - } - HandleException(context, ex); - } - - return context.Response; - } - - internal record ResourceGetCommandResult([property: JsonPropertyName("resources")] List Resources); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadCreateCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadCreateCommand.cs deleted file mode 100644 index dba5ead740..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadCreateCommand.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Thread; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public class ThreadCreateCommand : GlobalCommand -{ - private const string CommandTitle = "Create a Microsoft Foundry Agent Thread"; - public override string Id => "2a30ef19-fac5-4157-8a86-30591b15818a"; - public override string Name => "create"; - - - public override string Description => - """ - Creates a Microsoft Foundry Agent Thread that holds the messages between the Agent and the user. - """; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = false, - OpenWorld = false, - ReadOnly = false, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - command.Options.Add(FoundryOptionDefinitions.UserMessageOption); - } - - protected override ThreadCreateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption); - options.UserMessage = parseResult.GetValueOrDefault(FoundryOptionDefinitions.UserMessageOption); - return options; - } - - public override string Title => CommandTitle; - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - ThreadCreateResult result = await service.CreateThread( - options.Endpoint!, - options.UserMessage!, - options.Tenant, - cancellationToken: cancellationToken - ); - - context.Response.Results = ResponseResult.Create( - result, - FoundryJsonContext.Default.ThreadCreateResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadGetMessagesCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadGetMessagesCommand.cs deleted file mode 100644 index 034e3e5993..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadGetMessagesCommand.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Thread; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public class ThreadGetMessagesCommand : GlobalCommand -{ - private const string CommandTitle = "Get messages in a Microsoft Foundry Agent Thread"; - public override string Id => "7769d80f-31b0-4bc3-9e34-991e23d00fee"; - public override string Name => "get-messages"; - - - public override string Description => - """ - Get messages in a Microsoft Foundry Agent Thread. - """; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - command.Options.Add(FoundryOptionDefinitions.ThreadIdOption); - } - - protected override ThreadGetMessagesOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption); - options.ThreadId = parseResult.GetValueOrDefault(FoundryOptionDefinitions.ThreadIdOption); - return options; - } - - public override string Title => CommandTitle; - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - ThreadGetMessagesResult result = await service.GetMessages( - options.Endpoint!, - options.ThreadId!, - options.Tenant, - cancellationToken: cancellationToken - ); - - context.Response.Results = ResponseResult.Create( - result, - FoundryJsonContext.Default.ThreadGetMessagesResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadListCommand.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadListCommand.cs deleted file mode 100644 index 7afe9831e8..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/ThreadListCommand.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Thread; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Models.Command; - -namespace Azure.Mcp.Tools.Foundry.Commands; - -public class ThreadListCommand : GlobalCommand -{ - private const string CommandTitle = "List Microsoft Foundry Agent Threads"; - public override string Id => "ec6ce496-cfae-45b6-8ab3-97fb55f861c8"; - public override string Name => "list"; - - - public override string Description => - """ - List Microsoft Foundry Agent Threads. - """; - - public override ToolMetadata Metadata => new() - { - Destructive = false, - Idempotent = true, - OpenWorld = false, - ReadOnly = true, - LocalRequired = false, - Secret = false - }; - - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(FoundryOptionDefinitions.EndpointOption); - } - - protected override ThreadListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Endpoint = parseResult.GetValueOrDefault(FoundryOptionDefinitions.EndpointOption); - return options; - } - - public override string Title => CommandTitle; - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - - try - { - var service = context.GetService(); - ThreadListResult result = await service.ListThreads( - options.Endpoint!, - options.Tenant, - cancellationToken: cancellationToken - ); - - context.Response.Results = ResponseResult.Create( - result, - FoundryJsonContext.Default.ThreadListResult); - } - catch (Exception ex) - { - HandleException(context, ex); - } - - return context.Response; - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/FoundrySetup.cs b/tools/Azure.Mcp.Tools.Foundry/src/FoundrySetup.cs deleted file mode 100644 index 1839476dba..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/FoundrySetup.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Areas; -using Microsoft.Mcp.Core.Commands; - -namespace Azure.Mcp.Tools.Foundry; - -public class FoundrySetup : IAreaSetup -{ - public string Name => "foundry"; - - public string Title => "Microsoft Foundry"; - - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - } - - public CommandGroup RegisterCommands(IServiceProvider serviceProvider) - { - var foundry = new CommandGroup(Name, "Foundry service operations - Commands for listing and managing services and resources in Microsoft Foundry.", Title); - - var models = new CommandGroup("models", "Foundry models operations - Commands for listing and managing models in Microsoft Foundry."); - foundry.AddSubGroup(models); - - var deployments = new CommandGroup("deployments", "Foundry models deployments operations - Commands for listing and managing models deployments in Microsoft Foundry."); - models.AddSubGroup(deployments); - - var deploymentList = serviceProvider.GetRequiredService(); - deployments.AddCommand(deploymentList.Name, deploymentList); - - var modelList = serviceProvider.GetRequiredService(); - models.AddCommand(modelList.Name, modelList); - - var deploymentCommand = serviceProvider.GetRequiredService(); - models.AddCommand(deploymentCommand.Name, deploymentCommand); - - var knowledge = new CommandGroup("knowledge", "Foundry knowledge operations - Commands for managing knowledge bases and indexes in Microsoft Foundry."); - foundry.AddSubGroup(knowledge); - - var index = new CommandGroup("index", "Foundry knowledge index operations - Commands for managing knowledge indexes in Microsoft Foundry."); - knowledge.AddSubGroup(index); - - var indexList = serviceProvider.GetRequiredService(); - index.AddCommand(indexList.Name, indexList); - - var indexSchema = serviceProvider.GetRequiredService(); - index.AddCommand(indexSchema.Name, indexSchema); - - var openai = new CommandGroup("openai", "Foundry OpenAI operations - Commands for working with Azure OpenAI models deployed in Microsoft Foundry."); - foundry.AddSubGroup(openai); - - openai.AddCommand("create-completion", new OpenAiCompletionsCreateCommand()); - openai.AddCommand("embeddings-create", new OpenAiEmbeddingsCreateCommand()); - openai.AddCommand("models-list", new OpenAiModelsListCommand()); - openai.AddCommand("chat-completions-create", new OpenAiChatCompletionsCreateCommand()); - var agents = new CommandGroup("agents", "Foundry agents operations - Commands for listing, creating, querying, and evaluating agents in Microsoft Foundry."); - foundry.AddSubGroup(agents); - - agents.AddCommand("list", serviceProvider.GetRequiredService()); - agents.AddCommand("create", serviceProvider.GetRequiredService()); - agents.AddCommand("connect", serviceProvider.GetRequiredService()); - agents.AddCommand("query-and-evaluate", serviceProvider.GetRequiredService()); - agents.AddCommand("evaluate", serviceProvider.GetRequiredService()); - agents.AddCommand("get-sdk-sample", serviceProvider.GetRequiredService()); - - var resources = new CommandGroup("resource", "Foundry resource operations - Commands for listing and managing Microsoft Foundry resources."); - foundry.AddSubGroup(resources); - - resources.AddCommand("get", serviceProvider.GetRequiredService()); - - var threads = new CommandGroup("threads", "Foundry agent threads operations - Commands for listing, creating threads and getting messages in a thread in Microsoft Foundry."); - foundry.AddSubGroup(threads); - - threads.AddCommand("create", serviceProvider.GetRequiredService()); - threads.AddCommand("list", serviceProvider.GetRequiredService()); - threads.AddCommand("get-messages", serviceProvider.GetRequiredService()); - - return foundry; - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/GlobalUsings.cs b/tools/Azure.Mcp.Tools.Foundry/src/GlobalUsings.cs deleted file mode 100644 index 6f50f61768..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/GlobalUsings.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -global using System.CommandLine; -global using System.Text.Json; -global using Azure.Mcp.Core.Extensions; diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentFileSearchResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentFileSearchResult.cs deleted file mode 100644 index ac11cc2d8c..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentFileSearchResult.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.AI.Agents.Persistent; - -namespace Azure.Mcp.Tools.Foundry.Models; - -internal class AgentFileSearchResult -{ - [JsonPropertyName("fileId")] - public string? FileId { get; set; } - - [JsonPropertyName("fileName")] - public string? FileName { get; set; } - - [JsonPropertyName("score")] - public float Score { get; set; } - - [JsonPropertyName("content")] - public IReadOnlyList? Content { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsConnectResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsConnectResult.cs deleted file mode 100644 index ebc8341183..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsConnectResult.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Microsoft.Extensions.AI; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class AgentsConnectResult -{ - [JsonPropertyName("agentId")] - public string? AgentId { get; set; } - - [JsonPropertyName("threadId")] - public string? ThreadId { get; set; } - - [JsonPropertyName("runId")] - public string? RunId { get; set; } - - [JsonPropertyName("responseText")] - public string? ResponseText { get; set; } - - [JsonPropertyName("queryText")] - public string? QueryText { get; set; } - - [JsonPropertyName("response")] - public List? Response { get; set; } - - [JsonPropertyName("query")] - public List? Query { get; set; } - - [JsonPropertyName("toolDefinitions")] - public string? ToolDefinitions { get; set; } - - [JsonPropertyName("citations")] - public List? Citations { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsCreateResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsCreateResult.cs deleted file mode 100644 index 10f3f24ca5..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsCreateResult.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class AgentsCreateResult -{ - [JsonPropertyName("agentId")] - public string? AgentId { get; set; } - - [JsonPropertyName("agentName")] - public string? AgentName { get; set; } - - [JsonPropertyName("projectEndpoint")] - public string? ProjectEndpoint { get; set; } - - [JsonPropertyName("modelDeploymentName")] - public string? ModelDeploymentName { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsEvaluateResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsEvaluateResult.cs deleted file mode 100644 index f7b091f277..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsEvaluateResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Microsoft.Extensions.AI.Evaluation; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class AgentsEvaluateResult -{ - [JsonPropertyName("evaluator")] - public string? Evaluator { get; set; } - - [JsonPropertyName("evaluationResult")] - public EvaluationResult? EvaluationResult { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsGetSdkCodeSampleResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsGetSdkCodeSampleResult.cs deleted file mode 100644 index 1cdb333b43..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsGetSdkCodeSampleResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class AgentsGetSdkCodeSampleResult -{ - [JsonPropertyName("codeSampleText")] - public string? CodeSampleText { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsQueryAndEvaluateResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsQueryAndEvaluateResult.cs deleted file mode 100644 index 201c348ff8..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentsQueryAndEvaluateResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Microsoft.Extensions.AI.Evaluation; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class AgentsQueryAndEvaluateResult : AgentsConnectResult -{ - [JsonPropertyName("evaluators")] - public List? Evaluators { get; set; } - - [JsonPropertyName("evaluationResult")] - public EvaluationResult? EvaluationResult { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/AiResourceInformation.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/AiResourceInformation.cs deleted file mode 100644 index ce651407ba..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/AiResourceInformation.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class AiResourceInformation -{ - public string? ResourceName { get; set; } - public string? ResourceGroup { get; set; } - public string? SubscriptionName { get; set; } - public string? Location { get; set; } - public string? Endpoint { get; set; } - public string? Kind { get; set; } - public string? SkuName { get; set; } - public List? Deployments { get; set; } -} - -public class DeploymentInformation -{ - public string? DeploymentName { get; set; } - public string? ModelName { get; set; } - public string? ModelVersion { get; set; } - public string? ModelFormat { get; set; } - public string? SkuName { get; set; } - public int? SkuCapacity { get; set; } - public string? ScaleType { get; set; } - public string? ProvisioningState { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/ChatCompletionModels.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/ChatCompletionModels.cs deleted file mode 100644 index 4a3a6b6c33..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/ChatCompletionModels.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public sealed record ChatCompletionResult( - [property: JsonPropertyName("id")] string Id, - [property: JsonPropertyName("object")] string Object, - [property: JsonPropertyName("created")] long Created, - [property: JsonPropertyName("model")] string Model, - [property: JsonPropertyName("choices")] List Choices, - [property: JsonPropertyName("usage")] ChatCompletionUsageInfo Usage, - [property: JsonPropertyName("system_fingerprint")] string? SystemFingerprint); - -public sealed record ChatCompletionChoice( - [property: JsonPropertyName("index")] int Index, - [property: JsonPropertyName("message")] ChatCompletionMessage Message, - [property: JsonPropertyName("finish_reason")] string? FinishReason, - [property: JsonPropertyName("logprobs")] object? LogProbs); - -public sealed record ChatCompletionMessage( - [property: JsonPropertyName("role")] string Role, - [property: JsonPropertyName("content")] string? Content, - [property: JsonPropertyName("name")] string? Name, - [property: JsonPropertyName("function_call")] object? FunctionCall, - [property: JsonPropertyName("tool_calls")] object? ToolCalls); - -public sealed record ChatCompletionUsageInfo( - [property: JsonPropertyName("prompt_tokens")] int PromptTokens, - [property: JsonPropertyName("completion_tokens")] int CompletionTokens, - [property: JsonPropertyName("total_tokens")] int TotalTokens); - -public sealed record ChatCompletionCreateResult( - [property: JsonPropertyName("result")] ChatCompletionResult Result); diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/CompletionModels.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/CompletionModels.cs deleted file mode 100644 index f70d736043..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/CompletionModels.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public sealed record CompletionResult( - [property: JsonPropertyName("completionText")] string CompletionText, - [property: JsonPropertyName("usageInfo")] CompletionUsageInfo UsageInfo); - -public sealed record CompletionUsageInfo( - [property: JsonPropertyName("promptTokens")] int PromptTokens, - [property: JsonPropertyName("completionTokens")] int CompletionTokens, - [property: JsonPropertyName("totalTokens")] int TotalTokens); diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/EmbeddingModels.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/EmbeddingModels.cs deleted file mode 100644 index 82748fd40f..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/EmbeddingModels.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public sealed record EmbeddingResult( - [property: JsonPropertyName("object")] string Object, - [property: JsonPropertyName("data")] List Data, - [property: JsonPropertyName("model")] string Model, - [property: JsonPropertyName("usage")] EmbeddingUsageInfo Usage); - -public sealed record EmbeddingData( - [property: JsonPropertyName("object")] string Object, - [property: JsonPropertyName("index")] int Index, - [property: JsonPropertyName("embedding")] float[] Embedding); - -public sealed record EmbeddingUsageInfo( - [property: JsonPropertyName("prompt_tokens")] int PromptTokens, - [property: JsonPropertyName("total_tokens")] int TotalTokens); diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/KnowledgeIndexInformation.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/KnowledgeIndexInformation.cs deleted file mode 100644 index 7d844dbc22..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/KnowledgeIndexInformation.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class KnowledgeIndexInformation -{ - [JsonPropertyName("type")] - public string? Type { get; set; } - - [JsonPropertyName("id")] - public string? Id { get; set; } - - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("version")] - public string? Version { get; set; } - - [JsonPropertyName("description")] - public string? Description { get; set; } - - [JsonPropertyName("tags")] - public Dictionary? Tags { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/KnowledgeIndexSchema.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/KnowledgeIndexSchema.cs deleted file mode 100644 index a833437af2..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/KnowledgeIndexSchema.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -/// -/// A simplified, AOT-safe representation of a knowledge index schema. -/// -public sealed class KnowledgeIndexSchema -{ - [JsonPropertyName("type")] - public string? Type { get; set; } - - [JsonPropertyName("id")] - public string? Id { get; set; } - - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("version")] - public string? Version { get; set; } - - [JsonPropertyName("description")] - public string? Description { get; set; } - - [JsonPropertyName("tags")] - public Dictionary? Tags { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogFilter.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogFilter.cs deleted file mode 100644 index e3fc6885fb..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogFilter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class ModelCatalogFilter(string field, string[] values, string @operator) -{ - [JsonPropertyName("field")] - public string Field { get; set; } = field; - - [JsonPropertyName("values")] - public string[] Values { get; set; } = values; - - [JsonPropertyName("operator")] - public string Operator { get; set; } = @operator; -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogRequest.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogRequest.cs deleted file mode 100644 index 942eafc377..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogRequest.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class ModelCatalogRequest -{ - [JsonPropertyName("filters")] - public List Filters { get; set; } = []; - - [JsonPropertyName("continuationToken")] - public string? ContinuationToken { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogResponse.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogResponse.cs deleted file mode 100644 index 76073a5a00..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelCatalogResponse.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class ModelCatalogResponse -{ - [JsonPropertyName("summaries")] public List Summaries { get; set; } = []; - - [JsonPropertyName("totalCount")] public int TotalCount { get; set; } - - [JsonPropertyName("continuationToken")] - public string? ContinuationToken { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelDeploymentInformation.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelDeploymentInformation.cs deleted file mode 100644 index b1806a2354..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelDeploymentInformation.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class ModelDeploymentInformation -{ - [JsonPropertyName("openai")] public bool IsOpenAI { get; set; } - - [JsonPropertyName("serverless_endpoint")] - public bool IsServerlessEndpoint { get; set; } - - [JsonPropertyName("managed_compute")] public bool IsManagedCompute { get; set; } - - [JsonPropertyName("free_playground")] public bool IsFreePlayground { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelDeploymentResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelDeploymentResult.cs deleted file mode 100644 index 54d7d5b398..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelDeploymentResult.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.ResourceManager.Resources.Models; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public record ModelDeploymentResult -{ - [JsonPropertyName("hasData")] - public bool HasData { get; init; } - - [JsonPropertyName("id")] - public string? Id { get; init; } - - [JsonPropertyName("name")] - public string? Name { get; init; } - - [JsonPropertyName("type")] - public string? Type { get; init; } - - [JsonPropertyName("sku")] - public ResourcesSku? Sku { get; init; } - - [JsonPropertyName("tags")] - public IDictionary? Tags { get; init; } - - [JsonPropertyName("properties")] - public IDictionary? Properties { get; init; } -} - diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelInformation.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelInformation.cs deleted file mode 100644 index 77f0adb3d5..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/ModelInformation.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class ModelInformation -{ - [JsonPropertyName("id")] public string? Id { get; set; } - - [JsonPropertyName("name")] public string? Name { get; set; } - - [JsonPropertyName("publisher")] public string? Publisher { get; set; } - - [JsonPropertyName("description")] public string? Description { get; set; } - - [JsonPropertyName("azureOffers")] public List? AzureOffers { get; set; } = []; - - [JsonPropertyName("playgroundLimits")] public object? PlaygroundLimits { get; set; } - - [JsonPropertyName("deployment_options")] - public ModelDeploymentInformation DeploymentInformation { get; set; } = new(); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/OpenAiModelsListModels.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/OpenAiModelsListModels.cs deleted file mode 100644 index 51d71ea825..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/OpenAiModelsListModels.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public sealed record OpenAiModelsListResult( - [property: JsonPropertyName("models")] List Models, - [property: JsonPropertyName("resourceName")] string ResourceName); - -public sealed record OpenAiModelDeployment( - [property: JsonPropertyName("deploymentName")] string DeploymentName, - [property: JsonPropertyName("modelName")] string ModelName, - [property: JsonPropertyName("modelVersion")] string? ModelVersion, - [property: JsonPropertyName("scaleType")] string? ScaleType, - [property: JsonPropertyName("capacity")] int? Capacity, - [property: JsonPropertyName("provisioningState")] string? ProvisioningState, - [property: JsonPropertyName("createdAt")] DateTime? CreatedAt, - [property: JsonPropertyName("updatedAt")] DateTime? UpdatedAt, - [property: JsonPropertyName("capabilities")] OpenAiModelCapabilities? Capabilities); - -public sealed record OpenAiModelCapabilities( - [property: JsonPropertyName("completions")] bool Completions, - [property: JsonPropertyName("embeddings")] bool Embeddings, - [property: JsonPropertyName("chatCompletions")] bool ChatCompletions, - [property: JsonPropertyName("fineTuning")] bool FineTuning); diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/ThreadGetMessagesResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/ThreadGetMessagesResult.cs deleted file mode 100644 index 93d7e4f303..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Models/ThreadGetMessagesResult.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Microsoft.Extensions.AI; - -namespace Azure.Mcp.Tools.Foundry.Models; - -public class ThreadGetMessagesResult -{ - [JsonPropertyName("threadId")] - public string? ThreadId { get; set; } - - [JsonPropertyName("messages")] - public List? Messages { get; set; } - -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsConnectOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsConnectOptions.cs deleted file mode 100644 index 157d74feaa..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsConnectOptions.cs +++ /dev/null @@ -1,20 +0,0 @@ - -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options; - -public class AgentsConnectOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.AgentId)] - public string? AgentId { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.Query)] - public string? Query { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsCreateOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsCreateOptions.cs deleted file mode 100644 index 3846801aee..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsCreateOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Agents; - -public class AgentsCreateOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.ModelDeploymentName)] - public string? ModelDeploymentName { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.AgentName)] - public string? AgentName { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.SystemInstruction)] - public string? SystemInstruction { get; set; } -} - diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsEvaluateOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsEvaluateOptions.cs deleted file mode 100644 index cf505a3f76..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsEvaluateOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options; - -public class AgentsEvaluateOptions : GlobalOptions -{ - - [JsonPropertyName(FoundryOptionDefinitions.Query)] - public string? Query { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.AzureOpenAIEndpoint)] - public string? AzureOpenAIEndpoint { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.AzureOpenAIDeployment)] - public string? AzureOpenAIDeployment { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.EvaluatorName)] - public string? EvaluatorName { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.Response)] - public string? Response { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.ToolDefinitions)] - public string? ToolDefinitions { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsGetSdkSampleOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsGetSdkSampleOptions.cs deleted file mode 100644 index 2290a7b9df..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsGetSdkSampleOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Options.Agents; - -public class AgentsGetSdkSampleOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.ProgrammingLanguage)] - public string? ProgrammingLanguage { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsListOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsListOptions.cs deleted file mode 100644 index 416570b044..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsListOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options; - -public class AgentsListOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsQueryAndEvaluateOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsQueryAndEvaluateOptions.cs deleted file mode 100644 index 1f800b86ec..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Agents/AgentsQueryAndEvaluateOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options; - -public class AgentsQueryAndEvaluateOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.AgentId)] - public string? AgentId { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.Query)] - public string? Query { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.Evaluators)] - public string? Evaluators { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.AzureOpenAIEndpoint)] - public string? AzureOpenAIEndpoint { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.AzureOpenAIDeployment)] - public string? AzureOpenAIDeployment { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/FoundryOptionDefinitions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/FoundryOptionDefinitions.cs deleted file mode 100644 index 79368f672d..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/FoundryOptionDefinitions.cs +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Foundry.Options; - -public static class FoundryOptionDefinitions -{ - public const string Endpoint = "endpoint"; - public const string SearchForFreePlayground = "search-for-free-playground"; - public const string PublisherName = "publisher"; - public const string LicenseName = "license"; - public const string DeploymentName = "deployment"; - public const string ModelName = "model-name"; - public const string ModelFormat = "model-format"; - public const string AzureAiServicesName = "azure-ai-services"; - public const string ModelVersion = "model-version"; - public const string ModelSource = "model-source"; - public const string SkuName = "sku"; - public const string SkuCapacity = "sku-capacity"; - public const string ScaleType = "scale-type"; - public const string ScaleCapacity = "scale-capacity"; - public const string AgentId = "agent-id"; - public const string Query = "query"; - public const string Evaluators = "evaluators"; - public const string EvaluatorName = "evaluator"; - public const string Response = "response"; - public const string ToolCalls = "tool-calls"; - public const string ToolDefinitions = "tool-definitions"; - public const string AzureOpenAIEndpoint = "azure-openai-endpoint"; - public const string AzureOpenAIDeployment = "azure-openai-deployment"; - public const string IndexName = "index"; - public const string PromptText = "prompt-text"; - public const string MaxTokens = "max-tokens"; - public const string Temperature = "temperature"; - public const string ResourceName = "resource-name"; - public const string InputText = "input-text"; - public const string User = "user"; - public const string EncodingFormat = "encoding-format"; - public const string Dimensions = "dimensions"; - public const string MessageArray = "message-array"; - public const string TopP = "top-p"; - public const string FrequencyPenalty = "frequency-penalty"; - public const string PresencePenalty = "presence-penalty"; - public const string Stop = "stop"; - public const string Stream = "stream"; - public const string Seed = "seed"; - public const string ModelDeploymentName = "model-deployment"; - public const string AgentName = "agent-name"; - public const string SystemInstruction = "system-instruction"; - public const string UserMessage = "user-message"; - public const string ThreadId = "thread-id"; - public const string ProgrammingLanguage = "programming-language"; - - public static readonly Option EndpointOption = new( - $"--{Endpoint}" - ) - { - Description = "The endpoint URL for the Microsoft Foundry project/service. The endpoint follows this pattern https://.services.ai.azure.com/api/projects/.", - Required = true - }; - - public static readonly Option DeploymentNameOption = new( - $"--{DeploymentName}" - ) - { - Description = "The name of the deployment.", - Required = true - }; - - public static readonly Option ModelNameOption = new( - $"--{ModelName}" - ) - { - Description = "The name of the model to deploy.", - Required = true - }; - - public static readonly Option ModelFormatOption = new( - $"--{ModelFormat}" - ) - { - Description = "The format of the model (e.g., 'OpenAI', 'Meta', 'Microsoft').", - Required = true - }; - - public static readonly Option AzureAiServicesNameOption = new( - $"--{AzureAiServicesName}" - ) - { - Description = "The name of the Azure AI services account to deploy to.", - Required = true - }; - - public static readonly Option SearchForFreePlaygroundOption = new( - $"--{SearchForFreePlayground}" - ) - { - Description = "If true, filters models to include only those that can be used for free by users for prototyping." - }; - - public static readonly Option PublisherNameOption = new( - $"--{PublisherName}" - ) - { - Description = "A filter to specify the publisher of the models to retrieve." - }; - - public static readonly Option LicenseNameOption = new( - $"--{LicenseName}" - ) - { - Description = "A filter to specify the license type of the models to retrieve." - }; - - public static readonly Option ModelVersionOption = new( - $"--{ModelVersion}" - ) - { - Description = "The version of the model to deploy." - }; - - public static readonly Option ModelSourceOption = new( - $"--{ModelSource}" - ) - { - Description = "The source of the model." - }; - - public static readonly Option SkuNameOption = new( - $"--{SkuName}" - ) - { - Description = "The SKU name for the deployment." - }; - - public static readonly Option SkuCapacityOption = new( - $"--{SkuCapacity}" - ) - { - Description = "The SKU capacity for the deployment." - }; - - public static readonly Option ScaleTypeOption = new( - $"--{ScaleType}" - ) - { - Description = "The scale type for the deployment." - }; - - public static readonly Option ScaleCapacityOption = new( - $"--{ScaleCapacity}" - ) - { - Description = "The scale capacity for the deployment." - }; - - public static readonly Option AgentIdOption = new( - $"--{AgentId}" - ) - { - Description = "The ID of the agent to interact with.", - Required = true - }; - - public static readonly Option QueryOption = new( - $"--{Query}" - ) - { - Description = "The query sent to the agent.", - Required = true - }; - - public static readonly Option EvaluatorsOption = new( - $"--{Evaluators}" - ) - { - Description = "The list of evaluators to use for evaluation, separated by commas. If not specified, all evaluators will be used." - }; - - public static readonly Option EvaluatorNameOption = new( - $"--{EvaluatorName}" - ) - { - Description = "The name of the evaluator to use (intent_resolution, tool_call_accuracy, task_adherence).", - Required = true - }; - - public static readonly Option ResponseOption = new( - $"--{Response}" - ) - { - Description = "The response from the agent.", - }; - - public static readonly Option ToolCallsOption = new( - $"--{ToolCalls}" - ) - { - Description = "The tool calls made by the agent." - }; - - public static readonly Option ToolDefinitionsOption = new( - $"--{ToolDefinitions}" - ) - { - Description = "Optional tool definitions made by the agent in JSON format." - }; - - public static readonly Option AzureOpenAIEndpointOption = new( - $"--{AzureOpenAIEndpoint}" - ) - { - Description = "The endpoint URL for the Azure OpenAI service to be used in evaluation.", - Required = true - }; - - public static readonly Option AzureOpenAIDeploymentOption = new( - $"--{AzureOpenAIDeployment}" - ) - { - Description = "The deployment name for the Azure OpenAI model to be used in evaluation.", - Required = true - }; - - public static readonly Option IndexNameOption = new( - $"--{IndexName}" - ) - { - Description = "The name of the knowledge index.", - Required = true - }; - - public static readonly Option PromptTextOption = new( - $"--{PromptText}" - ) - { - Description = "The prompt text to send to the completion model.", - Required = true - }; - - public static readonly Option ResourceNameOption = new( - $"--{ResourceName}" - ) - { - Description = "The name of the Azure OpenAI resource.", - Required = true - }; - - public static readonly Option MaxTokensOption = new( - $"--{MaxTokens}" - ) - { - Description = "The maximum number of tokens to generate in the completion." - }; - - public static readonly Option TemperatureOption = new( - $"--{Temperature}" - ) - { - Description = "Controls randomness in the output. Lower values make it more deterministic." - }; - - public static readonly Option InputTextOption = new( - $"--{InputText}" - ) - { - Description = "The input text to generate embeddings for.", - Required = true - }; - - public static readonly Option UserOption = new( - $"--{User}" - ) - { - Description = "Optional user identifier for tracking and abuse monitoring." - }; - - public static readonly Option EncodingFormatOption = new( - $"--{EncodingFormat}" - ) - { - Description = "The format to return embeddings in (float or base64).", - DefaultValueFactory = _ => "float" - }; - - public static readonly Option DimensionsOption = new( - $"--{Dimensions}" - ) - { - Description = "The number of dimensions for the embedding output. Only supported in some models." - }; - - public static readonly Option MessageArrayOption = new( - $"--{MessageArray}" - ) - { - Description = "Array of messages in the conversation (JSON format). Each message should have 'role' and 'content' properties.", - Required = true - }; - - public static readonly Option TopPOption = new( - $"--{TopP}" - ) - { - Description = "Controls diversity via nucleus sampling (0.0 to 1.0). Default is 1.0." - }; - - public static readonly Option FrequencyPenaltyOption = new( - $"--{FrequencyPenalty}" - ) - { - Description = "Penalizes new tokens based on their frequency (-2.0 to 2.0). Default is 0." - }; - - public static readonly Option PresencePenaltyOption = new( - $"--{PresencePenalty}" - ) - { - Description = "Penalizes new tokens based on presence (-2.0 to 2.0). Default is 0." - }; - - public static readonly Option StopOption = new( - $"--{Stop}" - ) - { - Description = "Up to 4 sequences where the API will stop generating further tokens." - }; - - public static readonly Option StreamOption = new( - $"--{Stream}" - ) - { - Description = "Whether to stream back partial progress. Default is false." - }; - - public static readonly Option SeedOption = new( - $"--{Seed}" - ) - { - Description = "If specified, the system will make a best effort to sample deterministically." - }; - - public static readonly Option ModelDeploymentNameOption = new( - $"--{ModelDeploymentName}") - { - Description = "Name of the model deployment", - Required = true - }; - - public static readonly Option AgentNameOption = new( - $"--{AgentName}" - ) - { - Description = "A human-readable name of the Agent", - Required = true - }; - - public static readonly Option SystemInstructionOption = new( - $"--{SystemInstruction}" - ) - { - Description = "System instruction for the agent to follow when process messages", - Required = true - }; - - public static readonly Option UserMessageOption = new( - $"--{UserMessage}" - ) - { - Description = "The user message to add to the thread", - Required = true - }; - - public static readonly Option ThreadIdOption = new( - $"--{ThreadId}" - ) - { - Description = "The Foundry Agent Thread Id", - Required = true - }; - - public static readonly Option ProgrammingLanguageOption = new( - $"--{ProgrammingLanguage}" - ) - { - Description = "The programming language of the sdk for interacting with a Foundry Agent. Supported values are csharp, python and typescript.", - Required = true - }; -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/BaseKnowledgeIndexOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/BaseKnowledgeIndexOptions.cs deleted file mode 100644 index cac35435b3..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/BaseKnowledgeIndexOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public abstract class BaseKnowledgeIndexOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/DeploymentsListOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/DeploymentsListOptions.cs deleted file mode 100644 index 444a6ee38e..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/DeploymentsListOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class DeploymentsListOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/KnowledgeIndexListOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/KnowledgeIndexListOptions.cs deleted file mode 100644 index b822c6bdc3..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/KnowledgeIndexListOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class KnowledgeIndexListOptions : BaseKnowledgeIndexOptions -{ -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/KnowledgeIndexSchemaOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/KnowledgeIndexSchemaOptions.cs deleted file mode 100644 index d04733bdd1..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/KnowledgeIndexSchemaOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class KnowledgeIndexSchemaOptions : BaseKnowledgeIndexOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.IndexName)] - public string? IndexName { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ModelDeploymentOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ModelDeploymentOptions.cs deleted file mode 100644 index bfb96b7526..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ModelDeploymentOptions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class ModelDeploymentOptions : SubscriptionOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.DeploymentName)] - public string? DeploymentName { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.ModelName)] - public string? ModelName { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.ModelFormat)] - public string? ModelFormat { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.AzureAiServicesName)] - public string? AzureAiServicesName { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.ModelVersion)] - public string? ModelVersion { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.ModelSource)] - public string? ModelSource { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.SkuName)] - public string? SkuName { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.SkuCapacity)] - public int? SkuCapacity { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.ScaleType)] - public string? ScaleType { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.ScaleCapacity)] - public int? ScaleCapacity { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ModelsListOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ModelsListOptions.cs deleted file mode 100644 index a985cdb7f2..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ModelsListOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class ModelsListOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.SearchForFreePlayground)] - public bool? SearchForFreePlayground { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.PublisherName)] - public string? PublisherName { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.LicenseName)] - public string? LicenseName { get; set; } - [JsonPropertyName(FoundryOptionDefinitions.ModelName)] - public string? ModelName { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiChatCompletionsCreateOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiChatCompletionsCreateOptions.cs deleted file mode 100644 index b4586102fe..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiChatCompletionsCreateOptions.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.ComponentModel; -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class OpenAiChatCompletionsCreateOptions : SubscriptionOptions -{ - [JsonPropertyName("resource-name")] - [Description("The name of the Azure OpenAI resource")] - public string? ResourceName { get; set; } - - [JsonPropertyName("deployment-name")] - [Description("The name of the deployment for the chat model")] - public string? DeploymentName { get; set; } - - [JsonPropertyName("message-array")] - [Description("Array of messages in the conversation. Each message should have 'role' (system/user/assistant) and 'content' properties")] - public string? MessageArray { get; set; } - - [JsonPropertyName("max-tokens")] - [Description("Maximum number of tokens to generate in the completion")] - public int? MaxTokens { get; set; } - - [JsonPropertyName("temperature")] - [Description("Controls randomness in the model's output (0.0 to 2.0)")] - public double? Temperature { get; set; } - - [JsonPropertyName("top-p")] - [Description("Controls diversity of the model's output using nucleus sampling (0.0 to 1.0)")] - public double? TopP { get; set; } - - [JsonPropertyName("frequency-penalty")] - [Description("Penalizes new tokens based on their frequency in the text so far (-2.0 to 2.0)")] - public double? FrequencyPenalty { get; set; } - - [JsonPropertyName("presence-penalty")] - [Description("Penalizes new tokens based on whether they appear in the text so far (-2.0 to 2.0)")] - public double? PresencePenalty { get; set; } - - [JsonPropertyName("stop")] - [Description("Up to 4 sequences where the API will stop generating further tokens")] - public string? Stop { get; set; } - - [JsonPropertyName("stream")] - [Description("Whether to stream back partial progress")] - public bool? Stream { get; set; } - - [JsonPropertyName("seed")] - [Description("If specified, the system will make a best effort to sample deterministically")] - public int? Seed { get; set; } - - [JsonPropertyName("user")] - [Description("A unique identifier representing your end-user")] - public string? User { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiCompletionsCreateOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiCompletionsCreateOptions.cs deleted file mode 100644 index 204ed890ed..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiCompletionsCreateOptions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class OpenAiCompletionsCreateOptions : SubscriptionOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.DeploymentName)] - public string? DeploymentName { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.PromptText)] - public string? PromptText { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.MaxTokens)] - public int? MaxTokens { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.Temperature)] - public double? Temperature { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.ResourceName)] - public string? ResourceName { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiEmbeddingsCreateOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiEmbeddingsCreateOptions.cs deleted file mode 100644 index 30cadb4974..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiEmbeddingsCreateOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class OpenAiEmbeddingsCreateOptions : SubscriptionOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.ResourceName)] - public string? ResourceName { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.DeploymentName)] - public string? DeploymentName { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.InputText)] - public string? InputText { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.User)] - public string? User { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.EncodingFormat)] - public string? EncodingFormat { get; set; } = "float"; - - [JsonPropertyName(FoundryOptionDefinitions.Dimensions)] - public int? Dimensions { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiModelsListOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiModelsListOptions.cs deleted file mode 100644 index 5b79dc71d3..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/OpenAiModelsListOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class OpenAiModelsListOptions : SubscriptionOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.ResourceName)] - public string? ResourceName { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ResourceGetOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ResourceGetOptions.cs deleted file mode 100644 index 340ff3bac5..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Models/ResourceGetOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Models; - -public class ResourceGetOptions : SubscriptionOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.ResourceName)] - public string? ResourceName { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadCreateOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadCreateOptions.cs deleted file mode 100644 index 1021742571..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadCreateOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Thread; - -public class ThreadCreateOptions : GlobalOptions - -{ - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.UserMessage)] - public string? UserMessage { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadCreateResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadCreateResult.cs deleted file mode 100644 index c9d8921ca9..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadCreateResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Options.Thread; - -public class ThreadCreateResult -{ - [JsonPropertyName("threadId")] - public string? ThreadId { get; set; } - - [JsonPropertyName("projectEndpoint")] - public string? ProjectEndpoint { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadGetMessagesOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadGetMessagesOptions.cs deleted file mode 100644 index 1587d7bc0f..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadGetMessagesOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Thread; - -public class ThreadGetMessagesOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } - - [JsonPropertyName(FoundryOptionDefinitions.ThreadId)] - public string? ThreadId { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadListOptions.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadListOptions.cs deleted file mode 100644 index 6c8d84c144..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadListOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Foundry.Options.Thread; - -public class ThreadListOptions : GlobalOptions -{ - [JsonPropertyName(FoundryOptionDefinitions.Endpoint)] - public string? Endpoint { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadListResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadListResult.cs deleted file mode 100644 index aa6e723b52..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Options/Thread/ThreadListResult.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Foundry.Options.Thread; - -public class ThreadListResult -{ - [JsonPropertyName("threads")] - public ThreadItem[] Threads { get; set; } = []; -} - -public class ThreadItem -{ - [JsonPropertyName("threadId")] - public string? ThreadId { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Resources/AgentSdkSamples/csharp.md b/tools/Azure.Mcp.Tools.Foundry/src/Resources/AgentSdkSamples/csharp.md deleted file mode 100644 index 6e444c080a..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Resources/AgentSdkSamples/csharp.md +++ /dev/null @@ -1,164 +0,0 @@ -# Interact with Agent using Microsoft Foundry dotnet SDK - -## Prerequisites - -- LTS version of dotnet -- An Azure subscription -- A project in Microsoft Foundry -- The project endpoint string. This can be found in the Microsoft Foundry project overview page under "Project details" in Microsoft Foundry Portal. -- A model deployment in the project -- A user Entra ID to authenticate the client - - The user Entra ID needs to have "Azure AI User" RBAC role - - Azure CLI must be installed - - The user must sign in with the Entra ID in Azure CLI using `az login` command - - The default subscription in Azure CLI must be set to the same Azure subscription containing the Microsoft Foundry project - -## Install Microsoft Foundry dotnet SDK - -Use the dotnet command `dotnet add package Azure.AI.Agents.Persistent --prerelease`. You also need to install Azure.Identity package using this dotnet command `dotnet add package Azure.Identity`. - -## Create and authenticate PersistentAgentsClient - -Start by creating an `PersistentAgentsClient` with this piece of C# code - -```C# -var projectEndpoint = Environment.GetEnvironmentVariable("PROJECT_ENDPOINT"); -PersistentAgentsClient client = new(projectEndpoint, new DefaultAzureCredential()); -``` - -## Create an Agent - -Before creating an Agent, you need a model deployment. Ask the user for the model deployment details. If they aren't sure, show them this [documentation](https://learn.microsoft.com/azure/ai-foundry/foundry-models/how-to/create-model-deployments?pivots=ai-foundry-portal). - -Once you have a model deployment, use the following code to create an Agent - -```C# -PersistentAgent agent = await client.Administration.CreateAgentAsync( - model: modelDeploymentName, - name: "user_provided_agent_name", - instructions: "user_provided_agent_system_instruction" -); -``` - -Once an Agent is created, you can reuse it by getting it from the id. -The Agent ID can be found in the Microsoft Foundry Portal. - -```C# -PersistentAgent agent = await client.Administration.GetAgentAsync("user_provided_agent_id"); -``` - -Agents can be created with tools. To learn more about what tools it supports, refer to [agent creation with tools](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/ai/Azure.AI.Agents.Persistent#file-search). - -## Run an Agent - -Agents must run on a Thread. The Thread serves as the input to the Agent and will be updated to contain the output once the Agent finishes running. - -### Create a Thread - -Use the following code to create a new Thread - -```C# -PersistentAgentThread thread = await client.Threads.CreateThreadAsync(); -``` - -Agent references Thread by their ID. If you have an existing Thread, you can save its ID and reuse it for future Agent runs. - -### Create a user Message - -The user input are appended to a Thread as a 'user' message. Use the following code to append a new user message to a Thread. - -```C# -PersistentThreadMessage message = await client.Messages.CreateMessageAsync( - thread.Id, - MessageRole.User, - "user_provided_question_or_request"); -``` - -### Create and process a run - -Once you have a Thread and append the user input to it, create a Run using the following code - -```C# -ThreadRun run = await client.Runs.CreateRunAsync( - thread.Id, - agent.Id, - additionalInstructions: "user_provided_additional_instructions_for_this_run"); -``` - -Agent runs can take a while and you will need to poll to see its status. Use the following code to poll the status of the run until it finishes. - -```C# -do -{ - await Task.Delay(TimeSpan.FromMilliseconds(500)); - run = await client.Runs.GetRunAsync(thread.Id, run.Id); -} -while (run.Status == RunStatus.Queued - || run.Status == RunStatus.InProgress); -Assert.AreEqual( - RunStatus.Completed, - run.Status, - run.LastError?.Message); -``` - -Some tool usage needs manual approval from the client. To approve tool call requests, replace the polling code with the following code. - -```C# -do -{ - await Task.Delay(TimeSpan.FromMilliseconds(500)); - run = await client.Runs.GetRunAsync(thread.Id, run.Id); - - if (run.Status == RunStatus.RequiresAction - && run.RequiredAction is SubmitToolApprovalAction submitToolApprovalAction) - { - List toolApprovals = []; - foreach (RequiredToolCall toolCall in submitToolApprovalAction.SubmitToolApproval.ToolCalls) - { - if (toolCall is RequiredMcpToolCall mcpToolCall) - { - Console.WriteLine($"Approving MCP tool call: {mcpToolCall.Name}"); - toolApprovals.Add(new ToolApproval(mcpToolCall.Id, approve: true)); - } - } - if (toolApprovals.Count > 0) - { - run = await client.Runs.SubmitToolOutputsToRunAsync(thread.Id, run.Id, toolApprovals: toolApprovals); - } - } -} -while (run.Status == RunStatus.Queued - || run.Status == RunStatus.InProgress - || run.Status == RunStatus.RequiresAction); -Assert.AreEqual( - RunStatus.Completed, - run.Status, - run.LastError?.Message); -``` - -## View Agent output - -Output of the Agent are appended to the Thread it runs on. You can list and print all the messages of the thread to view the Agent output. - -```C# -AsyncPageable messages - = client.Messages.GetMessagesAsync( - threadId: thread.Id, order: ListSortOrder.Ascending); - -await foreach (PersistentThreadMessage threadMessage in messages) -{ - Console.Write($"{threadMessage.CreatedAt:yyyy-MM-dd HH:mm:ss} - {threadMessage.Role,10}: "); - foreach (MessageContent contentItem in threadMessage.ContentItems) - { - if (contentItem is MessageTextContent textItem) - { - Console.Write(textItem.Text); - } - else if (contentItem is MessageImageFileContent imageFileItem) - { - Console.Write($" -``` - -## Create an Agent - -Before creating an Agent, you need a model deployment. Ask the user for the model deployment details. If they aren't sure, show them this [documentation](https://learn.microsoft.com/azure/ai-foundry/foundry-models/how-to/create-model-deployments?pivots=ai-foundry-portal). - -Once you have a model deployment, use the following code to create an Agent - -```python -agent = agents_client.create_agent( - model=os.environ["MODEL_DEPLOYMENT_NAME"], - name="user_provided_agent_name", - instructions="user_provided_agent_system_instruction", -) -``` - -Once an Agent is created, you can reuse it by getting it from the id. The Agent ID can be found in Microsoft Foundry Portal. - -```python -agent = agents_client.get_agent("user_provided_agent_id") -``` - -Agents can be created with tools. To learn more about what tools it supports, refer to [agent creation with tools](https://learn.microsoft.com/python/api/overview/azure/ai-agents-readme?view=azure-python-preview&preserve-view=true#examples). - -## Run an Agent - -Agents must run on a Thread. The Thread serves as the input to the Agent and will be updated to contain the output once the Agent finishes running. - -### Create a Thread - -Use the following code to create a new Thread - -```python -thread = agents_client.threads.create() -``` - -Agents references Thread by their ID. If you have an existing Thread, you can save its ID and reuse it for future Agent runs. - -### Create a user Message - -The user input are appended to a Thread as a 'user' message. Use the following code to append a new user message to a Thread. - -```python -message = agents_client.messages.create(thread_id=thread.id, role="user", content="user_provided_question_or_request") -``` - -### Create and process a run - -Once you have a Thread and append the user input to it, create a Run using the following code - -```python -run = agents_client.runs.create(thread_id=thread.id, agent_id=agent.id) - -# Poll the run as long as run status is queued or in progress -while run.status in ["queued", "in_progress", "requires_action"]: - # Wait for a second - time.sleep(1) - run = agents_client.runs.get(thread_id=thread.id, run_id=run.id) -``` - -Agent runs can take a while and you will need to poll to see its status. Some tool usage needs manual approval from the client. To approve tool call requests, replace the while loop with the following code - -```python -while run.status in ["queued", "in_progress", "requires_action"]: - time.sleep(1) - run = agents_client.runs.get(thread_id=thread.id, run_id=run.id) - - if run.status == "requires_action" and isinstance(run.required_action, SubmitToolApprovalAction): - tool_calls = run.required_action.submit_tool_approval.tool_calls - if not tool_calls: - print("No tool calls provided - cancelling run") - agents_client.runs.cancel(thread_id=thread.id, run_id=run.id) - break - - tool_approvals = [] - for tool_call in tool_calls: - if isinstance(tool_call, RequiredMcpToolCall): - try: - print(f"Approving tool call: {tool_call}") - tool_approvals.append( - ToolApproval( - tool_call_id=tool_call.id, - approve=True, - headers=mcp_tool.headers, - ) - ) - except Exception as e: - print(f"Error approving tool_call {tool_call.id}: {e}") - - print(f"tool_approvals: {tool_approvals}") - if tool_approvals: - agents_client.runs.submit_tool_outputs( - thread_id=thread.id, run_id=run.id, tool_approvals=tool_approvals - ) - - print(f"Current run status: {run.status}") -``` - -## View Agent output - -Output of the Agent are appended to the Thread it runs on. You can list and print all the messages of the thread to view the Agent output - -```python -from azure.ai.agents.models import (ListSortOrder) - -# ... code interacting with the Agent - -messages = agents_client.messages.list(thread_id=thread.id, order=ListSortOrder.ASCENDING) - for msg in messages: - if msg.text_messages: - last_text = msg.text_messages[-1] - print(f"{msg.role}: {last_text.text.value}") -``` diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Resources/AgentSdkSamples/typescript.md b/tools/Azure.Mcp.Tools.Foundry/src/Resources/AgentSdkSamples/typescript.md deleted file mode 100644 index 6183819602..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Resources/AgentSdkSamples/typescript.md +++ /dev/null @@ -1,174 +0,0 @@ -# Interact with Agent using Microsoft Foundry TypeScript SDK - -## Prerequisites: - -- LTS version of Node.js -- Typescript is installed -- An Azure subscription -- A project in Microsoft Foundry -- The project endpoint string. This can be found in the Microsoft Foundry project overview page under "Project details" in Microsoft Foundry Portal. -- A model deployment in the project -- A user Entra ID to authenticate the client - - The user Entra ID needs to have "Azure AI User" RBAC role - - Azure CLI must be installed - - The user must sign in with the Entra ID in Azure CLI using `az login` command - - The default subscription in Azure CLI must be set to the same Azure subscription containing the Microsoft Foundry project - -## Install Microsoft Foundry JavaScript SDK - -Use the npm command `npm install @azure/ai-projects @azure/ai-agents @azure/identity` - -## Create and authenticate AIProjectClient - -Start by creating an `AIProjectClient` with this piece of Typescript code - -```typescript -import { AIProjectClient } from "@azure/ai-projects"; -import { DefaultAzureCredential } from "@azure/identity"; - -// Note: you need to provide this endpoint process.env["AZURE_AI_PROJECT_ENDPOINT_STRING"] -const endpoint = process.env["AZURE_AI_PROJECT_ENDPOINT_STRING"] || ""; -const projectClient = new AIProjectClient(endpoint, new DefaultAzureCredential()); -``` - -Once the `AIProjectClient` is created, access the `agents` for functionality related to Agents. - -```typescript -const agentsClient = projectClient.agents -// -``` - -## Create an Agent - -Before creating an Agent, you need a model deployment. Ask the user for the model deployment details. If they aren't sure, show them this [documentation](https://learn.microsoft.com/azure/ai-foundry/foundry-models/how-to/create-model-deployments?pivots=ai-foundry-portal). - -Once you have a model deployment, use the following code to create an Agent - -```typescript -// gpt-4o is the model deployment name which may vary -const agent = await agentsClient.createAgent("gpt-4o", { - name: "user_provided_agent_name", - instructions: "user_provided_agent_system_instruction", -}); -``` - -Once an Agent is created, you can reuse it by getting it from the id. The Agent ID can be found in Microsoft Foundry Portal. - -```typescript -const agent = agentsClient.getAgent("user_provided_agent_id") -``` - -Agents can be created with tools. To learn more about what tools it supports, refer to [agent creation with tools](https://learn.microsoft.com/javascript/api/overview/azure/ai-agents-readme?view=azure-node-latest#create-agent). - -## Run an Agent - -Agents must run on a Thread. The Thread serves as the input to the Agent and will be updated to contain the output once the Agent finishes running. - -### Create a Thread - -Use the following code to create a new Thread - -```typescript -const thread = await agentsClient.threads.create(); -console.log(`Created thread, thread ID: \${thread.id}`); -``` - -Agents references Thread by their ID. If you have an existing Thread, you can save its ID and reuse it for future Agent runs. - -### Create a user Message - -The user input are appended to a Thread as a 'user' message. Use the following code to append a new user message to a Thread. - -```typescript -const message = await agentsClient.messages.create(thread.id, "user", "user_provided_question_or_request"); -console.log(`Created message, message ID: \${message.id}`); -``` - -### Create and process a run - -Once you have a Thread and append the user input to it, create a Run using the following code - -```typescript -// Create and poll a run -console.log("Creating run..."); -const run = await agentsClient.runs.createAndPoll(thread.id, agent.id, { - pollingOptions: { - intervalInMs: 2000, - }, - onResponse: (response): void => { - console.log(`Received response with status: \${response.parsedBody.status}`); - }, -}); -console.log(`Run finished with status: \${run.status}`); -``` - -Agent runs can take a while and you will need to poll to see its status. Some tool usage needs manual approval from the client. To approve tool call requests, replace the onResponse callback with the following code - -```typescript -async function onResponse(response: any): Promise { - const parsedBody = - typeof response.parsedBody === "object" && response.parsedBody !== null - ? response.parsedBody - : null; - - if (!parsedBody || !("status" in parsedBody)) return; - - const run = parsedBody as ThreadRun; - console.log(`Current Run status - \${run.status}, run ID: \${run.id}`); - - // Ensure we have a run with requires_action status and requiredAction object - if (run.status === "requires_action" && run.requiredAction) { - console.log("Run requires action"); - - // Check if the requiredAction is of type submit_tool_outputs and has the expected structure - if (isOutputOfType(run.requiredAction, "submit_tool_outputs")) { - const submitToolOutputsActionOutput = run.requiredAction; - const toolCalls = submitToolOutputsActionOutput.submitToolOutputs.toolCalls; - const toolResponses: ToolOutput[] = []; - - for (const toolCall of toolCalls) { - if (isOutputOfType(toolCall, "function")) { - const toolResponse = functionToolExecutor.invokeTool(toolCall); - if (toolResponse) { - toolResponses.push(toolResponse); - } - } - } - if (toolResponses.length > 0) { - try { - await agentsClient.runs.submitToolOutputs(thread.id, run.id, toolResponses); - console.log(`Submitted tool responses successfully`); - } catch (err) { - console.error("Error submitting tool outputs:", err); - } - } - } - } -} -``` - -## View Agent output - -Output of the Agent are appended to the Thread it runs on. You can list and print all the messages of the thread to view the Agent output. - -```typescript -const messagesIterator = agentsClient.messages.list(thread.id); -const allMessages = []; -for await (const m of messagesIterator) { - allMessages.push(m); -} -console.log("Messages:", allMessages); - -// The messages are following in the reverse order, -// we will iterate them and output only text contents. -const messages = await agentsClient.messages.list(thread.id, { - order: "asc", -}); - -for await (const dataPoint of messages) { - const textContent = dataPoint.content.find((item) => item.type === "text"); - if (textContent && "text" in textContent) { - console.log(`\${dataPoint.role}: \${textContent.text.value}`); - } -} -``` diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs deleted file mode 100644 index 0248705b16..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs +++ /dev/null @@ -1,1908 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.ClientModel; -using System.ClientModel.Primitives; -using System.Reflection; -using System.Text; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization.Metadata; -using Azure.AI.Agents.Persistent; -using Azure.AI.OpenAI; -using Azure.AI.Projects; -using Azure.Core; -using Azure.Core.Pipeline; -using Azure.Mcp.Core.Helpers; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Core.Services.Azure; -using Azure.Mcp.Core.Services.Azure.Subscription; -using Azure.Mcp.Core.Services.Azure.Tenant; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options.Thread; -using Azure.ResourceManager; -using Azure.ResourceManager.CognitiveServices; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.AI.Evaluation; -using Microsoft.Extensions.AI.Evaluation.Quality; -using OpenAI.Chat; - -#pragma warning disable AIEVAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - -namespace Azure.Mcp.Tools.Foundry.Services; - -public class FoundryService( - IHttpClientFactory httpClientFactory, - ISubscriptionService subscriptionService, - ITenantService tenantService) - : BaseAzureResourceService(subscriptionService, tenantService), IFoundryService -{ - private static readonly Dictionary> AgentEvaluatorDictionary = new() - { - { "intent_resolution", () => new IntentResolutionEvaluator()}, - { "tool_call_accuracy", () => new ToolCallAccuracyEvaluator()}, - { "task_adherence", () => new TaskAdherenceEvaluator()}, - }; - - private static readonly Dictionary, EvaluationContext>> AgentEvaluatorContextDictionary = new() - { - { "intent_resolution", toolDefinitions => new IntentResolutionEvaluatorContext(toolDefinitions)}, - { "tool_call_accuracy", toolDefinitions => new ToolCallAccuracyEvaluatorContext(toolDefinitions)}, - { "task_adherence", toolDefinitions => new TaskAdherenceEvaluatorContext(toolDefinitions)}, - }; - - private readonly IHttpClientFactory _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); - private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService)); - - /// - /// Validates that the endpoint value satisfies the pattern of a Foundry project endpoint. - /// - private static void ValidateProjectEndpoint(string endpoint) - { - ArgumentException.ThrowIfNullOrWhiteSpace(endpoint, nameof(endpoint)); - - try - { - - if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var parsedUri)) - { - throw new ArgumentException("Invalid Uri"); - } - - // Example: https://{foundry-resource-name}.services.ai.azure.com/api/projects/{project-name} - - if (parsedUri.Scheme != Uri.UriSchemeHttps) - { - throw new ArgumentException("Scheme must be https"); - } - - const string knownSuffix = ".services.ai.azure.com"; - var host = parsedUri.Host; - if (!host.EndsWith(knownSuffix, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Host must end with Foundry service suffix"); - } - } - catch (Exception ex) - { - throw new ArgumentException( - $"Invalid Foundry project endpoint: '{TruncateForLogging(endpoint)}'", - nameof(endpoint), ex); - } - } - - /// - /// Validates that the endpoint value satisfies the pattern of an Azure OpenAI endpoint. - /// - private static void ValidateAzureOpenAiEndpoint(string endpoint) - { - ArgumentException.ThrowIfNullOrWhiteSpace(endpoint, nameof(endpoint)); - - try - { - if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var parsedUri)) - { - throw new ArgumentException("Invalid Uri"); - } - - // Example: https://{Azure-OpenAI-resource-name}.openai.azure.com/ - // Example: https://{Azure-OpenAI-resource-name}.cognitiveservices.azure.com/ - if (parsedUri.Scheme != Uri.UriSchemeHttps) - { - throw new ArgumentException("Scheme must be https"); - } - - string[] knownSuffixes = [".openai.azure.com", ".cognitiveservices.azure.com"]; - var host = parsedUri.Host; - var matchedSuffix = knownSuffixes.FirstOrDefault(suffix => host.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); - if (matchedSuffix == null) - { - throw new ArgumentException("Host must end with Azure OpenAI service suffix"); - } - - var azureOpenAIResourceName = host.Substring(0, host.Length - matchedSuffix.Length); - - // Validate Azure OpenAI resource name: 2-64 characters, alphanumeric and hyphens only, cannot start or end with hyphen - if (azureOpenAIResourceName.Length < 2 || azureOpenAIResourceName.Length > 64) - { - throw new ArgumentException("Azure OpenAI resource name must be between 2 and 64 characters"); - } - - if (azureOpenAIResourceName.StartsWith('-') || azureOpenAIResourceName.EndsWith('-')) - { - throw new ArgumentException("Azure OpenAI resource name cannot start or end with a hyphen"); - } - - if (!azureOpenAIResourceName.All(c => char.IsLetterOrDigit(c) || c == '-')) - { - throw new ArgumentException("Azure OpenAI resource name must contain only alphanumeric characters and hyphens"); - } - - // Validate path: should be empty or just "/" (root path) - var paths = parsedUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); - if (paths.Length != 0) - { - throw new ArgumentException("Azure OpenAI endpoint should not contain path segments"); - } - } - catch (Exception ex) - { - throw new ArgumentException( - $"Invalid Azure OpenAI endpoint: '{TruncateForLogging(endpoint)}'", - nameof(endpoint), ex); - } - } - - private static string TruncateForLogging(string value) - { - const int maxLength = 100; - return value.Length > maxLength ? value[..maxLength] + "..." : value; - } - - public async Task> ListModels( - bool searchForFreePlayground = false, - string publisherName = "", - string licenseName = "", - string modelName = "", - int maxPages = 3, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - string url = "https://api.catalog.azureml.ms/asset-gallery/v1.0/models"; - var request = new ModelCatalogRequest { Filters = [new ModelCatalogFilter("labels", ["latest"], "eq")] }; - - if (searchForFreePlayground) - { - request.Filters.Add(new ModelCatalogFilter("freePlayground", ["true"], "eq")); - } - - if (!string.IsNullOrEmpty(publisherName)) - { - request.Filters.Add(new ModelCatalogFilter("publisher", [publisherName], "contains")); - } - - if (!string.IsNullOrEmpty(licenseName)) - { - request.Filters.Add(new ModelCatalogFilter("license", [licenseName], "contains")); - } - - if (!string.IsNullOrEmpty(modelName)) - { - request.Filters.Add(new ModelCatalogFilter("name", [modelName], "eq")); - } - - var modelsList = new List(); - int pageCount = 0; - - try - { - while (pageCount < maxPages) - { - pageCount++; - try - { - var content = new StringContent( - JsonSerializer.Serialize(request, FoundryJsonContext.Default.ModelCatalogRequest), - Encoding.UTF8, - "application/json"); - - var client = _httpClientFactory.CreateClient(); - var httpResponse = await client.PostAsync(url, content, cancellationToken); - httpResponse.EnsureSuccessStatusCode(); - - var responseText = await httpResponse.Content.ReadAsStringAsync(cancellationToken); - var response = JsonSerializer.Deserialize(responseText, - FoundryJsonContext.Default.ModelCatalogResponse); - if (response == null || response.Summaries.Count == 0) - { - break; - } - - foreach (var summary in response.Summaries) - { - try - { - summary.DeploymentInformation.IsFreePlayground = summary.PlaygroundLimits != null; - if (!string.IsNullOrEmpty(summary.Publisher) && - summary.Publisher.Equals("openai", StringComparison.OrdinalIgnoreCase)) - { - summary.DeploymentInformation.IsOpenAI = true; - } - else - { - if (summary.AzureOffers != null) - { - summary.DeploymentInformation.IsServerlessEndpoint = - summary.AzureOffers.Contains("standard-paygo"); - - summary.DeploymentInformation.IsManagedCompute = - summary.AzureOffers.Contains("VM") || - summary.AzureOffers.Contains("VM-withSurcharge"); - } - } - - modelsList.Add(summary); - } - catch - { - // ignored - } - } - - if (string.IsNullOrEmpty(response.ContinuationToken)) - { - break; - } - - request.ContinuationToken = response.ContinuationToken; - } - catch (HttpRequestException) - { - break; - } - catch (JsonException) - { - break; - } - } - } - catch (Exception e) - { - throw new Exception($"Error retrieving models from model catalog: {e.Message}"); - } - - return modelsList; - } - - public async Task> ListDeployments( - string endpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters((nameof(endpoint), endpoint)); - ValidateProjectEndpoint(endpoint); - - try - { - var projectClient = await CreateAIProjectClientWithAuth(endpoint, tenantId, cancellationToken); - var deploymentsClient = projectClient.GetDeploymentsClient(); - - var deployments = new List(); - await foreach (var deployment in deploymentsClient.GetDeploymentsAsync(cancellationToken: cancellationToken)) - { - deployments.Add(deployment); - } - - return deployments; - } - catch (Exception ex) - { - throw new Exception($"Failed to list deployments: {ex.Message}", ex); - } - } - - public async Task DeployModel( - string deploymentName, - string modelName, - string modelFormat, - string azureAiServicesName, - string resourceGroup, - string subscriptionId, - string? modelVersion = null, - string? modelSource = null, - string? skuName = null, - int? skuCapacity = null, - string? scaleType = null, - int? scaleCapacity = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(deploymentName), deploymentName), - (nameof(modelName), modelName), - (nameof(modelFormat), modelFormat), - (nameof(azureAiServicesName), azureAiServicesName), - (nameof(resourceGroup), resourceGroup), - (nameof(subscriptionId), subscriptionId)); - - try - { - // Create ArmClient for deployments - ArmClient armClient = await CreateArmClientWithApiVersionAsync("Microsoft.CognitiveServices/accounts/deployments", "2025-06-01", null, retryPolicy); - - // Retrieve the Cognitive Services account - var cognitiveServicesAccount = await GetGenericResourceAsync( - armClient, - new ResourceIdentifier($"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.CognitiveServices/accounts/{azureAiServicesName}"), - cancellationToken); - - // Prepare data for the deployment - ResourceIdentifier deploymentId = new ResourceIdentifier($"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.CognitiveServices/accounts/{azureAiServicesName}/deployments/{deploymentName}"); - var deploymentData = new Models.CognitiveServicesAccountDeploymentData - { - Properties = new Models.CognitiveServicesAccountDeploymentProperties - { - Model = new Models.CognitiveServicesAccountDeploymentModel - { - Format = modelFormat, - Name = modelName, - Version = modelVersion, - Source = string.IsNullOrEmpty(modelSource) ? null : modelSource - }, - ScaleSettings = string.IsNullOrEmpty(scaleType) ? null : new Models.CognitiveServicesAccountDeploymentScaleSettings - { - ScaleType = scaleType, - Capacity = scaleCapacity - } - }, - Sku = string.IsNullOrEmpty(skuName) ? null : new Models.CognitiveServicesSku - { - Capacity = skuCapacity - } - }; - - var result = await CreateOrUpdateGenericResourceAsync( - armClient, - deploymentId, - cognitiveServicesAccount.Data.Location, - deploymentData, - FoundryJsonContext.Default.CognitiveServicesAccountDeploymentData); - if (!result.HasData) - { - return new ModelDeploymentResult - { - HasData = false - }; - } - else - { - return new ModelDeploymentResult - { - HasData = true, - Id = result.Data.Id.ToString(), - Name = result.Data.Name, - Type = result.Data.ResourceType.ToString(), - Sku = result.Data.Sku, - Tags = result.Data.Tags, - Properties = result.Data.Properties?.ToObjectFromJson(FoundryJsonContext.Default.IDictionaryStringObject) - }; - } - } - catch (Exception ex) - { - throw new Exception($"Failed to deploy model: {ex.Message}", ex); - } - } - - public async Task> ListKnowledgeIndexes( - string endpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters((nameof(endpoint), endpoint)); - ValidateProjectEndpoint(endpoint); - - try - { - var projectClient = await CreateAIProjectClientWithAuth(endpoint, tenantId, cancellationToken); - var indexesClient = projectClient.GetIndexesClient(); - - var indexes = new List(); - await foreach (var index in indexesClient.GetIndicesAsync(cancellationToken)) - { - // Determine the type based on the actual type of the index - string indexType = index switch - { - AzureAISearchIndex => "AzureAISearchIndex", - ManagedAzureAISearchIndex => "ManagedAzureAISearchIndex", - CosmosDBIndex => "CosmosDBIndex", - _ => index.GetType().Name - }; - - var knowledgeIndex = new KnowledgeIndexInformation - { - Type = indexType, - Id = index.Id, - Name = index.Name, - Version = index.Version, - Description = index.Description, - Tags = index.Tags?.ToDictionary(kvp => kvp.Key, kvp => (string?)kvp.Value) ?? null - }; - - indexes.Add(knowledgeIndex); - } - - return indexes; - } - catch (Exception ex) - { - throw new Exception($"Failed to list knowledge indexes: {ex.Message}", ex); - } - } - - public async Task GetKnowledgeIndexSchema( - string endpoint, - string indexName, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(endpoint), endpoint), - (nameof(indexName), indexName)); - ValidateProjectEndpoint(endpoint); - - try - { - var projectClient = await CreateAIProjectClientWithAuth(endpoint, tenantId, cancellationToken); - var indexesClient = projectClient.GetIndexesClient(); - - // Find the index by name using async enumerable - var index = await indexesClient.GetIndicesAsync(cancellationToken: cancellationToken) - .Where(i => string.Equals(i.Name, indexName, StringComparison.OrdinalIgnoreCase)) - .FirstOrDefaultAsync(cancellationToken: cancellationToken); - - if (index == null) - { - throw new Exception($"Knowledge index '{indexName}' not found."); - } - - // Map the SDK index to our AOT-safe schema type - string indexType = index switch - { - AzureAISearchIndex => "AzureAISearchIndex", - ManagedAzureAISearchIndex => "ManagedAzureAISearchIndex", - CosmosDBIndex => "CosmosDBIndex", - _ => index.GetType().Name - }; - - return new KnowledgeIndexSchema - { - Type = indexType, - Id = index.Id, - Name = index.Name, - Version = index.Version, - Description = index.Description, - Tags = index.Tags?.ToDictionary(kvp => kvp.Key, kvp => (string?)kvp.Value) - }; - } - catch (Exception ex) - { - throw new Exception($"Failed to get knowledge index schema: {ex.Message}", ex); - } - } - - public async Task CreateCompletionAsync( - string resourceName, - string deploymentName, - string promptText, - string subscription, - string resourceGroup, - int? maxTokens = null, - double? temperature = null, - string? tenant = null, - AuthMethod authMethod = AuthMethod.Credential, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(resourceName), resourceName), - (nameof(deploymentName), deploymentName), - (nameof(promptText), promptText), - (nameof(subscription), subscription), - (nameof(resourceGroup), resourceGroup)); - - var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken); - var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken: cancellationToken); - - // Get the Cognitive Services account - var cognitiveServicesAccounts = resourceGroupResource.Value.GetCognitiveServicesAccounts(); - var cognitiveServicesAccount = await cognitiveServicesAccounts.GetAsync(resourceName, cancellationToken: cancellationToken); - - // Get the endpoint - var accountData = cognitiveServicesAccount.Value.Data; - var endpoint = accountData.Properties.Endpoint; - - if (string.IsNullOrEmpty(endpoint)) - { - throw new InvalidOperationException($"Endpoint not found for resource '{resourceName}'"); - } - - // Create Azure OpenAI client with flexible authentication - AzureOpenAIClient client = await CreateOpenAIClientWithAuth(endpoint, resourceName, cognitiveServicesAccount.Value, authMethod, tenant, cancellationToken); - - var chatClient = client.GetChatClient(deploymentName); - - // Set up completion options - var chatOptions = new ChatCompletionOptions(); - - // Set max tokens with a default value if not provided - var effectiveMaxTokens = maxTokens ?? 1000; - if (effectiveMaxTokens <= 0) - { - effectiveMaxTokens = 1000; // Ensure we always have a positive value - } - chatOptions.MaxOutputTokenCount = effectiveMaxTokens; - - if (temperature.HasValue) - { - chatOptions.Temperature = (float)temperature.Value; - } - - // Create the completion request - var messages = new List - { - new UserChatMessage(promptText) - }; - - var completion = await chatClient.CompleteChatAsync(messages, chatOptions, cancellationToken: cancellationToken); - - var result = completion.Value; - var completionText = result.Content[0].Text; - - var usageInfo = new CompletionUsageInfo( - result.Usage.InputTokenCount, - result.Usage.OutputTokenCount, - result.Usage.TotalTokenCount); - - return new CompletionResult(completionText, usageInfo); - } - - public async Task CreateEmbeddingsAsync( - string resourceName, - string deploymentName, - string inputText, - string subscription, - string resourceGroup, - string? user = null, - string encodingFormat = "float", - int? dimensions = null, - string? tenant = null, - AuthMethod authMethod = AuthMethod.Credential, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(resourceName), resourceName), - (nameof(deploymentName), deploymentName), - (nameof(inputText), inputText), - (nameof(subscription), subscription), - (nameof(resourceGroup), resourceGroup) - ); - - var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken); - var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken: cancellationToken); - - // Get the Cognitive Services account - var cognitiveServicesAccounts = resourceGroupResource.Value.GetCognitiveServicesAccounts(); - var cognitiveServicesAccount = await cognitiveServicesAccounts.GetAsync(resourceName, cancellationToken: cancellationToken); - - // Get the endpoint - var accountData = cognitiveServicesAccount.Value.Data; - var endpoint = accountData.Properties.Endpoint; - - if (string.IsNullOrEmpty(endpoint)) - { - throw new InvalidOperationException($"Endpoint not found for resource '{resourceName}'"); - } - - // Create Azure OpenAI client with flexible authentication - AzureOpenAIClient client = await CreateOpenAIClientWithAuth(endpoint, resourceName, cognitiveServicesAccount.Value, authMethod, tenant, cancellationToken); - - var embeddingClient = client.GetEmbeddingClient(deploymentName); - - // Create the embedding request - var embedding = await embeddingClient.GenerateEmbeddingAsync(inputText, cancellationToken: cancellationToken); - - var result = embedding.Value; - - var embeddingData = new List - { - new EmbeddingData( - "embedding", - 0, - result.ToFloats().ToArray()) - }; - - // Note: Usage information might not be available in the current SDK version - // Using placeholder values for now - var splitInput = inputText.Split(' ', StringSplitOptions.RemoveEmptyEntries); - var usageInfo = new EmbeddingUsageInfo( - splitInput.Length, // Approximate token count - splitInput.Length); - - return new EmbeddingResult( - "list", - embeddingData, - deploymentName, - usageInfo); - } - - public async Task ListOpenAiModelsAsync( - string resourceName, - string subscription, - string resourceGroup, - string? tenant = null, - AuthMethod authMethod = AuthMethod.Credential, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters((nameof(resourceName), resourceName), (nameof(subscription), subscription), (nameof(resourceGroup), resourceGroup)); - - var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken); - var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken: cancellationToken); - - // Get the Cognitive Services account - var cognitiveServicesAccounts = resourceGroupResource.Value.GetCognitiveServicesAccounts(); - var cognitiveServicesAccount = await cognitiveServicesAccounts.GetAsync(resourceName, cancellationToken: cancellationToken); - - // Get all deployments for this account - var deployments = cognitiveServicesAccount.Value.GetCognitiveServicesAccountDeployments(); - var allDeployments = new List(); - - await foreach (var deployment in deployments.GetAllAsync(cancellationToken: cancellationToken)) - { - var deploymentData = deployment.Data; - var properties = deploymentData.Properties; - - // Determine model capabilities based on model name - var capabilities = DetermineModelCapabilities(properties.Model?.Name); - - var modelDeployment = new OpenAiModelDeployment( - DeploymentName: deploymentData.Name, - ModelName: properties.Model?.Name ?? "Unknown", - ModelVersion: properties.Model?.Version, - ScaleType: properties.ScaleSettings?.ScaleType?.ToString(), - Capacity: properties.ScaleSettings?.Capacity, - ProvisioningState: deploymentData.Properties.ProvisioningState?.ToString(), - CreatedAt: null, // This information may not be available in the current API - UpdatedAt: null, // This information may not be available in the current API - Capabilities: capabilities - ); - - allDeployments.Add(modelDeployment); - } - - return new OpenAiModelsListResult(allDeployments, resourceName); - } - - private static OpenAiModelCapabilities DetermineModelCapabilities(string? modelName) - { - if (string.IsNullOrEmpty(modelName)) - { - return new OpenAiModelCapabilities(false, false, false, false); - } - - var modelNameLower = modelName.ToLowerInvariant(); - - // Determine capabilities based on model name patterns - var isEmbeddingModel = modelNameLower.Contains("embedding") || modelNameLower.Contains("ada"); - var isCompletionModel = modelNameLower.Contains("gpt") || modelNameLower.Contains("davinci") || modelNameLower.Contains("curie") || modelNameLower.Contains("babbage"); - var isChatModel = modelNameLower.Contains("gpt-3.5") || modelNameLower.Contains("gpt-4") || modelNameLower.Contains("gpt-35"); - var supportsFineTuning = modelNameLower.Contains("gpt-3.5") || modelNameLower.Contains("gpt-35") || modelNameLower.Contains("davinci"); - - return new OpenAiModelCapabilities( - Completions: isCompletionModel, - Embeddings: isEmbeddingModel, - ChatCompletions: isChatModel, - FineTuning: supportsFineTuning - ); - } - - public async Task CreateChatCompletionsAsync( - string resourceName, - string deploymentName, - string subscription, - string resourceGroup, - List messages, - int? maxTokens = null, - double? temperature = null, - double? topP = null, - double? frequencyPenalty = null, - double? presencePenalty = null, - string? stop = null, - bool? stream = null, - int? seed = null, - string? user = null, - string? tenant = null, - AuthMethod authMethod = AuthMethod.Credential, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(resourceName), resourceName), - (nameof(deploymentName), deploymentName), - (nameof(subscription), subscription), - (nameof(resourceGroup), resourceGroup) - ); - - if (messages == null || messages.Count == 0) - { - throw new ArgumentException("Messages array cannot be null or empty", nameof(messages)); - } - - var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken); - var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken: cancellationToken); - - // Get the Cognitive Services account - var cognitiveServicesAccounts = resourceGroupResource.Value.GetCognitiveServicesAccounts(); - var cognitiveServicesAccount = await cognitiveServicesAccounts.GetAsync(resourceName, cancellationToken: cancellationToken); - - // Get the endpoint - var accountData = cognitiveServicesAccount.Value.Data; - var endpoint = accountData.Properties.Endpoint; - - if (string.IsNullOrEmpty(endpoint)) - { - throw new InvalidOperationException($"Endpoint not found for resource '{resourceName}'"); - } - - // Create Azure OpenAI client with flexible authentication - AzureOpenAIClient client = await CreateOpenAIClientWithAuth(endpoint, resourceName, cognitiveServicesAccount.Value, authMethod, tenant, cancellationToken); - - var chatClient = client.GetChatClient(deploymentName); - - // Convert messages to ChatMessage objects - var chatMessages = new List(); - foreach (var message in messages) - { - if (message is JsonObject jsonMessage) - { - var role = jsonMessage["role"]?.ToString(); - var content = jsonMessage["content"]?.ToString(); - - if (string.IsNullOrEmpty(role) || string.IsNullOrEmpty(content)) - { - throw new ArgumentException("Each message must have 'role' and 'content' properties"); - } - - OpenAI.Chat.ChatMessage chatMessage = role.ToLowerInvariant() switch - { - "system" => OpenAI.Chat.ChatMessage.CreateSystemMessage(content), - "user" => OpenAI.Chat.ChatMessage.CreateUserMessage(content), - "assistant" => OpenAI.Chat.ChatMessage.CreateAssistantMessage(content), - _ => throw new ArgumentException($"Invalid message role: {role}") - }; - - chatMessages.Add(chatMessage); - } - else - { - throw new ArgumentException("Messages must be valid JSON objects with 'role' and 'content' properties"); - } - } - - // Create chat completion options - var options = new ChatCompletionOptions(); - if (maxTokens.HasValue) - options.MaxOutputTokenCount = maxTokens.Value; - if (temperature.HasValue) - options.Temperature = (float)temperature.Value; - if (topP.HasValue) - options.TopP = (float)topP.Value; - if (frequencyPenalty.HasValue) - options.FrequencyPenalty = (float)frequencyPenalty.Value; - if (presencePenalty.HasValue) - options.PresencePenalty = (float)presencePenalty.Value; -#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - if (seed.HasValue) - options.Seed = seed.Value; -#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - if (!string.IsNullOrEmpty(user)) - options.EndUserId = user; - - // Handle stop sequences - if (!string.IsNullOrEmpty(stop)) - { - var stopSequences = stop.Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(s => s.Trim()) - .ToArray(); - foreach (var stopSequence in stopSequences) - { - options.StopSequences.Add(stopSequence); - } - } - - // Create the chat completion - var response = await chatClient.CompleteChatAsync(chatMessages, options, cancellationToken: cancellationToken); - var result = response.Value; - - // Convert response to our model - var choices = new List(); - for (int i = 0; i < result.Content.Count; i++) - { - var contentPart = result.Content[i]; - var message = new ChatCompletionMessage( - Role: "assistant", - Content: contentPart.Text, - Name: null, - FunctionCall: null, - ToolCalls: null - ); - - var choice = new ChatCompletionChoice( - Index: i, - Message: message, - FinishReason: result.FinishReason.ToString(), - LogProbs: null - ); - - choices.Add(choice); - } - - // Create usage information - var usage = new ChatCompletionUsageInfo( - PromptTokens: result.Usage?.InputTokenCount ?? 0, - CompletionTokens: result.Usage?.OutputTokenCount ?? 0, - TotalTokens: result.Usage?.TotalTokenCount ?? 0 - ); - - return new ChatCompletionResult( - Id: result.Id ?? Guid.NewGuid().ToString(), - Object: "chat.completion", - Created: DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - Model: deploymentName, - Choices: choices, - Usage: usage, - SystemFingerprint: result.SystemFingerprint - ); - } - - private async Task CreateOpenAIClientWithAuth( - string endpoint, - string resourceName, - CognitiveServicesAccountResource cognitiveServicesAccount, - AuthMethod authMethod, - string? tenant = null, - CancellationToken cancellationToken = default) - { - AzureOpenAIClient client; - - // Configure AzureOpenAIClientOptions with HttpClient transport for test proxy support - var httpClient = _httpClientFactory.CreateClient(); - var clientOptions = new AzureOpenAIClientOptions - { - Transport = new HttpClientPipelineTransport(httpClient) - }; - - switch (authMethod) - { - case AuthMethod.Key: - // Get the access key - var keys = await cognitiveServicesAccount.GetKeysAsync(cancellationToken); - var key = keys.Value.Key1; - - if (string.IsNullOrEmpty(key)) - { - throw new InvalidOperationException($"Access key not found for resource '{resourceName}'"); - } - - client = new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(key), clientOptions); - break; - - case AuthMethod.Credential: - default: - var credential = await GetCredential(cancellationToken); - client = new AzureOpenAIClient(new Uri(endpoint), credential, clientOptions); - break; - } - return client; - } - - private async Task CreateAIProjectClientWithAuth( - string endpoint, - string? tenant = null, - CancellationToken cancellationToken = default) - { - var credential = await GetCredential(tenant, cancellationToken); - var transport = CreateTransport(); - - var clientOptions = new AIProjectClientOptions - { - Transport = transport - }; - - return new AIProjectClient(new Uri(endpoint), credential, clientOptions); - } - - private async Task<(AIProjectClient ProjectClient, PersistentAgentsClient AgentsClient)> CreateAIProjectAndPersistentAgentsClientsAsync( - string endpoint, - string? tenant = null, - CancellationToken cancellationToken = default) - { - var credential = await GetCredential(tenant, cancellationToken); - var transport = CreateTransport(); - - var projectClientOptions = new AIProjectClientOptions - { - Transport = transport - }; - - var projectClient = new AIProjectClient(new Uri(endpoint), credential, projectClientOptions); - - var agentsClientOptions = new PersistentAgentsAdministrationClientOptions - { - Transport = transport - }; - - var agentsClient = new PersistentAgentsClient(endpoint, credential, agentsClientOptions); - - return (projectClient, agentsClient); - } - - private HttpClientTransport CreateTransport() - { - var httpClient = _httpClientFactory.CreateClient(); - var transport = new HttpClientTransport(httpClient); - - return transport; - } - - public async Task> ListAgents( - string endpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters((nameof(endpoint), endpoint)); - ValidateProjectEndpoint(endpoint); - - try - { - var (_, agentsClient) = await CreateAIProjectAndPersistentAgentsClientsAsync(endpoint, tenantId, cancellationToken); - - var agents = new List(); - await foreach (var agent in agentsClient.Administration.GetAgentsAsync(cancellationToken: cancellationToken)) - { - if (agent != null) - { - agents.Add(agent); - } - } - - return agents; - } - catch (Exception ex) - { - throw new Exception($"Failed to list agents: {ex.Message}", ex); - } - } - - public async Task CreateAgent( - string projectEndpoint, - string modelDeploymentName, - string agentName, - string systemInstruction, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(projectEndpoint), projectEndpoint), - (nameof(modelDeploymentName), modelDeploymentName), - (nameof(agentName), agentName), - (nameof(systemInstruction), systemInstruction)); - ValidateProjectEndpoint(projectEndpoint); - - var (projectClient, agentsClient) = await CreateAIProjectAndPersistentAgentsClientsAsync(projectEndpoint, tenantId, cancellationToken); - - // Validate if the model deployment exists - var deploymentsClient = projectClient.GetDeploymentsClient(); - try - { - await deploymentsClient.GetDeploymentAsync(modelDeploymentName, cancellationToken: cancellationToken); - } - catch (Exception ex) - { - throw new Exception($"Unable to create agent. Get model deployment failed with: {ex.Message}", ex); - } - try - { - PersistentAgent agent = await agentsClient.Administration.CreateAgentAsync(modelDeploymentName, agentName, null, systemInstruction, cancellationToken: cancellationToken); - return new AgentsCreateResult() - { - AgentId = agent.Id, - AgentName = agent.Name, - ProjectEndpoint = projectEndpoint, - ModelDeploymentName = modelDeploymentName - }; - } - catch (Exception ex) - { - throw new Exception($"Unable to create agent. Create agent request failed with: {ex.Message}", ex); - } - } - - public async Task ConnectAgent( - string agentId, - string query, - string endpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(agentId), agentId), - (nameof(query), query), - (nameof(endpoint), endpoint)); - ValidateProjectEndpoint(endpoint); - - try - { - - var (_, agentsClient) = await CreateAIProjectAndPersistentAgentsClientsAsync(endpoint, tenantId, cancellationToken); - - PersistentAgentThread thread = await agentsClient.Threads.CreateThreadAsync( - [new ThreadMessageOptions(MessageRole.User, query)], - cancellationToken: cancellationToken); - var threadId = thread.Id; - - var run = await agentsClient.Runs.CreateRunAsync(threadId, agentId, cancellationToken: cancellationToken); - var runId = run.Value.Id; - - while (run.Value.Status == RunStatus.Queued || run.Value.Status == RunStatus.InProgress || run.Value.Status == RunStatus.RequiresAction) - { - await Task.Delay(1, cancellationToken: cancellationToken); - run = await agentsClient.Runs.GetRunAsync(threadId, runId, cancellationToken: cancellationToken); - } - - if (run.Value.Status == RunStatus.Failed) - { - throw new Exception($"Agent run failed: {run.Value.LastError}"); - } - - var (textResponse, citations) = buildResponseAndCitations(agentsClient, threadId, cancellationToken: cancellationToken); - var messages = await agentsClient.Messages.GetMessagesAsync(threadId, order: ListSortOrder.Ascending, cancellationToken: cancellationToken).ToListAsync(cancellationToken: cancellationToken); - var runSteps = await agentsClient.Runs.GetRunStepsAsync(threadId, runId, order: ListSortOrder.Ascending, cancellationToken: cancellationToken).ToListAsync(cancellationToken: cancellationToken); - - List requestMessages = []; - List responseMessages = []; - - foreach (var message in messages) - { - if (message.Role == MessageRole.User) - { - requestMessages.Add(message); - } - else - { - responseMessages.Add(message); - } - } - - var convertedRequestMessages = ConvertMessages(requestMessages).ToList(); - convertedRequestMessages.Prepend(new Microsoft.Extensions.AI.ChatMessage(ChatRole.System, run.Value.Instructions)); - - // full list of messages converted to Microsoft.Extensions.AI.ChatMessage for evaluation - var convertedResponse = ConvertMessages(messages) - .Concat(ConvertSteps(runSteps)) - .OrderBy(o => o.RawRepresentation switch - { - PersistentThreadMessage m => m.CreatedAt, - RunStep s => s.CreatedAt, - _ => default, - }) - .ThenBy(o => o.Contents!.OfType().Any() ? 0 : o.Contents!.OfType().Any() ? 1 : 2) - .ToList(); - - var toolDefinitions = ConvertTools(run.Value.Tools).ToList(); - - return new AgentsConnectResult - { - AgentId = agentId, - ThreadId = threadId, - RunId = runId, - ResponseText = textResponse.ToString().Trim(), - QueryText = query, - Response = convertedResponse, - Query = convertedRequestMessages, - ToolDefinitions = JsonSerializer.Serialize(toolDefinitions, FoundryJsonContext.Default.ListToolDefinitionAIFunction), - Citations = citations - }; - } - catch (Exception ex) - { - throw new Exception($"Failed to connect to agent: {ex.Message}", ex); - } - } - - public async Task QueryAndEvaluateAgent( - string agentId, - string query, - string endpoint, - string azureOpenAIEndpoint, - string azureOpenAIDeployment, - string? tenant = null, - List? evaluatorNames = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(agentId), agentId), - (nameof(query), query), - (nameof(endpoint), endpoint)); - ValidateProjectEndpoint(endpoint); - ValidateAzureOpenAiEndpoint(azureOpenAIEndpoint); - - try - { - var connectAgentResult = await ConnectAgent(agentId, query, endpoint, tenant, retryPolicy, cancellationToken: cancellationToken); - - var credential = await GetCredential(tenant, cancellationToken: cancellationToken); - - List evaluators = []; - List evaluationContexts = []; - var toolDefinitions = connectAgentResult.ToolDefinitions; - var loadedToolDefinitions = ConvertToolDefinitionsFromString(toolDefinitions); - - if (evaluatorNames == null || evaluatorNames.Count == 0) - { - evaluatorNames = AgentEvaluatorDictionary.Keys.ToList(); - } - - foreach (var name in evaluatorNames) - { - var evaluatorName = name.ToLowerInvariant(); - if (AgentEvaluatorDictionary.TryGetValue(evaluatorName, out var createEvaluator)) - { - var evaluator = createEvaluator(); - evaluators.Add(evaluator); - evaluationContexts.Add(AgentEvaluatorContextDictionary[evaluatorName](loadedToolDefinitions ?? [])); - } - } - var compositeEvaluator = new CompositeEvaluator(evaluators); - - var azureOpenAIChatClient = GetAzureOpenAIChatClient(azureOpenAIEndpoint, azureOpenAIDeployment, credential, _httpClientFactory); - - var evaluationResult = await compositeEvaluator.EvaluateAsync( - connectAgentResult.Query ?? [], - new ChatResponse(connectAgentResult.Response ?? []), - new ChatConfiguration(azureOpenAIChatClient), - evaluationContexts, - cancellationToken: cancellationToken); - - return new AgentsQueryAndEvaluateResult - { - AgentId = agentId, - ThreadId = connectAgentResult.ThreadId, - RunId = connectAgentResult.RunId, - ResponseText = connectAgentResult.ResponseText, - QueryText = connectAgentResult.QueryText, - Response = connectAgentResult.Response, - Query = connectAgentResult.Query, - ToolDefinitions = connectAgentResult.ToolDefinitions, - Citations = connectAgentResult.Citations, - Evaluators = evaluatorNames, - EvaluationResult = evaluationResult - }; - } - catch (Exception ex) - { - throw new Exception($"Failed to connect to agent and evaluate its response: {ex.Message}", ex); - } - } - - public async Task EvaluateAgent( - string evaluatorName, - string query, - string agentResponse, - string azureOpenAIEndpoint, - string azureOpenAIDeployment, - string? toolDefinitions, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(evaluatorName), evaluatorName), - (nameof(query), query), - (nameof(agentResponse), agentResponse)); - ValidateAzureOpenAiEndpoint(azureOpenAIEndpoint); - - try - { - if (!AgentEvaluatorDictionary.ContainsKey(evaluatorName.ToLowerInvariant())) - { - throw new Exception($"Unknown evaluator {evaluatorName}. Supported evaluators are: {string.Join(", ", AgentEvaluatorDictionary.Keys)}"); - } - - var loadedQuery = JsonSerializer.Deserialize(query, (JsonTypeInfo>)AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(List))); - var loadedAgentResponse = JsonSerializer.Deserialize(agentResponse, (JsonTypeInfo>)AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(List))); - var loadedToolDefinitions = ConvertToolDefinitionsFromString(toolDefinitions); - - var evaluator = AgentEvaluatorDictionary[evaluatorName.ToLowerInvariant()](); - List evaluationContext = []; - evaluationContext.Add(AgentEvaluatorContextDictionary[evaluatorName.ToLowerInvariant()](loadedToolDefinitions!)); - - var credential = await GetCredential(tenantId, cancellationToken: cancellationToken); - - var azureOpenAIChatClient = GetAzureOpenAIChatClient(azureOpenAIEndpoint, azureOpenAIDeployment, credential, _httpClientFactory); - - var result = await evaluator.EvaluateAsync( - loadedQuery ?? [], - new ChatResponse(loadedAgentResponse), - new ChatConfiguration(azureOpenAIChatClient), - evaluationContext, - cancellationToken: cancellationToken); - - return new AgentsEvaluateResult - { - Evaluator = evaluatorName, - EvaluationResult = result - }; - } - catch (Exception ex) - { - throw new Exception($"Failed to evaluate agent response: {ex.Message}", ex); - } - } - - public async Task ListThreads( - string projectEndpoint, - string? tenantId, - RetryPolicyOptions? retryPolicy, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(projectEndpoint), projectEndpoint)); - ValidateProjectEndpoint(projectEndpoint); - - var (_, agentsClient) = await CreateAIProjectAndPersistentAgentsClientsAsync(projectEndpoint, tenantId, cancellationToken); - - var threadsIterator = agentsClient.Threads.GetThreadsAsync(cancellationToken: cancellationToken); - List threads = []; - try - { - await foreach (var thread in threadsIterator.WithCancellation(cancellationToken)) - { - threads.Add(new() - { - ThreadId = thread.Id - }); - } - - return new ThreadListResult() - { - Threads = [.. threads] - }; - } - catch (Exception ex) - { - throw new Exception($"Unable to list threads. Get threads request failed with: {ex.Message}", ex); - } - } - - public async Task CreateThread( - string projectEndpoint, - string userMessage, - string? tenantId, - RetryPolicyOptions? retryPolicy, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(projectEndpoint), projectEndpoint), - (nameof(userMessage), userMessage)); - ValidateProjectEndpoint(projectEndpoint); - - var (_, agentsClient) = await CreateAIProjectAndPersistentAgentsClientsAsync(projectEndpoint, tenantId, cancellationToken); - - try - { - PersistentAgentThread thread = await agentsClient.Threads.CreateThreadAsync( - [new ThreadMessageOptions(MessageRole.User, userMessage)], - cancellationToken: cancellationToken); - - return new ThreadCreateResult() - { - ThreadId = thread.Id, - ProjectEndpoint = projectEndpoint - }; - } - catch (Exception ex) - { - throw new Exception($"Unable to create thread. Create thread request failed with: {ex.Message}", ex); - } - } - - public async Task GetMessages( - string projectEndpoint, - string threadId, - string? tenantId, - RetryPolicyOptions? retryPolicy, - CancellationToken cancellationToken = default - ) - { - ValidateRequiredParameters( - (nameof(projectEndpoint), projectEndpoint), - (nameof(threadId), threadId)); - ValidateProjectEndpoint(projectEndpoint); - - var (_, agentsClient) = await CreateAIProjectAndPersistentAgentsClientsAsync(projectEndpoint, tenantId, cancellationToken); - - try - { - List messages = []; - var messagesIterator = agentsClient.Messages.GetMessagesAsync(threadId, cancellationToken: cancellationToken); - await foreach (var message in messagesIterator.WithCancellation(cancellationToken)) - { - messages.Add(message); - } - List convertedMessages = ConvertMessages(messages).ToList(); - return new ThreadGetMessagesResult() - { - ThreadId = threadId, - Messages = convertedMessages - }; - } - catch (Exception ex) - { - throw new Exception($"Unable to get messages. Get messages request failed with: {ex.Message}", ex); - } - } - - private List ConvertToolDefinitionsFromString(string? toolDefinitions) - { - if (string.IsNullOrEmpty(toolDefinitions)) - { - return []; - } - - List functionDefinitions = []; - using JsonDocument toolDefinitionsAsJson = JsonDocument.Parse(toolDefinitions, new() { AllowTrailingCommas = true }); - foreach (JsonElement jsonElement in toolDefinitionsAsJson.RootElement.EnumerateArray()) - { - functionDefinitions.Add(ToolDefinitionAIFunction.DeserializeToolDefinition(jsonElement)); - } - return functionDefinitions; - } - - private static IEnumerable ConvertMessages(IEnumerable messages) - { - foreach (PersistentThreadMessage message in messages) - { - Microsoft.Extensions.AI.ChatMessage result = new() - { - AuthorName = message.AssistantId, - MessageId = message.Id, - RawRepresentation = message, - Role = message.Role == MessageRole.Agent ? ChatRole.Assistant : ChatRole.User, - }; - - foreach (var messageContent in message.ContentItems) - { - AIContent content = messageContent switch - { - MessageTextContent mtc => new TextContent(mtc.Text), - _ => new AIContent(), - }; - content.RawRepresentation = messageContent; - result.Contents.Add(content); - } - - yield return result; - } - } - - private static IEnumerable ConvertSteps(IEnumerable steps) - { - foreach (RunStep step in steps) - { - if (step.StepDetails is RunStepToolCallDetails { ToolCalls: not null } details) - { - foreach (RunStepToolCall toolCall in details.ToolCalls) - { - switch (toolCall) - { - case RunStepFunctionToolCall function: - yield return CreateRequestMessage(toolCall.Id, function.Name, Parse(function.Arguments) ?? [], step); - // TODO: output doesn't appear to be available in the API - - static Dictionary? Parse(string arguments) - { - try - { return JsonSerializer.Deserialize(arguments, DictionaryTypeInfo); } - catch { return null; } - } - break; - - case RunStepCodeInterpreterToolCall code: - yield return CreateRequestMessage(toolCall.Id, "code_interpreter", new() { ["input"] = code.Input }, step); - yield return CreateResponseMessage(toolCall.Id, string.Concat(code.Outputs.OfType().Select(o => o.Logs)), step); - break; - - case RunStepBingGroundingToolCall bing: - yield return CreateRequestMessage(toolCall.Id, "bing_grounding", new() { ["requesturl"] = bing.BingGrounding["requesturl"] }, step); - break; - - case RunStepFileSearchToolCall fileSearch: - yield return CreateRequestMessage(toolCall.Id, "file_search", new() - { - ["ranking_options"] = JsonSerializer.SerializeToElement(new() - { - ["ranker"] = fileSearch.FileSearch.RankingOptions.Ranker, - ["score_threshold"] = fileSearch.FileSearch.RankingOptions.ScoreThreshold, - }, DictionaryTypeInfo) - }, step); - List fileSearchResults = fileSearch.FileSearch.Results.Select(r => new AgentFileSearchResult - { - FileId = r.FileId, - FileName = r.FileName, - Score = r.Score, - Content = r.Content, - }).ToList(); - yield return CreateResponseMessage(toolCall.Id, fileSearchResults, step); - break; - - case RunStepAzureAISearchToolCall aiSearch: - yield return CreateRequestMessage(toolCall.Id, "azure_ai_search", new() { ["input"] = aiSearch.AzureAISearch["input"] }, step); - yield return CreateResponseMessage(toolCall.Id, new Dictionary - { - ["output"] = aiSearch.AzureAISearch["output"] - }, step); - break; - - case RunStepMicrosoftFabricToolCall fabric: - yield return CreateRequestMessage(toolCall.Id, "fabric_dataagent", new() { ["input"] = fabric.MicrosoftFabric["input"] }, step); - yield return CreateResponseMessage(toolCall.Id, fabric.MicrosoftFabric["output"], step); - break; - } - } - } - } - } - - private static IEnumerable ConvertTools(IEnumerable tools) - { - foreach (var tool in tools) - { - switch (tool) - { - case FunctionToolDefinition functionToolDefinition: - yield return new ToolDefinitionAIFunction(functionToolDefinition.Name, - functionToolDefinition.Description, - DeserializeToElement(functionToolDefinition.Parameters)); - break; - case CodeInterpreterToolDefinition: - JsonObject codeInterpreterSchema = new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["input"] = new JsonObject { ["type"] = "string", ["description"] = "Generated code to be executed." }, - } - }; - yield return new ToolDefinitionAIFunction( - "code_interpreter", - "Use code interpreter to read and interpret information from datasets, " - + "generate code, and create graphs and charts using your data. Supports " - + "up to 20 files.", - JsonSerializer.SerializeToElement(codeInterpreterSchema, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonObject)))); - break; - case BingGroundingToolDefinition: - var bingGroundingSchema = new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["requesturl"] = new JsonObject - { - ["type"] = "string", - ["description"] = "URL used in Bing Search API." - } - } - }; - yield return new ToolDefinitionAIFunction( - "bing_grounding", - "Enhance model output with web data.", - JsonSerializer.SerializeToElement(bingGroundingSchema, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonObject)))); - break; - case FileSearchToolDefinition: - JsonObject fileSearchSchema = new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["ranking_options"] = new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["ranker"] = new JsonObject { ["type"] = "string", ["description"] = "Ranking algorithm to use" }, - ["score_threshold"] = new JsonObject { ["type"] = "number", ["description"] = "Minimum score threshold for search results." }, - }, - ["description"] = "Ranking options for search results." - } - } - }; - yield return new ToolDefinitionAIFunction( - "file_search", - "Search for data across uploaded files.", - JsonSerializer.SerializeToElement(fileSearchSchema, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonObject)))); - break; - case AzureAISearchToolDefinition: - JsonObject azureAISearchSchema = new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["input"] = new JsonObject { ["type"] = "string", ["description"] = "Search terms to use." }, - } - }; - yield return new ToolDefinitionAIFunction( - "azure_ai_search", - "Search an Azure AI Search index for relevant data.", - JsonSerializer.SerializeToElement(azureAISearchSchema, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonObject)))); - break; - case MicrosoftFabricToolDefinition: - JsonObject microsoftFabricSchema = new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["input"] = new JsonObject { ["type"] = "string", ["description"] = "Search terms to use." }, - } - }; - yield return new ToolDefinitionAIFunction( - "microsoft_fabric", - "Connect to Microsoft Fabric data agents to retrieve data across different data sources.", - JsonSerializer.SerializeToElement(microsoftFabricSchema, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonObject)))); - break; - } - } - } - - private static (string messageContents, List citations) buildResponseAndCitations( - PersistentAgentsClient agentClient, - string threadId, - CancellationToken cancellationToken = default) - { - var responseMessage = agentClient.Messages.GetMessagesAsync(threadId, cancellationToken: cancellationToken).FirstOrDefaultAsync(msg => msg.Role == MessageRole.Agent, cancellationToken: cancellationToken).Result; - - var result = new StringBuilder(); - var citations = new List(); - - if (responseMessage != null) - { - foreach (var messageContent in responseMessage.ContentItems) - { - if (messageContent is MessageTextContent messageTextContent) - { - result.AppendLine(messageTextContent.Text); - foreach (var messageTextAnnotation in messageTextContent.Annotations) - { - if (messageTextAnnotation is MessageTextUriCitationAnnotation messageTextUriCitationAnnotation) - { - var citation = $"[{messageTextUriCitationAnnotation.UriCitation.Title}]({messageTextUriCitationAnnotation.UriCitation.Uri})"; - if (!citations.Contains(citation)) - { - citations.Add(citation); - } - } - } - } - } - } - - if (citations.Count > 0) - { - result.AppendLine("\n\n## Sources"); - foreach (var citation in citations) - { - result.AppendLine($"- {citation}"); - } - } - return (result.ToString().Trim(), citations); - } - - private IChatClient GetAzureOpenAIChatClient(string azureOpenAIEndpoint, string azureOpenAIDeployment, TokenCredential credential, IHttpClientFactory httpClientFactory) - { - var azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); - - // Configure AzureOpenAIClientOptions with HttpClient transport for test proxy support - var httpClient = httpClientFactory.CreateClient(); - var clientOptions = new AzureOpenAIClientOptions - { - Transport = new HttpClientPipelineTransport(httpClient) - }; - - switch (azureOpenAIKey) - { - case null: - return new AzureOpenAIClient( - new Uri(azureOpenAIEndpoint), - credential, - clientOptions).GetChatClient(azureOpenAIDeployment).AsIChatClient(); - default: - return new AzureOpenAIClient( - new Uri(azureOpenAIEndpoint), - new ApiKeyCredential(azureOpenAIKey), - clientOptions).GetChatClient(azureOpenAIDeployment).AsIChatClient(); - } - } - - public async Task> ListAiResourcesAsync( - string subscription, - string? resourceGroup = null, - string? tenant = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters((nameof(subscription), subscription)); - - try - { - ArmClient armClient = await CreateArmClientAsync(tenant, retryPolicy, cancellationToken: cancellationToken); - var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken); - - var resources = new List(); - - // Get Cognitive Services accounts - if (string.IsNullOrEmpty(resourceGroup)) - { - // List all AI resources in the subscription - await foreach (var account in subscriptionResource.GetCognitiveServicesAccountsAsync(cancellationToken: cancellationToken)) - { - var resourceInfo = await BuildResourceInformation(account, subscriptionResource.Data.DisplayName, cancellationToken); - resources.Add(resourceInfo); - } - } - else - { - // List AI resources in specific resource group - use resource group scope for better performance - var resourceGroupResource = await subscriptionResource.GetResourceGroups().GetAsync(resourceGroup, cancellationToken: cancellationToken); - await foreach (var account in resourceGroupResource.Value.GetCognitiveServicesAccounts().GetAllAsync(cancellationToken: cancellationToken)) - { - var resourceInfo = await BuildResourceInformation(account, subscriptionResource.Data.DisplayName, cancellationToken); - resources.Add(resourceInfo); - if (account.Data.Id.ResourceGroupName?.Equals(resourceGroup, StringComparison.OrdinalIgnoreCase) == true) - { - var retrieved = await BuildResourceInformation(account, subscriptionResource.Data.DisplayName, cancellationToken); - resources.Add(retrieved); - } - } - } - - return resources; - } - catch (Exception ex) - { - throw new Exception($"Failed to list AI resources: {ex.Message}", ex); - } - } - - public async Task GetAiResourceAsync( - string subscription, - string resourceGroup, - string resourceName, - string? tenant = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default) - { - ValidateRequiredParameters( - (nameof(subscription), subscription), - (nameof(resourceGroup), resourceGroup), - (nameof(resourceName), resourceName)); - - try - { - ArmClient armClient = await CreateArmClientAsync(tenant, retryPolicy, cancellationToken: cancellationToken); - var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken); - var rgResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken: cancellationToken); - - if (rgResource?.Value == null) - { - throw new Exception($"Resource group '{resourceGroup}' not found in subscription '{subscription}'"); - } - - var account = await rgResource.Value.GetCognitiveServicesAccountAsync(resourceName, cancellationToken: cancellationToken); - if (account?.Value == null) - { - throw new Exception($"AI resource '{resourceName}' not found in resource group '{resourceGroup}'"); - } - - return await BuildResourceInformation(account.Value, subscriptionResource.Data.DisplayName, cancellationToken); - } - catch (Exception ex) - { - throw new Exception($"Failed to get AI resource: {ex.Message}", ex); - } - } - - public AgentsGetSdkCodeSampleResult GetSdkCodeSample(string programmingLanguage) - { - string programmingLanguageLowerCase = programmingLanguage.ToLowerInvariant(); - string resourceFileName; - if (programmingLanguageLowerCase == "csharp") - { - resourceFileName = "csharp.md"; - } - else if (programmingLanguageLowerCase == "typescript") - { - resourceFileName = "typescript.md"; - } - else if (programmingLanguageLowerCase == "python") - { - resourceFileName = "python.md"; - } - else - { - throw new Exception($"Unsupported programming language for Foundry Agent Sdk {programmingLanguage}"); - } - - Assembly assembly = typeof(FoundryService).Assembly; - - string resourceName = EmbeddedResourceHelper.FindEmbeddedResource(assembly, resourceFileName); - string codeSampleText = EmbeddedResourceHelper.ReadEmbeddedResource(assembly, resourceName); - - return new AgentsGetSdkCodeSampleResult() - { - CodeSampleText = codeSampleText - }; - } - - private async Task BuildResourceInformation( - CognitiveServicesAccountResource account, - string subscriptionName, - CancellationToken cancellationToken) - { - var resourceInfo = new AiResourceInformation - { - ResourceName = account.Data.Name, - ResourceGroup = account.Data.Id.ResourceGroupName, - SubscriptionName = subscriptionName, - Location = account.Data.Location.Name, - Endpoint = account.Data.Properties?.Endpoint, - Kind = account.Data.Kind, - SkuName = account.Data.Sku?.Name, - Deployments = [] - }; - - // Get deployments for this resource - try - { - await foreach (var deployment in account.GetCognitiveServicesAccountDeployments().WithCancellation(cancellationToken)) - { - var deploymentInfo = new DeploymentInformation - { - DeploymentName = deployment.Data.Name, - ModelName = deployment.Data.Properties?.Model?.Name, - ModelVersion = deployment.Data.Properties?.Model?.Version, - ModelFormat = deployment.Data.Properties?.Model?.Format, - SkuName = deployment.Data.Sku?.Name, - SkuCapacity = deployment.Data.Sku?.Capacity, - ScaleType = deployment.Data.Properties?.ScaleSettings?.ScaleType.ToString(), - ProvisioningState = deployment.Data.Properties?.ProvisioningState.ToString() - }; - resourceInfo.Deployments?.Add(deploymentInfo); - } - } - catch - { - // If we can't list deployments, continue with empty list - resourceInfo.Deployments = []; - } - - return resourceInfo; - } - - private static JsonTypeInfo> DictionaryTypeInfo { get; } = - (JsonTypeInfo>)AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(Dictionary)); - - private static JsonElement DeserializeToElement(BinaryData data) => - (JsonElement)JsonSerializer.Deserialize(data.ToMemory().Span, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!; - - public sealed class ToolDefinitionAIFunction(string name, string description, JsonElement? schema = null) : AIFunctionDeclaration - { - public override string Name => name; - public override string Description => description; - public override JsonElement JsonSchema => schema ?? base.JsonSchema; - - public static ToolDefinitionAIFunction DeserializeToolDefinition(JsonElement json) - { - string name = json.GetProperty("name").GetString() ?? ""; - string description = json.GetProperty("description").GetString() ?? ""; - JsonElement? schema = json.TryGetProperty("jsonSchema", out JsonElement schemaElement) ? schemaElement.Clone() : null; - return new ToolDefinitionAIFunction(name, description, schema); - } - - public string SerializeToolDefinition() - { - MemoryStream bytes = new(); - using (Utf8JsonWriter writer = new(bytes)) - { - writer.WriteStartObject(); - writer.WriteString("name", Name); - writer.WriteString("description", Description); - writer.WritePropertyName("jsonSchema"); - JsonSchema.WriteTo(writer); - writer.WriteEndObject(); - } - return Encoding.UTF8.GetString(bytes.GetBuffer(), 0, (int)bytes.Length); - } - } - - /// - /// The type of parameter arguments must be registered in the JsonSerializerContext. - /// - /// - internal static Microsoft.Extensions.AI.ChatMessage CreateRequestMessage(string toolCallId, string name, Dictionary arguments, RunStep step) - { - return new(ChatRole.Assistant, [new FunctionCallContent(toolCallId, name, arguments)]) - { - AuthorName = step.AssistantId, - MessageId = step.Id, - RawRepresentation = step, - }; - } - - /// - /// The type of parameter arguments must be serializable by default or registered in the JsonSerializerContext. - /// - /// - internal static Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(string toolCallId, List result, RunStep step) - { - return new(ChatRole.Tool, [new FunctionResultContent(toolCallId, result)]) - { - AuthorName = step.AssistantId, - MessageId = step.Id, - RawRepresentation = step, - }; - } - - internal static Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(string toolCallId, JsonElement result, RunStep step) - { - return new(ChatRole.Tool, [new FunctionResultContent(toolCallId, result)]) - { - AuthorName = step.AssistantId, - MessageId = step.Id, - RawRepresentation = step, - }; - } - - internal static Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(string toolCallId, string result, RunStep step) - { - return new(ChatRole.Tool, [new FunctionResultContent(toolCallId, result)]) - { - AuthorName = step.AssistantId, - MessageId = step.Id, - RawRepresentation = step, - }; - } - - /// - /// The type of parameter arguments must be serializable by default or registered in the JsonSerializerContext. - /// - /// - internal static Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(string toolCallId, IDictionary result, RunStep step) - { - return new(ChatRole.Tool, [new FunctionResultContent(toolCallId, result)]) - { - AuthorName = step.AssistantId, - MessageId = step.Id, - RawRepresentation = step, - }; - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/IFoundryService.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/IFoundryService.cs deleted file mode 100644 index 0b9bb5e10d..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/IFoundryService.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.AI.Agents.Persistent; -using Azure.AI.Projects; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options.Thread; - -namespace Azure.Mcp.Tools.Foundry.Services; - -public interface IFoundryService -{ - Task> ListModels( - bool searchForFreePlayground = false, - string publisherName = "", - string licenseName = "", - string modelName = "", - int maxPages = 3, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task> ListDeployments( - string endpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task DeployModel(string deploymentName, - string modelName, - string modelFormat, - string azureAiServicesName, - string resourceGroup, - string subscriptionId, - string? modelVersion = null, - string? modelSource = null, - string? skuName = null, - int? skuCapacity = null, - string? scaleType = null, - int? scaleCapacity = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task> ListKnowledgeIndexes( - string endpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default); - - Task GetKnowledgeIndexSchema( - string endpoint, - string indexName, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default); - - Task CreateCompletionAsync( - string resourceName, - string deploymentName, - string promptText, - string subscription, - string resourceGroup, - int? maxTokens = null, - double? temperature = null, - string? tenant = null, - AuthMethod authMethod = AuthMethod.Credential, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task CreateEmbeddingsAsync( - string resourceName, - string deploymentName, - string inputText, - string subscription, - string resourceGroup, - string? user = null, - string encodingFormat = "float", - int? dimensions = null, - string? tenant = null, - AuthMethod authMethod = AuthMethod.Credential, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task ListOpenAiModelsAsync( - string resourceName, - string subscription, - string resourceGroup, - string? tenant = null, - AuthMethod authMethod = AuthMethod.Credential, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task> ListAgents( - string endpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task ConnectAgent( - string agentId, - string query, - string endpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task QueryAndEvaluateAgent( - string agentId, - string query, - string endpoint, - string azureOpenAIEndpoint, - string azureOpenAIDeployment, - string? tenantId = null, - List? evaluatorNames = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task EvaluateAgent( - string evaluatorName, - string query, - string agentResponse, - string azureOpenAIEndpoint, - string azureOpenAIDeployment, - string? toolDefinitions, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task CreateChatCompletionsAsync( - string resourceName, - string deploymentName, - string subscription, - string resourceGroup, - List messages, - int? maxTokens = null, - double? temperature = null, - double? topP = null, - double? frequencyPenalty = null, - double? presencePenalty = null, - string? stop = null, - bool? stream = null, - int? seed = null, - string? user = null, - string? tenant = null, - AuthMethod authMethod = AuthMethod.Credential, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task> ListAiResourcesAsync( - string subscription, - string? resourceGroup = null, - string? tenant = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task GetAiResourceAsync( - string subscription, - string resourceGroup, - string resourceName, - string? tenant = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task CreateAgent( - string projectEndpoint, - string modelDeploymentName, - string agentName, - string systemInstruction, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task ListThreads( - string projectEndpoint, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task CreateThread( - string projectEndpoint, - string userMessage, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - Task GetMessages( - string projectEndpoint, - string threadId, - string? tenantId = null, - RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken = default - ); - - AgentsGetSdkCodeSampleResult GetSdkCodeSample( - string programmingLanguage - ); -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentData.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentData.cs deleted file mode 100644 index 5441d22d4f..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentData.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Foundry.Services.Models; - -/// -/// A class representing the CognitiveServicesAccountDeployment data model. -/// Cognitive Services account deployment. -/// -internal sealed class CognitiveServicesAccountDeploymentData -{ - /// The resource model definition representing SKU. - public CognitiveServicesSku? Sku { get; set; } - /// Properties of Cognitive Services account deployment. - public CognitiveServicesAccountDeploymentProperties? Properties { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentModel.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentModel.cs deleted file mode 100644 index 205051d3ae..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentModel.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Foundry.Services.Models; - -/// Properties of Cognitive Services account deployment model. -internal sealed class CognitiveServicesAccountDeploymentModel -{ - /// Deployment model format. - public string? Format { get; set; } - /// Deployment model name. - public string? Name { get; set; } - /// Optional. Deployment model version. If version is not specified, a default version will be assigned. The default version is different for different models and might change when there is new version available for a model. Default version for a model could be found from list models API. - public string? Version { get; set; } - /// Optional. Deployment model source ARM resource ID. - public string? Source { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentProperties.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentProperties.cs deleted file mode 100644 index 02b85110a2..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentProperties.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Foundry.Services.Models; - -/// Properties of Cognitive Services account deployment. -internal sealed class CognitiveServicesAccountDeploymentProperties -{ - /// Properties of Cognitive Services account deployment model. - public CognitiveServicesAccountDeploymentModel? Model { get; set; } - /// Properties of Cognitive Services account deployment model. (Deprecated, please use Deployment.sku instead.). - public CognitiveServicesAccountDeploymentScaleSettings? ScaleSettings { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentScaleSettings.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentScaleSettings.cs deleted file mode 100644 index e4246f6ccb..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesAccountDeploymentScaleSettings.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Foundry.Services.Models; - -/// Properties of Cognitive Services account deployment model. (Deprecated, please use Deployment.sku instead.). -internal sealed class CognitiveServicesAccountDeploymentScaleSettings -{ - /// Deployment scale type. - public string? ScaleType { get; set; } - /// Deployment capacity. - public int? Capacity { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesSku.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesSku.cs deleted file mode 100644 index 2663b42d09..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/Models/CognitiveServicesSku.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Foundry.Services.Models; - -/// The resource model definition representing SKU. -internal sealed class CognitiveServicesSku -{ - /// The name of the SKU. Ex - P3. It is typically a letter+number code. - public string? Name { get; set; } - /// If the SKU supports scale out/in then the capacity integer should be included. If scale out/in is not possible for the resource this may be omitted. - public int? Capacity { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/Azure.Mcp.Tools.Foundry.LiveTests.csproj b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/Azure.Mcp.Tools.Foundry.LiveTests.csproj deleted file mode 100644 index 0f06a032a0..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/Azure.Mcp.Tools.Foundry.LiveTests.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - true - Exe - - - - - - - - - - - - - diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/FoundryCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/FoundryCommandTests.cs deleted file mode 100644 index 95c03cef66..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/FoundryCommandTests.cs +++ /dev/null @@ -1,1269 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json; -using Azure.Mcp.Tests; -using Azure.Mcp.Tests.Client; -using Azure.Mcp.Tests.Client.Helpers; -using Azure.Mcp.Tests.Generated.Models; -using Azure.Mcp.Tests.Helpers; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.LiveTests; - -public class FoundryCommandTests(ITestOutputHelper output, TestProxyFixture fixture, LiveServerFixture liveServerFixture) - : RecordedCommandTestsBase(output, fixture, liveServerFixture) -{ - // Sanitize subscription IDs in URIs to allow playback to work - public override List UriRegexSanitizers => - [ - new UriRegexSanitizer(new UriRegexSanitizerBody - { - Regex = "/subscriptions/(?[^/]+)/", - GroupForReplace = "sub", - Value = "00000000-0000-0000-0000-000000000000" - }), - new UriRegexSanitizer(new UriRegexSanitizerBody - { - Regex = "/resourcegroups/(?[^/?]+)", - GroupForReplace = "rg", - Value = "Sanitized" - }), - new UriRegexSanitizer(new UriRegexSanitizerBody - { - Regex = "/resourceGroups/(?[^/?]+)", - GroupForReplace = "rg", - Value = "Sanitized" - }), - new UriRegexSanitizer(new UriRegexSanitizerBody - { - Regex = "/projects/(?[^/?]+)", - GroupForReplace = "project", - Value = "Sanitized-ai-projects" - }) - ]; - - public override List BodyRegexSanitizers => - [ - .. base.BodyRegexSanitizers, - // Sanitize resource group names with usernames in connection IDs - new BodyRegexSanitizer(new BodyRegexSanitizerBody - { - Regex = @"SSS3PT_[^/\""]+", - Value = "Sanitized" - }) - ]; - - [Fact] - public async Task Should_list_foundry_models() - { - var result = await CallToolAsync( - "foundry_models_list", - new() - { - { "search-for-free-playground", "true" } - }); - - var modelsArray = result.AssertProperty("models"); - Assert.Equal(JsonValueKind.Array, modelsArray.ValueKind); - Assert.NotEmpty(modelsArray.EnumerateArray()); - } - - [Fact] - public async Task Should_list_foundry_model_deployments() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var result = await CallToolAsync( - "foundry_models_deployments_list", - new() - { - { "endpoint", $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}" }, - { "tenant", Settings.TenantId } - }); - - var deploymentsArray = result.AssertProperty("deployments"); - Assert.Equal(JsonValueKind.Array, deploymentsArray.ValueKind); - Assert.NotEmpty(deploymentsArray.EnumerateArray()); - } - - [Fact] - public async Task Should_deploy_foundry_model() - { - var azureAiServicesAccount = Settings.ResourceBaseName; - RegisterVariable("azureAiServicesAccount", azureAiServicesAccount); - - var deploymentName = $"test-deploy-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - RegisterVariable("deploymentName", deploymentName); - - var result = await CallToolAsync( - "foundry_models_deploy", - new() - { - { "deployment", TestVariables.GetValueOrDefault("deploymentName", deploymentName) }, - { "model-name", "gpt-4o" }, - { "model-format", "OpenAI"}, - { "azure-ai-services", TestVariables.GetValueOrDefault("azureAiServicesAccount", Settings.ResourceBaseName) }, - { "resource-group", Settings.ResourceGroupName }, - { "subscription", TestVariables.GetValueOrDefault("subscriptionId", Settings.SubscriptionId) }, - }); - - var deploymentResource = result.AssertProperty("deploymentData"); - Assert.Equal(JsonValueKind.Object, deploymentResource.ValueKind); - Assert.NotEmpty(deploymentResource.EnumerateObject()); - } - - [Fact] - public async Task Should_list_foundry_knowledge_indexes() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var result = await CallToolAsync( - "foundry_knowledge_index_list", - new() - { - { "endpoint", $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}" }, - { "tenant", Settings.TenantId } - }); - - // The command may return null if no indexes exist, or an array if indexes are found - if (result.HasValue && result.Value.TryGetProperty("indexes", out var indexesArray)) - { - Assert.Equal(JsonValueKind.Array, indexesArray.ValueKind); - } - // If no "indexes" property or result is null, the command succeeded with no content - } - - [Fact] - public async Task Should_get_foundry_knowledge_index_schema() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var endpoint = $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}"; - - // First get list of indexes to find one to test with - var listResult = await CallToolAsync( - "foundry_knowledge_index_list", - new() - { - { "endpoint", endpoint }, - { "tenant", Settings.TenantId } - }); - - // Check if we have indexes to test with - if (listResult.HasValue && listResult.Value.TryGetProperty("indexes", out var indexesArray) && indexesArray.GetArrayLength() > 0) - { - var firstIndex = indexesArray[0]; - var indexName = firstIndex.GetProperty("name").GetString(); - - var result = await CallToolAsync( - "foundry_knowledge_index_schema", - new() - { - { "endpoint", endpoint }, - { "index", indexName! }, - { "tenant", Settings.TenantId } - }); - - var schema = result.AssertProperty("schema"); - Assert.Equal(JsonValueKind.Object, schema.ValueKind); - } - else - { - // Skip test if no indexes are available - Output.WriteLine("Skipping knowledge index schema test - no indexes available for testing"); - } - } - - [Fact] - public async Task Should_create_openai_completion() - { - var resourceName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNT", "dummy-test"); - var deploymentName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIDEPLOYMENTNAME", "gpt-4o-mini"); - var resourceGroup = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNTRESOURCEGROUP", "static-test-resources"); - var tenantId = Settings.TenantId; - - // Register variables for playback mode - RegisterVariable("resourceName", resourceName); - RegisterVariable("deploymentName", deploymentName); - RegisterVariable("resourceGroup", resourceGroup); - RegisterVariable("tenantId", tenantId); - - var subscriptionId = Settings.SubscriptionId; - var result = await CallToolAsync( - "foundry_openai_create-completion", - new() - { - { "subscription", subscriptionId }, - { "resource-group", TestVariables["resourceGroup"] }, - { "resource-name", TestVariables["resourceName"] }, - { "deployment", TestVariables["deploymentName"] }, - { "prompt-text", "What is Azure? Please provide a brief answer." }, - { "max-tokens", "50" }, - { "temperature", "0.7" }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure - var completionText = result.AssertProperty("completionText"); - Assert.Equal(JsonValueKind.String, completionText.ValueKind); - Assert.NotEmpty(completionText.GetString()!); - - var usageInfo = result.AssertProperty("usageInfo"); - Assert.Equal(JsonValueKind.Object, usageInfo.ValueKind); - - // Verify usage info contains expected properties - var promptTokens = usageInfo.AssertProperty("promptTokens"); - var completionTokens = usageInfo.AssertProperty("completionTokens"); - var totalTokens = usageInfo.AssertProperty("totalTokens"); - - Assert.Equal(JsonValueKind.Number, promptTokens.ValueKind); - Assert.Equal(JsonValueKind.Number, completionTokens.ValueKind); - Assert.Equal(JsonValueKind.Number, totalTokens.ValueKind); - - // Verify total tokens = prompt + completion - Assert.Equal( - promptTokens.GetInt32() + completionTokens.GetInt32(), - totalTokens.GetInt32() - ); - } - - [Fact] - public async Task Should_create_openai_embeddings() - { - var resourceName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNT", "dummy-test"); - var deploymentName = Settings.DeploymentOutputs.GetValueOrDefault("EMBEDDINGDEPLOYMENTNAME", "text-embedding-ada-002"); - var resourceGroup = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNTRESOURCEGROUP", "static-test-resources"); - var subscriptionId = Settings.SubscriptionId; - var tenantId = Settings.TenantId; - var inputText = "Generate embeddings for this test text using Azure OpenAI."; - - RegisterVariable("resourceName", resourceName); - RegisterVariable("deploymentName", deploymentName); - RegisterVariable("resourceGroup", resourceGroup); - RegisterVariable("subscriptionId", subscriptionId); - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_openai_embeddings-create", - new() - { - { "subscription", TestVariables["subscriptionId"] }, - { "resource-group", TestVariables["resourceGroup"] }, - { "resource-name", TestVariables["resourceName"] }, - { "deployment", TestVariables["deploymentName"] }, - { "input-text", inputText }, - { "user", "test-user" }, - { "encoding-format", "float" }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure - var embeddingResult = result.AssertProperty("embeddingResult"); - Assert.Equal(JsonValueKind.Object, embeddingResult.ValueKind); - - // Verify embedding result properties - var objectType = embeddingResult.AssertProperty("object"); - Assert.Equal(JsonValueKind.String, objectType.ValueKind); - Assert.Equal("list", objectType.GetString()); - - var data = embeddingResult.AssertProperty("data"); - Assert.Equal(JsonValueKind.Array, data.ValueKind); - Assert.NotEmpty(data.EnumerateArray()); - - // Verify first embedding data element - var firstEmbedding = data.EnumerateArray().First(); - var embeddingObject = firstEmbedding.GetProperty("object"); - Assert.Equal("embedding", embeddingObject.GetString()); - - var embeddingVector = firstEmbedding.GetProperty("embedding"); - Assert.Equal(JsonValueKind.Array, embeddingVector.ValueKind); - - // Verify embedding vector contains float values and has reasonable dimensions - var vectorArray = embeddingVector.EnumerateArray().ToArray(); - Assert.True(vectorArray.Length > 0, "Embedding vector should not be empty"); - Assert.True(vectorArray.Length >= 1536, $"Embedding vector should have at least 1536 dimensions, got {vectorArray.Length}"); // Ada-002 has 1536 dimensions - - // Verify all values are valid numbers - foreach (var value in vectorArray) - { - Assert.Equal(JsonValueKind.Number, value.ValueKind); - var floatValue = value.GetSingle(); - Assert.True(!float.IsNaN(floatValue), "Embedding values should not be NaN"); - Assert.True(!float.IsInfinity(floatValue), "Embedding values should not be infinity"); - } - - // Verify model name in response - var model = embeddingResult.AssertProperty("model"); - Assert.Equal(JsonValueKind.String, model.ValueKind); - Assert.Equal(TestVariables["deploymentName"], model.GetString()); - - // Verify usage information - var usage = embeddingResult.AssertProperty("usage"); - Assert.Equal(JsonValueKind.Object, usage.ValueKind); - - var promptTokens = usage.AssertProperty("prompt_tokens"); - var totalTokens = usage.AssertProperty("total_tokens"); - - Assert.Equal(JsonValueKind.Number, promptTokens.ValueKind); - Assert.Equal(JsonValueKind.Number, totalTokens.ValueKind); - - // For embeddings, prompt tokens should equal total tokens (no completion tokens) - Assert.Equal(promptTokens.GetInt32(), totalTokens.GetInt32()); - Assert.True(promptTokens.GetInt32() > 0, "Should have used some tokens"); - - // Verify metadata properties are present - var resourceNameProperty = result.AssertProperty("resourceName"); - var deploymentNameProperty = result.AssertProperty("deploymentName"); - var inputTextProperty = result.AssertProperty("inputText"); - - Assert.Equal(JsonValueKind.String, resourceNameProperty.ValueKind); - Assert.Equal(JsonValueKind.String, deploymentNameProperty.ValueKind); - Assert.Equal(JsonValueKind.String, inputTextProperty.ValueKind); - - Assert.Equal(TestVariables["resourceName"], resourceNameProperty.GetString()); - Assert.Equal(TestVariables["deploymentName"], deploymentNameProperty.GetString()); - Assert.Equal(inputText, inputTextProperty.GetString()); - } - - [Fact] - public async Task Should_create_openai_embeddings_with_optional_parameters() - { - var resourceName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNT", "dummy-test"); - var deploymentName = Settings.DeploymentOutputs.GetValueOrDefault("EMBEDDINGDEPLOYMENTNAME", "text-embedding-ada-002"); - var resourceGroup = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNTRESOURCEGROUP", "static-test-resources"); - var subscriptionId = Settings.SubscriptionId; - var tenantId = Settings.TenantId; - var inputText = "Test embeddings with optional parameters."; - var dimensions = 512; // Test with reduced dimensions if supported - - RegisterVariable("resourceName", resourceName); - RegisterVariable("deploymentName", deploymentName); - RegisterVariable("resourceGroup", resourceGroup); - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_openai_embeddings-create", - new() - { - { "subscription", subscriptionId }, - { "resource-group", TestVariables["resourceGroup"] }, - { "resource-name", TestVariables["resourceName"] }, - { "deployment", TestVariables["deploymentName"] }, - { "input-text", inputText }, - { "user", "test-user-with-params" }, - { "encoding-format", "float" }, - { "dimensions", dimensions.ToString() }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure (same as basic test) - var embeddingResult = result.AssertProperty("embeddingResult"); - var data = embeddingResult.AssertProperty("data"); - var firstEmbedding = data.EnumerateArray().First(); - var embeddingVector = firstEmbedding.GetProperty("embedding"); - - // Verify embedding vector dimensions match requested dimensions (if model supports it) - var vectorArray = embeddingVector.EnumerateArray().ToArray(); - Assert.True(vectorArray.Length > 0, "Embedding vector should not be empty"); - - // Note: Some models may not support custom dimensions and will return default size - // So we just verify we got a reasonable response, not necessarily the exact dimensions requested - Assert.True(vectorArray.Length >= 512, $"Embedding vector should have reasonable dimensions, got {vectorArray.Length}"); - - // Verify all values are valid numbers - foreach (var value in vectorArray) - { - Assert.Equal(JsonValueKind.Number, value.ValueKind); - var floatValue = value.GetSingle(); - Assert.True(!float.IsNaN(floatValue), "Embedding values should not be NaN"); - Assert.True(!float.IsInfinity(floatValue), "Embedding values should not be infinity"); - } - - // Verify usage information shows token consumption - var usage = embeddingResult.AssertProperty("usage"); - var totalTokens = usage.AssertProperty("total_tokens"); - Assert.True(totalTokens.GetInt32() > 0, "Should have consumed tokens"); - } - - [Fact] - public async Task Should_list_openai_models() - { - var resourceName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNT", "dummy-test"); - var resourceGroup = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNTRESOURCEGROUP", "static-test-resources"); - var tenantId = Settings.TenantId; - - // Register variables for recording sanitization - RegisterVariable("resourceName", resourceName); - RegisterVariable("resourceGroup", resourceGroup); - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_openai_models-list", - new() - { - { "subscription", Settings.SubscriptionId }, // Don't register - test proxy auto-sanitizes GUIDs in URLs - { "resource-group", TestVariables["resourceGroup"] }, - { "resource-name", TestVariables["resourceName"] }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure - var modelsListResult = result.AssertProperty("modelsListResult"); - Assert.Equal(JsonValueKind.Object, modelsListResult.ValueKind); - - // Verify resource name matches - var returnedResourceName = modelsListResult.AssertProperty("resourceName"); - Assert.Equal(JsonValueKind.String, returnedResourceName.ValueKind); - Assert.Equal(TestVariables["resourceName"], returnedResourceName.GetString()); - - // Verify models array exists (may be empty if no models deployed) - var models = modelsListResult.AssertProperty("models"); - Assert.Equal(JsonValueKind.Array, models.ValueKind); - - // If models exist, verify their structure - var modelArray = models.EnumerateArray().ToArray(); - if (modelArray.Length > 0) - { - foreach (var model in modelArray) - { - // Verify required properties exist - var deploymentName = model.GetProperty("deploymentName"); - var modelName = model.GetProperty("modelName"); - - Assert.Equal(JsonValueKind.String, deploymentName.ValueKind); - Assert.Equal(JsonValueKind.String, modelName.ValueKind); - Assert.NotEmpty(deploymentName.GetString()!); - Assert.NotEmpty(modelName.GetString()!); - - // Verify modelVersion if present - if (model.TryGetProperty("modelVersion", out var modelVersion)) - { - Assert.Equal(JsonValueKind.String, modelVersion.ValueKind); - Assert.NotEmpty(modelVersion.GetString()!); - } - - // Verify capabilities structure if present - if (model.TryGetProperty("capabilities", out var capabilities)) - { - Assert.Equal(JsonValueKind.Object, capabilities.ValueKind); - - // Check boolean capability properties (only validate the ones that are present) - if (capabilities.TryGetProperty("completions", out var completions)) - { - Assert.True(completions.ValueKind == JsonValueKind.True || completions.ValueKind == JsonValueKind.False, - "completions should be a boolean value"); - } - - if (capabilities.TryGetProperty("embeddings", out var embeddings)) - { - Assert.True(embeddings.ValueKind == JsonValueKind.True || embeddings.ValueKind == JsonValueKind.False, - "embeddings should be a boolean value"); - } - - if (capabilities.TryGetProperty("chatCompletions", out var chatCompletions)) - { - Assert.True(chatCompletions.ValueKind == JsonValueKind.True || chatCompletions.ValueKind == JsonValueKind.False, - "chatCompletions should be a boolean value"); - } - - if (capabilities.TryGetProperty("fineTuning", out var fineTuning)) - { - Assert.True(fineTuning.ValueKind == JsonValueKind.True || fineTuning.ValueKind == JsonValueKind.False, - "fineTuning should be a boolean value"); - } - } - - // Verify provisioningState if present - if (model.TryGetProperty("provisioningState", out var provisioningState)) - { - Assert.Equal(JsonValueKind.String, provisioningState.ValueKind); - Assert.NotEmpty(provisioningState.GetString()!); - } - - // Verify optional capacity property if present - if (model.TryGetProperty("capacity", out var capacity)) - { - Assert.Equal(JsonValueKind.Number, capacity.ValueKind); - Assert.True(capacity.GetInt32() > 0); - } - } - } - - // Verify command metadata (returned resource name should match input) - var commandResourceName = result.AssertProperty("resourceName"); - Assert.Equal(JsonValueKind.String, commandResourceName.ValueKind); - Assert.Equal(TestVariables["resourceName"], commandResourceName.GetString()); - } - - [Fact] - public async Task Should_create_agent() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var agentName = $"test-agent-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - var endpoint = $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}"; - // Model deployment name hardcoded in the test-resources.bicep - var modelDeploymentName = "gpt-4o"; - var systemInstruction = "Help user with your knowledge"; - - // Register variables for recording sanitization - RegisterVariable("endpoint", endpoint); - RegisterVariable("agentName", agentName); - RegisterVariable("modelDeploymentName", modelDeploymentName); - - var result = await CallToolAsync( - "foundry_agents_create", - new() - { - { "endpoint", TestVariables["endpoint"] }, - { "model-deployment", TestVariables["modelDeploymentName"] }, - { "agent-name", TestVariables["agentName"] }, - { "system-instruction", systemInstruction } - }); - - var agentIdResult = result.AssertProperty("agentId"); - var agentNameResult = result.AssertProperty("agentName"); - var projectEndpointResult = result.AssertProperty("projectEndpoint"); - var modelDeploymentNameResult = result.AssertProperty("modelDeploymentName"); - Assert.Equal(JsonValueKind.String, agentIdResult.ValueKind); - Assert.Equal(JsonValueKind.String, agentNameResult.ValueKind); - Assert.Equal(JsonValueKind.String, projectEndpointResult.ValueKind); - Assert.Equal(JsonValueKind.String, modelDeploymentNameResult.ValueKind); - } - - [Fact] - public async Task Should_connect_agent() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var agentName = $"test-agent-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - var query = "What is the weather today in NYC?"; - var endpoint = $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}"; - - // Register variables for recording sanitization - RegisterVariable("endpoint", endpoint); - RegisterVariable("agentName", agentName); - - var createResult = await CallToolAsync( - "foundry_agents_create", - new() - { - { "endpoint", TestVariables["endpoint"] }, - { "model-deployment", "gpt-4o" }, - { "agent-name", TestVariables["agentName"] }, - { "system-instruction", "Help user with your knowledge" } - }); - var agentId = createResult.AssertProperty("agentId").GetString()!; - RegisterVariable("agentId", agentId); - - var result = await CallToolAsync( - "foundry_agents_connect", - new() - { - { "agent-id", TestVariables["agentId"] }, - { "query", query }, - { "endpoint", TestVariables["endpoint"] } - }); - var response = result.AssertProperty("response"); - Assert.Equal(JsonValueKind.Object, response.ValueKind); - Assert.NotEmpty(response.EnumerateObject()); - response.AssertProperty("query"); - response.AssertProperty("response"); - response.AssertProperty("queryText"); - response.AssertProperty("responseText"); - response.AssertProperty("agentId"); - response.AssertProperty("toolDefinitions"); - } - - [Fact] - public async Task Should_list_agents() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var agentName = $"test-agent-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - var endpoint = $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}"; - - // Register variables for recording sanitization - RegisterVariable("endpoint", endpoint); - RegisterVariable("agentName", agentName); - - await CallToolAsync( - "foundry_agents_create", - new() - { - { "endpoint", TestVariables["endpoint"] }, - { "model-deployment", "gpt-4o" }, - { "agent-name", TestVariables["agentName"] }, - { "system-instruction", "Help user with your knowledge" } - }); - - var result = await CallToolAsync( - "foundry_agents_list", - new() - { - { "endpoint", TestVariables["endpoint"] } - }); - var agentsArray = result.AssertProperty("agents"); - Assert.Equal(JsonValueKind.Array, agentsArray.ValueKind); - Assert.NotEmpty(agentsArray.EnumerateArray()); - } - - [Theory] - [InlineData("task_adherence", "Task Adherence")] - [InlineData("tool_call_accuracy", "Tool Call Accuracy")] - [InlineData("intent_resolution", "Intent Resolution")] - public async Task Should_query_and_evaluate_agent(string evaluatorName, string evaluationMetric) - { - // to be filled in - - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var agentName = $"test-agent-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - var endpoint = $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}"; - var azureOpenAIEndpoint = $"https://{accounts}.cognitiveservices.azure.com"; - var azureOpenAIDeployment = "gpt-4o"; - - RegisterVariable("endpoint", endpoint); - RegisterVariable("agentName", agentName); - - var createResult = await CallToolAsync( - "foundry_agents_create", - new() - { - { "endpoint", TestVariables["endpoint"] }, - { "model-deployment", "gpt-4o" }, - { "agent-name", TestVariables["agentName"] }, - { "system-instruction", "Help user with your knowledge" } - }); - var agentId = createResult.AssertProperty("agentId").GetString()!; - var result = await CallToolAsync( - "foundry_agents_query-and-evaluate", - new() - { - { "agent-id", agentId }, - { "query", "What is the weather in NYC today?"}, - { "endpoint", endpoint }, - { "azure-openai-endpoint", azureOpenAIEndpoint }, - { "azure-openai-deployment", azureOpenAIDeployment }, - { "evaluators", evaluatorName } - }); - - var response = result.AssertProperty("response"); - Assert.Equal(JsonValueKind.Object, response.ValueKind); - Assert.NotEmpty(response.EnumerateObject()); - response.AssertProperty("query"); - response.AssertProperty("response"); - response.AssertProperty("queryText"); - response.AssertProperty("responseText"); - response.AssertProperty("evaluators"); - var evaluationResults = response.AssertProperty("evaluationResult"); - Assert.Equal(JsonValueKind.Object, evaluationResults.ValueKind); - Assert.NotEmpty(evaluationResults.EnumerateObject()); - var metrics = evaluationResults.AssertProperty("metrics"); - Assert.Equal(JsonValueKind.Object, metrics.ValueKind); - var metric = metrics.AssertProperty(evaluationMetric); - Assert.Equal(JsonValueKind.Object, metric.ValueKind); - - // Check if diagnostics exist (indicates evaluation error/warning) - if (metric.TryGetProperty("diagnostics", out var diagnostics)) - { - // When diagnostics are present, value/reason/interpretation/context may not exist - Assert.Equal(JsonValueKind.Array, diagnostics.ValueKind); - Assert.NotEmpty(diagnostics.EnumerateArray()); - } - else - { - // Normal case: value, reason, interpretation, context should exist - metric.AssertProperty("value"); - metric.AssertProperty("reason"); - var interpretation = metric.AssertProperty("interpretation"); - Assert.Equal(JsonValueKind.Object, interpretation.ValueKind); - var context = metric.AssertProperty("context"); - Assert.Equal(JsonValueKind.Object, context.ValueKind); - } - } - - [Theory] - [InlineData("task_adherence", "Task Adherence")] - [InlineData("tool_call_accuracy", "Tool Call Accuracy")] - [InlineData("intent_resolution", "Intent Resolution")] - public async Task Should_evaluate_agent(string evaluatorName, string evaluationMetric) - { - // to be filled in - var query = "[{\"role\":\"user\",\"contents\":[{\"$type\":\"text\",\"text\":\"What is the weather in NYC today?\"}],\"messageId\":\"msg_fakeMessageHash1\"}]"; - var agentResponse = "[{\"role\":\"user\",\"contents\":[{\"$type\":\"text\",\"text\":\"What is the weather in NYC today?\"}],\"messageId\":\"msg_fakeMessageHash1\"},{\"authorName\":\"asst_XNa6yxvWUvRhCpWmE3kxk09u\",\"role\":\"assistant\",\"contents\":[{\"$type\":\"functionCall\",\"callId\":\"call_fakeCallHash1\",\"name\":\"bing_grounding\",\"arguments\":{\"requesturl\":\"https://api.bing.microsoft.com/v7.0/search?q=NewYorkCityweatherAugust42025\"}}],\"messageId\":\"step_fakeRunStepHash1\"},{\"authorName\":\"asst_XNa6yxvWUvRhCpWmE3kxk09u\",\"role\":\"assistant\",\"contents\":[{\"$type\":\"text\",\"text\":\"The weather in New York City today, August 4, 2025, is expected to have a high of 88°F during the day and a low of 70°F at night. There is a 25% chance of precipitation, with light winds at about 7 mph.\\u30103:2\\u2020source\\u3011.\"}],\"messageId\":\"msg_fakeMessageHash2\"}]"; - var toolDefinitions = "[{\"name\": \"bing_grounding\", \"description\": \"Enhance model output with web data.\", \"jsonSchema\": {\"type\": \"object\",\"properties\": {\"requesturl\": {\"type\": \"string\",\"description\": \"URL used in Bing Search API.\"}}}}]"; - var accounts = Settings.ResourceBaseName; - var azureOpenAIEndpoint = $"https://{accounts}.cognitiveservices.azure.com"; - var azureOpenAIDeployment = "gpt-4o"; - var result = await CallToolAsync( - "foundry_agents_evaluate", - new() - { - { "evaluator", evaluatorName }, - { "query", query}, - { "response", agentResponse }, - { "azure-openai-endpoint", azureOpenAIEndpoint }, - { "azure-openai-deployment", azureOpenAIDeployment }, - { "tool-definitions", toolDefinitions }, - { "evaluators", evaluatorName } - }); - - var response = result.AssertProperty("response"); - Assert.Equal(JsonValueKind.Object, response.ValueKind); - Assert.NotEmpty(response.EnumerateObject()); - var evaluationResults = response.AssertProperty("evaluationResult"); - Assert.Equal(JsonValueKind.Object, evaluationResults.ValueKind); - Assert.NotEmpty(evaluationResults.EnumerateObject()); - var metrics = evaluationResults.AssertProperty("metrics"); - Assert.Equal(JsonValueKind.Object, metrics.ValueKind); - var metric = metrics.AssertProperty(evaluationMetric); - Assert.Equal(JsonValueKind.Object, metric.ValueKind); - metric.AssertProperty("value"); - metric.AssertProperty("reason"); - var interpretation = metric.AssertProperty("interpretation"); - Assert.Equal(JsonValueKind.Object, interpretation.ValueKind); - var context = metric.AssertProperty("context"); - Assert.Equal(JsonValueKind.Object, context.ValueKind); - } - - [Fact] - public async Task Should_create_openai_chat_completions() - { - var resourceName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNT", "dummy-test"); - var deploymentName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIDEPLOYMENTNAME", "gpt-4o-mini"); - var resourceGroup = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNTRESOURCEGROUP", "static-test-resources"); - var subscriptionId = Settings.SubscriptionId; - var tenantId = Settings.TenantId; - var messages = JsonSerializer.Serialize(new[] - { - new { role = "system", content = "You are a helpful assistant." }, - new { role = "user", content = "Hello, how are you today?" } - }); - - RegisterVariable("resourceName", resourceName); - RegisterVariable("deploymentName", deploymentName); - RegisterVariable("resourceGroup", resourceGroup); - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_openai_chat-completions-create", - new() - { - { "subscription", subscriptionId }, - { "resource-group", TestVariables["resourceGroup"] }, - { "resource-name", TestVariables["resourceName"] }, - { "deployment", TestVariables["deploymentName"] }, - { "message-array", messages }, - { "max-tokens", "150" }, - { "temperature", "0.7" }, - { "user", "test-user" }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure - var chatResult = result.AssertProperty("result"); - Assert.Equal(JsonValueKind.Object, chatResult.ValueKind); - - // Verify chat completion result properties - var id = chatResult.AssertProperty("id"); - Assert.Equal(JsonValueKind.String, id.ValueKind); - Assert.False(string.IsNullOrEmpty(id.GetString())); - - var objectType = chatResult.AssertProperty("object"); - Assert.Equal(JsonValueKind.String, objectType.ValueKind); - Assert.Equal("chat.completion", objectType.GetString()); - - var model = chatResult.AssertProperty("model"); - Assert.Equal(JsonValueKind.String, model.ValueKind); - Assert.Equal(deploymentName, model.GetString()); - - var choices = chatResult.AssertProperty("choices"); - Assert.Equal(JsonValueKind.Array, choices.ValueKind); - Assert.NotEmpty(choices.EnumerateArray()); - - // Verify first choice - var firstChoice = choices.EnumerateArray().First(); - - var message = firstChoice.GetProperty("message"); - var role = message.GetProperty("role"); - Assert.Equal("assistant", role.GetString()); - - var content = message.GetProperty("content"); - Assert.Equal(JsonValueKind.String, content.ValueKind); - Assert.False(string.IsNullOrEmpty(content.GetString())); - - var finishReason = firstChoice.GetProperty("finish_reason"); - Assert.Equal(JsonValueKind.String, finishReason.ValueKind); - - // Verify usage information - var usage = chatResult.AssertProperty("usage"); - var promptTokens = usage.AssertProperty("prompt_tokens"); - Assert.True(promptTokens.GetInt32() > 0, "Should have consumed prompt tokens"); - - var completionTokens = usage.AssertProperty("completion_tokens"); - Assert.True(completionTokens.GetInt32() > 0, "Should have generated completion tokens"); - - var totalTokens = usage.AssertProperty("total_tokens"); - Assert.True(totalTokens.GetInt32() > 0, "Should have total token usage"); - Assert.Equal(promptTokens.GetInt32() + completionTokens.GetInt32(), totalTokens.GetInt32()); - - // Verify command metadata (returned resource and deployment names should match input) - var commandResourceName = result.AssertProperty("resourceName"); - Assert.Equal(JsonValueKind.String, commandResourceName.ValueKind); - Assert.Equal(TestVariables["resourceName"], commandResourceName.GetString()); - - var commandDeploymentName = result.AssertProperty("deploymentName"); - Assert.Equal(JsonValueKind.String, commandDeploymentName.ValueKind); - Assert.Equal(TestVariables["deploymentName"], commandDeploymentName.GetString()); - } - - [Fact] - public async Task Should_create_openai_chat_completions_with_conversation_history() - { - var resourceName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNT", "dummy-test"); - var deploymentName = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIDEPLOYMENTNAME", "gpt-4o-mini"); - var resourceGroup = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNTRESOURCEGROUP", "static-test-resources"); - var subscriptionId = Settings.SubscriptionId; - var tenantId = Settings.TenantId; - var messages = JsonSerializer.Serialize(new[] - { - new { role = "system", content = "You are a helpful assistant that answers questions about Azure." }, - new { role = "user", content = "What is Azure OpenAI Service?" }, - new { role = "assistant", content = "Azure OpenAI Service is a cloud service that provides REST API access to OpenAI's language models including GPT-4, GPT-3.5-turbo, and Embeddings model series." }, - new { role = "user", content = "How can I use it for chat applications?" } - }); - - RegisterVariable("resourceName", resourceName); - RegisterVariable("deploymentName", deploymentName); - RegisterVariable("resourceGroup", resourceGroup); - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_openai_chat-completions-create", - new() - { - { "subscription", subscriptionId }, - { "resource-group", TestVariables["resourceGroup"] }, - { "resource-name", TestVariables["resourceName"] }, - { "deployment", TestVariables["deploymentName"] }, - { "message-array", messages }, - { "max-tokens", "200" }, - { "temperature", "0.5" }, - { "top-p", "0.9" }, - { "user", "test-user-conversation" }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify response structure (same checks as basic test) - var chatResult = result.AssertProperty("result"); - Assert.Equal(JsonValueKind.Object, chatResult.ValueKind); - - // Verify chat completion result properties - var id = chatResult.AssertProperty("id"); - Assert.Equal(JsonValueKind.String, id.ValueKind); - Assert.False(string.IsNullOrEmpty(id.GetString())); - - var objectType = chatResult.AssertProperty("object"); - Assert.Equal(JsonValueKind.String, objectType.ValueKind); - Assert.Equal("chat.completion", objectType.GetString()); - - var model = chatResult.AssertProperty("model"); - Assert.Equal(JsonValueKind.String, model.ValueKind); - Assert.Equal(deploymentName, model.GetString()); - - var choices = chatResult.AssertProperty("choices"); - Assert.Equal(JsonValueKind.Array, choices.ValueKind); - Assert.NotEmpty(choices.EnumerateArray()); - - // Verify first choice - var firstChoice = choices.EnumerateArray().First(); - - var message = firstChoice.GetProperty("message"); - var role = message.GetProperty("role"); - Assert.Equal("assistant", role.GetString()); - - var content = message.GetProperty("content"); - Assert.Equal(JsonValueKind.String, content.ValueKind); - - // Verify the response is relevant to the conversation context - var responseText = content.GetString(); - Assert.False(string.IsNullOrEmpty(responseText)); - - // The response should be contextually relevant to chat applications - // We don't check for specific words to avoid brittleness, just that we got a meaningful response - Assert.True(responseText.Length > 10, "Response should be substantial"); - - var finishReason = firstChoice.GetProperty("finish_reason"); - Assert.Equal(JsonValueKind.String, finishReason.ValueKind); - - // Verify usage information - var usage = chatResult.AssertProperty("usage"); - var promptTokens = usage.AssertProperty("prompt_tokens"); - Assert.True(promptTokens.GetInt32() > 0, "Should have consumed prompt tokens"); - - var completionTokens = usage.AssertProperty("completion_tokens"); - Assert.True(completionTokens.GetInt32() > 0, "Should have generated completion tokens"); - - var totalTokens = usage.AssertProperty("total_tokens"); - Assert.True(totalTokens.GetInt32() > 50, "Conversation should consume reasonable tokens"); - Assert.Equal(promptTokens.GetInt32() + completionTokens.GetInt32(), totalTokens.GetInt32()); - - // Verify command metadata (returned resource and deployment names should match input) - var commandResourceName = result.AssertProperty("resourceName"); - Assert.Equal(JsonValueKind.String, commandResourceName.ValueKind); - Assert.Equal(TestVariables["resourceName"], commandResourceName.GetString()); - - var commandDeploymentName = result.AssertProperty("deploymentName"); - Assert.Equal(JsonValueKind.String, commandDeploymentName.ValueKind); - Assert.Equal(TestVariables["deploymentName"], commandDeploymentName.GetString()); - } - - [Fact] - public async Task Should_list_all_foundry_resources_in_subscription() - { - var subscriptionId = Settings.SubscriptionId; - var tenantId = Settings.TenantId; - - // Register variables for recording sanitization - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_resource_get", - new() - { - { "subscription", subscriptionId }, // Don't register - test proxy auto-sanitizes GUIDs - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure - var resources = result.AssertProperty("resources"); - Assert.Equal(JsonValueKind.Array, resources.ValueKind); - - // Should have at least one resource (the test resource) - Assert.NotEmpty(resources.EnumerateArray()); - - // Verify first resource structure - var firstResource = resources.EnumerateArray().First(); - - // Verify required properties exist - var resourceName = firstResource.AssertProperty("resourceName"); - Assert.Equal(JsonValueKind.String, resourceName.ValueKind); - Assert.NotEmpty(resourceName.GetString()!); - - var resourceGroup = firstResource.AssertProperty("resourceGroup"); - Assert.Equal(JsonValueKind.String, resourceGroup.ValueKind); - Assert.NotEmpty(resourceGroup.GetString()!); - - var subscriptionName = firstResource.AssertProperty("subscriptionName"); - Assert.Equal(JsonValueKind.String, subscriptionName.ValueKind); - Assert.NotEmpty(subscriptionName.GetString()!); - - var location = firstResource.AssertProperty("location"); - Assert.Equal(JsonValueKind.String, location.ValueKind); - Assert.NotEmpty(location.GetString()!); - - var endpoint = firstResource.AssertProperty("endpoint"); - Assert.Equal(JsonValueKind.String, endpoint.ValueKind); - Assert.NotEmpty(endpoint.GetString()!); - - var kind = firstResource.AssertProperty("kind"); - Assert.Equal(JsonValueKind.String, kind.ValueKind); - Assert.NotEmpty(kind.GetString()!); - - var skuName = firstResource.AssertProperty("skuName"); - Assert.Equal(JsonValueKind.String, skuName.ValueKind); - Assert.NotEmpty(skuName.GetString()!); - - // Verify deployments array exists (may be empty) - var deployments = firstResource.AssertProperty("deployments"); - Assert.Equal(JsonValueKind.Array, deployments.ValueKind); - } - - [Fact] - public async Task Should_list_foundry_resources_in_resource_group() - { - var subscriptionId = Settings.SubscriptionId; - var resourceGroup = Settings.ResourceGroupName; - var tenantId = Settings.TenantId; - - // Register variables for recording sanitization - RegisterVariable("resourceGroup", resourceGroup); - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_resource_get", - new() - { - { "subscription", subscriptionId }, // Don't register - test proxy auto-sanitizes GUIDs - { "resource-group", TestVariables["resourceGroup"] }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure - var resources = result.AssertProperty("resources"); - Assert.Equal(JsonValueKind.Array, resources.ValueKind); - - // Should have at least one resource in this resource group - Assert.NotEmpty(resources.EnumerateArray()); - - // Verify all resources are in the specified resource group - foreach (var resource in resources.EnumerateArray()) - { - var rg = resource.GetProperty("resourceGroup"); - var rgValue = rg.GetString(); - // In playback mode, resource group may be sanitized - Assert.True( - rgValue == TestVariables["resourceGroup"] || rgValue?.Contains("Sanitized") == true, - $"Expected resource group '{TestVariables["resourceGroup"]}' or sanitized variant, got '{rgValue}'"); - } - } - - [Fact] - public async Task Should_get_specific_foundry_resource() - { - var subscriptionId = Settings.SubscriptionId; - var resourceGroup = Settings.ResourceGroupName; - var resourceName = Settings.ResourceBaseName; - var tenantId = Settings.TenantId; - - // Register variables for recording sanitization - RegisterVariable("resourceGroup", resourceGroup); - RegisterVariable("resourceName", resourceName); - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_resource_get", - new() - { - { "subscription", subscriptionId }, // Don't register - test proxy auto-sanitizes GUIDs - { "resource-group", TestVariables["resourceGroup"] }, - { "resource-name", TestVariables["resourceName"] }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure - var resources = result.AssertProperty("resources"); - Assert.Equal(JsonValueKind.Array, resources.ValueKind); - - // Should return exactly one resource - Assert.Single(resources.EnumerateArray()); - - var resource = resources.EnumerateArray().First(); - - // Verify resource details match the request - var returnedResourceName = resource.AssertProperty("resourceName"); - Assert.Equal(TestMode == Tests.Helpers.TestMode.Playback ? "Sanitized" : TestVariables["resourceName"], returnedResourceName.GetString()); - - var returnedResourceGroup = resource.AssertProperty("resourceGroup"); - Assert.Equal(TestMode == Tests.Helpers.TestMode.Playback ? "Sanitized" : TestVariables["resourceGroup"], returnedResourceGroup.GetString()); - - // Verify all required properties - var subscriptionName = resource.AssertProperty("subscriptionName"); - Assert.Equal(JsonValueKind.String, subscriptionName.ValueKind); - Assert.NotEmpty(subscriptionName.GetString()!); - - var location = resource.AssertProperty("location"); - Assert.Equal(JsonValueKind.String, location.ValueKind); - Assert.NotEmpty(location.GetString()!); - - var endpoint = resource.AssertProperty("endpoint"); - Assert.Equal(JsonValueKind.String, endpoint.ValueKind); - Assert.NotEmpty(endpoint.GetString()!); - Assert.StartsWith("https://", endpoint.GetString()); - - var kind = resource.AssertProperty("kind"); - Assert.Equal(JsonValueKind.String, kind.ValueKind); - Assert.Contains(kind.GetString(), new[] { "OpenAI", "AIServices", "CognitiveServices" }); - - var skuName = resource.AssertProperty("skuName"); - Assert.Equal(JsonValueKind.String, skuName.ValueKind); - Assert.NotEmpty(skuName.GetString()!); - - // Verify deployments array structure - var deployments = resource.AssertProperty("deployments"); - Assert.Equal(JsonValueKind.Array, deployments.ValueKind); - - // If deployments exist, verify their structure - var deploymentsArray = deployments.EnumerateArray().ToArray(); - if (deploymentsArray.Length > 0) - { - var firstDeployment = deploymentsArray[0]; - - var deploymentName = firstDeployment.AssertProperty("deploymentName"); - Assert.Equal(JsonValueKind.String, deploymentName.ValueKind); - Assert.NotEmpty(deploymentName.GetString()!); - - var modelName = firstDeployment.AssertProperty("modelName"); - Assert.Equal(JsonValueKind.String, modelName.ValueKind); - Assert.NotEmpty(modelName.GetString()!); - - // Optional properties - verify structure if present - if (firstDeployment.TryGetProperty("modelVersion", out var modelVersion)) - { - Assert.Equal(JsonValueKind.String, modelVersion.ValueKind); - } - - if (firstDeployment.TryGetProperty("modelFormat", out var modelFormat)) - { - Assert.Equal(JsonValueKind.String, modelFormat.ValueKind); - } - - if (firstDeployment.TryGetProperty("skuName", out var deploymentSkuName)) - { - Assert.Equal(JsonValueKind.String, deploymentSkuName.ValueKind); - } - - if (firstDeployment.TryGetProperty("skuCapacity", out var skuCapacity)) - { - Assert.Equal(JsonValueKind.Number, skuCapacity.ValueKind); - Assert.True(skuCapacity.GetInt32() > 0); - } - - if (firstDeployment.TryGetProperty("provisioningState", out var provisioningState)) - { - Assert.Equal(JsonValueKind.String, provisioningState.ValueKind); - } - } - } - - [Fact] - public async Task Should_get_foundry_resource_using_static_resources() - { - // Use the static OpenAI account that's defined in test-resources.bicep - var staticOpenAIAccount = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNT", "azmcp-test"); - var staticResourceGroup = Settings.DeploymentOutputs.GetValueOrDefault("OPENAIACCOUNTRESOURCEGROUP", "static-test-resources"); - var subscriptionId = Settings.SubscriptionId; - var tenantId = Settings.TenantId; - - // Register variables for recording sanitization - RegisterVariable("staticOpenAIAccount", staticOpenAIAccount); - RegisterVariable("staticResourceGroup", staticResourceGroup); - RegisterVariable("tenantId", tenantId); - - var result = await CallToolAsync( - "foundry_resource_get", - new() - { - { "subscription", subscriptionId }, // Don't register - test proxy auto-sanitizes GUIDs - { "resource-group", TestVariables["staticResourceGroup"] }, - { "resource-name", TestVariables["staticOpenAIAccount"] }, - { "tenant", TestVariables["tenantId"] } - }); - - // Verify the response structure - var resources = result.AssertProperty("resources"); - Assert.Equal(JsonValueKind.Array, resources.ValueKind); - - // Should return the static resource - Assert.NotEmpty(resources.EnumerateArray()); - - var resource = resources.EnumerateArray().First(); - - // Verify resource matches static configuration - var resourceName = resource.AssertProperty("resourceName"); - Assert.Equal(TestMode == Tests.Helpers.TestMode.Playback ? "Sanitized" : TestVariables["staticOpenAIAccount"], resourceName.GetString()); - - var resourceGroup = resource.AssertProperty("resourceGroup"); - Assert.Equal(TestVariables["staticResourceGroup"], resourceGroup.GetString()); - - // Verify endpoint is valid - var endpoint = resource.AssertProperty("endpoint"); - Assert.Equal(JsonValueKind.String, endpoint.ValueKind); - Assert.NotEmpty(endpoint.GetString()!); - Assert.StartsWith("https://", endpoint.GetString()); - - // Verify deployments exist for static resource - var deployments = resource.AssertProperty("deployments"); - Assert.Equal(JsonValueKind.Array, deployments.ValueKind); - - // Static resource should have at least the gpt-4o-mini deployment - var deploymentsArray = deployments.EnumerateArray().ToArray(); - if (deploymentsArray.Length > 0) - { - // Check if gpt-4o-mini deployment exists - var hasExpectedDeployment = deploymentsArray.Any(d => - { - if (d.TryGetProperty("deploymentName", out var name) || - d.TryGetProperty("modelName", out name)) - { - var nameStr = name.GetString(); - return nameStr != null && nameStr.Contains("gpt-4o-mini", StringComparison.OrdinalIgnoreCase); - } - return false; - }); - - Output.WriteLine($"Found {deploymentsArray.Length} deployment(s) on static resource"); - } - } - - [Fact] - public async Task Should_create_thread() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var endpoint = $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}"; - var userMessage = "Message from user"; - var result = await CallToolAsync( - "foundry_threads_create", - new() - { - { "endpoint", endpoint }, - { "user-message", userMessage } - }); - var threadIdResult = result.AssertProperty("threadId"); - var projectEndpointResult = result.AssertProperty("projectEndpoint"); - Assert.Equal(JsonValueKind.String, threadIdResult.ValueKind); - Assert.Equal(JsonValueKind.String, projectEndpointResult.ValueKind); - } - - [Fact] - public async Task Should_list_threads() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var endpoint = $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}"; - var result = await CallToolAsync( - "foundry_threads_list", - new() - { - { "endpoint", endpoint } - }); - var threads = result.AssertProperty("threads"); - Assert.Equal(JsonValueKind.Array, threads.ValueKind); - } - - [Fact] - public async Task Should_get_messages() - { - var projectName = $"{Settings.ResourceBaseName}-ai-projects"; - var accounts = Settings.ResourceBaseName; - var endpoint = $"https://{accounts}.services.ai.azure.com/api/projects/{projectName}"; - - var createThreadResult = await CallToolAsync( - "foundry_threads_create", - new() - { - { "endpoint", endpoint }, - { "user-message", "Hello from user" } - }); - var threadId = createThreadResult.AssertProperty("threadId").GetString()!; - - var result = await CallToolAsync( - "foundry_threads_get-messages", - new() - { - { "endpoint", endpoint }, - { "thread-id", threadId } - }); - var threadIdResult = result.AssertProperty("threadId"); - var messagesResult = result.AssertProperty("messages"); - Assert.Equal(JsonValueKind.String, threadIdResult.ValueKind); - Assert.Equal(JsonValueKind.Array, messagesResult.ValueKind); - } - - // Helper methods removed - use CallToolAsync instead to ensure proper test proxy recording - // Previously these helpers made direct SDK calls that bypassed the test proxy recording mechanism. - // All tests now use CallToolAsync("foundry_agents_create") etc. which properly routes through the test proxy. -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/assets.json b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/assets.json deleted file mode 100644 index c21207e5ce..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.LiveTests/assets.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "AssetsRepo": "Azure/azure-sdk-assets", - "AssetsRepoPrefixPath": "", - "TagPrefix": "Azure.Mcp.Tools.Foundry.LiveTests", - "Tag": "Azure.Mcp.Tools.Foundry.LiveTests_5b8dc863ee" -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/AgentsCreateCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/AgentsCreateCommandTests.cs deleted file mode 100644 index d5f00f3c05..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/AgentsCreateCommandTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class AgentsCreateCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public AgentsCreateCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Theory] - [InlineData("", FoundryOptionDefinitions.Endpoint)] - [InlineData("--endpoint https://test-endpoint.com", FoundryOptionDefinitions.ModelDeploymentName)] - [InlineData("--endpoint https://test-endpoint.com --model-deployment modeldeployment", FoundryOptionDefinitions.AgentName)] - [InlineData("--endpoint https://test-endpoint.com --model-deployment modeldeployment --agent-name agentname", FoundryOptionDefinitions.SystemInstruction)] - public async Task ExecuteAsync_Fails_WhenMissingRequiredParameter(string argsString, string missingArgName) - { - var command = new AgentsCreateCommand(); - var args = command.GetCommand().Parse(argsString); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.BadRequest, response.Status); - Assert.Contains(missingArgName, response.Message); - } - - [Fact] - public async Task ExecuteAsync_ReturnsCreatedAgent() - { - var endpoint = "https://test-endpoint.com"; - var modelDeploymentName = "model-deployment"; - var agentName = "agent-name"; - var systemInstruction = "system-instruction"; - - var expectedResult = new AgentsCreateResult() - { - AgentId = "agent-id", - AgentName = agentName, - ProjectEndpoint = endpoint, - ModelDeploymentName = modelDeploymentName - }; - - _foundryService.CreateAgent( - Arg.Is(endpoint), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - var command = new AgentsCreateCommand(); - var args = command.GetCommand().Parse(["--endpoint", endpoint, "--model-deployment", modelDeploymentName, "--agent-name", agentName, "--system-instruction", systemInstruction]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/AgentsGetSdkCodeSampleCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/AgentsGetSdkCodeSampleCommandTests.cs deleted file mode 100644 index 28e1e2c794..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/AgentsGetSdkCodeSampleCommandTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using Azure.Mcp.Core.Services.Azure.Subscription; -using Azure.Mcp.Core.Services.Azure.Tenant; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class AgentsGetSdkCodeSampleCommandTests -{ - private readonly IServiceProvider _serviceProvider; - - public AgentsGetSdkCodeSampleCommandTests() - { - var collection = new ServiceCollection(); - var httpClientFactory = Substitute.For(); - httpClientFactory.CreateClient(Arg.Any()).Returns(new HttpClient()); - var subscriptionService = Substitute.For(); - var tenantService = Substitute.For(); - collection.AddSingleton(httpClientFactory); - collection.AddSingleton(subscriptionService); - collection.AddSingleton(tenantService); - collection.AddSingleton(); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Theory] - [InlineData("", FoundryOptionDefinitions.ProgrammingLanguage)] - public async Task ExecuteAsync_Fails_WhenMissingRequiredParameter(string argsString, string missingArgName) - { - var command = new AgentsGetSdkSampleCommand(); - var args = command.GetCommand().Parse(argsString); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.BadRequest, response.Status); - Assert.Contains(missingArgName, response.Message); - } - - [Theory] - [InlineData("python")] - [InlineData("csharp")] - [InlineData("typescript")] - - public async Task ExecuteAsync_ReturnsSdkCodeSample(string programmingLanguage) - { - var command = new AgentsGetSdkSampleCommand(); - var args = command.GetCommand().Parse(["--programming-language", programmingLanguage]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/Azure.Mcp.Tools.Foundry.UnitTests.csproj b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/Azure.Mcp.Tools.Foundry.UnitTests.csproj deleted file mode 100644 index 446bfea723..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/Azure.Mcp.Tools.Foundry.UnitTests.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - true - Exe - - - - - - - - - - - - - \ No newline at end of file diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/DeploymentsListCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/DeploymentsListCommandTests.cs deleted file mode 100644 index 1a09b34749..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/DeploymentsListCommandTests.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using Azure.AI.Projects; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class DeploymentsListCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public DeploymentsListCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Fact] - public async Task ExecuteAsync_ReturnsDeployments_WhenDeploymentsExist() - { - var endpoint = "https://test-endpoint.com"; - var expectedDeployments = new List - { - AIProjectsModelFactory.Deployment("type", "deployment1"), - AIProjectsModelFactory.Deployment("type", "deployment2"), - }; - - _foundryService.ListDeployments( - Arg.Is(endpoint), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedDeployments); - - var command = new DeploymentsListCommand(); - var args = command.GetCommand().Parse(["--endpoint", endpoint]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.NotNull(response.Results); - } - - [Fact] - public async Task ExecuteAsync_ReturnsEmpty_WhenNoDeploymentsExist() - { - var endpoint = "https://test-endpoint.com"; - - _foundryService.ListDeployments( - Arg.Is(endpoint), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns([]); - - var command = new DeploymentsListCommand(); - var args = command.GetCommand().Parse(["--endpoint", endpoint]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.NotNull(response.Results); - } - - [Fact] - public async Task ExecuteAsync_HandlesException() - { - var endpoint = "https://test-endpoint.com"; - var expectedError = "Test error"; - - _foundryService.ListDeployments( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - var command = new DeploymentsListCommand(); - var args = command.GetCommand().Parse(["--endpoint", endpoint]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.InternalServerError, response.Status); - Assert.StartsWith(expectedError, response.Message); - } - - [Fact] - public async Task ExecuteAsync_ReturnsError_WhenMissingEndpoint() - { - var endpoint = "https://test-endpoint.com"; - var expectedError = "Test error"; - - _foundryService.ListDeployments( - Arg.Is(endpoint), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - var command = new DeploymentsListCommand(); - var args = command.GetCommand().Parse([]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.BadRequest, response.Status); - Assert.StartsWith("Missing Required options: --endpoint", response.Message); - } - -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/FoundryServiceEndpointValidationTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/FoundryServiceEndpointValidationTests.cs deleted file mode 100644 index b48b072873..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/FoundryServiceEndpointValidationTests.cs +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Core.Services.Azure.Subscription; -using Azure.Mcp.Core.Services.Azure.Tenant; -using Azure.Mcp.Tools.Foundry.Services; -using NSubstitute; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -/// -/// Tests to verify endpoint validation in FoundryService. -/// These tests ensure that malformed or malicious endpoints are rejected. -/// -public class FoundryServiceEndpointValidationTests -{ - private readonly ISubscriptionService _subscriptionService; - private readonly ITenantService _tenantService; - private readonly IHttpClientFactory _httpClientFactory; - private readonly FoundryService _service; - - public FoundryServiceEndpointValidationTests() - { - _subscriptionService = Substitute.For(); - _tenantService = Substitute.For(); - _httpClientFactory = Substitute.For(); - _service = new FoundryService(_httpClientFactory, _subscriptionService, _tenantService); - } - - #region Test Data - - public static IEnumerable InvalidProjectEndpoints => - [ - ["http://my-foundry.services.ai.azure.com/api/projects/my-project"], // HTTP instead of HTTPS - ["https://my-foundry.wrongdomain.com/api/projects/my-project"], // Wrong domain - ["my-foundry.services.ai.azure.com/api/projects/my-project"], // Missing protocol - ["https://167.128.3.12"], // An arbitrary endpoint - ["https://evil.com/api/projects/steal-data"], // Malicious domain - ["https://my-foundry.services.ai.azure.com.evil.com/api/projects/my-project"], // Domain spoofing attempt - ]; - - public static IEnumerable InvalidAzureOpenAiEndpoints => - [ - ["http://my-openai.openai.azure.com/"], // HTTP instead of HTTPS - ["https://-openai.openai.azure.com/"], // Starts with hyphen - ["https://openai-.openai.azure.com/"], // Ends with hyphen - ["https://a.openai.azure.com/"], // Single character resource name - ["https://my_openai.openai.azure.com/"], // Contains underscore - ["https://my-openai.wrongdomain.com/"], // Wrong domain - ["my-openai.openai.azure.com/"], // Missing protocol - ["https://my-openai.openai.azure.com/extra/path"], // Extra path - ["https://a-very-long-resource-name-that-exceeds-the-maximum-length-limit-of-64-characters.openai.azure.com/"], // Resource name too long - ["https://.openai.azure.com/"], // Missing resource name - ["https://167.128.3.12"], // An arbitrary endpoint - ["https://evil.com/"], // Malicious domain - ["https://my-openai.openai.azure.com.evil.com/"], // Domain spoofing attempt - ]; - - #endregion - - #region Project Endpoint Validation Tests - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task ListDeployments_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.ListDeployments(invalidEndpoint, cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task CreateAgent_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - async () => await _service.CreateAgent( - invalidEndpoint, - "test-deployment", - "test-agent", - "You are a helpful assistant", - cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task ConnectAgent_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - async () => await _service.ConnectAgent( - "agent-id", - "test query", - invalidEndpoint, - cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task ListThreads_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.ListThreads( - invalidEndpoint, - null, - null, - TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task QueryAndEvaluateAgent_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.QueryAndEvaluateAgent( - "agent-id", - "test query", - invalidEndpoint, - "https://my-openai.openai.azure.com/", - "gpt-4", - cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task GetMessages_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.GetMessages( - invalidEndpoint, - "thread-id", - null, - null, - TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task ListKnowledgeIndexes_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.ListKnowledgeIndexes( - invalidEndpoint, - cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task GetKnowledgeIndexSchema_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.GetKnowledgeIndexSchema( - invalidEndpoint, - "test-index", - cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task ListAgents_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.ListAgents( - invalidEndpoint, - cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidProjectEndpoints))] - public async Task CreateThread_RejectsInvalidProjectEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.CreateThread( - invalidEndpoint, - "test message", - null, - null, - TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Foundry project endpoint", exception.Message); - } - - #endregion - - #region Azure OpenAI Endpoint Validation Tests - - [Theory] - [MemberData(nameof(InvalidAzureOpenAiEndpoints))] - public async Task QueryAndEvaluateAgent_RejectsInvalidAzureOpenAIEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.QueryAndEvaluateAgent( - "agent-id", - "test query", - "https://my-foundry.services.ai.azure.com/api/projects/my-project", - invalidEndpoint, - "gpt-4", - cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Azure OpenAI endpoint", exception.Message); - } - - [Theory] - [MemberData(nameof(InvalidAzureOpenAiEndpoints))] - public async Task EvaluateAgent_RejectsInvalidAzureOpenAIEndpoints(string invalidEndpoint) - { - var exception = await Assert.ThrowsAsync( - () => _service.EvaluateAgent( - "coherence", - "[]", - "[]", - invalidEndpoint, - "gpt-4", - null, - cancellationToken: TestContext.Current.CancellationToken)); - - Assert.Contains("Invalid Azure OpenAI endpoint", exception.Message); - } - - #endregion -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/KnowledgeIndexListCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/KnowledgeIndexListCommandTests.cs deleted file mode 100644 index 733bfbff06..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/KnowledgeIndexListCommandTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.CommandLine; -using System.Net; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class KnowledgeIndexListCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _service; - private readonly ILogger _logger; - private readonly KnowledgeIndexListCommand _command; - private readonly CommandContext _context; - private readonly Command _commandDefinition; - - public KnowledgeIndexListCommandTests() - { - _service = Substitute.For(); - _logger = Substitute.For>(); - - var collection = new ServiceCollection().AddSingleton(_service); - _serviceProvider = collection.BuildServiceProvider(); - _command = new(); - _context = new CommandContext(_serviceProvider); - _commandDefinition = _command.GetCommand(); - } - - [Fact] - public void Constructor_InitializesCommandCorrectly() - { - var command = _command.GetCommand(); - Assert.Equal("list", command.Name); - Assert.NotNull(command.Description); - Assert.NotEmpty(command.Description); - } - - [Theory] - [InlineData("--endpoint https://example.com", true)] - [InlineData("", false)] - public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldSucceed) - { - // Arrange - if (shouldSucceed) - { - _service.ListKnowledgeIndexes(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns( - [ - new() { Name = "test-index", Type = "aisearch", Version = "1.0", Description = "Test index" } - ]); - } - - var parseResult = _commandDefinition.Parse(args); - - // Act - var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); - - // Assert - Assert.Equal(shouldSucceed ? HttpStatusCode.OK : HttpStatusCode.BadRequest, response.Status); - if (shouldSucceed) - { - Assert.NotNull(response.Results); - Assert.Equal("Success", response.Message); - } - else - { - Assert.Contains("required", response.Message.ToLower()); - } - } - - [Fact] - public async Task ExecuteAsync_HandlesServiceErrors() - { - // Arrange - _service.ListKnowledgeIndexes(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.FromException>(new Exception("Test error"))); - - var parseResult = _commandDefinition.Parse(["--endpoint", "https://example.com"]); - - // Act - var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); - - // Assert - Assert.Equal(HttpStatusCode.InternalServerError, response.Status); - Assert.Contains("Test error", response.Message); - Assert.Contains("troubleshooting", response.Message); - } - - [Fact] - public async Task ExecuteAsync_ReturnsExpectedResults() - { - // Arrange - var expectedIndexes = new List - { - new() { Name = "test-index1", Type = "aisearch", Version = "1.0", Description = "First test index" }, - new() { Name = "test-index2", Type = "aisearch", Version = "1.1", Description = "Second test index" } - }; - - _service.ListKnowledgeIndexes(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(expectedIndexes); - - var parseResult = _commandDefinition.Parse(["--endpoint", "https://example.com"]); - - // Act - var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - Assert.Equal("Success", response.Message); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/KnowledgeIndexSchemaCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/KnowledgeIndexSchemaCommandTests.cs deleted file mode 100644 index 8566e1b487..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/KnowledgeIndexSchemaCommandTests.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.CommandLine; -using System.Net; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class KnowledgeIndexSchemaCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _service; - private readonly ILogger _logger; - private readonly KnowledgeIndexSchemaCommand _command; - private readonly CommandContext _context; - private readonly Command _commandDefinition; - - public KnowledgeIndexSchemaCommandTests() - { - _service = Substitute.For(); - _logger = Substitute.For>(); - - var collection = new ServiceCollection().AddSingleton(_service); - _serviceProvider = collection.BuildServiceProvider(); - _command = new(); - _context = new CommandContext(_serviceProvider); - _commandDefinition = _command.GetCommand(); - } - - [Fact] - public void Constructor_InitializesCommandCorrectly() - { - var command = _command.GetCommand(); - Assert.Equal("schema", command.Name); - Assert.NotNull(command.Description); - Assert.NotEmpty(command.Description); - } - - [Theory] - [InlineData("--endpoint https://example.com --index test-index", true)] - [InlineData("--endpoint https://example.com", false)] // Missing index name - [InlineData("--index test-index", false)] // Missing endpoint - [InlineData("", false)] // Missing both - public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldSucceed) - { - // Arrange - if (shouldSucceed) - { - var mockSchema = new Models.KnowledgeIndexSchema - { - Name = "test-index", - Type = "AzureAISearchIndex", - Version = "1.0", - Description = "desc", - Tags = new Dictionary { { "env", "test" } } - }; - _service.GetKnowledgeIndexSchema(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(mockSchema); - } - - var parseResult = _commandDefinition.Parse(args.Split(' ', StringSplitOptions.RemoveEmptyEntries)); - - // Act - var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); - - // Assert - Assert.Equal(shouldSucceed ? HttpStatusCode.OK : HttpStatusCode.BadRequest, response.Status); - if (shouldSucceed) - { - Assert.NotNull(response.Results); - Assert.Equal("Success", response.Message); - } - else - { - Assert.Contains("required", response.Message.ToLower()); - } - } - - [Fact] - public async Task ExecuteAsync_HandlesServiceErrors() - { - // Arrange - _service.GetKnowledgeIndexSchema(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.FromException(new Exception("Test error"))); - - var parseResult = _commandDefinition.Parse(["--endpoint", "https://example.com", "--index", "test-index"]); - - // Act - var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); - - // Assert - Assert.Equal(HttpStatusCode.InternalServerError, response.Status); - Assert.Contains("Test error", response.Message); - Assert.Contains("troubleshooting", response.Message); - } - - [Fact] - public async Task ExecuteAsync_ReturnsExpectedResults() - { - // Arrange - var expectedSchema = new Models.KnowledgeIndexSchema - { - Name = "test-index", - Type = "AzureAISearchIndex", - Version = "1.0" - }; - - _service.GetKnowledgeIndexSchema(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(expectedSchema); - - var parseResult = _commandDefinition.Parse(["--endpoint", "https://example.com", "--index", "test-index"]); - - // Act - var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - Assert.Equal("Success", response.Message); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ModelDeploymentCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ModelDeploymentCommandTests.cs deleted file mode 100644 index 90237bda59..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ModelDeploymentCommandTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class ModelDeploymentCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public ModelDeploymentCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Fact] - public async Task ExecuteAsync_DeploysModel_WhenValidOptionsProvided() - { - var deploymentName = "test-deployment"; - var modelName = "test-model"; - var modelFormat = "OpenAI"; - var aiServicesName = "test-ai-services"; - var resourceGroup = "test-resource-group"; - var subscriptionId = "test-subscription-id"; - - var expectedResponse = new ModelDeploymentResult - { - HasData = true - }; - - _foundryService.DeployModel( - Arg.Is(deploymentName), - Arg.Is(modelName), - Arg.Is(modelFormat), - Arg.Is(aiServicesName), - Arg.Is(resourceGroup), - Arg.Is(subscriptionId), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedResponse); - - var command = new ModelDeploymentCommand(); - var args = command.GetCommand().Parse(["--deployment", deploymentName, "--model-name", modelName, "--model-format", modelFormat, "--azure-ai-services", aiServicesName, "--resource-group", resourceGroup, "--subscription", subscriptionId]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.NotNull(response.Results); - } - - [Fact] - public async Task ExecuteAsync_OptionalParameters_PassedToService() - { - var deploymentName = "test-deployment"; - var modelName = "test-model"; - var modelFormat = "OpenAI"; - var aiServicesName = "test-ai-services"; - var resourceGroup = "test-resource-group"; - var subscriptionId = "test-subscription-id"; - var modelVersion = "1.0"; - var modelSource = "AzureOpenAI"; - var skuName = "Standard"; - var skuCapacity = 1; - var scaleType = "Standard"; - var scaleCapacity = 2; - - var expectedResponse = new ModelDeploymentResult - { - HasData = true - }; - - _foundryService.DeployModel( - Arg.Is(deploymentName), - Arg.Is(modelName), - Arg.Is(modelFormat), - Arg.Is(aiServicesName), - Arg.Is(resourceGroup), - Arg.Is(subscriptionId), - Arg.Is(modelVersion), - Arg.Is(modelSource), - Arg.Is(skuName), - Arg.Is(skuCapacity), - Arg.Is(scaleType), - Arg.Is(scaleCapacity), - Arg.Any(), - Arg.Any()) - .Returns(expectedResponse); - - var command = new ModelDeploymentCommand(); - var args = command.GetCommand().Parse(["--deployment", deploymentName, "--model-name", modelName, "--model-format", modelFormat, "--azure-ai-services", aiServicesName, "--resource-group", resourceGroup, "--subscription", subscriptionId, "--model-version", modelVersion, "--model-source", modelSource, "--sku", skuName, "--sku-capacity", skuCapacity.ToString(), "--scale-type", scaleType, "--scale-capacity", scaleCapacity.ToString()]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.NotNull(response.Results); - } - - [Fact] - public async Task ExecuteAsync_HandlesException() - { - var deploymentName = "test-deployment"; - var modelName = "test-model"; - var modelFormat = "OpenAI"; - var aiServicesName = "test-ai-services"; - var resourceGroup = "test-resource-group"; - var subscriptionId = "test-subscription-id"; - var expectedError = "Test error"; - - _foundryService.DeployModel( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - var command = new ModelDeploymentCommand(); - var args = command.GetCommand().Parse(["--deployment", deploymentName, "--model-name", modelName, "--model-format", modelFormat, "--azure-ai-services", aiServicesName, "--resource-group", resourceGroup, "--subscription", subscriptionId]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.InternalServerError, response.Status); - Assert.StartsWith(expectedError, response.Message); - } - -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ModelsListCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ModelsListCommandTests.cs deleted file mode 100644 index 8337a485a3..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ModelsListCommandTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using System.Text.Json; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class ModelsListCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public ModelsListCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Fact] - public async Task ExecuteAsync_ReturnsModels_WhenModelsExist() - { - var expectedModels = new List - { - new() { Id = "model1", Name = "Model 1", Publisher = "Publisher 1" }, - new() { Id = "model2", Name = "Model 2", Publisher = "Publisher 2" } - }; - - _foundryService.ListModels( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedModels); - - var command = new ModelsListCommand(); - var args = command.GetCommand().Parse(""); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json, FoundryJsonContext.Default.ModelsListCommandResult); - - Assert.NotNull(result); - Assert.NotNull(result.Models); - Assert.Equal(expectedModels.Count, result.Models.Count()); - } - - [Fact] - public async Task ExecuteAsync_ReturnsModels_WhenModelsExistWithFlags() - { - string publisherName = "TestPublisher"; - string license = "TestLicense"; - string modelName = "TestModel"; - var expectedModels = new List - { - new() { Id = "model1", Name = "Model 1", Publisher = "Publisher 1" }, - new() { Id = "model2", Name = "Model 2", Publisher = "Publisher 2" } - }; - - _foundryService.ListModels( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedModels); - - var command = new ModelsListCommand(); - var args = command.GetCommand().Parse(["--search-for-free-playground", "--publisher", publisherName, "--license", license, "--model-name", modelName]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json, FoundryJsonContext.Default.ModelsListCommandResult); - - Assert.NotNull(result); - Assert.NotNull(result.Models); - Assert.Equal(expectedModels.Count, result.Models.Count()); - } - - [Fact] - public async Task ExecuteAsync_ReturnsEmpty_WhenNoModels() - { - _foundryService.ListModels( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns([]); - - var command = new ModelsListCommand(); - var args = command.GetCommand().Parse(""); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json, FoundryJsonContext.Default.ModelsListCommandResult); - - Assert.NotNull(result); - Assert.Empty(result.Models); - } - - [Fact] - public async Task ExecuteAsync_HandlesException() - { - var expectedError = "Test error"; - - _foundryService.ListModels( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - var command = new ModelsListCommand(); - var args = command.GetCommand().Parse(""); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.InternalServerError, response.Status); - Assert.StartsWith(expectedError, response.Message); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiChatCompletionsCreateCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiChatCompletionsCreateCommandTests.cs deleted file mode 100644 index 641986b2c6..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiChatCompletionsCreateCommandTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Azure.Mcp.Tools.Foundry.Commands; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class OpenAiChatCompletionsCreateCommandTests -{ - [Fact] - public void Name_ReturnsCorrectCommandName() - { - // Arrange - var command = new OpenAiChatCompletionsCreateCommand(); - - // Act & Assert - Assert.Equal("chat-completions-create", command.Name); - } - - [Fact] - public void Description_ContainsExpectedContent() - { - // Arrange - var command = new OpenAiChatCompletionsCreateCommand(); - - // Act & Assert - Assert.Contains("Create chat completions", command.Description); - Assert.Contains("Azure OpenAI", command.Description); - Assert.Contains("Microsoft Foundry", command.Description); - Assert.Contains("message-array", command.Description); - } - - [Fact] - public void Title_ReturnsCorrectValue() - { - // Arrange - var command = new OpenAiChatCompletionsCreateCommand(); - - // Act & Assert - Assert.Equal("Create OpenAI Chat Completions", command.Title); - } - - [Fact] - public void Metadata_HasCorrectProperties() - { - // Arrange - var command = new OpenAiChatCompletionsCreateCommand(); - - // Act & Assert - Assert.False(command.Metadata.Destructive); - Assert.False(command.Metadata.Idempotent); - Assert.False(command.Metadata.OpenWorld); - Assert.True(command.Metadata.ReadOnly); - Assert.False(command.Metadata.LocalRequired); - Assert.False(command.Metadata.Secret); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiCompletionsCreateCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiCompletionsCreateCommandTests.cs deleted file mode 100644 index f77576b3ae..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiCompletionsCreateCommandTests.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json; -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class OpenAiCompletionsCreateCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public OpenAiCompletionsCreateCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Fact] - public async Task ExecuteAsync_CreatesCompletion_WhenValidOptionsProvided() - { - // Arrange - var resourceName = "test-openai"; - var deploymentName = "gpt-35-turbo"; - var promptText = "What is Azure?"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - - var expectedUsage = new CompletionUsageInfo(10, 50, 60); - var expectedResult = new CompletionResult("Azure is a cloud computing platform...", expectedUsage); - - _foundryService.CreateCompletionAsync( - Arg.Is(s => s == resourceName), - Arg.Is(s => s == deploymentName), - Arg.Is(s => s == promptText), - Arg.Is(s => s == subscriptionId), - Arg.Is(s => s == resourceGroup), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - // Act - var command = new OpenAiCompletionsCreateCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName, - "--deployment", deploymentName, - "--prompt-text", promptText - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json); - - Assert.NotNull(result); - Assert.Equal(expectedResult.CompletionText, result.CompletionText); - Assert.Equal(expectedUsage.PromptTokens, result.UsageInfo.PromptTokens); - Assert.Equal(expectedUsage.CompletionTokens, result.UsageInfo.CompletionTokens); - Assert.Equal(expectedUsage.TotalTokens, result.UsageInfo.TotalTokens); - } - - [Fact] - public async Task ExecuteAsync_OptionalParameters_PassedToService() - { - // Arrange - var resourceName = "test-openai"; - var deploymentName = "gpt-35-turbo"; - var promptText = "What is Azure?"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - var maxTokens = 100; - var temperature = 0.7; - - var expectedUsage = new CompletionUsageInfo(10, 50, 60); - var expectedResult = new CompletionResult("Azure is a cloud computing platform...", expectedUsage); - - _foundryService.CreateCompletionAsync( - Arg.Is(s => s == resourceName), - Arg.Is(s => s == deploymentName), - Arg.Is(s => s == promptText), - Arg.Is(s => s == subscriptionId), - Arg.Is(s => s == resourceGroup), - Arg.Is(i => i == maxTokens), - Arg.Is(d => d == temperature), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - // Act - var command = new OpenAiCompletionsCreateCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName, - "--deployment", deploymentName, - "--prompt-text", promptText, - "--max-tokens", maxTokens.ToString(), - "--temperature", temperature.ToString() - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.NotNull(response.Results); - - // Verify the service was called at least once with the core parameters - await _foundryService.Received(1).CreateCompletionAsync( - resourceName, - deploymentName, - promptText, - subscriptionId, - resourceGroup, - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()); - } - - [Fact] - public async Task ExecuteAsync_HandlesException() - { - // Arrange - var resourceName = "test-openai"; - var deploymentName = "gpt-35-turbo"; - var promptText = "What is Azure?"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - var expectedError = "Test error"; - - _foundryService.CreateCompletionAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - // Act - var command = new OpenAiCompletionsCreateCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName, - "--deployment", deploymentName, - "--prompt-text", promptText - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.Equal(500, (int)response.Status); - Assert.StartsWith(expectedError, response.Message); - } - - [Fact] - public void Command_HasCorrectName() - { - // Arrange & Act - var command = new OpenAiCompletionsCreateCommand(); - - // Assert - Assert.Equal("create-completion", command.Name); - } - - [Fact] - public void Command_HasCorrectMetadata() - { - // Arrange & Act - var command = new OpenAiCompletionsCreateCommand(); - - // Assert - Assert.False(command.Metadata.Destructive); - Assert.True(command.Metadata.ReadOnly); - } - - private class OpenAiCompletionsCreateCommandResult - { - [JsonPropertyName("completionText")] - public string CompletionText { get; set; } = string.Empty; - - [JsonPropertyName("usageInfo")] - public CompletionUsageInfo UsageInfo { get; set; } = new CompletionUsageInfo(0, 0, 0); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiEmbeddingsCreateCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiEmbeddingsCreateCommandTests.cs deleted file mode 100644 index bcb20ccec1..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiEmbeddingsCreateCommandTests.cs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json; -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class OpenAiEmbeddingsCreateCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public OpenAiEmbeddingsCreateCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Fact] - public async Task ExecuteAsync_CreatesEmbeddings_WhenValidOptionsProvided() - { - // Arrange - var resourceName = "test-openai"; - var deploymentName = "text-embedding-ada-002"; - var inputText = "Hello world"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - - var expectedEmbedding = new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f }; - var expectedUsage = new EmbeddingUsageInfo(2, 2); - var expectedData = new List - { - new EmbeddingData("embedding", 0, expectedEmbedding) - }; - var expectedResult = new EmbeddingResult("list", expectedData, deploymentName, expectedUsage); - - _foundryService.CreateEmbeddingsAsync( - Arg.Is(s => s == resourceName), - Arg.Is(s => s == deploymentName), - Arg.Is(s => s == inputText), - Arg.Is(s => s == subscriptionId), - Arg.Is(s => s == resourceGroup), - Arg.Any(), - Arg.Is(s => s == "float"), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - // Act - var command = new OpenAiEmbeddingsCreateCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName, - "--deployment", deploymentName, - "--input-text", inputText - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json); - - Assert.NotNull(result); - Assert.Equal(expectedResult.Object, result.EmbeddingResult.Object); - Assert.Equal(expectedResult.Model, result.EmbeddingResult.Model); - Assert.Equal(resourceName, result.ResourceName); - Assert.Equal(deploymentName, result.DeploymentName); - Assert.Equal(inputText, result.InputText); - Assert.Single(result.EmbeddingResult.Data); - Assert.Equal(expectedEmbedding.Length, result.EmbeddingResult.Data[0].Embedding.Length); - } - - [Fact] - public async Task ExecuteAsync_OptionalParameters_PassedToService() - { - // Arrange - var resourceName = "test-openai"; - var deploymentName = "text-embedding-ada-002"; - var inputText = "Test embedding text"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - var user = "test-user"; - var dimensions = 1536; - - var expectedEmbedding = new float[dimensions]; - for (int i = 0; i < dimensions; i++) - { - expectedEmbedding[i] = 0.1f; - } - - var expectedUsage = new EmbeddingUsageInfo(4, 4); - var expectedData = new List - { - new EmbeddingData("embedding", 0, expectedEmbedding) - }; - var expectedResult = new EmbeddingResult("list", expectedData, deploymentName, expectedUsage); - - _foundryService.CreateEmbeddingsAsync( - Arg.Is(s => s == resourceName), - Arg.Is(s => s == deploymentName), - Arg.Is(s => s == inputText), - Arg.Is(s => s == subscriptionId), - Arg.Is(s => s == resourceGroup), - Arg.Is(s => s == user), - Arg.Is(s => s == "float"), - Arg.Is(i => i == dimensions), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - // Act - var command = new OpenAiEmbeddingsCreateCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName, - "--deployment", deploymentName, - "--input-text", inputText, - "--user", user, - "--dimensions", dimensions.ToString() - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.NotNull(response.Results); - - // Verify the service was called at least once with the core parameters - await _foundryService.Received(1).CreateEmbeddingsAsync( - resourceName, - deploymentName, - inputText, - subscriptionId, - resourceGroup, - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()); - } - - [Fact] - public async Task ExecuteAsync_HandlesException() - { - // Arrange - var resourceName = "test-openai"; - var deploymentName = "text-embedding-ada-002"; - var inputText = "Test input"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - var expectedError = "Test embedding error"; - - _foundryService.CreateEmbeddingsAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - // Act - var command = new OpenAiEmbeddingsCreateCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName, - "--deployment", deploymentName, - "--input-text", inputText - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.Equal(500, (int)response.Status); - Assert.StartsWith(expectedError, response.Message); - } - - [Fact] - public void Command_HasCorrectName() - { - // Arrange & Act - var command = new OpenAiEmbeddingsCreateCommand(); - - // Assert - Assert.Equal("embeddings-create", command.Name); - } - - [Fact] - public void Command_HasCorrectMetadata() - { - // Arrange & Act - var command = new OpenAiEmbeddingsCreateCommand(); - - // Assert - Assert.False(command.Metadata.Destructive); - Assert.True(command.Metadata.ReadOnly); - Assert.False(command.Metadata.Idempotent); - } - - [Theory] - [InlineData("")] - [InlineData(null)] - public async Task ExecuteAsync_RequiredParameterMissing_ReturnsValidationError(string? inputText) - { - // Arrange - var resourceName = "test-openai"; - var deploymentName = "text-embedding-ada-002"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - - var command = new OpenAiEmbeddingsCreateCommand(); - var parseArgs = new List - { - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName, - "--deployment", deploymentName - }; - - if (!string.IsNullOrEmpty(inputText)) - { - parseArgs.AddRange(["--input-text", inputText]); - } - - var args = command.GetCommand().Parse(parseArgs.ToArray()); - var context = new CommandContext(_serviceProvider); - - // Act - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - // The command should handle validation and return appropriate error - if (string.IsNullOrEmpty(inputText)) - { - Assert.NotEqual(200, (int)response.Status); - } - } - - private class OpenAiEmbeddingsCreateCommandResult - { - [JsonPropertyName("embeddingResult")] - public EmbeddingResult EmbeddingResult { get; set; } = new EmbeddingResult("", new List(), "", new EmbeddingUsageInfo(0, 0)); - - [JsonPropertyName("resourceName")] - public string ResourceName { get; set; } = string.Empty; - - [JsonPropertyName("deploymentName")] - public string DeploymentName { get; set; } = string.Empty; - - [JsonPropertyName("inputText")] - public string InputText { get; set; } = string.Empty; - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiModelsListCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiModelsListCommandTests.cs deleted file mode 100644 index 4eb31bb7dd..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/OpenAiModelsListCommandTests.cs +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json; -using System.Text.Json.Serialization; -using Azure.Mcp.Core.Models; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class OpenAiModelsListCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public OpenAiModelsListCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Fact] - public async Task ExecuteAsync_ListsModels_WhenValidOptionsProvided() - { - // Arrange - var resourceName = "test-openai"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - - var expectedModels = new List - { - new OpenAiModelDeployment( - DeploymentName: "gpt-4o", - ModelName: "gpt-4o", - ModelVersion: "2024-05-13", - ScaleType: "Standard", - Capacity: 30, - ProvisioningState: "Succeeded", - CreatedAt: DateTime.UtcNow.AddDays(-1), - UpdatedAt: DateTime.UtcNow, - Capabilities: new OpenAiModelCapabilities(true, false, true, false) - ), - new OpenAiModelDeployment( - DeploymentName: "text-embedding-ada-002", - ModelName: "text-embedding-ada-002", - ModelVersion: "2", - ScaleType: "Standard", - Capacity: 120, - ProvisioningState: "Succeeded", - CreatedAt: DateTime.UtcNow.AddDays(-2), - UpdatedAt: DateTime.UtcNow.AddHours(-1), - Capabilities: new OpenAiModelCapabilities(false, true, false, false) - ) - }; - - var expectedResult = new OpenAiModelsListResult(expectedModels, resourceName); - - _foundryService.ListOpenAiModelsAsync( - Arg.Is(s => s == resourceName), - Arg.Is(s => s == subscriptionId), - Arg.Is(s => s == resourceGroup), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - // Act - var command = new OpenAiModelsListCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json); - - Assert.NotNull(result); - Assert.Equal(resourceName, result.ResourceName); - Assert.Equal(resourceName, result.ModelsListResult.ResourceName); - Assert.Equal(2, result.ModelsListResult.Models.Count); - - // Verify GPT model - var gptModel = result.ModelsListResult.Models.First(m => m.ModelName == "gpt-4o"); - Assert.Equal("gpt-4o", gptModel.DeploymentName); - Assert.Equal(30, gptModel.Capacity); - Assert.True(gptModel.Capabilities?.ChatCompletions); - Assert.False(gptModel.Capabilities?.Embeddings); - - // Verify embedding model - var embeddingModel = result.ModelsListResult.Models.First(m => m.ModelName == "text-embedding-ada-002"); - Assert.Equal("text-embedding-ada-002", embeddingModel.DeploymentName); - Assert.Equal(120, embeddingModel.Capacity); - Assert.True(embeddingModel.Capabilities?.Embeddings); - Assert.False(embeddingModel.Capabilities?.ChatCompletions); - } - - [Fact] - public async Task ExecuteAsync_ReturnsEmptyList_WhenNoModelsDeployed() - { - // Arrange - var resourceName = "test-openai-empty"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - - var expectedResult = new OpenAiModelsListResult(new List(), resourceName); - - _foundryService.ListOpenAiModelsAsync( - Arg.Is(s => s == resourceName), - Arg.Is(s => s == subscriptionId), - Arg.Is(s => s == resourceGroup), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - // Act - var command = new OpenAiModelsListCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json); - - Assert.NotNull(result); - Assert.Equal(resourceName, result.ResourceName); - Assert.Empty(result.ModelsListResult.Models); - } - - [Fact] - public async Task ExecuteAsync_HandlesException() - { - // Arrange - var resourceName = "test-openai"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - var expectedError = "Test models list error"; - - _foundryService.ListOpenAiModelsAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - // Act - var command = new OpenAiModelsListCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.Equal(500, (int)response.Status); - Assert.StartsWith(expectedError, response.Message); - } - - [Fact] - public void Command_HasCorrectName() - { - // Arrange & Act - var command = new OpenAiModelsListCommand(); - - // Assert - Assert.Equal("models-list", command.Name); - } - - [Fact] - public void Command_HasCorrectMetadata() - { - // Arrange & Act - var command = new OpenAiModelsListCommand(); - - // Assert - Assert.False(command.Metadata.Destructive); - Assert.True(command.Metadata.ReadOnly); - Assert.True(command.Metadata.Idempotent); - } - - [Theory] - [InlineData("--resource-name myresource --subscription sub --resource-group rg", true)] - [InlineData("--subscription sub --resource-group rg", false)] // Missing resource-name - [InlineData("--resource-name myresource --subscription sub", false)] // Missing resource-group - [InlineData("--resource-name myresource --resource-group rg", false)] // Missing subscription - public async Task ExecuteAsync_ValidatesRequiredParameters(string args, bool shouldSucceed) - { - // Arrange - var command = new OpenAiModelsListCommand(); - var context = new CommandContext(_serviceProvider); - - var expectedResult = new OpenAiModelsListResult(new List(), "myresource"); - - if (shouldSucceed) - { - _foundryService.ListOpenAiModelsAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - } - - var parseResult = command.GetCommand().Parse(args.Split(' ')); - - // Act - var response = await command.ExecuteAsync(context, parseResult, TestContext.Current.CancellationToken); - - // Assert - if (shouldSucceed) - { - Assert.NotNull(response.Results); - } - else - { - // Should fail validation for missing required parameters - Assert.NotEqual(200, (int)response.Status); - } - } - - [Fact] - public async Task ExecuteAsync_VerifiesServiceCall_WithCorrectParameters() - { - // Arrange - var resourceName = "test-resource"; - var subscriptionId = "test-sub"; - var resourceGroup = "test-rg"; - - var expectedResult = new OpenAiModelsListResult(new List(), resourceName); - - _foundryService.ListOpenAiModelsAsync( - Arg.Is(s => s == resourceName), - Arg.Is(s => s == subscriptionId), - Arg.Is(s => s == resourceGroup), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - var command = new OpenAiModelsListCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName - ]); - var context = new CommandContext(_serviceProvider); - - // Act - await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Verify the service was called with exact parameters - await _foundryService.Received(1).ListOpenAiModelsAsync( - resourceName, - subscriptionId, - resourceGroup, - Arg.Any(), - AuthMethod.Credential, - Arg.Any(), - Arg.Any()); - } - - [Fact] - public async Task ExecuteAsync_HandlesServiceAuthentication_Exception() - { - // Arrange - var resourceName = "test-openai"; - var subscriptionId = "test-subscription-id"; - var resourceGroup = "test-resource-group"; - - _foundryService.ListOpenAiModelsAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Is(s => s == AuthMethod.Credential), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new UnauthorizedAccessException("Authentication failed")); - - // Act - var command = new OpenAiModelsListCommand(); - var args = command.GetCommand().Parse([ - "--subscription", subscriptionId, - "--resource-group", resourceGroup, - "--resource-name", resourceName - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - // Assert - Assert.NotNull(response); - Assert.NotEqual(200, (int)response.Status); - Assert.Contains("Authentication failed", response.Message); - } - - private class OpenAiModelsListCommandResult - { - [JsonPropertyName("modelsListResult")] - public OpenAiModelsListResult ModelsListResult { get; set; } = new OpenAiModelsListResult(new List(), ""); - - [JsonPropertyName("resourceName")] - public string ResourceName { get; set; } = string.Empty; - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ResourceGetCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ResourceGetCommandTests.cs deleted file mode 100644 index 3fa27a012d..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ResourceGetCommandTests.cs +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using System.Text.Json; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class ResourceGetCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - private readonly ILogger _logger; - - public ResourceGetCommandTests() - { - _foundryService = Substitute.For(); - _logger = Substitute.For>(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - collection.AddSingleton(_logger); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Fact] - public void Constructor_InitializesCommandCorrectly() - { - var command = new ResourceGetCommand(_logger); - - Assert.Equal("get", command.Name); - Assert.NotEmpty(command.Description); - Assert.NotNull(command.Metadata); - Assert.True(command.Metadata.ReadOnly); - Assert.True(command.Metadata.Idempotent); - Assert.False(command.Metadata.Destructive); - } - - [Fact] - public async Task ExecuteAsync_ListsAllResources_WhenNoResourceNameProvided() - { - var expectedResources = new List - { - new() - { - ResourceName = "resource1", - ResourceGroup = "rg1", - SubscriptionName = "sub1", - Location = "eastus", - Endpoint = "https://resource1.openai.azure.com/", - Kind = "OpenAI", - SkuName = "S0", - Deployments = new List() - }, - new() - { - ResourceName = "resource2", - ResourceGroup = "rg1", - SubscriptionName = "sub1", - Location = "westus", - Endpoint = "https://resource2.openai.azure.com/", - Kind = "AIServices", - SkuName = "S0", - Deployments = new List() - } - }; - - _foundryService.ListAiResourcesAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedResources); - - var command = new ResourceGetCommand(_logger); - var args = command.GetCommand().Parse(["--subscription", "test-sub"]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json, FoundryJsonContext.Default.ResourceGetCommandResult); - - Assert.NotNull(result); - Assert.NotNull(result.Resources); - Assert.Equal(expectedResources.Count, result.Resources.Count); - } - - [Fact] - public async Task ExecuteAsync_ListsResourcesInResourceGroup_WhenResourceGroupProvided() - { - var expectedResources = new List - { - new() - { - ResourceName = "resource1", - ResourceGroup = "test-rg", - SubscriptionName = "sub1", - Location = "eastus", - Endpoint = "https://resource1.openai.azure.com/", - Kind = "OpenAI", - SkuName = "S0", - Deployments = new List() - } - }; - - _foundryService.ListAiResourcesAsync( - Arg.Any(), - Arg.Is("test-rg"), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedResources); - - var command = new ResourceGetCommand(_logger); - var args = command.GetCommand().Parse(["--subscription", "test-sub", "--resource-group", "test-rg"]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json, FoundryJsonContext.Default.ResourceGetCommandResult); - - Assert.NotNull(result); - Assert.NotNull(result.Resources); - Assert.Single(result.Resources); - Assert.Equal("test-rg", result.Resources[0].ResourceGroup); - } - - [Fact] - public async Task ExecuteAsync_GetsSpecificResource_WhenResourceNameAndGroupProvided() - { - var expectedResource = new AiResourceInformation - { - ResourceName = "test-resource", - ResourceGroup = "test-rg", - SubscriptionName = "sub1", - Location = "eastus", - Endpoint = "https://test-resource.openai.azure.com/", - Kind = "OpenAI", - SkuName = "S0", - Deployments = new List - { - new() - { - DeploymentName = "gpt-4o", - ModelName = "gpt-4o", - ModelVersion = "2024-11-20", - ModelFormat = "OpenAI", - SkuName = "Standard", - SkuCapacity = 100, - ProvisioningState = "Succeeded" - } - } - }; - - _foundryService.GetAiResourceAsync( - Arg.Any(), - Arg.Is("test-rg"), - Arg.Is("test-resource"), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedResource); - - var command = new ResourceGetCommand(_logger); - var args = command.GetCommand().Parse([ - "--subscription", "test-sub", - "--resource-group", "test-rg", - "--resource-name", "test-resource" - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json, FoundryJsonContext.Default.ResourceGetCommandResult); - - Assert.NotNull(result); - Assert.NotNull(result.Resources); - Assert.Single(result.Resources); - Assert.Equal("test-resource", result.Resources[0].ResourceName); - Assert.Equal("test-rg", result.Resources[0].ResourceGroup); - Assert.NotEmpty(result.Resources[0].Deployments!); - } - - [Fact] - public async Task ExecuteAsync_ReturnsEmpty_WhenNoResourcesExist() - { - _foundryService.ListAiResourcesAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns([]); - - var command = new ResourceGetCommand(_logger); - var args = command.GetCommand().Parse(["--subscription", "test-sub"]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json, FoundryJsonContext.Default.ResourceGetCommandResult); - - Assert.NotNull(result); - Assert.Empty(result.Resources); - } - - [Fact] - public async Task ExecuteAsync_HandlesListException() - { - var expectedError = "Failed to list resources"; - - _foundryService.ListAiResourcesAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - var command = new ResourceGetCommand(_logger); - var args = command.GetCommand().Parse(["--subscription", "test-sub"]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.InternalServerError, response.Status); - Assert.StartsWith(expectedError, response.Message); - } - - [Fact] - public async Task ExecuteAsync_HandlesGetException() - { - var expectedError = "Resource not found"; - - _foundryService.GetAiResourceAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .ThrowsAsync(new Exception(expectedError)); - - var command = new ResourceGetCommand(_logger); - var args = command.GetCommand().Parse([ - "--subscription", "test-sub", - "--resource-group", "test-rg", - "--resource-name", "test-resource" - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.InternalServerError, response.Status); - Assert.StartsWith(expectedError, response.Message); - } - - [Theory] - [InlineData("--subscription", "test-sub")] - [InlineData("--subscription", "test-sub", "--resource-group", "test-rg")] - [InlineData("--subscription", "test-sub", "--resource-group", "test-rg", "--resource-name", "test-resource")] - public async Task ExecuteAsync_ValidatesInputCorrectly(params string[] args) - { - _foundryService.ListAiResourcesAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns([]); - - _foundryService.GetAiResourceAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(new AiResourceInformation()); - - var command = new ResourceGetCommand(_logger); - var parsedArgs = command.GetCommand().Parse(args); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, parsedArgs, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - } - - [Fact] - public async Task ExecuteAsync_DeserializationValidation() - { - var resourceWithDeployments = new AiResourceInformation - { - ResourceName = "test-resource", - ResourceGroup = "test-rg", - SubscriptionName = "Test Subscription", - Location = "eastus", - Endpoint = "https://test-resource.openai.azure.com/", - Kind = "OpenAI", - SkuName = "S0", - Deployments = new List - { - new() - { - DeploymentName = "gpt-4o", - ModelName = "gpt-4o", - ModelVersion = "2024-11-20", - ModelFormat = "OpenAI", - SkuName = "GlobalStandard", - SkuCapacity = 450, - ScaleType = "Standard", - ProvisioningState = "Succeeded" - }, - new() - { - DeploymentName = "text-embedding-ada-002", - ModelName = "text-embedding-ada-002", - ModelVersion = "2", - ModelFormat = "OpenAI", - SkuName = "Standard", - SkuCapacity = 120, - ProvisioningState = "Succeeded" - } - } - }; - - _foundryService.GetAiResourceAsync( - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(resourceWithDeployments); - - var command = new ResourceGetCommand(_logger); - var args = command.GetCommand().Parse([ - "--subscription", "test-sub", - "--resource-group", "test-rg", - "--resource-name", "test-resource" - ]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.NotNull(response.Results); - - // Serialize and deserialize to validate JSON context - var json = JsonSerializer.Serialize(response.Results); - var result = JsonSerializer.Deserialize(json, FoundryJsonContext.Default.ResourceGetCommandResult); - - Assert.NotNull(result); - Assert.NotNull(result.Resources); - Assert.Single(result.Resources); - - var resource = result.Resources[0]; - Assert.Equal("test-resource", resource.ResourceName); - Assert.Equal("test-rg", resource.ResourceGroup); - Assert.Equal("Test Subscription", resource.SubscriptionName); - Assert.Equal("eastus", resource.Location); - Assert.Equal("https://test-resource.openai.azure.com/", resource.Endpoint); - Assert.Equal("OpenAI", resource.Kind); - Assert.Equal("S0", resource.SkuName); - - Assert.NotNull(resource.Deployments); - Assert.Equal(2, resource.Deployments.Count); - - var firstDeployment = resource.Deployments[0]; - Assert.Equal("gpt-4o", firstDeployment.DeploymentName); - Assert.Equal("gpt-4o", firstDeployment.ModelName); - Assert.Equal("2024-11-20", firstDeployment.ModelVersion); - Assert.Equal("OpenAI", firstDeployment.ModelFormat); - Assert.Equal("GlobalStandard", firstDeployment.SkuName); - Assert.Equal(450, firstDeployment.SkuCapacity); - Assert.Equal("Succeeded", firstDeployment.ProvisioningState); - - var secondDeployment = resource.Deployments[1]; - Assert.Equal("text-embedding-ada-002", secondDeployment.DeploymentName); - Assert.Equal("text-embedding-ada-002", secondDeployment.ModelName); - Assert.Equal("2", secondDeployment.ModelVersion); - } - - [Fact] - public void BindOptions_BindsOptionsCorrectly() - { - var command = new ResourceGetCommand(_logger); - var args = command.GetCommand().Parse([ - "--subscription", "test-sub", - "--resource-group", "test-rg", - "--resource-name", "test-resource", - "--tenant", "test-tenant" - ]); - - var context = new CommandContext(_serviceProvider); - // We can't directly access BindOptions, but we can verify the command parses correctly - Assert.Empty(args.Errors); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadCreateCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadCreateCommandTests.cs deleted file mode 100644 index d7de0e03d0..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadCreateCommandTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Thread; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class ThreadCreateCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public ThreadCreateCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Theory] - [InlineData("", FoundryOptionDefinitions.Endpoint)] - [InlineData("--endpoint https://test-endpoint.com", FoundryOptionDefinitions.UserMessage)] - public async Task ExecuteAsync_Fails_WhenMissingRequiredParameter(string argsString, string missingArgName) - { - var command = new ThreadCreateCommand(); - var args = command.GetCommand().Parse(argsString); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.BadRequest, response.Status); - Assert.Contains(missingArgName, response.Message); - } - - [Fact] - public async Task ExecuteAsync_ReturnsCreatedThread() - { - var endpoint = "https://test-endpoint.com"; - var userMessage = "usermessage"; - - var expectedResult = new ThreadCreateResult() - { - ThreadId = "threadId", - }; - - _foundryService.CreateThread( - Arg.Is(endpoint), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - var command = new ThreadCreateCommand(); - var args = command.GetCommand().Parse(["--endpoint", endpoint, "--user-message", userMessage]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadGetMessagesCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadGetMessagesCommandTests.cs deleted file mode 100644 index b674a14121..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadGetMessagesCommandTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Models; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class ThreadGetMessagesCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public ThreadGetMessagesCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Theory] - [InlineData("", FoundryOptionDefinitions.Endpoint)] - [InlineData("--endpoint https://test-endpoint.com", FoundryOptionDefinitions.ThreadId)] - public async Task ExecuteAsync_Fails_WhenMissingRequiredParameter(string argsString, string missingArgName) - { - var command = new ThreadGetMessagesCommand(); - var args = command.GetCommand().Parse(argsString); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.BadRequest, response.Status); - Assert.Contains(missingArgName, response.Message); - } - - [Fact] - public async Task ExecuteAsync_ReturnsMessages() - { - var endpoint = "https://test-endpoint.com"; - var threadId = "threadId"; - - var expectedResult = new ThreadGetMessagesResult() - { - ThreadId = "threadId", - Messages = [new ChatMessage()] - }; - - _foundryService.GetMessages( - Arg.Is(endpoint), - Arg.Any(), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - var command = new ThreadGetMessagesCommand(); - var args = command.GetCommand().Parse(["--endpoint", endpoint, "--thread-id", threadId]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadListCommandTests.cs b/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadListCommandTests.cs deleted file mode 100644 index c57d4dccdb..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/Azure.Mcp.Tools.Foundry.UnitTests/ThreadListCommandTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Net; -using Azure.Mcp.Core.Options; -using Azure.Mcp.Tools.Foundry.Commands; -using Azure.Mcp.Tools.Foundry.Options; -using Azure.Mcp.Tools.Foundry.Options.Thread; -using Azure.Mcp.Tools.Foundry.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Mcp.Core.Models.Command; -using NSubstitute; -using Xunit; - -namespace Azure.Mcp.Tools.Foundry.UnitTests; - -public class ThreadListCommandTests -{ - private readonly IServiceProvider _serviceProvider; - private readonly IFoundryService _foundryService; - - public ThreadListCommandTests() - { - _foundryService = Substitute.For(); - - var collection = new ServiceCollection(); - collection.AddSingleton(_foundryService); - - _serviceProvider = collection.BuildServiceProvider(); - } - - [Theory] - [InlineData("", FoundryOptionDefinitions.Endpoint)] - public async Task ExecuteAsync_Fails_WhenMissingRequiredParameter(string argsString, string missingArgName) - { - var command = new ThreadListCommand(); - var args = command.GetCommand().Parse(argsString); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.BadRequest, response.Status); - Assert.Contains(missingArgName, response.Message); - } - - [Fact] - public async Task ExecuteAsync_ReturnsListedThreads() - { - var endpoint = "https://test-endpoint.com"; - - var expectedResult = new ThreadListResult() - { - Threads = [new (){ - ThreadId = "threadId" - } - ], - }; - - _foundryService.ListThreads( - Arg.Is(endpoint), - Arg.Any(), - Arg.Any(), - Arg.Any()) - .Returns(expectedResult); - - var command = new ThreadListCommand(); - var args = command.GetCommand().Parse(["--endpoint", endpoint]); - var context = new CommandContext(_serviceProvider); - var response = await command.ExecuteAsync(context, args, TestContext.Current.CancellationToken); - - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.OK, response.Status); - Assert.NotNull(response.Results); - } -} diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/test-resources-post.ps1 b/tools/Azure.Mcp.Tools.Foundry/tests/test-resources-post.ps1 deleted file mode 100644 index 3e5818718d..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/test-resources-post.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -param( - [string] $TenantId, - [string] $TestApplicationId, - [string] $ResourceGroupName, - [string] $BaseName, - [hashtable] $DeploymentOutputs, - [hashtable] $AdditionalParameters -) - -$ErrorActionPreference = "Stop" - -. "$PSScriptRoot/../../../eng/common/scripts/common.ps1" -. "$PSScriptRoot/../../../eng/scripts/helpers/TestResourcesHelpers.ps1" - -$testSettings = New-TestSettings @PSBoundParameters -OutputPath $PSScriptRoot - -# $testSettings contains: -# - TenantId -# - TenantName -# - SubscriptionId -# - SubscriptionName -# - ResourceGroupName -# - ResourceBaseName - -# $DeploymentOutputs keys are all UPPERCASE - -# Add your post deployment steps here -# For example, you might want to configure resources or run additional scripts. \ No newline at end of file diff --git a/tools/Azure.Mcp.Tools.Foundry/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.Foundry/tests/test-resources.bicep deleted file mode 100644 index f553937449..0000000000 --- a/tools/Azure.Mcp.Tools.Foundry/tests/test-resources.bicep +++ /dev/null @@ -1,207 +0,0 @@ -targetScope = 'resourceGroup' - -@minLength(3) -@maxLength(17) -@description('The base resource name.') -param baseName string = resourceGroup().name - -@description('The location of the resource. By default, this is the same as the resource group.') -param location string = resourceGroup().location - -@description('The tenant ID to which the application and resources belong.') -param tenantId string = '72f988bf-86f1-41af-91ab-2d7cd011db47' - -@description('The client OID to grant access to test resources.') -param testApplicationOid string - -// Static resource names for consistent testing -var staticOpenAIAccount = 'azmcp-test' -var staticOpenAIDeploymentName = 'gpt-4o-mini' -var staticOpenAIAccountResourceGroup = 'static-test-resources' -var staticEmbeddingModel = 'embedding-model' - -var cognitiveServicesContributorRoleId = '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' // Cognitive Services Contributor role - -resource aiServicesAccount 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = { - name: baseName - location: location - kind: 'AIServices' - identity: { - type: 'SystemAssigned' - } - sku: { - name: 'S0' - } - properties: { - isAiFoundryType: true - customSubDomainName: baseName - dynamicThrottlingEnabled: false - networkAcls: { - defaultAction: 'Allow' - } - publicNetworkAccess: 'Enabled' - disableLocalAuth: true - allowProjectManagement: true - encryption: { - keySource: 'Microsoft.CognitiveServices' - } - } -} - -resource contributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(cognitiveServicesContributorRoleId, testApplicationOid, aiServicesAccount.id) - scope: aiServicesAccount - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesContributorRoleId) - principalId: testApplicationOid - } -} - -resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { - name: '${baseName}foundry' - location: location - sku: { - name: 'Standard_LRS' - } - kind: 'StorageV2' - properties: { - allowSharedKeyAccess: false - } - - resource blobServices 'blobServices' = { - name: 'default' - resource foundryContainer 'containers' = { name: 'foundry' } - } -} - -resource aiProjects 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = { - parent: aiServicesAccount - name: '${baseName}-ai-projects' - location: location - kind: 'AIServices' - identity: { - type: 'SystemAssigned' - } - properties: { - customSubDomainName: '${baseName}-ai-projects' - publicNetworkAccess: 'Enabled' - networkAcls: { - defaultAction: 'Allow' - virtualNetworkRules: [] - ipRules: [] - } - } - sku: { - name: 'S0' - } -} - -resource aiProjectsRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(cognitiveServicesContributorRoleId, testApplicationOid, aiProjects.id) - scope: aiProjects - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesContributorRoleId) - principalId: testApplicationOid - } -} - -resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = { - parent: aiServicesAccount - name: 'gpt-4o' - sku: { - name: 'Standard' - capacity: 30 - } - properties: { - model: { - format: 'OpenAI' - name: 'gpt-4o' - } - } -} - -resource bingGroundingSearch 'Microsoft.Bing/accounts@2020-06-10' = { - name: '${baseName}-bing-grounding' - location: 'Global' - sku: { - name: 'G1' - } - kind: 'Bing.Grounding' -} - -resource bingGroundingConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { - parent: aiProjects - name: '${baseName}-bing-connection' - properties: { - authType: 'ApiKey' - metadata: { - type: 'bing_grounding' - ApiType: 'Azure' - ResourceId: bingGroundingSearch.id - } - target: 'https://api.bing.microsoft.com/' - category: 'GroundingWithBingSearch' - group: 'AzureAI' - credentials: { - key: bingGroundingSearch.listKeys().key1 - } - } -} - -resource searchService 'Microsoft.Search/searchServices@2023-11-01' = { - name: '${baseName}-search' - location: location - sku: { - name: 'basic' - } - properties: { - replicaCount: 1 - partitionCount: 1 - hostingMode: 'default' - publicNetworkAccess: 'enabled' - networkRuleSet: { - ipRules: [] - } - encryptionWithCmk: { - enforcement: 'Unspecified' - } - disableLocalAuth: true - } - identity: { - type: 'SystemAssigned' - } -} - -resource searchServiceRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid('8ebe5a00-799e-43f5-93ac-243d3dce84a7', testApplicationOid, searchService.id) // Search Index Data Contributor role - scope: searchService - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7') - principalId: testApplicationOid - } -} - -resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { - name: '${baseName}-deployment-identity' - location: location -} - -resource managedIdentityRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid('7ca78c08-252a-4471-8644-bb5ff32d4ba0', managedIdentity.id, searchService.id) // Search Service Contributor role - scope: searchService - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0') - principalId: managedIdentity.properties.principalId - } -} - -output searchServiceName string = searchService.name -output searchServiceEndpoint string = 'https://${searchService.name}.search.windows.net' -output knowledgeIndexName string = '${baseName}-knowledge-index' -output aiProjectsEndpoint string = 'https://${aiServicesAccount.name}.services.ai.azure.com/api/projects/${aiProjects.name}' - -// Static resource outputs for test configuration -output openAIAccount string = staticOpenAIAccount -output openAIDeploymentName string = staticOpenAIDeploymentName -output openAIAccountResourceGroup string = staticOpenAIAccountResourceGroup -output embeddingDeploymentName string = staticEmbeddingModel