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.