Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Microsoft.Agents.AI.Hyperlight;
/// </remarks>
public sealed class HyperlightCodeActProviderOptions
{

/// <summary>
/// Initializes a new instance configured for the JavaScript backend.
/// Equivalent to <see cref="CreateForJavaScript()"/>.
Expand Down Expand Up @@ -69,6 +70,20 @@ public static HyperlightCodeActProviderOptions CreateForJavaScript()
/// </summary>
public string? StackSize { get; set; }

/// <summary>
/// Gets or sets the maximum execution time in seconds. Defaults to 30.
/// The value must be between 1 and 4,294,967 seconds.
/// </summary>
/// <remarks>
/// The limit interrupts guest execution but cannot interrupt runtime initialization
/// or a host tool callback. Host tools must enforce their own cancellation policy.
/// </remarks>
public int ExecutionTimeoutSeconds
{
get;
set => field = Throw.IfOutOfRange(value, 1, 4_294_967);
} = 30;

/// <summary>
/// Gets or sets the initial set of provider-owned CodeAct tools made available
/// inside the sandbox via <c>call_tool(...)</c>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
this.ModulePath = options.ModulePath;
this.HeapSize = options.HeapSize;
this.StackSize = options.StackSize;
this.ExecutionTimeout = TimeSpan.FromSeconds(options.ExecutionTimeoutSeconds);
this.HostInputDirectory = options.HostInputDirectory;
this.IncludeDetailedErrors = options.IncludeDetailedErrors;
this.ToolRegistryVersion = toolRegistryVersion;
Expand Down Expand Up @@ -81,6 +82,8 @@

public string? StackSize { get; }

public TimeSpan ExecutionTimeout { get; }

public string? HostInputDirectory { get; }

public bool IncludeDetailedErrors { get; }
Expand Down Expand Up @@ -224,14 +227,23 @@
await this._executionLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
this.EnsureInitialized(snapshot);
using var timeLimit = new CancellationTokenSource(snapshot.ExecutionTimeout);
using var executionCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeLimit.Token);
try
{
this.EnsureInitialized(snapshot, executionCancellation.Token);

if (this._warmSnapshot is not null)
{
this._sandbox!.Restore(this._warmSnapshot);
}

if (this._warmSnapshot is not null)
return BuildResult(this._sandbox!.Run(code, executionCancellation.Token));

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-test (net10.0, ubuntu-latest, Release, false)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-test (net10.0, ubuntu-latest, Release, false)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-integration-test (net10.0, ubuntu-latest, Release, true)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-integration-test (net10.0, ubuntu-latest, Release, true)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments

Check failure on line 241 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments
}
catch (OperationCanceledException) when (timeLimit.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
this._sandbox!.Restore(this._warmSnapshot);
return BuildErrorResult("Code execution exceeded its time limit.");
}

return BuildResult(this._sandbox!.Run(code));
}
#pragma warning disable CA1031 // Return ordinary lifecycle failures as policy-controlled execution errors.
catch (Exception ex) when (ex is not OperationCanceledException)
Expand All @@ -255,7 +267,7 @@
}
}

private void EnsureInitialized(RunSnapshot snapshot)
private void EnsureInitialized(RunSnapshot snapshot, CancellationToken cancellationToken)
{
if (this._sandbox is not null && string.Equals(this._lastConfigFingerprint, snapshot.ConfigFingerprint, StringComparison.Ordinal))
{
Expand All @@ -265,7 +277,7 @@
// Configuration changed (or first run) — dispose the previous sandbox
// so the new one picks up the current capabilities and runtime options.
this.ClearSandboxState();
this.BuildAndWarmUp(snapshot);
this.BuildAndWarmUp(snapshot, cancellationToken);
}

private void ClearSandboxState()
Expand All @@ -285,7 +297,7 @@
}
}

private void BuildAndWarmUp(RunSnapshot snapshot)
private void BuildAndWarmUp(RunSnapshot snapshot, CancellationToken cancellationToken)
{
var builder = new SandboxBuilder()
.WithBackend(snapshot.Backend);
Expand Down Expand Up @@ -336,7 +348,7 @@
// Backend-specific no-op used to trigger lazy guest runtime initialization
// before the warm snapshot is captured. Matches the values used by the
// upstream HyperlightSandbox.Extensions.AI CodeExecutionTool reference.
_ = sandbox.Run(snapshot.Backend == SandboxBackend.JavaScript ? "void 0;" : "None");
_ = sandbox.Run(snapshot.Backend == SandboxBackend.JavaScript ? "void 0;" : "None", cancellationToken);

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-test (net10.0, ubuntu-latest, Release, false)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-test (net10.0, ubuntu-latest, Release, false)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-integration-test (net10.0, ubuntu-latest, Release, true)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-integration-test (net10.0, ubuntu-latest, Release, true)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net10.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net472, windows-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net8.0, ubuntu-latest, Release)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments

Check failure on line 351 in dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs

View workflow job for this annotation

GitHub Actions / dotnet-build (net9.0, windows-latest, Debug)

No overload for method 'Run' takes 2 arguments
this._warmSnapshot = sandbox.Snapshot();
this._lastConfigFingerprint = snapshot.ConfigFingerprint;
}
Expand Down
29 changes: 29 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.Hyperlight/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,35 @@ The same boundary covers initialization, warm-up, snapshot restore, and executio
failures. Ordinary failures dispose and clear failed sandbox state before returning
an error; cancellation still propagates.

## Execution limits

Guest execution has a finite 30-second limit by default. Configure
`HyperlightCodeActProviderOptions.ExecutionTimeoutSeconds` to change it; values
must be positive and within the supported timer range. Run snapshots capture the
limit independently of later option changes.

Caller cancellation interrupts running guest code and propagates as
`OperationCanceledException`. Reaching the configured limit returns an unsuccessful
execution result. The next invocation restores the warm snapshot before reuse.
Native execution finishes before another invocation can enter the sandbox.

Runtime initialization and host tool callbacks cannot be interrupted by the guest
limit. Host tools must enforce their own cancellation and execution limits.
This behavior requires an upstream SDK version with the cancellation-aware `Run` overload.

### KVM guest regression tests

Ordinary unit tests retain reflection-disabled JSON settings. To run the guest
regressions on a KVM-capable host with the corresponding native SDK installed:

```powershell
$env:HYPERLIGHT_RUN_GUEST_TESTS = "true"
dotnet test --project dotnet\tests\Microsoft.Agents.AI.Hyperlight.UnitTests -f net10.0 -p:HyperlightGuestTests=true
```

The explicit guest configuration enables reflection-based serialization required
by the upstream SDK; without opt-in, guest-only tests are skipped.

## Network access and guest packages

`HyperlightCodeActProviderOptions.AllowedDomains` controls outbound requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0')) and '$(HyperlightGuestTests)' != 'true'">
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

<PropertyGroup Condition="'$(HyperlightGuestTests)' == 'true'">
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Hyperlight\Microsoft.Agents.AI.Hyperlight.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,135 @@ await Assert.ThrowsAnyAsync<OperationCanceledException>(
() => executor.ExecuteAsync(snapshot, "void 0;", cancellation.Token));
}

[Fact]
public async Task ExecuteAsync_CancellationDuringGuestExecutionReleasesLockAsync()
{
// Arrange
Assert.SkipUnless(
string.Equals(Environment.GetEnvironmentVariable("HYPERLIGHT_RUN_GUEST_TESTS"), "true", StringComparison.OrdinalIgnoreCase),
"Requires explicit Hyperlight KVM guest-test configuration.");
using var cancellation = new CancellationTokenSource();
var cancelRun = AIFunctionFactory.Create(() =>
{
cancellation.Cancel();
return "cancelled";
}, name: "cancel_run");
var snapshot = new SandboxExecutor.RunSnapshot(
[cancelRun], [], [], new HyperlightCodeActProviderOptions { IncludeDetailedErrors = true });
using var executor = new SandboxExecutor();
var warmup = await executor.ExecuteAsync(snapshot, "console.log('ready');", CancellationToken.None);
Comment thread
baywet marked this conversation as resolved.
Resolved
Comment thread
baywet marked this conversation as resolved.
Resolved
using var warmupJson = JsonDocument.Parse(warmup);
Assert.True(
warmupJson.RootElement.GetProperty("success").GetBoolean(),
warmupJson.RootElement.GetProperty("stderr").GetString());

// Act / Assert
await Assert.ThrowsAnyAsync<OperationCanceledException>(
Comment thread
baywet marked this conversation as resolved.
Resolved
() => executor.ExecuteAsync(snapshot, "call_tool('cancel_run', {});", cancellation.Token));
Comment on lines +90 to +91

using var recoveryCancellation = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var recovered = await executor.ExecuteAsync(snapshot, "console.log('recovered');", recoveryCancellation.Token)
.WaitAsync(TimeSpan.FromSeconds(10));
using var recoveredJson = JsonDocument.Parse(recovered);
Assert.True(
recoveredJson.RootElement.GetProperty("success").GetBoolean(),
recoveredJson.RootElement.GetProperty("stderr").GetString());
Assert.Contains("recovered", recoveredJson.RootElement.GetProperty("stdout").GetString(), StringComparison.Ordinal);
}

[Fact]
public async Task ExecuteAsync_DefaultTimeoutInterruptsGuestAndAllowsReuseAsync()
{
// Arrange
Assert.SkipUnless(
string.Equals(Environment.GetEnvironmentVariable("HYPERLIGHT_RUN_GUEST_TESTS"), "true", StringComparison.OrdinalIgnoreCase),
"Requires explicit Hyperlight KVM guest-test configuration.");
var snapshot = new SandboxExecutor.RunSnapshot(
[], [], [], new HyperlightCodeActProviderOptions { IncludeDetailedErrors = true });
using var executor = new SandboxExecutor();
var warmup = await executor.ExecuteAsync(snapshot, "console.log('ready');", CancellationToken.None);
using var warmupJson = JsonDocument.Parse(warmup);
Assert.True(
warmupJson.RootElement.GetProperty("success").GetBoolean(),
warmupJson.RootElement.GetProperty("stderr").GetString());
var elapsed = System.Diagnostics.Stopwatch.StartNew();

// Act
var result = await executor.ExecuteAsync(snapshot, """
const end = Date.now() + 32000;
while (Date.now() < end) {}
console.log('finished');
""", CancellationToken.None);

// Assert
using var json = JsonDocument.Parse(result);
Assert.False(json.RootElement.GetProperty("success").GetBoolean());
Assert.Equal("Code execution exceeded its time limit.", json.RootElement.GetProperty("stderr").GetString());
Assert.True(elapsed.Elapsed < TimeSpan.FromSeconds(31), "The default execution timeout was not enforced.");
using var recoveryCancellation = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var recovered = await executor.ExecuteAsync(snapshot, "console.log('recovered');", recoveryCancellation.Token)
.WaitAsync(TimeSpan.FromSeconds(10));
using var recoveredJson = JsonDocument.Parse(recovered);
Assert.True(
recoveredJson.RootElement.GetProperty("success").GetBoolean(),
recoveredJson.RootElement.GetProperty("stderr").GetString());
Assert.Contains("recovered", recoveredJson.RootElement.GetProperty("stdout").GetString(), StringComparison.Ordinal);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task ExecuteAsync_ConfiguredTimeoutUsesSnapshotAndAllowsReuseAsync(bool useWasm)
{
// Arrange
Assert.SkipUnless(
string.Equals(Environment.GetEnvironmentVariable("HYPERLIGHT_RUN_GUEST_TESTS"), "true", StringComparison.OrdinalIgnoreCase),
"Requires explicit Hyperlight KVM guest-test configuration.");
var options = useWasm
? HyperlightCodeActProviderOptions.CreateForWasm(
Assert.IsType<string>(Environment.GetEnvironmentVariable("HYPERLIGHT_PYTHON_GUEST_PATH")))
: new HyperlightCodeActProviderOptions();
using var executor = new SandboxExecutor();
var warmSnapshot = new SandboxExecutor.RunSnapshot([], [], [], options);
var warmup = await executor.ExecuteAsync(warmSnapshot, useWasm ? "print('ready')" : "console.log('ready');", CancellationToken.None);
using var warmupJson = JsonDocument.Parse(warmup);
Assert.True(
warmupJson.RootElement.GetProperty("success").GetBoolean(),
warmupJson.RootElement.GetProperty("stderr").GetString());
options.ExecutionTimeoutSeconds = 1;
var limitedSnapshot = new SandboxExecutor.RunSnapshot([], [], [], options);
options.ExecutionTimeoutSeconds = 30;
var elapsed = System.Diagnostics.Stopwatch.StartNew();

// Act
var result = await executor.ExecuteAsync(limitedSnapshot, useWasm ? """
import time
end = time.monotonic() + 5
while time.monotonic() < end:
pass
print('finished')
""" : """
const end = Date.now() + 5000;
while (Date.now() < end) {}
console.log('finished');
""", CancellationToken.None);

// Assert
using var json = JsonDocument.Parse(result);
Assert.False(json.RootElement.GetProperty("success").GetBoolean());
Assert.Equal("Code execution exceeded its time limit.", json.RootElement.GetProperty("stderr").GetString());
Assert.True(elapsed.Elapsed < TimeSpan.FromSeconds(2), "The captured execution timeout was not enforced.");
using var recoveryCancellation = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var recovered = await executor.ExecuteAsync(
warmSnapshot, useWasm ? "print('recovered')" : "console.log('recovered');", recoveryCancellation.Token)
.WaitAsync(TimeSpan.FromSeconds(10));
using var recoveredJson = JsonDocument.Parse(recovered);
Assert.True(
recoveredJson.RootElement.GetProperty("success").GetBoolean(),
recoveredJson.RootElement.GetProperty("stderr").GetString());
Assert.Contains("recovered", recoveredJson.RootElement.GetProperty("stdout").GetString(), StringComparison.Ordinal);
}

[Fact]
public void Fingerprint_DifferentToolSets_DifferentFingerprints()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ stack traces and inner exceptions. This is separate from the
outer `FunctionInvokingChatClient` error policy and sensitive-data logging; outer redaction cannot repair
diagnostics already caught and returned by generated code.

Hyperlight guest execution defaults to a finite 30-second limit, configurable through
`HyperlightCodeActProviderOptions.ExecutionTimeoutSeconds`. Caller cancellation interrupts
guest code, but neither cancellation nor the limit interrupts runtime initialization
or host tool callbacks. Host tools must enforce their own limits. This requires the
upstream SDK's cancellation-aware `Run` API; verify that it is available in the selected
package version before using this option.

For AG-UI, remember that thread and run IDs are correlation identifiers, not authentication boundaries. The
application owns authorization, durable snapshots, scaling, and rendering policy.

Expand Down
Loading