-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathcanvas.ts
More file actions
189 lines (175 loc) · 7.08 KB
/
canvas.ts
File metadata and controls
189 lines (175 loc) · 7.08 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
185
186
187
188
189
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
import type {
CanvasJsonSchema,
CanvasProviderCloseRequest,
CanvasProviderInvokeActionRequest,
CanvasProviderOpenRequest,
CanvasProviderOpenResult,
} from "./generated/rpc.js";
export type {
CanvasJsonSchema,
CanvasHostContext,
CanvasHostContextCapabilities,
} from "./generated/rpc.js";
/**
* Extension-owned canvases declared via
* `joinSession({ canvases: [createCanvas({...})] })`.
*
* The runtime sends provider callbacks as `canvas.open`, `canvas.close`, and
* `canvas.action.invoke` JSON-RPC requests via the codegen client session API
* pipeline. The SDK routes those requests by `canvasId` to the in-process
* handlers bound by `createCanvas`. Re-opening with an existing `instanceId`
* is how the host focuses an existing panel; reload is a renderer-only concern.
*
* @experimental Canvas types are part of an experimental wire-protocol surface
* and may change or be removed in future SDK or CLI releases.
*/
/**
* A single agent-callable action contributed by a canvas. The metadata
* (`name`, `description`, `inputSchema`) is serialized over the wire on
* `session.create` / `session.resume`; the `handler` closure is stripped
* before the declaration is sent and dispatched in-process by the SDK.
*
* Names MUST NOT start with `canvas.` — that prefix is reserved for
* lifecycle verbs.
*
* @experimental This type is part of an experimental wire-protocol surface
* and may change or be removed in future SDK or CLI releases.
*/
export interface CanvasAction {
/** Action identifier, unique within the canvas. */
name: string;
/** Description shown to the model when picking an action. */
description?: string;
/** Optional JSON Schema for the action's `input` payload. */
inputSchema?: CanvasJsonSchema;
/** Required per-action dispatch handler. */
handler: (ctx: CanvasProviderInvokeActionRequest) => Promise<unknown> | unknown;
}
/**
* Declarative metadata for a single canvas, serialized over the wire on
* `session.create` / `session.resume`.
*
* @experimental This type is part of an experimental wire-protocol surface
* and may change or be removed in future SDK or CLI releases.
*/
export interface CanvasDeclaration {
/** Canvas id, unique within the declaring connection. */
id: string;
/** Human-readable label shown in discovery and host UI chrome. */
displayName: string;
/** Short, single-sentence description shown to the agent in canvas catalogs. */
description: string;
/** Optional JSON Schema for the `input` payload accepted by `canvas.open`. */
inputSchema?: CanvasJsonSchema;
/** Agent-invocable actions exposed via `invoke_canvas_action`. */
actions?: Omit<CanvasAction, "handler">[];
}
/**
* Structured error returned from canvas handlers.
*
* @experimental This class is part of an experimental wire-protocol surface
* and may change or be removed in future SDK or CLI releases.
*/
export class CanvasError extends Error {
constructor(
public readonly code: string,
message: string
) {
super(message);
this.name = "CanvasError";
}
/** Default error when an action is declared but no `handler` is wired. */
static noHandler(): CanvasError {
return new CanvasError(
"canvas_action_no_handler",
"No handler implemented for this canvas action"
);
}
}
/**
* Options accepted by {@link createCanvas}. Combines the declarative
* {@link CanvasDeclaration} fields with the in-process handler closures.
*
* @experimental This interface is part of an experimental wire-protocol surface
* and may change or be removed in future SDK or CLI releases.
*/
export interface CanvasOptions {
/** @see CanvasDeclaration.id */
id: string;
/** @see CanvasDeclaration.displayName */
displayName: string;
/** @see CanvasDeclaration.description */
description: string;
/** @see CanvasDeclaration.inputSchema */
inputSchema?: CanvasJsonSchema;
/**
* Agent-invocable actions exposed via `invoke_canvas_action`. Each action
* carries its own required `handler`; the action's wire metadata
* (`name`, `description`, `inputSchema`) is what reaches the runtime.
*/
actions?: CanvasAction[];
/** Required. Open a new canvas instance. */
open: (
ctx: CanvasProviderOpenRequest
) => Promise<CanvasProviderOpenResult> | CanvasProviderOpenResult;
/**
* Optional. Notified when a canvas instance is closed by the user, the
* agent, or the host. Fire-and-forget: the return value is ignored and
* errors are logged but not surfaced to the runtime.
*/
onClose?: (ctx: CanvasProviderCloseRequest) => Promise<void> | void;
}
/** A registered canvas: declarative metadata + in-process handler closures.
*
* Node intentionally uses a per-canvas factory pattern (mirroring
* {@link https://github.com/github/copilot-sdk | `DefineTool`}'s co-location
* ergonomics) where other SDKs (Rust, Python, Go, .NET) expose a single
* `CanvasHandler` per session that switches on `canvasId`. Both shapes target
* the same JSON-RPC wire protocol; the divergence is API ergonomics only.
*
* @experimental This class is part of an experimental wire-protocol surface
* and may change or be removed in future SDK or CLI releases.
*/
export class Canvas {
readonly declaration: CanvasDeclaration;
readonly open: NonNullable<CanvasOptions["open"]>;
readonly onClose?: CanvasOptions["onClose"];
/** @internal */
readonly actionHandlers: Map<string, CanvasAction["handler"]>;
/** @internal */
constructor(options: CanvasOptions) {
const actionHandlers = new Map<string, CanvasAction["handler"]>();
const wireActions: Omit<CanvasAction, "handler">[] | undefined = options.actions?.map(
({ handler, ...wire }) => {
actionHandlers.set(wire.name, handler);
return wire;
}
);
this.declaration = {
id: options.id,
displayName: options.displayName,
description: options.description,
inputSchema: options.inputSchema,
actions: wireActions,
};
this.open = options.open;
this.onClose = options.onClose;
this.actionHandlers = actionHandlers;
}
}
/** Create a canvas declaration with bound in-process handlers.
*
* Node intentionally uses this per-canvas factory pattern (mirroring
* `DefineTool`'s co-location ergonomics) where other SDKs (Rust, Python, Go,
* .NET) expose a single `CanvasHandler` per session that switches on
* `canvasId`. Both shapes target the same JSON-RPC wire protocol.
*
* @experimental This function is part of an experimental wire-protocol surface
* and may change or be removed in future SDK or CLI releases.
*/
export function createCanvas(options: CanvasOptions): Canvas {
return new Canvas(options);
}