feat: enable scratchpad tooling backend

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-25 21:14:19 +01:00
co-authored by Copilot
parent ab4e217d74
commit 4ecfa5bff6
5 changed files with 209 additions and 133 deletions
@@ -28,8 +28,7 @@ internal sealed class CopilotAgentBundle : IAsyncDisposable
List<IAsyncDisposable> disposables = [];
List<AIAgent> agents = [];
CopilotClientOptions clientOptions = CopilotCliPathResolver.CreateClientOptions();
bool isScratchpad = string.Equals(command.WorkspaceKind, "scratchpad", StringComparison.OrdinalIgnoreCase);
SessionToolingBundle? toolingBundle = isScratchpad
SessionToolingBundle? toolingBundle = command.Tooling is null
? null
: await SessionToolingBundle.CreateAsync(command.Tooling, command.ProjectPath, cancellationToken)
.ConfigureAwait(false);
@@ -58,22 +57,7 @@ internal sealed class CopilotAgentBundle : IAsyncDisposable
Streaming = true,
};
if (isScratchpad)
{
sessionConfig.AvailableTools = [];
}
else if (toolingBundle is not null)
{
if (toolingBundle.McpServers.Count > 0)
{
sessionConfig.McpServers = toolingBundle.McpServers;
}
if (toolingBundle.Tools.Count > 0)
{
sessionConfig.Tools = toolingBundle.Tools.ToList();
}
}
ApplySessionTooling(sessionConfig, toolingBundle?.McpServers, toolingBundle?.Tools);
GitHubCopilotAgent agent = new(
client,
@@ -92,6 +76,22 @@ internal sealed class CopilotAgentBundle : IAsyncDisposable
return bundle;
}
internal static void ApplySessionTooling(
SessionConfig sessionConfig,
Dictionary<string, object>? mcpServers,
IReadOnlyList<AIFunction>? tools)
{
if (mcpServers is { Count: > 0 })
{
sessionConfig.McpServers = mcpServers;
}
if (tools is { Count: > 0 })
{
sessionConfig.Tools = tools.ToList();
}
}
public Workflow BuildWorkflow(PatternDefinitionDto pattern)
{
return pattern.Mode switch
@@ -0,0 +1,73 @@
using System.Reflection;
using Eryx.AgentHost.Services;
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;
namespace Eryx.AgentHost.Tests;
public sealed class CopilotAgentBundleTests
{
[Fact]
public void ApplySessionTooling_MapsMcpServersAndToolsOntoTheSessionConfig()
{
SessionConfig sessionConfig = new()
{
AvailableTools = ["glob"],
};
Dictionary<string, object> mcpServers = new(StringComparer.OrdinalIgnoreCase)
{
["Git MCP"] = new McpLocalServerConfig
{
Type = "local",
Command = "node",
Args = ["server.js"],
Tools = ["git.status"],
},
};
AIFunction tool = CreateTool();
CopilotAgentBundle.ApplySessionTooling(sessionConfig, mcpServers, [tool]);
Assert.Same(mcpServers, sessionConfig.McpServers);
Assert.NotNull(sessionConfig.Tools);
AIFunction configuredTool = Assert.Single(sessionConfig.Tools);
Assert.Same(tool, configuredTool);
Assert.Equal(["glob"], sessionConfig.AvailableTools);
}
[Fact]
public void ApplySessionTooling_LeavesSessionConfigUnsetWhenNoToolingIsProvided()
{
SessionConfig sessionConfig = new()
{
AvailableTools = ["glob", "view"],
};
CopilotAgentBundle.ApplySessionTooling(sessionConfig, null, []);
Assert.Null(sessionConfig.McpServers);
Assert.Null(sessionConfig.Tools);
Assert.Equal(["glob", "view"], sessionConfig.AvailableTools);
}
private static AIFunction CreateTool()
{
ToolTarget target = new();
MethodInfo method = typeof(ToolTarget).GetMethod(nameof(ToolTarget.Echo))
?? throw new InvalidOperationException("Expected test method to exist.");
return AIFunctionFactory.Create(
method,
target,
new AIFunctionFactoryOptions
{
Name = "echo",
Description = "Echo test tool",
});
}
private sealed class ToolTarget
{
public string Echo() => "ok";
}
}