-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathCopilotTool.cs
More file actions
169 lines (144 loc) · 7.54 KB
/
Copy pathCopilotTool.cs
File metadata and controls
169 lines (144 loc) · 7.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
using Microsoft.Extensions.AI;
namespace GitHub.Copilot;
/// <summary>
/// Provides helpers for defining Copilot tools.
/// </summary>
public static class CopilotTool
{
/// <summary>The key used in <see cref="AITool.AdditionalProperties"/> to indicate that a tool intentionally overrides a built-in Copilot tool with the same name.</summary>
internal const string OverridesBuiltInToolKey = "is_override";
/// <summary>The key used in <see cref="AITool.AdditionalProperties"/> to indicate that a tool can execute without a permission prompt.</summary>
internal const string SkipPermissionKey = "skip_permission";
/// <summary>The key used in <see cref="AITool.AdditionalProperties"/> to carry the tool's <see cref="CopilotToolDefer"/> deferral mode.</summary>
internal const string DeferKey = "defer";
/// <summary>
/// Defines a tool for use in a <see cref="CopilotSession"/>.
/// </summary>
/// <param name="method">The delegate to invoke when the tool is called.</param>
/// <param name="factoryOptions">The Microsoft.Extensions.AI options used to create the function.</param>
/// <param name="toolOptions">Copilot-specific tool options.</param>
/// <returns>An <see cref="AIFunction"/> that can be added to <see cref="SessionConfigBase.Tools"/>.</returns>
/// <remarks>
/// This is a helper on top of <see cref="AIFunctionFactory.Create(Delegate, AIFunctionFactoryOptions)"/> that applies additional configuration to support
/// Copilot tools, such as binding a <see cref="ToolInvocation"/> parameter and adding Copilot-specific metadata properties based on the provided
/// <see cref="CopilotToolOptions"/>. Any <see cref="AIFunction"/> may be used as a Copilot tool; this helper simply provides additional conveniences
/// for tools that opt in to advanced features.
/// </remarks>
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<string, object?> 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;
}
}
}
}
/// <summary>
/// Copilot-specific options for tools defined with <see cref="CopilotTool"/>.
/// </summary>
public sealed class CopilotToolOptions
{
/// <summary>
/// Gets or sets a value indicating whether this tool intentionally overrides a built-in Copilot tool with the same name.
/// </summary>
/// <remarks>
/// When a <see cref="CopilotToolOptions"/> with <see cref="OverridesBuiltInTool"/> set to true is used to define a tool,
/// the resulting <see cref="AIFunction"/> will include "is_override": true in its <see cref="AITool.AdditionalProperties"/>.
/// </remarks>
public bool OverridesBuiltInTool { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this tool can execute without a permission prompt.
/// </summary>
/// <remarks>
/// When a <see cref="CopilotToolOptions"/> with <see cref="SkipPermission"/> set to true is used to define a tool,
/// the resulting <see cref="AIFunction"/> will include "skip_permission": true in its <see cref="AITool.AdditionalProperties"/>.
/// </remarks>
public bool SkipPermission { get; set; }
/// <summary>
/// Gets or sets a value controlling whether this tool may be deferred (loaded lazily via tool search) rather than always pre-loaded.
/// </summary>
/// <remarks>
/// When set, the resulting <see cref="AIFunction"/> carries the value in its <see cref="AITool.AdditionalProperties"/> and the
/// SDK forwards it to the CLI as the tool's <c>defer</c> mode. Defaults to "auto".
/// </remarks>
public CopilotToolDefer? Defer { get; set; }
}
/// <summary>
/// Controls whether a tool may be deferred (loaded lazily via tool search) rather than always pre-loaded.
/// </summary>
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter<CopilotToolDefer>))]
public enum CopilotToolDefer
{
/// <summary>The tool can be deferred and surfaced through tool search.</summary>
[System.Text.Json.Serialization.JsonStringEnumMemberName("auto")]
Auto,
/// <summary>The tool is always pre-loaded.</summary>
[System.Text.Json.Serialization.JsonStringEnumMemberName("never")]
Never
}