-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathCanvas.cs
More file actions
184 lines (158 loc) · 7.17 KB
/
Canvas.cs
File metadata and controls
184 lines (158 loc) · 7.17 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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Copilot.Rpc;
namespace GitHub.Copilot;
/// <summary>
/// Declarative metadata for a single canvas, sent over the wire on
/// <c>session.create</c> / <c>session.resume</c>.
/// </summary>
[Experimental(Diagnostics.Experimental)]
public sealed class CanvasDeclaration
{
/// <summary>Canvas identifier, unique within the declaring connection.</summary>
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
/// <summary>Human-readable name shown in host UI and canvas pickers.</summary>
[JsonPropertyName("displayName")]
public string DisplayName { get; set; } = string.Empty;
/// <summary>Short, single-sentence description shown to the agent in canvas catalogs.</summary>
[JsonPropertyName("description")]
public string Description { get; set; } = string.Empty;
/// <summary>JSON Schema for the <c>input</c> payload accepted by <c>canvas.open</c>.</summary>
[JsonPropertyName("inputSchema")]
public JsonElement? InputSchema { get; set; }
/// <summary>Agent-callable actions this canvas exposes.</summary>
[JsonPropertyName("actions")]
public IList<CanvasAction>? Actions { get; set; }
}
/// <summary>
/// Stable extension identity for session participants that provide canvases.
/// </summary>
[Experimental(Diagnostics.Experimental)]
public sealed class ExtensionInfo
{
/// <summary>Extension namespace/source, e.g. <c>"github-app"</c>.</summary>
[JsonPropertyName("source")]
public string Source { get; set; } = string.Empty;
/// <summary>Stable provider name within the source namespace.</summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
}
/// <summary>Structured error returned from canvas handlers.</summary>
/// <remarks>
/// Throw this from <see cref="ICanvasHandler"/> implementations to surface a
/// machine-readable error code to the runtime. Any other exception is wrapped
/// in a generic <c>canvas_handler_error</c> envelope.
/// </remarks>
[Experimental(Diagnostics.Experimental)]
public sealed class CanvasError : Exception
{
/// <summary>Initializes a new <see cref="CanvasError"/>.</summary>
/// <param name="code">Machine-readable error code.</param>
/// <param name="message">Human-readable message.</param>
public CanvasError(string code, string message) : base(message)
{
Code = code;
}
/// <summary>Machine-readable error code.</summary>
public string Code { get; }
/// <summary>
/// Default error returned when a custom action has no handler.
/// </summary>
public static CanvasError NoHandler() => new(
"canvas_action_no_handler",
"No handler implemented for this canvas action");
}
/// <summary>
/// Internal helpers used by the session runtime to translate <see cref="CanvasError"/>
/// (and other handler-thrown exceptions) into structured JSON-RPC error responses.
/// </summary>
internal static class CanvasErrorHelpers
{
private const int InternalError = -32603;
public static LocalRpcInvocationException HandlerUnset() => Build(
"canvas_handler_unset",
"No canvas handler is registered on this session");
public static LocalRpcInvocationException HandlerError(string message) => Build(
"canvas_handler_error",
message);
public static LocalRpcInvocationException ToRpcException(CanvasError error) => Build(error.Code, error.Message);
private static LocalRpcInvocationException Build(string code, string message)
{
var json = JsonSerializer.Serialize(
new CanvasErrorPayload { Code = code, Message = message },
CanvasJsonContext.Default.CanvasErrorPayload);
using var doc = JsonDocument.Parse(json);
return new LocalRpcInvocationException(InternalError, message, doc.RootElement.Clone());
}
internal sealed class CanvasErrorPayload
{
[JsonPropertyName("code")]
public string Code { get; set; } = string.Empty;
[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;
}
}
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(CanvasErrorHelpers.CanvasErrorPayload))]
internal partial class CanvasJsonContext : JsonSerializerContext;
/// <summary>
/// Provider-side canvas lifecycle handler.
/// </summary>
/// <remarks>
/// A session installs a single <see cref="ICanvasHandler"/> via
/// <c>SessionConfigBase.CanvasHandler</c>. The handler receives every
/// inbound <c>canvas.open</c> / <c>canvas.close</c> / <c>canvas.action.invoke</c>
/// JSON-RPC request the runtime issues for this session and decides — typically
/// by inspecting <see cref="CanvasProviderOpenRequest.CanvasId"/> — which
/// application-side canvas should handle the call.
/// <para>
/// The SDK does not maintain a per-canvas registry; multiplexing across
/// declared canvases is the implementor's responsibility.
/// </para>
/// <para>
/// Implementations targeting <c>netstandard2.0</c> cannot rely on default
/// interface methods; derive from <see cref="CanvasHandlerBase"/> to inherit
/// sensible defaults for <see cref="OnCloseAsync"/> and <see cref="OnActionAsync"/>.
/// </para>
/// </remarks>
[Experimental(Diagnostics.Experimental)]
public interface ICanvasHandler
{
/// <summary>Open a new canvas instance.</summary>
Task<CanvasProviderOpenResult> OnOpenAsync(CanvasProviderOpenRequest context, CancellationToken cancellationToken);
/// <summary>Canvas was closed by the user or agent. Default: no-op.</summary>
Task OnCloseAsync(CanvasProviderCloseRequest context, CancellationToken cancellationToken);
/// <summary>
/// Handle a non-lifecycle action declared by the canvas.
/// Default: throws <see cref="CanvasError.NoHandler"/>.
/// </summary>
Task<object?> OnActionAsync(CanvasProviderInvokeActionRequest context, CancellationToken cancellationToken);
}
/// <summary>
/// Convenience base class for <see cref="ICanvasHandler"/> that supplies
/// default no-op / no-handler implementations of the optional callbacks.
/// </summary>
[Experimental(Diagnostics.Experimental)]
public abstract class CanvasHandlerBase : ICanvasHandler
{
/// <inheritdoc />
public abstract Task<CanvasProviderOpenResult> OnOpenAsync(CanvasProviderOpenRequest context, CancellationToken cancellationToken);
/// <inheritdoc />
public virtual Task OnCloseAsync(CanvasProviderCloseRequest context, CancellationToken cancellationToken)
#if NET8_0_OR_GREATER
=> Task.CompletedTask;
#else
=> Task.FromResult<object?>(null);
#endif
/// <inheritdoc />
public virtual Task<object?> OnActionAsync(CanvasProviderInvokeActionRequest context, CancellationToken cancellationToken)
=> Task.FromException<object?>(CanvasError.NoHandler());
}