* 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 {
name: string;
description?: string;
inputSchema?: CanvasJsonSchema;
* Required per-action dispatch handler. The returned value becomes the
* `invoke_canvas_action` tool result. Return a `ToolResultObject`
* (with `binaryResultsForLlm`) to send text and images to the model, as a
* tool handler would; any other value is rendered to the model as JSON text.
*/
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 {
id: string;
displayName: string;
description: string;
inputSchema?: CanvasJsonSchema;
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";
}
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 {
id: string;
displayName: string;
description: string;
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[];
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;
}
*
* 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"];
readonly actionHandlers: Map<string, CanvasAction["handler"]>;
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;
}
}
*
* 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);
}