forked from admin/VolcanicAgenticCore
83 lines
4.2 KiB
C#
83 lines
4.2 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Time.Testing;
|
|
using Volcanic.AgenticCore.Abstractions.Extensions;
|
|
using Volcanic.AgenticCore.Abstractions.Processes;
|
|
using Volcanic.AgenticCore.Extensions.Mcp;
|
|
using Volcanic.AgenticCore.Features.Agent;
|
|
using Volcanic.AgenticCore.Features.Extensions;
|
|
using Volcanic.AgenticCore.Features.Processes;
|
|
using Volcanic.AgenticCore.Features.Sessions;
|
|
using Volcanic.AgenticCore.Hosting;
|
|
using Volcanic.AgenticCore.Testing;
|
|
|
|
namespace Volcanic.AgenticCore.Extensions.Tests.Mcp.Support;
|
|
|
|
/// <summary>
|
|
/// A core host with the FauxProvider and the MCP extension over an injected host environment: the policy and the
|
|
/// extension's <c>${env:…}</c> lookup both read <see cref="HostEnvironment"/>, and the catalogue cache is private to the host.
|
|
/// </summary>
|
|
internal sealed class McpTestHost : IAsyncDisposable
|
|
{
|
|
private readonly ServiceProvider _services;
|
|
private readonly string _root = Path.Combine(Path.GetTempPath(), "volcanic-mcp-host", Guid.NewGuid().ToString("N"));
|
|
private readonly List<AgentSession> _sessions = [];
|
|
|
|
public McpTestHost(McpOptions options, Action<Dictionary<string, string?>>? environment = null, IEnumerable<string>? secretValues = null, params IExtension[] extra)
|
|
: this(_ => options, environment, secretValues, extra) { }
|
|
|
|
/// <summary><paramref name="options"/> runs once the host's paths exist, so configs can point at <see cref="LogPath"/>.</summary>
|
|
public McpTestHost(Func<McpTestHost, McpOptions> options, Action<Dictionary<string, string?>>? environment = null, IEnumerable<string>? secretValues = null, params IExtension[] extra)
|
|
{
|
|
Directory.CreateDirectory(Workspace);
|
|
Directory.CreateDirectory(AgentDir);
|
|
HostEnvironment = McpTestEnvironment.Snapshot(("VOLCANIC_AGENT_DIR", AgentDir));
|
|
environment?.Invoke(HostEnvironment);
|
|
var cache = new McpCatalogCache(Time);
|
|
var lookup = McpTestEnvironment.Lookup(HostEnvironment);
|
|
|
|
var processOptions = new ProcessEnvironmentOptions { HardenHostProcess = false };
|
|
foreach (var secret in secretValues ?? []) processOptions.SecretValues.Add(secret);
|
|
|
|
var services = new ServiceCollection();
|
|
services.AddSingleton<ILoggerFactory>(Logs);
|
|
services.AddSingleton<IProcessSpawnPolicy>(new ProcessSpawnPolicy(processOptions, null, () => HostEnvironment));
|
|
services.AddAgenticCore(o =>
|
|
{
|
|
o.AgentDir = AgentDir;
|
|
o.DiscoverExtensionAssemblies = false;
|
|
o.DefaultModel = FauxProvider.ModelKey;
|
|
o.ProcessEnvironment = processOptions;
|
|
o.SessionStoreFactory = _ => new InMemorySessionStore();
|
|
o.Extensions.Add(ExtensionRegistration.FromInstance(Faux.Extension));
|
|
var mcpOptions = options(this);
|
|
o.Extensions.Add(new ExtensionRegistration("mcp", _ => new McpExtension(mcpOptions, cache, lookup, Time), "test"));
|
|
foreach (var e in extra) o.Extensions.Add(ExtensionRegistration.FromInstance(e));
|
|
});
|
|
_services = services.BuildServiceProvider();
|
|
}
|
|
|
|
public FauxProvider Faux { get; } = new();
|
|
public FakeTimeProvider Time { get; } = new();
|
|
public RecordingLoggerFactory Logs { get; } = new();
|
|
public Dictionary<string, string?> HostEnvironment { get; }
|
|
public string Workspace => Path.Combine(_root, "ws");
|
|
public string AgentDir => Path.Combine(_root, "agent");
|
|
public string LogPath => Path.Combine(_root, "fake.log");
|
|
|
|
public async Task<AgentSession> CreateSessionAsync(TimeZoneInfo? zone = null, Volcanic.AgenticCore.Abstractions.Ui.IChatChannel? channel = null)
|
|
{
|
|
var session = await _services.GetRequiredService<IAgentSessionFactory>()
|
|
.CreateAsync(new AgentSessionOptions { Cwd = Workspace, TimeZone = zone, Channel = channel });
|
|
lock (_sessions) _sessions.Add(session);
|
|
return session;
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
foreach (var session in _sessions) await session.DisposeAsync();
|
|
await _services.DisposeAsync();
|
|
try { Directory.Delete(_root, true); } catch (IOException) { } catch (UnauthorizedAccessException) { }
|
|
}
|
|
}
|