mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
e7dc3b91f1
* Add Microsoft.Agents.AI.Hyperlight package for CodeAct integration Introduces a new Microsoft.Agents.AI.Hyperlight package that enables CodeAct-style sandboxed code execution via Hyperlight (hyperlight-sandbox .NET SDK, PR #46) for .NET agents, following the docs/features/code_act/dotnet-implementation.md design and the Python agent_framework_hyperlight reference. Highlights: - HyperlightCodeActProvider (AIContextProvider): injects an execute_code tool and CodeAct guidance per invocation; single-instance-per-agent via a fixed StateKeys value; supports multiple provider-owned tools (exposed inside the sandbox via call_tool), file mounts, and an outbound domain allow-list; snapshot/restore per run. - HyperlightExecuteCodeFunction: standalone AIFunction for manual/static wiring when the sandbox configuration is fixed. - Approval model via CodeActApprovalMode (AlwaysRequire / NeverRequire) with propagation from ApprovalRequiredAIFunction-wrapped tools. - Unit tests (instruction builder, tool bridge, approval computation, provider CRUD, ProvideAIContextAsync snapshot isolation and approval wrapping). - Env-gated integration test (HYPERLIGHT_PYTHON_GUEST_PATH). - Three samples under samples/02-agents/AgentWithCodeAct (interpreter, tool-enabled, manual wiring). Build is not yet runnable: requires .NET SDK 10.0.200 and the not-yet-published HyperlightSandbox.Api 0.1.0-preview NuGet package. Package is marked IsPackable=false until the dependency is available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR #5329 review feedback for Hyperlight CodeAct provider - A. Build-breakers: drop unused usings, override test TargetFrameworks off net472, drop redundant Microsoft.Extensions.AI.Abstractions PackageRef. - B. API: keep CRUD but rebuild sandbox when config fingerprint changes; add HyperlightCodeActProviderOptions.CreateForWasm/CreateForJavaScript factory methods (Backend/ModulePath now read-only); rename WorkspaceRoot to HostInputDirectory; convert AllowedDomain & FileMount from record to sealed class; drop ToolBridge.Unwrap (ApprovalRequiredAIFunction is invocable as-is). - C. ToolBridge: collapse SerializeResult switch; add comment explaining AOT-driven choice to keep JsonNode.Parse over typed Deserialize. - D. InstructionBuilder: drop language-specific 'Python code' phrasing; strip host filesystem paths from execute_code description. - E. Style polish: ternary expression-body for ComputeApprovalRequired, .Where(x is not null), .ToList() over .ToArray() in IReadOnlyList returns. - F. Samples: add guest-module / KVM-WHP build instructions to Step01; note future Excel-upload sample in Step02. Also adds SandboxExecutorTests covering the new RunSnapshot.ComputeFingerprint used for sandbox-rebuild detection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Align Hyperlight package id and JS warm-up with merged upstream SDK The .NET SDK in hyperlight-dev/hyperlight-sandbox PR #46 has merged. The published package id is Hyperlight.HyperlightSandbox.Api (the bare HyperlightSandbox.Api remains the assembly/namespace) and the reference CodeExecutionTool uses 'void 0;' as the JavaScript warm-up no-op. Update the package reference, project comment, README, and SandboxExecutor warm-up accordingly. No functional change beyond that — all other public APIs we depend on (SandboxBuilder.With*, Sandbox.Run/RegisterToolAsync/AllowDomain/Snapshot/ Restore, ExecutionResult, SandboxBackend) match the merged shape. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump Hyperlight package to 0.4.0 and fix build/test issues Hyperlight.HyperlightSandbox.Api 0.4.0 is now published on nuget.org. Bump the version reference and address the analyzer/runtime issues that surfaced once restore could complete: - Add HyperlightJsonContext source-generated JsonSerializerContext for the execute_code result + tool error envelopes; route arbitrary AIFunction results through AIJsonUtilities.DefaultOptions to keep IsAotCompatible=true. - Replace explicit ObjectDisposedException throws with ObjectDisposedException.ThrowIf (CA1513). - Use HyperlightSandbox.Api.SandboxBackend in cref docs to disambiguate. - Update tests to match AIContext.Tools being IEnumerable<AITool>, drop ConfigureAwait(false) in xUnit test methods (xUnit1030), use collection expressions for AllowedDomain methods. - Add 'using OpenAI.Chat;' to all three samples so AsAIAgent resolves. - Verified: dotnet build of all four hyperlight projects + samples succeeds on net8/9/10; dotnet test for the unit tests passes 32/32 on net10.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CI check failures: file encoding (UTF-8 BOM + LF) and broken markdown link - Convert all new .cs/.csproj files to UTF-8 with BOM and LF line endings to satisfy the dotnet/.editorconfig charset/end_of_line settings enforced by check-format. - Drop unused System.Collections.Generic using in HyperlightCodeActProviderTests. - Add missing using Microsoft.Extensions.AI in CodeActApprovalMode.cs and shorten ApprovalRequiredAIFunction cref (IDE0001). - Fix broken README link to docs/decisions/0024-codeact-integration.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: AIFunction inheritance, packaging, GetService approval check - HyperlightExecuteCodeFunction now inherits AIFunction directly. The AsAIFunction() indirection is gone; instances are accepted anywhere an AIFunction is. Approval requirement is surfaced via GetService<ApprovalRequiredAIFunction>() which lazily exposes a wrapping ApprovalRequiredAIFunction proxy when the effective ApprovalMode/tool stack requires it. - ComputeApprovalRequired now uses GetService<ApprovalRequiredAIFunction>() so approval-required tools nested anywhere in the AITool decorator stack are detected (not just the top-most class). - csproj: drop IsPackable=false (ready to release with the published Hyperlight.HyperlightSandbox.Api 0.4.0 dependency); add PackageReadmeFile and pack README.md at the package root, matching the pattern used by Aspire.Hosting.AgentFramework.DevUI / Microsoft.Agents.AI.DurableTask. - Update Step03 sample and README wording to reflect direct AIFunction usage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
174 lines
4.9 KiB
C#
174 lines
4.9 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System.Linq;
|
|
using Microsoft.Extensions.AI;
|
|
|
|
namespace Microsoft.Agents.AI.Hyperlight.UnitTests;
|
|
|
|
public sealed class HyperlightCodeActProviderTests
|
|
{
|
|
[Fact]
|
|
public void Ctor_NullOptions_UsesDefaults()
|
|
{
|
|
// Act
|
|
using var provider = new HyperlightCodeActProvider();
|
|
|
|
// Assert
|
|
Assert.Empty(provider.GetTools());
|
|
Assert.Empty(provider.GetFileMounts());
|
|
Assert.Empty(provider.GetAllowedDomains());
|
|
Assert.Equal([HyperlightCodeActProvider.FixedStateKey], provider.StateKeys);
|
|
}
|
|
|
|
[Fact]
|
|
public void StateKeys_IsFixedSingleKey()
|
|
{
|
|
// Arrange
|
|
using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());
|
|
|
|
// Act / Assert
|
|
Assert.Equal([HyperlightCodeActProvider.FixedStateKey], provider.StateKeys);
|
|
}
|
|
|
|
[Fact]
|
|
public void Tools_Crud_AddReplacesByName()
|
|
{
|
|
// Arrange
|
|
using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());
|
|
var first = AIFunctionFactory.Create(() => "a", name: "t");
|
|
var replacement = AIFunctionFactory.Create(() => "b", name: "t");
|
|
|
|
// Act
|
|
provider.AddTools(first);
|
|
provider.AddTools(replacement);
|
|
|
|
// Assert
|
|
var tools = provider.GetTools();
|
|
Assert.Single(tools);
|
|
Assert.Same(replacement, tools[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Tools_RemoveAndClear_Work()
|
|
{
|
|
// Arrange
|
|
using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());
|
|
provider.AddTools(
|
|
AIFunctionFactory.Create(() => "a", name: "a"),
|
|
AIFunctionFactory.Create(() => "b", name: "b"));
|
|
|
|
// Act
|
|
provider.RemoveTools("a");
|
|
|
|
// Assert
|
|
Assert.Single(provider.GetTools());
|
|
Assert.Equal("b", provider.GetTools()[0].Name);
|
|
|
|
// Act
|
|
provider.ClearTools();
|
|
|
|
// Assert
|
|
Assert.Empty(provider.GetTools());
|
|
}
|
|
|
|
[Fact]
|
|
public void FileMounts_Crud_ReplaceByMountPath()
|
|
{
|
|
// Arrange
|
|
using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());
|
|
var m1 = new FileMount("/host/a", "/input/a");
|
|
var m2 = new FileMount("/host/a-new", "/input/a");
|
|
var m3 = new FileMount("/host/b", "/input/b");
|
|
|
|
// Act
|
|
provider.AddFileMounts(m1, m3);
|
|
provider.AddFileMounts(m2);
|
|
|
|
// Assert
|
|
var mounts = provider.GetFileMounts().OrderBy(m => m.MountPath).ToArray();
|
|
Assert.Equal(2, mounts.Length);
|
|
Assert.Same(m2, mounts[0]);
|
|
Assert.Same(m3, mounts[1]);
|
|
|
|
// Act
|
|
provider.RemoveFileMounts("/input/a");
|
|
|
|
// Assert
|
|
Assert.Single(provider.GetFileMounts());
|
|
|
|
// Act
|
|
provider.ClearFileMounts();
|
|
|
|
// Assert
|
|
Assert.Empty(provider.GetFileMounts());
|
|
}
|
|
|
|
[Fact]
|
|
public void AllowedDomains_Crud_ReplaceByTarget()
|
|
{
|
|
// Arrange
|
|
using var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());
|
|
var d1 = new AllowedDomain("https://a", ["GET"]);
|
|
var d2 = new AllowedDomain("https://a", ["POST"]);
|
|
var d3 = new AllowedDomain("https://b");
|
|
|
|
// Act
|
|
provider.AddAllowedDomains(d1, d3);
|
|
provider.AddAllowedDomains(d2);
|
|
|
|
// Assert
|
|
var domains = provider.GetAllowedDomains().OrderBy(d => d.Target).ToArray();
|
|
Assert.Equal(2, domains.Length);
|
|
Assert.Same(d2, domains[0]);
|
|
Assert.Same(d3, domains[1]);
|
|
|
|
// Act
|
|
provider.RemoveAllowedDomains("https://a");
|
|
|
|
// Assert
|
|
Assert.Single(provider.GetAllowedDomains());
|
|
|
|
// Act
|
|
provider.ClearAllowedDomains();
|
|
|
|
// Assert
|
|
Assert.Empty(provider.GetAllowedDomains());
|
|
}
|
|
|
|
[Fact]
|
|
public void Ctor_SeedsFromOptions()
|
|
{
|
|
// Arrange
|
|
var tool = AIFunctionFactory.Create(() => "x", name: "x");
|
|
var options = new HyperlightCodeActProviderOptions
|
|
{
|
|
Tools = new[] { tool },
|
|
FileMounts = new[] { new FileMount("/h", "/m") },
|
|
AllowedDomains = new[] { new AllowedDomain("https://a") },
|
|
};
|
|
|
|
// Act
|
|
using var provider = new HyperlightCodeActProvider(options);
|
|
|
|
// Assert
|
|
Assert.Single(provider.GetTools());
|
|
Assert.Single(provider.GetFileMounts());
|
|
Assert.Single(provider.GetAllowedDomains());
|
|
}
|
|
|
|
[Fact]
|
|
public void Dispose_IsIdempotentAndBlocksFurtherAddTools()
|
|
{
|
|
// Arrange
|
|
var provider = new HyperlightCodeActProvider(new HyperlightCodeActProviderOptions());
|
|
var tool = AIFunctionFactory.Create(() => "x", name: "x");
|
|
|
|
// Act
|
|
provider.Dispose();
|
|
provider.Dispose();
|
|
|
|
// Assert
|
|
Assert.Throws<System.ObjectDisposedException>(() => provider.AddTools(tool));
|
|
}
|
|
}
|