/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
using Microsoft.Extensions.AI;
namespace GitHub.Copilot;
///
/// Provides helpers for defining Copilot tools.
///
public static class CopilotTool
{
/// The key used in to indicate that a tool intentionally overrides a built-in Copilot tool with the same name.
internal const string OverridesBuiltInToolKey = "is_override";
/// The key used in to indicate that a tool can execute without a permission prompt.
internal const string SkipPermissionKey = "skip_permission";
/// The key used in to carry the tool's deferral mode.
internal const string DeferKey = "defer";
///
/// Defines a tool for use in a .
///
/// The delegate to invoke when the tool is called.
/// The Microsoft.Extensions.AI options used to create the function.
/// Copilot-specific tool options.
/// An that can be added to .
///
/// This is a helper on top of that applies additional configuration to support
/// Copilot tools, such as binding a parameter and adding Copilot-specific metadata properties based on the provided
/// . Any may be used as a Copilot tool; this helper simply provides additional conveniences
/// for tools that opt in to advanced features.
///
public static AIFunction DefineTool(
Delegate method,
CopilotToolOptions? toolOptions = null,
AIFunctionFactoryOptions? factoryOptions = null)
{
ArgumentNullException.ThrowIfNull(method);
factoryOptions ??= new();
ApplyToolOptions(factoryOptions, toolOptions);
ApplyToolInvocationBinding(factoryOptions);
return AIFunctionFactory.Create(method, factoryOptions);
static void ApplyToolInvocationBinding(AIFunctionFactoryOptions factoryOptions)
{
var configureParameterBinding = factoryOptions.ConfigureParameterBinding;
factoryOptions.ConfigureParameterBinding = pi =>
{
var bindingOptions = configureParameterBinding?.Invoke(pi) ?? default;
if (bindingOptions.BindParameter is null &&
!bindingOptions.ExcludeFromSchema &&
pi.ParameterType == typeof(ToolInvocation))
{
return new AIFunctionFactoryOptions.ParameterBindingOptions
{
ExcludeFromSchema = true,
BindParameter = static (pi, arguments) =>
{
// CopilotClient/CopilotSession attach this context object before invoking the AIFunction.
if (arguments.Context is not null &&
arguments.Context.TryGetValue(typeof(ToolInvocation), out var invocation) &&
invocation is ToolInvocation toolInvocation)
{
return toolInvocation;
}
if (pi.HasDefaultValue)
{
return null;
}
throw new InvalidOperationException($"No {nameof(ToolInvocation)} was provided for the tool call.");
}
};
}
return bindingOptions;
};
}
static void ApplyToolOptions(AIFunctionFactoryOptions factoryOptions, CopilotToolOptions? toolOptions)
{
if (toolOptions is not null && (toolOptions.OverridesBuiltInTool || toolOptions.SkipPermission || toolOptions.Defer is not null))
{
Dictionary additionalProperties = new(StringComparer.Ordinal);
if (factoryOptions.AdditionalProperties is not null)
{
foreach (var (key, value) in factoryOptions.AdditionalProperties)
{
additionalProperties[key] = value;
}
}
if (toolOptions.OverridesBuiltInTool)
{
additionalProperties[OverridesBuiltInToolKey] = true;
}
if (toolOptions.SkipPermission)
{
additionalProperties[SkipPermissionKey] = true;
}
if (toolOptions.Defer is { } defer)
{
additionalProperties[DeferKey] = defer;
}
factoryOptions.AdditionalProperties = additionalProperties;
}
}
}
}
///
/// Copilot-specific options for tools defined with .
///
public sealed class CopilotToolOptions
{
///
/// Gets or sets a value indicating whether this tool intentionally overrides a built-in Copilot tool with the same name.
///
///
/// When a with set to true is used to define a tool,
/// the resulting will include "is_override": true in its .
///
public bool OverridesBuiltInTool { get; set; }
///
/// Gets or sets a value indicating whether this tool can execute without a permission prompt.
///
///
/// When a with set to true is used to define a tool,
/// the resulting will include "skip_permission": true in its .
///
public bool SkipPermission { get; set; }
///
/// Gets or sets a value controlling whether this tool may be deferred (loaded lazily via tool search) rather than always pre-loaded.
///
///
/// When set, the resulting carries the value in its and the
/// SDK forwards it to the CLI as the tool's defer mode. Defaults to "auto".
///
public CopilotToolDefer? Defer { get; set; }
}
///
/// Controls whether a tool may be deferred (loaded lazily via tool search) rather than always pre-loaded.
///
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
public enum CopilotToolDefer
{
/// The tool can be deferred and surfaced through tool search.
[System.Text.Json.Serialization.JsonStringEnumMemberName("auto")]
Auto,
/// The tool is always pre-loaded.
[System.Text.Json.Serialization.JsonStringEnumMemberName("never")]
Never
}