Add SDK canvas runtime support#1401
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds cross-SDK “canvas runtime” support, enabling SDK consumers to declare canvases, receive direct provider callbacks (canvas.*), and use host-side session.canvas.* RPCs with stable extension identity (extensionInfo)—without requiring the larger handler refactor from the prototype branch.
Changes:
- Rust: Introduces a new
canvasmodule (declarations, handler traits, dispatch, host RPC types) and wiresSessionConfig/ResumeSessionConfigto serialize canvas-related fields onsession.create/session.resume. - Rust: Adds
Session::canvas()host API and updates the session event loop/router to handle direct provider callback requests (canvas.open|focus|reload|close|action.invoke). - Node.js: Adds a canvas surface (
createCanvas, types, dispatch), forwards canvas declarations + request flags +extensionInfoon session create/resume, and routes directcanvas.*JSON-RPC requests to registered canvases with tests.
Show a summary per file
| File | Description |
|---|---|
| rust/tests/session_test.rs | Adds coverage for Rust canvas wire fields, provider dispatch routing, and SessionCanvas host API behavior. |
| rust/tests/e2e/elicitation.rs | Updates capability struct test to include the new ui.canvases field. |
| rust/src/wire.rs | Extends session create/resume wire payloads with canvases, request flags, and extension_info. |
| rust/src/types.rs | Adds ExtensionInfo, canvas config fields on session configs, resume result open-canvas capture, and UiCapabilities.canvases. |
| rust/src/session.rs | Adds SessionCanvas host API + open-canvas tracking and routes canvas.* provider callbacks through a per-session registry. |
| rust/src/router.rs | Adds debug logging when routing requests to a registered session. |
| rust/src/lib.rs | Exposes the new canvas module publicly. |
| rust/src/jsonrpc.rs | Adds debug logging for incoming JSON-RPC requests from the runtime. |
| rust/src/canvas.rs | New Rust canvas module: declarations, handler traits, registry + dispatch, request/response types, and unit tests. |
| nodejs/test/extension.test.ts | Verifies createCanvas is exported from the extension surface. |
| nodejs/test/client.test.ts | Adds tests for forwarding canvas fields on create/resume and for direct provider dispatch behavior. |
| nodejs/src/types.ts | Adds SessionCapabilities.ui.canvases, introduces ExtensionInfo, and extends session configs with canvas fields. |
| nodejs/src/session.ts | Adds per-session canvas registration and lookup to support direct dispatch routing. |
| nodejs/src/index.ts | Exports canvas APIs/types and ExtensionInfo from the main entrypoint. |
| nodejs/src/extension.ts | Exports canvas APIs/types and ExtensionInfo from the extension surface. |
| nodejs/src/client.ts | Forwards canvas fields on create/resume and registers handlers for direct canvas.* provider callbacks. |
| nodejs/src/canvas.ts | New Node canvas module: canvas declaration/types, createCanvas, and provider request dispatch helper. |
Copilot's findings
- Files reviewed: 17/17 changed files
- Comments generated: 5
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
1202bc1 to
c50b728
Compare
This comment has been minimized.
This comment has been minimized.
Add Node extension canvas APIs and direct canvas provider callback routing. Add Rust canvas declarations, provider handlers, create/resume wiring, and host session.canvas APIs aligned with the runtime schema. Validation: nodejs typecheck/lint/tests; rust fmt/check/clippy; cargo test --all-features. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Expose stable extension identity metadata on Node and Rust session create/resume options and forward extensionInfo on the wire for canvas providers. Validation: nodejs typecheck/lint/vitest; rust fmt/clippy/test --all-features. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop CanvasToolDefinition, CanvasToolDefinitionDefer, and the CanvasOpenResponse.tools / OpenCanvasInstance.tools fields from both the Node and Rust SDKs. The CLI side is being removed in lockstep, so the wire contract no longer carries this field. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move per-canvas registry, Canvas builder, dispatch helpers, and the
SessionCanvas host helper out of the SDK. The Rust canvas surface now
matches the other typed extension points (PermissionHandler /
UserInputHandler / HookHandler):
SessionConfig
.with_canvases([CanvasDeclaration, ...])
.with_canvas_handler(Arc::new(MyHandler))
Removed:
- canvas::Canvas, CanvasBuilder (declaration+handler bundle)
- canvas::CanvasRegistry, build_registry, dispatch_canvas_*
- session::SessionCanvas + Session::canvas() accessor
(callers move to session.rpc().canvas().*)
Kept (the wire boundary + typed extension point):
- All wire types (CanvasDeclaration, OpenCanvasInstance, ...)
- CanvasHandler trait + on_open/on_action/on_close
- SessionConfig/ResumeSessionConfig.canvases (now Vec<CanvasDeclaration>)
- SessionConfig/ResumeSessionConfig.canvas_handler
handle_request dispatches canvas.open/close/action.invoke directly to
the handler; the per-canvas registry now lives in the app layer.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3a8b85f to
3c0850d
Compare
This comment has been minimized.
This comment has been minimized.
Removed CanvasInstanceAvailability, OpenCanvasInstance, CanvasAgentActionDeclaration (-> CanvasAction), CanvasDiscoverResult, DiscoveredCanvas, CanvasListOpenResult, CanvasOpenRequest, CanvasCloseRequest, CanvasInvokeActionRequest, and CanvasInvokeActionResult from canvas.rs; consumers import these from crate::generated::api_types directly. The remaining hand-written types (CanvasDeclaration, CanvasOpenResponse, handler trait, contexts, CanvasError) are genuinely additive provider-authoring contracts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
The canvas wire types were deduplicated against generated/api_types.rs, renaming CanvasAgentActionDeclaration to CanvasAction. A doc comment in canvas.rs still referenced the old name, which broke cargo doc on CI (broken_intra_doc_links is denied). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Mirrors the Rust SDK canvas surface in rust/src/canvas.rs: - CanvasDeclaration, CanvasOpenResponse, CanvasHostContext, CanvasOpenContext / CanvasActionContext / CanvasLifecycleContext, CanvasError, CanvasHandler interface + CanvasHandlerDefaults, and ExtensionInfo. - SessionConfig / ResumeSessionConfig: Canvases, RequestCanvasRenderer, RequestExtensions, CanvasHandler, ExtensionInfo. - Inbound JSON-RPC dispatch for canvas.open, canvas.close, and canvas.action.invoke, with a canvas_handler_unset error envelope when no handler is installed and a canvas_handler_error envelope when a handler returns a non-CanvasError error. - Session.OpenCanvases() surfaces the openCanvases snapshot from the session.resume response. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirrors the Rust SDK design: callers declare canvases on session.create / session.resume, install a single CanvasHandler, and the SDK dispatches inbound canvas.open / canvas.close / canvas.action.invoke JSON-RPC requests to that handler. Resume populates session.open_canvases from the response. JSON-RPC dispatch was loosened to allow handlers to return any JSON value (canvas.action.invoke result is arbitrary JSON). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Ports the canvas runtime surface from the Rust SDK to the .NET SDK so .NET hosts can declare canvases on session create/resume, advertise an extension identity, and handle inbound canvas.open / canvas.close / canvas.action.invoke RPC calls. * New public Canvas.cs surface (CanvasDeclaration, ExtensionInfo, CanvasOpenResponse, CanvasHostContext, lifecycle/action/open contexts, CanvasError, ICanvasHandler, CanvasHandlerBase). All marked [Experimental(GHCP001)]. * SessionConfigBase gains Canvases, RequestCanvasRenderer, RequestExtensions, ExtensionInfo, CanvasHandler. * CreateSession/ResumeSession requests forward the new fields and surface OpenCanvases on the response. CopilotSession exposes the returned canvases via OpenCanvases. * CopilotClient registers canvas.open / canvas.close / canvas.action.invoke handlers and dispatches them to the session, which invokes the user's ICanvasHandler and returns structured CanvasError data via a new JsonRpc LocalRpcInvocationException path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
ghost
left a comment
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1401 · ● 11M
- Node: add openCanvases accessor on CopilotSession and OpenCanvases field on ResumeSessionConfig so callers can both rehydrate from the resume response and pre-populate canvas state on resume. - Node: document why createCanvas/Canvas intentionally diverges from the per-session CanvasHandler pattern used by Rust/Python/Go/.NET. - Go: add ResumeSessionConfig.OpenCanvases, thread through to the resume request wire payload, and add a serialization test. - .NET: add ResumeSessionConfig.OpenCanvases, thread through to the internal ResumeSessionRequest record, and add a serialization test. Mirrors what Rust and Python already do, fixing wire-protocol parity across SDKs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cross-SDK Consistency Review 🔍Overall, this is an impressively well-aligned multi-language implementation. The five SDKs (Rust, Node.js, Python, Go, .NET) are highly consistent on wire format, lifecycle callbacks ( ✅ Intentional Divergence (well-documented)Node.js per-canvas factory vs single handler: The 🔎 Minor Inconsistency Found
In all other SDKs,
In Node.js however, the capabilities are typed inline: // nodejs/src/canvas.ts line 67
export interface CanvasHostContext {
capabilities?: { // ← anonymous inline type
canvases?: boolean;
};
}
This means Node.js consumers can't reference the capabilities type by name (e.g., to annotate a variable or destructure typed), while consumers in every other SDK can. A small addition to maintain parity: /** Host capability details passed to canvas provider callbacks. */
export interface CanvasHostCapabilities {
canvases?: boolean;
}
export interface CanvasHostContext {
capabilities?: CanvasHostCapabilities;
}...and export i️ Note on
|
ghost
left a comment
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1401 · ● 16.2M
| { | ||
| /// <summary>Whether the host supports canvas rendering.</summary> | ||
| [JsonPropertyName("canvases")] | ||
| public bool Canvases { get; set; } |
There was a problem hiding this comment.
This name confuses me a bit. Should this be specific to rendering, as elaborated on in the comment? It's not clear to me from "Canvases" that it's specific to rendering, and presumably there are other capabilities a canvas host will be capable of related to canvases.
| /// default no-op / no-handler implementations of the optional callbacks. | ||
| /// </summary> | ||
| [Experimental(Diagnostics.Experimental)] | ||
| public abstract class CanvasHandlerBase : ICanvasHandler |
There was a problem hiding this comment.
Is this necessary? Doesn't seem like it provides much in the way of convenience.
There was a problem hiding this comment.
CanvasHandlerBase is there for netstandard2.0 consumers — that target can't use default interface methods on ICanvasHandler, so without the base class every netstandard2.0 user has to write the OnCloseAsync no-op + OnActionAsync throw boilerplate themselves. Happy to drop it (and just put the boilerplate in the README) if you'd rather, but leaving as-is in #1420 — let me know if you want it removed in a follow-up.
There was a problem hiding this comment.
Thanks, I think we should drop it. Implementing this will be rare, and the cited boilerplate is a trivial one-liner. Better to not add additional surface area to the namespace.
| Assert.Equal(1, openCanvases.GetArrayLength()); | ||
| Assert.Equal("canvas-id", openCanvases[0].GetProperty("canvasId").GetString()); | ||
| } | ||
|
|
There was a problem hiding this comment.
Is there a way we can add some good E2E tests in the SDK for this canvas support, in each language? We have a fairly extensive set of hundreds of E2E tests in each of the languages, and while we have some gaps we're working to fill, we're trying to make it so that we have great E2E coverage for every API exposed.
What changed
Adds canvas runtime support across the Rust, Node, Python, Go, and .NET SDKs so consumers can declare canvases on
session.create/session.resume, install a singleCanvasHandler, and receive routedcanvas.open/canvas.close/canvas.action.invokeJSON-RPC requests from the runtime. Resume surfacesopenCanvasesfor rehydration, and host code can callsession.canvas.*for native UI / chrome actions.Per-language surface
rust/src/canvas.rs):CanvasDeclaration,CanvasHandlertrait,CanvasHostContext/CanvasOpenContext/CanvasActionContext/CanvasLifecycleContext,CanvasError,CanvasOpenResponse,ExtensionInfo. Wire types (OpenCanvasInstance,CanvasAction,CanvasInstanceAvailability,CanvasInvokeActionRequest/Result, etc.) consumed directly fromgenerated::api_types— no duplication.Session::open_canvases()accessor; host APIs viasession.rpc().canvas().nodejs/src/canvas.ts):createCanvas, canvas declarations, handler contexts, errors, exports. Session create/resume threadcanvases,requestCanvasRenderer,requestExtensions,extensionInfo.python/copilot/canvas.py):CanvasHandlerABC with defaulton_close/on_action(raisescanvas_action_no_handler)._jsonrpcloosened to allow arbitrary JSON return values for action results.go/canvas.go):CanvasHandlerinterface +CanvasHandlerDefaultsembeddable for partial impls. Inbound RPC registered viajsonrpc2.RequestHandlerForwith{code,message}error envelope.dotnet/src/Canvas.cs): idiomaticICanvasHandlerwith async methods; same dispatch shape.Java is intentionally not included in this PR and will follow separately.
Shared design
CanvasHandlerper session. Consumers switch oncanvas_idthemselves — no per-canvas registry inside the SDK.{"code":"canvas_handler_unset","message":"..."}.ExtensionInfo { source, name }lets consumers opt into stable agent-facing extension IDs (github-app:<provider-name>).Notes for reviewers
Additive on top of the existing permission / user-input / tool handler models — no broad handler refactor. Canvas declarations require
displayName.capabilities.ui.canvasesis boolean.session.canvas.listOpenis a real RPC.Validation
Per language:
cargo +nightly-2026-04-14 fmt --check,cargo clippy --all-features --all-targets -- -D warnings,cargo test --all-features,cargo doc --no-deps --all-featuresnpm run typecheck,npm run lint,npx vitest runuv run ruff format/check,uv run ty check copilot,uv run pytestgofmt,go build/vet/test ./...dotnet format --verify-no-changes,dotnet build,dotnet test