Add canvas artifact RPC foundation for A2UI

This commit is contained in:
William Valentin
2026-02-16 12:36:02 -08:00
parent fe8674e108
commit 8a0b4f3dbb
11 changed files with 497 additions and 3 deletions
+78
View File
@@ -7,10 +7,12 @@ import { createAgentHandlers } from './agent.js';
import { createIntentHandlers } from './intents.js';
import { createRoutingHandlers } from './routing.js';
import { createHistoryHandlers } from './history.js';
import { createCanvasHandlers } from './canvas.js';
import { createConfigHandlers, redactConfig } from './config.js';
import { createPairingHandlers } from './pairing.js';
import { PairingManager } from '../../channels/pairing.js';
import { LaneQueue } from '../lane-queue.js';
import { CanvasStore } from '../canvas-store.js';
import { ErrorCode } from '../protocol.js';
import type { GatewayRequest, GatewayResponse, GatewayError, GatewayEvent, OutboundMessage } from '../protocol.js';
import { ComponentRegistry } from '../../intents/index.js';
@@ -350,6 +352,82 @@ describe('session handlers', () => {
});
});
describe('canvas handlers', () => {
it('supports put/get/list/delete/clear lifecycle', async () => {
const handlers = createCanvasHandlers({ store: new CanvasStore() });
const putReq: GatewayRequest = {
id: 1,
method: 'canvas.put',
params: {
sessionId: 'ws:abc',
artifactId: 'card-1',
type: 'note',
title: 'Draft',
content: { text: 'hello' },
},
};
const putRes = await handlers['canvas.put'](putReq) as GatewayResponse;
expect(getPath(putRes.result, 'artifact', 'id')).toBe('card-1');
const getRes = await handlers['canvas.get']({
id: 2,
method: 'canvas.get',
params: { sessionId: 'ws:abc', artifactId: 'card-1' },
}) as GatewayResponse;
expect(getPath(getRes.result, 'artifact', 'title')).toBe('Draft');
const listRes = await handlers['canvas.list']({
id: 3,
method: 'canvas.list',
params: { sessionId: 'ws:abc' },
}) as GatewayResponse;
expect((getPath(listRes.result, 'artifacts') as unknown[]).length).toBe(1);
const delRes = await handlers['canvas.delete']({
id: 4,
method: 'canvas.delete',
params: { sessionId: 'ws:abc', artifactId: 'card-1' },
}) as GatewayResponse;
expect(getPath(delRes.result, 'deleted')).toBe(true);
await handlers['canvas.put']({
id: 5,
method: 'canvas.put',
params: { sessionId: 'ws:abc', artifactId: 'card-2', type: 'note', content: 'a' },
});
await handlers['canvas.put']({
id: 6,
method: 'canvas.put',
params: { sessionId: 'ws:abc', artifactId: 'card-3', type: 'note', content: 'b' },
});
const clearRes = await handlers['canvas.clear']({
id: 7,
method: 'canvas.clear',
params: { sessionId: 'ws:abc' },
}) as GatewayResponse;
expect(getPath(clearRes.result, 'cleared')).toBe(2);
});
it('validates required params', async () => {
const handlers = createCanvasHandlers({ store: new CanvasStore() });
const missingSession = await handlers['canvas.list']({
id: 8,
method: 'canvas.list',
params: {},
}) as GatewayError;
expect(missingSession.error.code).toBe(ErrorCode.InvalidRequest);
const missingContent = await handlers['canvas.put']({
id: 9,
method: 'canvas.put',
params: { sessionId: 'ws:abc', type: 'note' },
}) as GatewayError;
expect(missingContent.error.code).toBe(ErrorCode.InvalidRequest);
});
});
describe('tool handlers', () => {
const mockTool = {
name: 'test.tool',