diff --git a/dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/InstructionBuilder.cs b/dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/InstructionBuilder.cs
index a4c2a43266d..e198987f8f9 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/InstructionBuilder.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/InstructionBuilder.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Text;
+using System.Text.Json;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Hyperlight.Internal;
@@ -42,7 +43,9 @@ public static string BuildContextInstructions(bool toolsVisibleToModel)
///
/// Host-side filesystem paths are intentionally omitted from the
/// description — only sandbox-visible mount paths are exposed to the
- /// model.
+ /// model. Host-tool parameter shapes come from each tool's existing
+ /// JsonSchema and are documentation for the model only; they do
+ /// not change the execute_code input schema.
///
public static string BuildExecuteCodeDescription(
IReadOnlyList tools,
@@ -72,6 +75,18 @@ public static string BuildExecuteCodeDescription(
}
sb.AppendLine();
+
+ // Surface the host tool's existing parameter schema so the model can see
+ // names, requiredness, descriptions, enums, and nested shapes.
+ // JSON Schema roots are objects or booleans (true/false); reject other kinds.
+ JsonElement schema = tool.JsonSchema;
+ if (schema.ValueKind is JsonValueKind.Object or JsonValueKind.True or JsonValueKind.False)
+ {
+ sb.AppendLine(" Parameters (JSON Schema):");
+ sb.Append(" ");
+ sb.Append(schema.GetRawText());
+ sb.AppendLine();
+ }
}
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/InstructionBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/InstructionBuilderTests.cs
index deab2b75e9b..d02dc1e622d 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/InstructionBuilderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/InstructionBuilderTests.cs
@@ -1,13 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
using Microsoft.Agents.AI.Hyperlight.Internal;
using Microsoft.Extensions.AI;
+using Moq;
namespace Microsoft.Agents.AI.Hyperlight.UnitTests;
public sealed class InstructionBuilderTests
{
+ private static readonly AIAgent s_mockAgent = new Mock().Object;
+
[Fact]
public void BuildContextInstructions_HiddenTools_MentionsCallTool()
{
@@ -69,6 +76,80 @@ public void BuildExecuteCodeDescription_WithTools_IncludesToolNames()
Assert.Contains("fetch docs", text);
}
+ [Fact]
+ public async Task BuildExecuteCodeDescription_WithParameterizedTools_IncludesHostToolJsonSchemaAsync()
+ {
+ // Arrange — zero-parameter tools already have coverage above; this locks the
+ // host-tool JsonSchema gap tracked by microsoft/agent-framework#8446.
+ // Stick to reflection-friendly primitives so AOT/source-gen test hosts can build the schema.
+ static string Lookup(
+ [Description("Search text")] string query,
+ [Description("Maximum results")] int limit = 10) => $"{query}:{limit}";
+
+ var tool = AIFunctionFactory.Create(
+ Lookup,
+ name: "lookup",
+ description: "Look up an item.");
+
+ // Act
+ var text = InstructionBuilder.BuildExecuteCodeDescription(
+ tools: [tool],
+ fileMounts: [],
+ allowedDomains: [],
+ hasHostInputDirectory: false);
+
+ // Assert — model-facing description must carry the host tool parameter schema.
+ Assert.Contains("lookup", text);
+ Assert.Contains("Look up an item.", text);
+ Assert.Contains("Parameters (JSON Schema)", text);
+ Assert.Contains(tool.JsonSchema.GetRawText(), text);
+ Assert.Contains("query", text);
+ Assert.Contains("Search text", text);
+ Assert.Contains("limit", text);
+ Assert.Contains("Maximum results", text);
+
+ // Host schemas are documentation only; they must not replace execute_code's code-only input.
+ using var executeCode = new HyperlightExecuteCodeFunction(new HyperlightCodeActProviderOptions
+ {
+ Tools = [tool],
+ });
+ Assert.Contains("\"code\"", executeCode.JsonSchema.GetRawText());
+ Assert.DoesNotContain("\"query\"", executeCode.JsonSchema.GetRawText());
+ Assert.DoesNotContain("\"limit\"", executeCode.JsonSchema.GetRawText());
+
+ using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions
+ {
+ Tools = [tool],
+ });
+ var context = await provider.InvokingAsync(
+ new AIContextProvider.InvokingContext(s_mockAgent, session: null, new AIContext()));
+ var providerFn = Assert.IsAssignableFrom(context!.Tools!.First());
+ Assert.Contains("\"code\"", providerFn.JsonSchema.GetRawText());
+ Assert.DoesNotContain("\"query\"", providerFn.JsonSchema.GetRawText());
+ Assert.DoesNotContain("\"limit\"", providerFn.JsonSchema.GetRawText());
+ }
+
+ [Theory]
+ [InlineData("true")]
+ [InlineData("false")]
+ public void BuildExecuteCodeDescription_WithBooleanJsonSchema_IncludesBooleanRoot(string schemaJson)
+ {
+ // Arrange — boolean JSON Schema roots are valid and must surface to the model.
+ var tool = new BooleanSchemaTool("gate", "Always-on gate.", schemaJson);
+
+ // Act
+ var text = InstructionBuilder.BuildExecuteCodeDescription(
+ tools: [tool],
+ fileMounts: [],
+ allowedDomains: [],
+ hasHostInputDirectory: false);
+
+ // Assert
+ Assert.Contains("gate", text);
+ Assert.Contains("Parameters (JSON Schema)", text);
+ Assert.Contains(schemaJson, text);
+ }
+
[Fact]
public void BuildExecuteCodeDescription_WithFilesystem_IncludesSandboxPathsOnly()
{
@@ -105,4 +186,27 @@ public void BuildExecuteCodeDescription_WithAllowedDomains_IncludesNetworkSectio
Assert.Contains("GET", text);
Assert.Contains("POST", text);
}
+
+ private sealed class BooleanSchemaTool : AIFunction
+ {
+ private readonly JsonDocument _schemaDocument;
+
+ public BooleanSchemaTool(string name, string description, string schemaJson)
+ {
+ this.Name = name;
+ this.Description = description;
+ this._schemaDocument = JsonDocument.Parse(schemaJson);
+ }
+
+ public override string Name { get; }
+
+ public override string Description { get; }
+
+ public override JsonElement JsonSchema => this._schemaDocument.RootElement;
+
+ protected override ValueTask