// Copyright (c) Microsoft. All rights reserved. using System; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Hyperlight.Internal; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Hyperlight; /// /// Standalone execute_code backed by a /// Hyperlight sandbox. Use this for manual/static wiring when an /// lifecycle is not needed — for example /// when the tool registry and capability configuration are fixed for the /// lifetime of the agent. /// /// /// Unlike , this type does not hook /// into the pipeline. It captures a single /// snapshot of the provided /// at construction time and reuses it for the lifetime of the instance. /// The instance can be passed directly anywhere an /// is accepted; when the configuration requires approval (per /// or because a /// configured tool is itself an ), /// the instance surfaces an via /// , which is how the rest of /// the framework discovers approval requirements. /// public sealed class HyperlightExecuteCodeFunction : AIFunction, IDisposable { private const string ExecuteCodeName = "execute_code"; private static readonly JsonElement s_schema = JsonDocument.Parse( """ { "type": "object", "properties": { "code": { "type": "string", "description": "Code to execute using the provider's configured backend/runtime behavior." } }, "required": ["code"] } """).RootElement; private readonly SandboxExecutor _executor; private readonly SandboxExecutor.RunSnapshot _snapshot; private readonly string _description; private readonly bool _approvalRequired; private ApprovalRequiredAIFunction? _approvalProxy; private bool _disposed; /// /// Initializes a new instance of the class. /// /// /// Optional configuration options. When the defaults of /// are used. /// public HyperlightExecuteCodeFunction(HyperlightCodeActProviderOptions? options = null) { var effective = options ?? new HyperlightCodeActProviderOptions(); this._executor = new SandboxExecutor(effective); var tools = (effective.Tools?.Where(t => t is not null) ?? []).ToList(); var fileMounts = (effective.FileMounts?.Where(m => m is not null) ?? []).ToList(); var allowedDomains = (effective.AllowedDomains?.Where(d => d is not null) ?? []).ToList(); this._snapshot = new SandboxExecutor.RunSnapshot(tools, fileMounts, allowedDomains, effective.HostInputDirectory); this._description = InstructionBuilder.BuildExecuteCodeDescription( this._snapshot.Tools, this._snapshot.FileMounts, this._snapshot.AllowedDomains, hasHostInputDirectory: !string.IsNullOrEmpty(this._snapshot.HostInputDirectory)); this._approvalRequired = HyperlightCodeActProvider.ComputeApprovalRequired(effective.ApprovalMode, this._snapshot.Tools); } /// public override string Name => ExecuteCodeName; /// public override string Description => this._description; /// public override JsonElement JsonSchema => s_schema; /// /// Builds a CodeAct instruction string describing the available tools and capabilities. /// /// /// When , the instructions assume tools are only accessible /// through CodeAct (via call_tool). When , the instructions /// are abbreviated for cases where the same tools are already visible to the model as /// direct agent tools. /// public string BuildInstructions(bool toolsVisibleToModel = false) { this.ThrowIfDisposed(); return InstructionBuilder.BuildContextInstructions(toolsVisibleToModel); } /// public override object? GetService(Type serviceType, object? serviceKey = null) { if (serviceKey is null && this._approvalRequired && serviceType == typeof(ApprovalRequiredAIFunction)) { return this._approvalProxy ??= new ApprovalRequiredAIFunction(this); } return base.GetService(serviceType, serviceKey); } /// protected override async ValueTask InvokeCoreAsync( AIFunctionArguments arguments, CancellationToken cancellationToken) { this.ThrowIfDisposed(); if (arguments is null || !arguments.TryGetValue("code", out var codeObj) || codeObj is null) { throw new ArgumentException("Missing required parameter 'code'.", nameof(arguments)); } var code = codeObj switch { string s => s, JsonElement { ValueKind: JsonValueKind.String } el => el.GetString() ?? string.Empty, _ => codeObj.ToString() ?? string.Empty, }; if (string.IsNullOrWhiteSpace(code)) { throw new ArgumentException("Parameter 'code' must not be empty.", nameof(arguments)); } return await this._executor.ExecuteAsync(this._snapshot, code, cancellationToken).ConfigureAwait(false); } private void ThrowIfDisposed() => ObjectDisposedException.ThrowIf(this._disposed, this); /// Releases the underlying sandbox and associated native resources. public void Dispose() { if (this._disposed) { return; } this._disposed = true; this._executor.Dispose(); } }