Skip to main content

Type-safe tool definitions come to the Copilot .NET SDK

As a big fan of the GitHub Copilot SDK, I'm trying to keep up with the list of changes. The beta 6 version of the GitHub Copilot SDK now ships a typed CopilotTool.DefineTool helper that lets you configure override flags, skip-permission behavior, and other Copilot-specific metadata without sprinkling magic strings throughout your codebase. This makes working with Copilot tools in C# a lot cleaner.

It brings the .NET SDK in line with typed helper APIs already available in other language SDKs and makes tool definitions easier to write, refactor, and reason about.

The old way vs the new way

Before this change, wiring up Copilot-specific metadata meant relying on loosely typed configuration — easy to mistype, hard to discover, and brittle to refactor. Now, the same intent is expressed through a clean, strongly typed API:

Before

// Metadata via magic strings
options["is_override"] = "true";
options["skip_permission"] = "true";

After

new CopilotToolOptions
{
  OverridesBuiltInTool= true
}

Usage

Here's what defining a tool with override behavior looks like end-to-end:

var tool = CopilotTool.DefineTool(
    "edit",
    new CopilotToolOptions { OverridesBuiltInTool = true },
async (params, ctx) => { // your custom edit implementation });

What OverridesBuiltInTool means: marking a tool as an override tells Copilot to use your implementation in place of the built-in behavior for that tool name. Combined with SkipPermission, you can also bypass the standard user-confirmation step for trusted automation workflows.

A real world example

In my PowerPilot application, I originally created my own Tool function to tackle the same issue:

// All our tools are safe read-only lookups — skip the permission prompt.
var skipPermission = new ReadOnlyDictionary<string, object?>(
    new Dictionary<string, object?> { ["skip_permission"] = true });

AIFunction Tool(Delegate fn, string name, string description) =>
    AIFunctionFactory.Create(fn, new AIFunctionFactoryOptions
    {
        Name = name,
        Description = description,
        AdditionalProperties = skipPermission,
    });

return new List<AIFunction>
{
    Tool(() => energyPlugin.GetCurrentPower(),
        "get_current_power",
        "Get the current real-time power consumption and production in kilowatts"),

    Tool(() => energyPlugin.GetTodayStatsAsync(),
        "get_today_stats",
        "Get energy consumption and production statistics for today"),

    Tool(([Description("Time period: today, yesterday, week, or month")] string period = "today")
            => energyPlugin.GetEnergyStatsAsync(period),
        "get_energy_stats",
        "Get energy statistics for a given time period"),

    Tool(() => energyPlugin.GetHourlyProfileAsync(),
        "get_hourly_profile",
        "Get the hourly energy profile for today to understand usage patterns"),

    Tool(([Description("Appliance name, e.g. dishwasher, washing machine, dryer, EV charger")] string appliance)
            => energyPlugin.GetApplianceAdviceAsync(appliance),
        "get_appliance_advice",
        "Get advice on the best time to run a high-power appliance based on current and historical production data"),

    Tool(() => weatherPlugin.GetCurrentWeatherAsync(),
        "get_current_weather",
        "Get current weather including temperature, cloud cover, and estimated solar irradiance"),

    Tool(() => weatherPlugin.GetSolarForecastAsync(),
        "get_solar_forecast",
        "Get the solar production forecast for the next 24 hours based on weather data"),
}.AsReadOnly();

But thanks to the updated code I no longer need this and can update my code to the following:

// All our tools are safe read-only lookups — skip the permission prompt.
var toolOptions = new CopilotToolOptions
{
    SkipPermission = true,
};

return new List<AIFunction>
{
    CopilotTool.DefineTool(()=> energyPlugin.GetCurrentPower(),
        toolOptions,new AIFunctionFactoryOptions() 
        { 
            Name = "get_current_power", 
            Description= "Get the current real-time power consumption and production in kilowatts" 
        }),
    CopilotTool.DefineTool(()=> energyPlugin.GetTodayStatsAsync(),toolOptions, new AIFunctionFactoryOptions()
        {
            Name = "get_today_stats",
            Description= "Get energy consumption and production statistics for today"
        }),
    CopilotTool.DefineTool(([Description("Time period: today, yesterday, week, or month")] string period = "today")
        => energyPlugin.GetEnergyStatsAsync(period),toolOptions, new AIFunctionFactoryOptions()
        {
            Name = "get_energy_stats",
            Description= "Get energy statistics for a given time period"
        }),
    CopilotTool.DefineTool(() =>energyPlugin.GetHourlyProfileAsync(), toolOptions, new AIFunctionFactoryOptions()
        {
            Name = "get_hourly_profile",
            Description= "Get the hourly energy profile for today to understand usage patterns"
        }),
    CopilotTool.DefineTool(([Description("Appliance name, e.g. dishwasher, washing machine, dryer, EV charger")] string appliance)
            => energyPlugin.GetApplianceAdviceAsync(appliance),toolOptions, new AIFunctionFactoryOptions()
            {
                Name = "get_appliance_advice",
                Description= "Get advice on the best time to run a high-power appliance based on current and historical production data"
            }),
    CopilotTool.DefineTool(()=> weatherPlugin.GetCurrentWeatherAsync(),toolOptions, new AIFunctionFactoryOptions()
            {
                Name = "get_weather_forecast",
                Description= "Get current weather including temperature, cloud cover, and estimated solar irradiance"
            }),
    CopilotTool.DefineTool(()=> weatherPlugin.GetSolarForecastAsync(),toolOptions, new AIFunctionFactoryOptions()
            {
                Name = "get_solar_forecast",
                Description= "Get the solar production forecast for the next 24 hours based on weather data" 
            })
}.AsReadOnly();

Conclusion

The Copilot SDK team has been steadily unifying the developer experience across language bindings. Typed tool definition helpers have existed in other SDKs for a while — this update closes the gap for C# developers who previously had to work against the grain to express the same intent.

The practical benefits are real: CopilotToolOptions is discoverable via IntelliSense, validated at compile time, and refactor-friendly. If a property is renamed in a future SDK version, your build tells you immediately — instead of a runtime surprise.

More information

Release v1.0.0-beta.6 · github/copilot-sdk

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Azure DevOps/ GitHub emoji

I’m really bad at remembering emoji’s. So here is cheat sheet with all emoji’s that can be used in tools that support the github emoji markdown markup: All credits go to rcaviers who created this list.

VS Code Planning mode

After the introduction of Plan mode in Visual Studio , it now also found its way into VS Code. Planning mode, or as I like to call it 'Hannibal mode', extends GitHub Copilot's Agent Mode capabilities to handle larger, multi-step coding tasks with a structured approach. Instead of jumping straight into code generation, Planning mode creates a detailed execution plan. If you want more details, have a look at my previous post . Putting plan mode into action VS Code takes a different approach compared to Visual Studio when using plan mode. Instead of a configuration setting that you can activate but have limited control over, planning is available as a separate chat mode/agent: I like this approach better than how Visual Studio does it as you have explicit control when plan mode is activated. Instead of immediately diving into execution, the plan agent creates a plan and asks some follow up questions: You can further edit the plan by clicking on ‘Open in Editor’: ...