Repository navigation
canvas: expose registry state and URI-addressed open - #528
Joaquín Ruales (jruales) wants to merge 4 commits into
Conversation
9a3f79a to
37f5151
Compare
| /** | ||
| * Lists canvas types currently available to open for one exact chat. | ||
| * | ||
| * This read MAY lazily resume an existing persisted chat so its extension | ||
| * registry can be reconstructed. It MUST NOT create a backing runtime for a | ||
| * brand-new chat, send a user message, or invoke a model. | ||
| * | ||
| * Clients MUST NOT call this command unless the selected agent advertises | ||
| * {@link AgentCapabilities.canvases}. | ||
| * | ||
| * @category Commands | ||
| * @method listCanvasTypes | ||
| * @direction Client → Server | ||
| * @messageType Request | ||
| * @version 1 | ||
| */ | ||
| export interface ListCanvasTypesParams extends BaseParams { | ||
| /** Exact chat URI whose extension registry should be queried. */ | ||
| channel: URI; | ||
| } | ||
|
|
||
| /** | ||
| * Canvas types available to open for the requested chat. | ||
| * | ||
| * @category Commands | ||
| */ | ||
| export interface ListCanvasTypesResult { | ||
| /** Available canvas types and their declaring extensions. */ | ||
| items: CanvasTypeDeclaration[]; | ||
| } |
There was a problem hiding this comment.
Is enumerating canvas types expensive? If not I would expect these to be on a subscribable state object somewhere so they update in realtime.
There was a problem hiding this comment.
Good point. I changed this from an imperative listCanvasTypes snapshot to authoritative exact-chat state. ChatState.canvasRegistry is kept current through full chat/canvasRegistryChanged replacements, so clients do not poll or use live-instance changes as registry invalidation.
Cold materialization is separated because it can resume the backing runtime and execute extension startup code: a client explicitly dispatches chat/canvasRegistryRequested, whose reducer is a no-op, and the host publishes the resulting registry through chat/canvasRegistryChanged.
🤖 Written by GitHub Copilot on behalf of Joaquín Ruales (@jruales).
Auto-replied by the GitHub Copilot app
| * @version 1 | ||
| */ | ||
| export interface OpenCanvasParams extends BaseParams { | ||
| /** Exact chat URI that owns the canvas instance. */ |
There was a problem hiding this comment.
Note this is an ahp-canvas channel
There was a problem hiding this comment.
Updated. openCanvas.channel is now the client-chosen ahp-canvas: resource URI. The exact owning chat is passed separately in chat, and authoritative membership and presentation continue through ChatState.canvases plus subscription to that Canvas channel.
🤖 Written by GitHub Copilot on behalf of Joaquín Ruales (@jruales).
Auto-replied by the GitHub Copilot app
| extensionId: string; | ||
| /** Provider-local canvas type identifier. */ | ||
| canvasId: string; | ||
| /** Caller-chosen stable instance identifier. */ |
There was a problem hiding this comment.
why do we need this in addition to the URI?
There was a problem hiding this comment.
I removed the client-supplied instanceId from OpenCanvasParams; the client-chosen ahp-canvas: URI is now the AHP resource identity. Reusing the URI for the same chat and declaration focuses or refreshes that logical instance, while conflicting reuse must fail.
The existing CanvasState.instanceId remains only as provider-facing correlation metadata and is documented as not being a second AHP identity.
🤖 Written by GitHub Copilot on behalf of Joaquín Ruales (@jruales).
Auto-replied by the GitHub Copilot app
| /** Whether opening this canvas requires an input flow. */ | ||
| requiresInput: boolean; | ||
| /** Number of actions the canvas declares. */ | ||
| actionCount: number; |
There was a problem hiding this comment.
How do these get used by the UI?
There was a problem hiding this comment.
I traced these through the current UI consumers. requiresInput is used by the Add Tab/Open Canvas UI to exclude declarations that need an input flow the menu cannot provide, so I retained it. actionCount had no active consumer, and a count would not be sufficient for future action invocation anyway, so I removed it.
🤖 Written by GitHub Copilot on behalf of Joaquín Ruales (@jruales).
Auto-replied by the GitHub Copilot app
| /** Stable identifier of the extension that declares the canvas. */ | ||
| extensionId: string; | ||
| /** Where the runtime discovered the declaring extension. */ | ||
| extensionSource: CanvasExtensionSource; | ||
| /** Human-readable extension name, when available. */ | ||
| extensionName?: string; |
There was a problem hiding this comment.
What does a UI do with these things? I doubt we should denormalize extensionSource/extensionName into individual canvas declarations
There was a problem hiding this comment.
Agreed. I normalized this into CanvasRegistry.extensions: each CanvasExtensionDeclaration carries extensionId, source, and display name once, with its type-local CanvasTypeDeclaration[] nested beneath it.
The UI uses the extension name to disambiguate duplicate Canvas display names and the explicit source as contribution provenance/openability metadata. extensionId is now documented as opaque, so clients must not infer source or management behavior by parsing it.
🤖 Written by GitHub Copilot on behalf of Joaquín Ruales (@jruales).
Auto-replied by the GitHub Copilot app
Add capability-gated exact-chat listCanvasTypes and openCanvas commands, including owning extension metadata, and regenerate all protocol clients and schemas. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a6aa762-4b80-4e33-be5d-68d9f3398af2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a6aa762-4b80-4e33-be5d-68d9f3398af2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a6aa762-4b80-4e33-be5d-68d9f3398af2
Replace snapshot-only Canvas listing with an authoritative exact-chat registry, explicit cold materialization action, and URI-addressed Canvas opening. Normalize provider metadata and update every generated client, reducer, fixture, and specification. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a6aa762-4b80-4e33-be5d-68d9f3398af2
4493b21 to
d21b6fa
Compare
Summary
AHP already projects live Canvas instances through
ChatState.canvasesand experimentalahp-canvas:channels, but clients also need an authoritative way to discover the Canvas types available to one exact chat and open a selected type.This PR models discovery as chat state rather than a point-in-time list command:
ChatState.canvasRegistryis the authoritative exact-chat Canvas type registry.chat/canvasRegistryChangedreplaces or clears that registry when live provider registration changes.chat/canvasRegistryRequestedexplicitly requests potentially effectful materialization for a persisted cold chat.openCanvasuses a client-chosenahp-canvas:URI as the AHP instance identity and separately identifies the owning chat and declaration.CanvasExtensionDeclaration; each nestedCanvasTypeDeclarationcontains only type-local metadata.AgentCapabilities.canvases.Protocol behavior
Live registry state
For a live chat, the host keeps
canvasRegistrysynchronized with provider registration and emitschat/canvasRegistryChangedonly when the authoritative registry changes. Clients do not poll and do not treat changes to the open-instance list as registry invalidation.The registry distinguishes three states:
{ extensions: [] }: materialized, with no available Canvas types;(extensionId, canvasId).extensionIdis opaque. Clients must not infer source or management behavior by parsing it.requiresInputremains so clients can exclude declarations whose input flow they do not support; the previous speculativeactionCountfield is removed.Cold materialization
When a persisted chat is cold, a client may dispatch
chat/canvasRegistryRequested. The reducer intentionally leaves optimistic state unchanged while the host may resume the backing runtime, discover extensions, and execute extension startup code. The host then publishes the complete result throughchat/canvasRegistryChanged.Materialization must not create a backing for a brand-new chat, send a user message, or invoke a model.
URI-addressed opening
openCanvas.channelis the client-chosenahp-canvas:resource URI. Reusing that URI for the same chat and declaration focuses or refreshes the logical instance; reusing it for a different chat, extension, or Canvas type must fail withConflict.CanvasState.instanceIdremains provider-facing correlation metadata. The Canvas URI is the AHP resource identity.sequenceDiagram participant C as Client participant Chat as Exact chat channel participant H as Agent Host participant Canvas as ahp-canvas resource C->>Chat: subscribe Chat-->>C: snapshot with canvasRegistry or no registry opt Registry is absent and the chat is persisted C->>Chat: dispatch canvasRegistryRequested H->>H: resume runtime and discover providers H-->>Chat: canvasRegistryChanged(full registry) Chat-->>C: authoritative registry action end C->>H: openCanvas(channel, chat, extensionId, canvasId) H-->>Chat: canvasesChanged(CanvasReference) C->>Canvas: subscribe(reference.resource) Canvas-->>C: CanvasStateCompatibility and scope
AgentCapabilities.canvasesremain unsupported without probing.1.1.0; both registry actions are registered at1.1.0.Validation
RUSTUP_TOOLCHAIN=stable npm run generatenpm testRUSTUP_TOOLCHAIN=stable npm run docs:buildnpm test && npm run buildswift test🤖 Written by GitHub Copilot on behalf of Joaquín Ruales (@jruales).