diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 46e0d61924..a5dfdfba9b 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -22,9 +22,9 @@
-
-
-
+
+
+
@@ -188,4 +188,4 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
\ No newline at end of file
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index b3a95ea1f6..dc13d4f0e9 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -162,6 +162,7 @@
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Agent_Step25_ToolboxServerSideTools.csproj b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Agent_Step25_ToolboxServerSideTools.csproj
new file mode 100644
index 0000000000..0db6ba9fe6
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Agent_Step25_ToolboxServerSideTools.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Program.cs
new file mode 100644
index 0000000000..12731f4723
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Program.cs
@@ -0,0 +1,148 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to load a Foundry toolbox and pass its tools as server-side
+// tools when creating an agent. The Foundry platform handles tool execution — the agent
+// process does not invoke tools locally.
+
+using System.ClientModel;
+using System.ClientModel.Primitives;
+using Azure.AI.Projects;
+using Azure.AI.Projects.Agents;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using OpenAI.Responses;
+
+#pragma warning disable OPENAI001 // Experimental API
+#pragma warning disable AAIP001 // AgentToolboxes is experimental
+#pragma warning disable CS8321 // Local functions may be commented-out alternatives
+
+// Replace with your own Foundry toolbox name.
+const string ToolboxName = "research_toolbox";
+// Used only by CombineToolboxes — swap in a second toolbox you own.
+const string SecondToolboxName = "analysis_toolbox";
+// Replace with any question that exercises the tools configured in your toolbox.
+const string Query = "Introduce yourself and briefly describe the tools you can use to help me.";
+
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("Set FOUNDRY_PROJECT_ENDPOINT to your Foundry project endpoint.");
+string model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
+
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+var projectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
+
+await Main(projectClient, model, endpoint);
+// await CombineToolboxes(projectClient, model, endpoint);
+
+// ---------------------------------------------------------------------------
+// Main: single toolbox
+// ---------------------------------------------------------------------------
+static async Task Main(AIProjectClient projectClient, string model, string endpoint)
+{
+ Console.WriteLine("=== Foundry Toolbox Server-Side Tools Example ===");
+
+ // Comment out if the toolbox already exists in your Foundry project.
+ await CreateSampleToolboxAsync(ToolboxName, endpoint);
+
+ // Omit the version to resolve the toolbox's current default version at runtime.
+ var tools = await projectClient.GetToolboxToolsAsync(ToolboxName);
+
+ AIAgent agent = projectClient
+ .AsAIAgent(
+ model: model,
+ instructions: "You are a research assistant. Use the available tools to answer questions.",
+ tools: tools.ToList());
+
+ Console.WriteLine($"User: {Query}");
+ Console.WriteLine($"Result: {await agent.RunAsync(Query)}\n");
+}
+
+// ---------------------------------------------------------------------------
+// Alternative: combine tools from multiple toolboxes
+// ---------------------------------------------------------------------------
+static async Task CombineToolboxes(AIProjectClient projectClient, string model, string endpoint)
+{
+ Console.WriteLine("=== Combine Toolboxes Example ===");
+
+ // Comment out if the toolboxes already exist in your Foundry project.
+ await CreateSampleToolboxAsync(ToolboxName, endpoint);
+ await CreateSampleToolboxAsync(SecondToolboxName, endpoint);
+
+ var toolboxA = await projectClient.GetToolboxToolsAsync(ToolboxName);
+ var toolboxB = await projectClient.GetToolboxToolsAsync(SecondToolboxName);
+
+ var allTools = toolboxA.Concat(toolboxB).ToList();
+
+ AIAgent agent = projectClient
+ .AsAIAgent(
+ model: model,
+ instructions: "You are a research assistant. Use all available tools to answer questions.",
+ tools: allTools);
+
+ Console.WriteLine($"User: {Query}");
+ Console.WriteLine($"Combined-toolbox result: {await agent.RunAsync(Query)}\n");
+}
+
+// ---------------------------------------------------------------------------
+// Helper: create (or replace) a sample toolbox so the sample works out-of-the-box
+// ---------------------------------------------------------------------------
+static async Task CreateSampleToolboxAsync(string name, string endpoint)
+{
+ // Toolboxes are normally configured in the Foundry portal or a deployment
+ // script, not the application itself. This helper exists so the sample can
+ // be run end-to-end without first setting a toolbox up by hand.
+
+ // The Foundry-Features header is currently required for toolbox CRUD operations.
+ var options = new AgentAdministrationClientOptions();
+ options.AddPolicy(new FoundryFeaturesPolicy("Toolboxes=V1Preview"), PipelinePosition.PerCall);
+ var adminClient = new AgentAdministrationClient(
+ new Uri(endpoint),
+ new DefaultAzureCredential(),
+ options);
+ var toolboxClient = adminClient.GetAgentToolboxes();
+
+ // Delete existing toolbox if present (ignore 404).
+ try
+ {
+ await toolboxClient.DeleteToolboxAsync(name);
+ Console.WriteLine($"Deleted existing toolbox '{name}'");
+ }
+ catch (ClientResultException ex) when (ex.Status == 404)
+ {
+ // Toolbox does not exist — nothing to delete.
+ }
+
+ // Create a fresh version with a single MCP tool.
+ ProjectsAgentTool mcpTool = ProjectsAgentTool.AsProjectTool(ResponseTool.CreateMcpTool(
+ serverLabel: "api-specs",
+ serverUri: new Uri("https://gitmcp.io/Azure/azure-rest-api-specs"),
+ toolCallApprovalPolicy: new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval)));
+
+ var created = (await toolboxClient.CreateToolboxVersionAsync(
+ name: name,
+ tools: [mcpTool],
+ description: "Sample toolbox with an MCP tool — created by Agent_Step25 sample.")).Value;
+
+ Console.WriteLine($"Created toolbox '{created.Name}' v{created.Version} ({created.Tools.Count} tool(s))");
+}
+
+// ---------------------------------------------------------------------------
+// Pipeline policy that adds the Foundry-Features header for toolbox CRUD
+// ---------------------------------------------------------------------------
+internal sealed class FoundryFeaturesPolicy(string feature) : PipelinePolicy
+{
+ private const string FeatureHeader = "Foundry-Features";
+
+ public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ {
+ message.Request.Headers.Add(FeatureHeader, feature);
+ ProcessNext(message, pipeline, currentIndex);
+ }
+
+ public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ {
+ message.Request.Headers.Add(FeatureHeader, feature);
+ return ProcessNextAsync(message, pipeline, currentIndex);
+ }
+}
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/README.md
new file mode 100644
index 0000000000..75c6b3eb9d
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/README.md
@@ -0,0 +1,46 @@
+# Agent_Step25_ToolboxServerSideTools
+
+This sample demonstrates loading a named Foundry toolbox and passing its tools as
+**server-side tools** when creating an agent via `AsAIAgent()`.
+
+When tools from a toolbox are passed this way, they are sent as tool definitions in
+the Responses API request. The Foundry platform handles tool execution — the agent
+process does not invoke tools locally.
+
+This is the dotnet equivalent of the Python sample:
+`python/samples/02-agents/providers/foundry/foundry_chat_client_with_toolbox.py`
+
+## Prerequisites
+
+- A Microsoft Foundry project
+- `AZURE_AI_PROJECT_ENDPOINT` environment variable set to your Foundry project endpoint
+- `AZURE_AI_MODEL_DEPLOYMENT_NAME` environment variable set (defaults to `gpt-5.4-mini`)
+
+The sample recreates the toolbox on each run, replacing any existing toolbox with
+the same name. Comment out the `CreateSampleToolboxAsync` call if you want to keep
+an existing toolbox unchanged.
+
+## How it works
+
+1. `projectClient.GetToolboxVersionAsync(name)` fetches the toolbox definition from the
+ Foundry project API (resolving the default version if none is specified)
+2. `ToolboxVersion.ToAITools()` converts each tool definition to an `AITool` instance
+3. The tools are passed to `AsAIAgent(tools: ...)` which includes them in the Responses
+ API request as server-side tool definitions
+
+For a one-liner, use `projectClient.GetToolboxToolsAsync(name)` to fetch and convert in one call.
+
+## Sample flows
+
+| Flow | Description |
+|------|-------------|
+| `Main` (default) | Loads a single toolbox and runs an agent with its tools |
+| `CombineToolboxes` | Loads two toolboxes and merges their tools into one agent |
+
+Uncomment the desired flow in the top-level statements to try each one.
+
+## Running the sample
+
+```bash
+dotnet run
+```
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AIProjectClientToolboxExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AIProjectClientToolboxExtensions.cs
new file mode 100644
index 0000000000..48c50eaedb
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AIProjectClientToolboxExtensions.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Agents.AI.Foundry.Hosting;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+#pragma warning disable OPENAI001
+#pragma warning disable AAIP001 // AgentToolboxes is experimental in Azure.AI.Projects.Agents
+
+namespace Azure.AI.Projects;
+
+///
+/// Provides extension methods on for fetching
+/// Foundry toolbox definitions as server-side tools.
+///
+///
+/// These extensions mirror Python's FoundryChatClient.get_toolbox() pattern,
+/// allowing a single call on the project client to retrieve tools ready for use
+/// with AsAIAgent(model, instructions, tools: ...).
+///
+[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
+public static class AIProjectClientToolboxExtensions
+{
+ ///
+ /// Fetches a toolbox from the Foundry project and returns its tools as instances
+ /// ready for use as server-side tools in the Responses API.
+ ///
+ /// The to use. Cannot be .
+ /// The name of the toolbox to fetch.
+ ///
+ /// The specific toolbox version to fetch. When , the toolbox's
+ /// default version is resolved automatically.
+ ///
+ /// A token to monitor for cancellation requests.
+ /// A read-only list of instances from the toolbox.
+ ///
+ /// Thrown when or is .
+ ///
+ public static async Task> GetToolboxToolsAsync(
+ this AIProjectClient projectClient,
+ string name,
+ string? version = null,
+ CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(projectClient);
+ Throw.IfNullOrWhitespace(name);
+
+ var toolboxClient = projectClient.AgentAdministrationClient.GetAgentToolboxes();
+ var toolboxVersion = await FoundryToolbox.GetToolboxVersionCoreAsync(toolboxClient, name, version, cancellationToken).ConfigureAwait(false);
+ return toolboxVersion.ToAITools();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryToolbox.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryToolbox.cs
new file mode 100644
index 0000000000..19f6f6823a
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryToolbox.cs
@@ -0,0 +1,223 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel;
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text.Json.Nodes;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.AI.Projects.Agents;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+using OpenAI.Responses;
+
+#pragma warning disable OPENAI001
+#pragma warning disable AAIP001 // AgentToolboxes is experimental in Azure.AI.Projects.Agents
+#pragma warning disable IL2026 // ModelReaderWriter.Read uses reflection; suppressed for Azure SDK model types.
+#pragma warning disable IL3050 // ModelReaderWriter.Read requires dynamic code; suppressed for Azure SDK model types.
+
+namespace Microsoft.Agents.AI.Foundry.Hosting;
+
+///
+/// Provides methods for fetching Foundry toolbox definitions and converting their tools
+/// to instances for use as server-side tools in the Responses API.
+///
+///
+///
+/// When tools from a toolbox are passed to a Foundry agent (e.g. via AsAIAgent(model, instructions, tools: ...)),
+/// they are sent as server-side tool definitions in the Responses API request. The Foundry platform
+/// handles tool execution — the agent process does not invoke tools locally.
+///
+///
+/// This is the dotnet equivalent of Python's FoundryChatClient.get_toolbox() pattern.
+///
+///
+[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
+public static class FoundryToolbox
+{
+ ///
+ /// Fetches a toolbox version from the Foundry project and returns the raw SDK .
+ ///
+ /// The Foundry project endpoint URI.
+ /// The authentication credential used to access the Foundry project.
+ /// The name of the toolbox to fetch.
+ ///
+ /// The specific toolbox version to fetch. When , the toolbox's
+ /// default version is resolved automatically (requires an additional API call).
+ ///
+ /// A token to monitor for cancellation requests.
+ /// The containing tool definitions.
+ ///
+ /// Thrown when , , or is .
+ ///
+ /// Thrown when the Foundry project API returns an error.
+ public static async Task GetToolboxVersionAsync(
+ Uri projectEndpoint,
+ AuthenticationTokenProvider credential,
+ string name,
+ string? version = null,
+ CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(projectEndpoint);
+ Throw.IfNull(credential);
+ Throw.IfNullOrWhitespace(name);
+
+ var toolboxClient = CreateToolboxClient(projectEndpoint, credential);
+ return await GetToolboxVersionCoreAsync(toolboxClient, name, version, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Fetches a toolbox from the Foundry project and returns its tools as instances
+ /// ready for use as server-side tools in the Responses API.
+ ///
+ /// The Foundry project endpoint URI.
+ /// The authentication credential used to access the Foundry project.
+ /// The name of the toolbox to fetch.
+ ///
+ /// The specific toolbox version to fetch. When , the toolbox's
+ /// default version is resolved automatically.
+ ///
+ /// A token to monitor for cancellation requests.
+ /// A read-only list of instances from the toolbox.
+ ///
+ /// Thrown when , , or is .
+ ///
+ /// Thrown when the Foundry project API returns an error.
+ public static async Task> GetToolsAsync(
+ Uri projectEndpoint,
+ AuthenticationTokenProvider credential,
+ string name,
+ string? version = null,
+ CancellationToken cancellationToken = default)
+ {
+ var toolboxVersion = await GetToolboxVersionAsync(projectEndpoint, credential, name, version, cancellationToken).ConfigureAwait(false);
+ return toolboxVersion.ToAITools();
+ }
+
+ ///
+ /// Converts the tools in a to instances
+ /// suitable for use as server-side tools in the Responses API.
+ ///
+ /// The toolbox version whose tools to convert.
+ /// A read-only list of instances.
+ /// Thrown when is .
+ ///
+ ///
+ /// Each in the toolbox is cast to
+ /// and converted via AsAITool(). Non-function hosted tools (MCP, web_search,
+ /// code_interpreter, etc.) are included as server-side tool definitions — the Foundry
+ /// platform handles their execution.
+ ///
+ ///
+ /// Non-function tools are sanitized to remove decoration fields (name, description)
+ /// that the toolbox API returns but the Responses API rejects.
+ ///
+ ///
+ public static IReadOnlyList ToAITools(this ToolboxVersion toolboxVersion)
+ {
+ Throw.IfNull(toolboxVersion);
+
+ if (toolboxVersion.Tools?.Any() != true)
+ {
+ return [];
+ }
+
+ return toolboxVersion.Tools
+ .Select(SanitizeAndConvert)
+ .ToList();
+ }
+
+ #region Internal helpers (visible to unit tests via InternalsVisibleTo)
+
+ ///
+ /// Sanitizes a by removing decoration fields that the
+ /// toolbox API returns but the Responses API rejects, then converts to .
+ ///
+ ///
+ /// The Azure AI Projects toolbox API may return name and description on
+ /// hosted tool objects (MCP, code_interpreter, file_search, etc.). The Responses API
+ /// rejects at least name with "Unknown parameter: 'tools[0].name'". We strip
+ /// these decoration fields for non-function tools. Function tools keep them since
+ /// name and description are expected parts of the function schema.
+ ///
+ internal static AITool SanitizeAndConvert(ProjectsAgentTool tool)
+ {
+ var toolJson = ModelReaderWriter.Write(tool, new ModelReaderWriterOptions("J"));
+ var node = JsonNode.Parse(toolJson.ToString());
+ if (node is not JsonObject obj)
+ {
+ return ((ResponseTool)tool).AsAITool();
+ }
+
+ var toolType = obj["type"]?.GetValue();
+
+ // Function tools need name/description — don't strip
+ if (toolType is "function" or "custom")
+ {
+ return ((ResponseTool)tool).AsAITool();
+ }
+
+ // Strip decoration fields that the Responses API rejects
+ bool modified = false;
+ modified |= obj.Remove("name");
+ modified |= obj.Remove("description");
+
+ if (!modified)
+ {
+ return ((ResponseTool)tool).AsAITool();
+ }
+
+ var sanitizedJson = obj.ToJsonString();
+ var sanitizedTool = ModelReaderWriter.Read(BinaryData.FromString(sanitizedJson))!;
+ return sanitizedTool.AsAITool();
+ }
+
+ internal static async Task GetToolboxVersionAsync(
+ Uri projectEndpoint,
+ AuthenticationTokenProvider credential,
+ string name,
+ string? version,
+ AgentAdministrationClientOptions? clientOptions,
+ CancellationToken cancellationToken)
+ {
+ Throw.IfNull(projectEndpoint);
+ Throw.IfNull(credential);
+ Throw.IfNullOrWhitespace(name);
+
+ var toolboxClient = CreateToolboxClient(projectEndpoint, credential, clientOptions);
+ return await GetToolboxVersionCoreAsync(toolboxClient, name, version, cancellationToken).ConfigureAwait(false);
+ }
+
+ internal static AgentToolboxes CreateToolboxClient(
+ Uri projectEndpoint,
+ AuthenticationTokenProvider credential,
+ AgentAdministrationClientOptions? clientOptions = null)
+ {
+ clientOptions ??= new AgentAdministrationClientOptions();
+ var adminClient = new AgentAdministrationClient(projectEndpoint, credential, clientOptions);
+ return adminClient.GetAgentToolboxes();
+ }
+
+ internal static async Task GetToolboxVersionCoreAsync(
+ AgentToolboxes toolboxClient,
+ string name,
+ string? version,
+ CancellationToken cancellationToken)
+ {
+ if (version is null)
+ {
+ var record = await toolboxClient.GetToolboxAsync(name, cancellationToken).ConfigureAwait(false);
+ version = record.Value.DefaultVersion
+ ?? throw new InvalidOperationException($"Toolbox '{name}' does not have a default version. Specify an explicit version.");
+ }
+
+ var result = await toolboxClient.GetToolboxVersionAsync(name, version, cancellationToken).ConfigureAwait(false);
+ return result.Value;
+ }
+
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/InputConverter.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/InputConverter.cs
index cc97049ae9..a65b79a747 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/InputConverter.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/InputConverter.cs
@@ -237,7 +237,7 @@ private static ChatMessage ConvertItemFunctionToolCall(ItemFunctionToolCall func
{
OutputItemMessage msg => ConvertOutputItemMessageToChat(msg),
OutputItemFunctionToolCall funcCall => ConvertOutputItemFunctionCall(funcCall),
- FunctionToolCallOutputResource funcOutput => ConvertFunctionToolCallOutputResource(funcOutput),
+ OutputItemFunctionToolCallOutput funcOutput => ConvertFunctionToolCallOutput(funcOutput),
OutputItemReasoningItem => null,
_ => null
};
@@ -332,7 +332,7 @@ private static ChatMessage ConvertOutputItemFunctionCall(OutputItemFunctionToolC
[new FunctionCallContent(funcCall.CallId, funcCall.Name, arguments)]);
}
- private static ChatMessage ConvertFunctionToolCallOutputResource(FunctionToolCallOutputResource funcOutput)
+ private static ChatMessage ConvertFunctionToolCallOutput(OutputItemFunctionToolCallOutput funcOutput)
{
return new ChatMessage(
ChatRole.Tool,
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/OutputConverter.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/OutputConverter.cs
index 58ba989ebf..2d7728361e 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/OutputConverter.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/OutputConverter.cs
@@ -251,16 +251,25 @@ private static ResponseUsage ConvertUsage(UsageDetails details, ResponseUsage? e
var outputTokens = details.OutputTokenCount ?? 0;
var totalTokens = details.TotalTokenCount ?? 0;
+ var cachedTokens = details.AdditionalCounts?.TryGetValue("InputTokenDetails.CachedTokenCount", out var cached) ?? false
+ ? cached : 0;
+ var reasoningTokens = details.AdditionalCounts?.TryGetValue("OutputTokenDetails.ReasoningTokenCount", out var reasoning) ?? false
+ ? reasoning : 0;
+
if (existing is not null)
{
inputTokens += existing.InputTokens;
outputTokens += existing.OutputTokens;
totalTokens += existing.TotalTokens;
+ cachedTokens += existing.InputTokensDetails?.CachedTokens ?? 0;
+ reasoningTokens += existing.OutputTokensDetails?.ReasoningTokens ?? 0;
}
- return AzureAIAgentServerResponsesModelFactory.ResponseUsage(
+ return new ResponseUsage(
inputTokens: inputTokens,
+ inputTokensDetails: new ResponseUsageInputTokensDetails(cachedTokens),
outputTokens: outputTokens,
+ outputTokensDetails: new ResponseUsageOutputTokensDetails(reasoningTokens),
totalTokens: totalTokens);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTelemetryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTelemetryTests.cs
index 9b17fa9fae..48daf490ee 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTelemetryTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTelemetryTests.cs
@@ -164,10 +164,8 @@ public async Task CreateAsync_DefaultAgent_SpanDisplayNameContainsAgentNameAsync
private static (CreateResponse request, ResponseContext context) BuildRequest(string? agentKey = null)
{
var request = agentKey is null
- ? AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test")
- : AzureAIAgentServerResponsesModelFactory.CreateResponse(
- model: "test",
- agentReference: new AgentReference(agentKey));
+ ? new CreateResponse { Model = "test" }
+ : new CreateResponse { Model = "test", AgentReference = new AgentReference(agentKey) };
request.Input = BinaryData.FromObjectAsJson(new[]
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTests.cs
index 75a495f05b..d7fc8fc884 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTests.cs
@@ -34,7 +34,7 @@ public async Task CreateAsync_WithDefaultAgent_ProducesStreamEventsAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -72,9 +72,7 @@ public async Task CreateAsync_WithKeyedAgent_ResolvesCorrectAgentAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(
- model: "test",
- agentReference: new AgentReference("my-agent"));
+ var request = new CreateResponse { Model = "test", AgentReference = new AgentReference("my-agent") };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -109,7 +107,7 @@ public async Task CreateAsync_NoAgentRegistered_ThrowsInvalidOperationExceptionA
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -158,7 +156,7 @@ public async Task CreateAsync_ResolvesAgentByModelFieldAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "my-agent");
+ var request = new CreateResponse { Model = "my-agent" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -195,7 +193,7 @@ public async Task CreateAsync_ResolvesAgentByEntityIdMetadataAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "");
+ var request = new CreateResponse { Model = "" };
var metadata = new Metadata();
metadata.AdditionalProperties["entity_id"] = "entity-agent";
request.Metadata = metadata;
@@ -235,9 +233,7 @@ public async Task CreateAsync_NamedAgentNotFound_FallsBackToDefaultAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(
- model: "test",
- agentReference: new AgentReference("nonexistent-agent"));
+ var request = new CreateResponse { Model = "test", AgentReference = new AgentReference("nonexistent-agent") };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -272,9 +268,7 @@ public async Task CreateAsync_NoAgentFound_ErrorMessageIncludesAgentNameAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(
- model: "test",
- agentReference: new AgentReference("missing-agent"));
+ var request = new CreateResponse { Model = "test", AgentReference = new AgentReference("missing-agent") };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -308,7 +302,7 @@ public async Task CreateAsync_NoAgentNoName_ErrorMessageIsGenericAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "");
+ var request = new CreateResponse { Model = "" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -342,7 +336,7 @@ public async Task CreateAsync_AgentResolvedBeforeEmitCreated_ExceptionHasNoEvent
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -387,7 +381,7 @@ public async Task CreateAsync_WithHistory_PrependsHistoryToMessagesAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -435,7 +429,7 @@ public async Task CreateAsync_WithInputItems_UsesResolvedInputItemsAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -478,7 +472,7 @@ public async Task CreateAsync_NoInputItems_FallsBackToRawRequestInputAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -517,9 +511,11 @@ public async Task CreateAsync_PassesInstructionsToAgentAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(
- model: "test",
- instructions: "You are a helpful assistant.");
+ var request = new CreateResponse
+ {
+ Model = "test",
+ Instructions = "You are a helpful assistant.",
+ };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -557,7 +553,7 @@ public async Task CreateAsync_AgentThrows_EmitsFailedEventWithErrorMessageAsync(
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -598,9 +594,7 @@ public async Task CreateAsync_MultipleKeyedAgents_ResolvesCorrectOneAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(
- model: "test",
- agentReference: new AgentReference("agent-2"));
+ var request = new CreateResponse { Model = "test", AgentReference = new AgentReference("agent-2") };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -637,7 +631,7 @@ public async Task CreateAsync_CancellationDuringExecution_PropagatesOperationCan
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
@@ -674,7 +668,7 @@ public async Task CreateAsync_DefaultAgent_IsAutoWrappedWithOpenTelemetryAsync()
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = BinaryData.FromObjectAsJson(new[]
{
new { type = "message", id = "msg_1", status = "completed", role = "user",
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxTests.cs
new file mode 100644
index 0000000000..3f0a8a54dd
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxTests.cs
@@ -0,0 +1,329 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel;
+using System.ClientModel.Primitives;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using Azure.AI.Projects;
+using Azure.AI.Projects.Agents;
+using Microsoft.Agents.AI.Foundry.Hosting;
+using Microsoft.Extensions.AI;
+
+#pragma warning disable OPENAI001
+#pragma warning disable AAIP001
+
+namespace Microsoft.Agents.AI.Foundry.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public class FoundryToolboxTests
+{
+ private static readonly Uri s_testEndpoint = new("https://test.services.ai.azure.com/api/projects/test-project");
+
+ #region Parameter validation tests
+
+ [Fact]
+ public async Task GetToolboxVersionAsync_NullEndpoint_ThrowsAsync()
+ {
+ await Assert.ThrowsAsync(() =>
+ FoundryToolbox.GetToolboxVersionAsync(
+ projectEndpoint: null!,
+ credential: new FakeAuthenticationTokenProvider(),
+ name: "test-toolbox"));
+ }
+
+ [Fact]
+ public async Task GetToolboxVersionAsync_NullCredential_ThrowsAsync()
+ {
+ await Assert.ThrowsAsync(() =>
+ FoundryToolbox.GetToolboxVersionAsync(
+ projectEndpoint: s_testEndpoint,
+ credential: null!,
+ name: "test-toolbox"));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public async Task GetToolboxVersionAsync_InvalidName_ThrowsAsync(string? name)
+ {
+ await Assert.ThrowsAnyAsync(() =>
+ FoundryToolbox.GetToolboxVersionAsync(
+ projectEndpoint: s_testEndpoint,
+ credential: new FakeAuthenticationTokenProvider(),
+ name: name!));
+ }
+
+ [Fact]
+ public async Task GetToolsAsync_NullEndpoint_ThrowsAsync()
+ {
+ await Assert.ThrowsAsync(() =>
+ FoundryToolbox.GetToolsAsync(
+ projectEndpoint: null!,
+ credential: new FakeAuthenticationTokenProvider(),
+ name: "test-toolbox"));
+ }
+
+ [Fact]
+ public void ToAITools_NullToolboxVersion_Throws()
+ {
+ Assert.Throws(() =>
+ FoundryToolbox.ToAITools(null!));
+ }
+
+ #endregion
+
+ #region ToAITools conversion tests
+
+ [Fact]
+ public void ToAITools_EmptyTools_ReturnsEmptyList()
+ {
+ var version = ProjectsAgentsModelFactory.ToolboxVersion(
+ metadata: null,
+ id: "ver-1",
+ name: "empty-toolbox",
+ version: "v1",
+ description: "Empty",
+ createdAt: DateTimeOffset.UtcNow,
+ tools: Array.Empty(),
+ policies: null);
+
+ var tools = version.ToAITools();
+
+ Assert.Empty(tools);
+ }
+
+ [Fact]
+ public void ToAITools_NullTools_ReturnsEmptyList()
+ {
+ var version = ProjectsAgentsModelFactory.ToolboxVersion(
+ metadata: null,
+ id: "ver-1",
+ name: "null-tools-toolbox",
+ version: "v1",
+ description: "Null tools",
+ createdAt: DateTimeOffset.UtcNow,
+ tools: null,
+ policies: null);
+
+ var tools = version.ToAITools();
+
+ Assert.Empty(tools);
+ }
+
+ [Fact]
+ public void ToAITools_WithCodeInterpreterTool_ReturnsAITool()
+ {
+ var json = TestDataUtil.GetToolboxVersionResponseJson();
+ var version = ModelReaderWriter.Read(BinaryData.FromString(json))!;
+
+ var tools = version.ToAITools();
+
+ Assert.Single(tools);
+ Assert.IsAssignableFrom(tools[0]);
+ }
+
+ [Fact]
+ public void ToAITools_SanitizesDecorationFieldsOnNonFunctionTools()
+ {
+ var json = TestDataUtil.GetToolboxVersionWithDecorationFieldsJson();
+ var version = ModelReaderWriter.Read(BinaryData.FromString(json))!;
+
+ var tools = version.ToAITools();
+
+ Assert.Single(tools);
+ Assert.IsAssignableFrom(tools[0]);
+ }
+
+ [Fact]
+ public void SanitizeAndConvert_FunctionTool_PreservesNameAndDescription()
+ {
+ const string ToolJson = @"{""type"":""function"",""name"":""get_weather"",""description"":""Get weather"",""parameters"":{""type"":""object"",""properties"":{}}}";
+ var tool = ModelReaderWriter.Read(BinaryData.FromString(ToolJson))!;
+
+ var aiTool = FoundryToolbox.SanitizeAndConvert(tool);
+
+ Assert.NotNull(aiTool);
+ Assert.IsAssignableFrom(aiTool);
+ }
+
+ [Fact]
+ public void SanitizeAndConvert_CodeInterpreterWithExtraFields_StripsDecorationFields()
+ {
+ const string ToolJson = @"{""type"":""code_interpreter"",""name"":""code_interpreter"",""description"":""Execute code""}";
+ var tool = ModelReaderWriter.Read(BinaryData.FromString(ToolJson))!;
+
+ var aiTool = FoundryToolbox.SanitizeAndConvert(tool);
+
+ Assert.NotNull(aiTool);
+ }
+
+ #endregion
+
+ #region Integration tests with mock HTTP
+
+ [Fact]
+ public async Task GetToolboxVersionAsync_WithExplicitVersion_FetchesVersionDirectlyAsync()
+ {
+ var versionJson = TestDataUtil.GetToolboxVersionResponseJson();
+ using var httpHandler = new HttpHandlerAssert((request) =>
+ {
+ Assert.Contains("/toolboxes/research_tools/versions/v5", request.RequestUri!.PathAndQuery);
+
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(versionJson, Encoding.UTF8, "application/json")
+ };
+ });
+
+#pragma warning disable CA5399
+ using var httpClient = new HttpClient(httpHandler);
+#pragma warning restore CA5399
+ var clientOptions = new AgentAdministrationClientOptions { Transport = new HttpClientPipelineTransport(httpClient) };
+
+ var result = await FoundryToolbox.GetToolboxVersionAsync(
+ s_testEndpoint,
+ new FakeAuthenticationTokenProvider(),
+ "research_tools",
+ version: "v5",
+ clientOptions: clientOptions,
+ cancellationToken: default);
+
+ Assert.Equal("research_tools", result.Name);
+ Assert.Equal("v5", result.Version);
+ Assert.Single(result.Tools);
+ }
+
+ [Fact]
+ public async Task GetToolboxVersionAsync_WithoutVersion_ResolvesDefaultThenFetchesAsync()
+ {
+ var recordJson = TestDataUtil.GetToolboxRecordResponseJson();
+ var versionJson = TestDataUtil.GetToolboxVersionResponseJson();
+ var callCount = 0;
+
+ using var httpHandler = new HttpHandlerAssert((request) =>
+ {
+ callCount++;
+ var path = request.RequestUri!.PathAndQuery;
+
+ if (!path.Contains("/versions/"))
+ {
+ Assert.Contains("/toolboxes/research_tools", path);
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(recordJson, Encoding.UTF8, "application/json")
+ };
+ }
+
+ Assert.Contains("/toolboxes/research_tools/versions/v5", path);
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(versionJson, Encoding.UTF8, "application/json")
+ };
+ });
+
+#pragma warning disable CA5399
+ using var httpClient = new HttpClient(httpHandler);
+#pragma warning restore CA5399
+ var clientOptions = new AgentAdministrationClientOptions { Transport = new HttpClientPipelineTransport(httpClient) };
+
+ var result = await FoundryToolbox.GetToolboxVersionAsync(
+ s_testEndpoint,
+ new FakeAuthenticationTokenProvider(),
+ "research_tools",
+ version: null,
+ clientOptions: clientOptions,
+ cancellationToken: default);
+
+ Assert.Equal(2, callCount);
+ Assert.Equal("research_tools", result.Name);
+ Assert.Equal("v5", result.Version);
+ }
+
+ [Fact]
+ public async Task GetToolboxVersionAsync_ApiError_ThrowsClientResultExceptionAsync()
+ {
+ using var httpHandler = new HttpHandlerAssert((_) =>
+ new HttpResponseMessage(HttpStatusCode.NotFound)
+ {
+ Content = new StringContent("{\"error\":\"not found\"}", Encoding.UTF8, "application/json")
+ });
+
+#pragma warning disable CA5399
+ using var httpClient = new HttpClient(httpHandler);
+#pragma warning restore CA5399
+ var clientOptions = new AgentAdministrationClientOptions { Transport = new HttpClientPipelineTransport(httpClient) };
+
+ await Assert.ThrowsAsync(() =>
+ FoundryToolbox.GetToolboxVersionAsync(
+ s_testEndpoint,
+ new FakeAuthenticationTokenProvider(),
+ "nonexistent-toolbox",
+ version: "v1",
+ clientOptions: clientOptions,
+ cancellationToken: default));
+ }
+
+ [Fact]
+ public async Task GetToolsAsync_ReturnsConvertedAIToolsAsync()
+ {
+ var versionJson = TestDataUtil.GetToolboxVersionResponseJson();
+ using var httpHandler = new HttpHandlerAssert((_) =>
+ new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(versionJson, Encoding.UTF8, "application/json")
+ });
+
+#pragma warning disable CA5399
+ using var httpClient = new HttpClient(httpHandler);
+#pragma warning restore CA5399
+ var clientOptions = new AgentAdministrationClientOptions { Transport = new HttpClientPipelineTransport(httpClient) };
+
+ var result = await FoundryToolbox.GetToolboxVersionAsync(
+ s_testEndpoint,
+ new FakeAuthenticationTokenProvider(),
+ "research_tools",
+ version: "v5",
+ clientOptions: clientOptions,
+ cancellationToken: default);
+
+ var tools = result.ToAITools();
+
+ Assert.Single(tools);
+ Assert.IsAssignableFrom(tools[0]);
+ }
+
+ #endregion
+
+ #region AIProjectClient extension tests
+
+ [Fact]
+ public async Task AIProjectClientExtension_GetToolboxToolsAsync_ReturnsAIToolsAsync()
+ {
+ var versionJson = TestDataUtil.GetToolboxVersionResponseJson();
+ using var httpHandler = new HttpHandlerAssert((_) =>
+ new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(versionJson, Encoding.UTF8, "application/json")
+ });
+
+#pragma warning disable CA5399
+ using var httpClient = new HttpClient(httpHandler);
+#pragma warning restore CA5399
+ var clientOptions = new AIProjectClientOptions();
+ clientOptions.Transport = new HttpClientPipelineTransport(httpClient);
+ var client = new AIProjectClient(s_testEndpoint, new FakeAuthenticationTokenProvider(), clientOptions);
+
+ var tools = await client.GetToolboxToolsAsync("research_tools", version: "v5");
+
+ Assert.Single(tools);
+ Assert.IsAssignableFrom(tools[0]);
+ }
+
+ #endregion
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs
index e2f6159a6e..f10a015c4b 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs
@@ -2,7 +2,6 @@
using System;
using System.Linq;
-using Azure.AI.AgentServer.Responses;
using Azure.AI.AgentServer.Responses.Models;
using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;
@@ -146,11 +145,7 @@ public void ConvertInputToMessages_MultipleItems_ReturnsAllMessages()
[Fact]
public void ConvertToChatOptions_SetsTemperatureAndTopP()
{
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(
- temperature: 0.7,
- topP: 0.9,
- maxOutputTokens: 1000,
- model: "gpt-4o");
+ var request = new CreateResponse { Temperature = 0.7, TopP = 0.9, MaxOutputTokens = 1000, Model = "gpt-4o" };
var options = InputConverter.ConvertToChatOptions(request);
@@ -211,9 +206,9 @@ public void ConvertOutputItemsToMessages_FunctionToolCall_ReturnsAssistantMessag
}
[Fact]
- public void ConvertOutputItemsToMessages_FunctionToolCallOutputResource_ReturnsToolMessage()
+ public void ConvertOutputItemsToMessages_FunctionToolCallOutput_ReturnsToolMessage()
{
- var funcOutput = new FunctionToolCallOutputResource(
+ var funcOutput = new OutputItemFunctionToolCallOutput(
callId: "call_def",
output: BinaryData.FromString("result data"));
@@ -229,8 +224,7 @@ public void ConvertOutputItemsToMessages_FunctionToolCallOutputResource_ReturnsT
[Fact]
public void ConvertOutputItemsToMessages_ReasoningItem_ReturnsNull()
{
- var reasoning = AzureAIAgentServerResponsesModelFactory.OutputItemReasoningItem(
- id: "reason_001");
+ var reasoning = new OutputItemReasoningItem("reason_001", []);
var messages = InputConverter.ConvertOutputItemsToMessages([reasoning]);
@@ -661,7 +655,7 @@ public void ConvertOutputItemsToMessages_UnknownOutputItemType_IsSkipped()
[Fact]
public void ConvertToChatOptions_ModelId_NotSetFromRequest()
{
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "my-model");
+ var request = new CreateResponse { Model = "my-model" };
var options = InputConverter.ConvertToChatOptions(request);
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/OutputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/OutputConverterTests.cs
index 876339ea2c..2b8abfefcb 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/OutputConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/OutputConverterTests.cs
@@ -20,7 +20,7 @@ public class OutputConverterTests
private static (ResponseEventStream stream, Mock mockContext) CreateTestStream()
{
var mockContext = new Mock("resp_" + new string('0', 46)) { CallBase = true };
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test-model");
+ var request = new CreateResponse { Model = "test-model" };
var stream = new ResponseEventStream(mockContext.Object, request);
return (stream, mockContext);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/WorkflowIntegrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/WorkflowIntegrationTests.cs
index 05be11c3d8..d87406f815 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/WorkflowIntegrationTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/WorkflowIntegrationTests.cs
@@ -160,9 +160,7 @@ public async Task WorkflowAgent_RegisteredWithKey_ResolvesCorrectlyAsync()
var sp = services.BuildServiceProvider();
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(
- model: "test",
- agentReference: new AgentReference("my-workflow"));
+ var request = new CreateResponse { Model = "test", AgentReference = new AgentReference("my-workflow") };
request.Input = CreateUserInput("Test keyed workflow");
var mockContext = CreateMockContext();
@@ -363,7 +361,7 @@ private static (AgentFrameworkResponseHandler handler, CreateResponse request, R
var sp = services.BuildServiceProvider();
var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test");
+ var request = new CreateResponse { Model = "test" };
request.Input = CreateUserInput(userMessage);
var mockContext = CreateMockContext();
@@ -393,7 +391,7 @@ private static Mock CreateMockContext()
private static (ResponseEventStream stream, Mock mockContext) CreateTestStream()
{
var mockContext = new Mock("resp_" + new string('0', 46)) { CallBase = true };
- var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "test-model");
+ var request = new CreateResponse { Model = "test-model" };
var stream = new ResponseEventStream(mockContext.Object, request);
return (stream, mockContext);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj
index 2cead6029a..2265719711 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj
@@ -10,7 +10,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
@@ -50,6 +50,15 @@
Always
+
+ Always
+
+
+ Always
+
+
+ Always
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxRecordResponse.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxRecordResponse.json
new file mode 100644
index 0000000000..5208a57262
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxRecordResponse.json
@@ -0,0 +1,5 @@
+{
+ "id": "tbx-123",
+ "name": "research_tools",
+ "default_version": "v5"
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json
new file mode 100644
index 0000000000..13f686ef8d
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json
@@ -0,0 +1,11 @@
+{
+ "metadata": {},
+ "id": "tbv-research_tools-v5",
+ "name": "research_tools",
+ "version": "v5",
+ "description": "Example research toolbox",
+ "created_at": 1775779200,
+ "tools": [
+ { "type": "code_interpreter" }
+ ]
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
new file mode 100644
index 0000000000..b78698bfb0
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
@@ -0,0 +1,11 @@
+{
+ "metadata": {},
+ "id": "tbv-dirty-v1",
+ "name": "dirty_toolbox",
+ "version": "v1",
+ "description": "Toolbox with decoration fields on tools",
+ "created_at": 1775779200,
+ "tools": [
+ { "type": "code_interpreter", "name": "code_interpreter", "description": "Execute Python code" }
+ ]
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs
index 3460362efd..898e0293c7 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs
@@ -14,6 +14,9 @@ internal static class TestDataUtil
private static readonly string s_agentResponseJson = File.ReadAllText("TestData/AgentResponse.json");
private static readonly string s_agentVersionResponseJson = File.ReadAllText("TestData/AgentVersionResponse.json");
private static readonly string s_openAIDefaultResponseJson = File.ReadAllText("TestData/OpenAIDefaultResponse.json");
+ private static readonly string s_toolboxRecordResponseJson = File.ReadAllText("TestData/ToolboxRecordResponse.json");
+ private static readonly string s_toolboxVersionResponseJson = File.ReadAllText("TestData/ToolboxVersionResponse.json");
+ private static readonly string s_toolboxVersionWithDecorationFieldsJson = File.ReadAllText("TestData/ToolboxVersionWithDecorationFields.json");
private const string AgentDefinitionPlaceholder = "\"agent-definition-placeholder\"";
@@ -162,4 +165,19 @@ private static string ApplyDescription(string json, string? description)
}
return json;
}
+
+ ///
+ /// Gets the toolbox record response JSON.
+ ///
+ public static string GetToolboxRecordResponseJson() => s_toolboxRecordResponseJson;
+
+ ///
+ /// Gets the toolbox version response JSON.
+ ///
+ public static string GetToolboxVersionResponseJson() => s_toolboxVersionResponseJson;
+
+ ///
+ /// Gets the toolbox version response JSON with decoration fields on tools.
+ ///
+ public static string GetToolboxVersionWithDecorationFieldsJson() => s_toolboxVersionWithDecorationFieldsJson;
}