mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
* Add shell support to the HarnessAgent * Address PR comments * Address PR comments
86 lines
4.0 KiB
C#
86 lines
4.0 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.AI;
|
|
|
|
namespace Microsoft.Agents.AI.Tools.Shell;
|
|
|
|
/// <summary>
|
|
/// Pluggable backend that runs shell commands on behalf of a tool.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <see cref="LocalShellExecutor"/> runs commands directly on the host (no
|
|
/// isolation; approval-in-the-loop is the security boundary).
|
|
/// <see cref="DockerShellExecutor"/> runs them inside a container with resource
|
|
/// limits, network isolation, and a non-root user.
|
|
/// </para>
|
|
/// <para>
|
|
/// This is an abstract class rather than an interface so the surface can be
|
|
/// extended in future versions (e.g., adding new lifecycle hooks) without
|
|
/// breaking existing third-party implementations. Mirrors the Python
|
|
/// <c>ShellExecutor</c> Protocol in
|
|
/// <c>agent_framework_tools.shell._executor_base</c>.
|
|
/// </para>
|
|
/// <para>
|
|
/// Lifetime: <see cref="InitializeAsync"/> is invoked at most once per
|
|
/// instance (idempotent); <see cref="DisposeAsync"/> tears the executor down
|
|
/// at the end of its life. There is no public Shutdown step — disposal is the
|
|
/// teardown.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Concurrency and session ownership.</b> A single executor instance is
|
|
/// intended to serve a single conversation / agent session — i.e., a single
|
|
/// user. Stateless mode is safe to share across concurrent callers (each
|
|
/// <c>RunAsync</c> spawns a fresh process or container, so there is no
|
|
/// shared mutable state). Persistent mode is <em>not</em> shareable: a
|
|
/// single long-lived shell process backs every call, it carries mutable
|
|
/// state (working directory, exported variables, history, in-flight
|
|
/// background jobs) that is visible to every subsequent command, and
|
|
/// concurrent commands would interleave on its stdin/stdout. The framework
|
|
/// does not isolate one caller's state from another's. Build one executor
|
|
/// per session, treat it as owned by that session for its lifetime, and
|
|
/// dispose it when the session ends. If you register an executor with a DI
|
|
/// container, use a per-request / per-conversation scope, not a singleton.
|
|
/// </para>
|
|
/// </remarks>
|
|
public abstract class ShellExecutor : IAsyncDisposable
|
|
{
|
|
/// <summary>
|
|
/// Eagerly initialize the backend. Idempotent; subsequent calls are
|
|
/// no-ops once the executor is started. For stateless executors this is
|
|
/// typically a no-op (the default implementation returns
|
|
/// <see cref="Task.CompletedTask"/>).
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
public virtual Task InitializeAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
|
|
|
/// <summary>
|
|
/// Run a single command and return its result. Implementations are
|
|
/// expected to apply the configured per-command timeout and surface it
|
|
/// via <see cref="ShellResult.TimedOut"/> + <c>ExitCode = 124</c>.
|
|
/// </summary>
|
|
/// <param name="command">The shell command to execute.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
public abstract Task<ShellResult> RunAsync(string command, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Build an <see cref="AIFunction"/> bound to this executor, suitable for
|
|
/// registering with an agent as a callable tool.
|
|
/// </summary>
|
|
/// <param name="name">Function name visible to the model.</param>
|
|
/// <param name="description">Function description for the model.</param>
|
|
/// <param name="requireApproval">
|
|
/// When <see langword="true"/> (the default), wraps the function in
|
|
/// <see cref="ApprovalRequiredAIFunction"/> so every invocation requires
|
|
/// explicit user approval before executing.
|
|
/// </param>
|
|
/// <returns>An <see cref="AIFunction"/> wrapping <see cref="RunAsync"/>.</returns>
|
|
public abstract AIFunction AsAIFunction(string name = "run_shell", string? description = null, bool requireApproval = true);
|
|
|
|
/// <inheritdoc />
|
|
public abstract ValueTask DisposeAsync();
|
|
}
|