pmx-canvas 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/Readme.md +865 -0
  4. package/dist/canvas/global.css +3173 -0
  5. package/dist/canvas/index.js +183 -0
  6. package/dist/json-render/index.css +2 -0
  7. package/dist/json-render/index.js +389 -0
  8. package/dist/types/cli/agent.d.ts +13 -0
  9. package/dist/types/cli/index.d.ts +2 -0
  10. package/dist/types/cli/watch.d.ts +5 -0
  11. package/dist/types/client/App.d.ts +1 -0
  12. package/dist/types/client/canvas/AttentionHistory.d.ts +1 -0
  13. package/dist/types/client/canvas/AttentionToast.d.ts +1 -0
  14. package/dist/types/client/canvas/CanvasNode.d.ts +8 -0
  15. package/dist/types/client/canvas/CanvasViewport.d.ts +8 -0
  16. package/dist/types/client/canvas/CommandPalette.d.ts +4 -0
  17. package/dist/types/client/canvas/ContextMenu.d.ts +24 -0
  18. package/dist/types/client/canvas/ContextPinBar.d.ts +1 -0
  19. package/dist/types/client/canvas/ContextPinHud.d.ts +1 -0
  20. package/dist/types/client/canvas/DockedNode.d.ts +4 -0
  21. package/dist/types/client/canvas/EdgeLayer.d.ts +8 -0
  22. package/dist/types/client/canvas/ExpandedNodeOverlay.d.ts +1 -0
  23. package/dist/types/client/canvas/FocusFieldLayer.d.ts +1 -0
  24. package/dist/types/client/canvas/Minimap.d.ts +23 -0
  25. package/dist/types/client/canvas/SelectionBar.d.ts +1 -0
  26. package/dist/types/client/canvas/ShortcutOverlay.d.ts +3 -0
  27. package/dist/types/client/canvas/SnapshotPanel.d.ts +7 -0
  28. package/dist/types/client/canvas/snap-guides.d.ts +23 -0
  29. package/dist/types/client/canvas/use-node-drag.d.ts +15 -0
  30. package/dist/types/client/canvas/use-node-resize.d.ts +15 -0
  31. package/dist/types/client/canvas/use-pan-zoom.d.ts +16 -0
  32. package/dist/types/client/ext-app/bridge.d.ts +161 -0
  33. package/dist/types/client/icons.d.ts +70 -0
  34. package/dist/types/client/index.d.ts +1 -0
  35. package/dist/types/client/nodes/ContextNode.d.ts +34 -0
  36. package/dist/types/client/nodes/ExtAppFrame.d.ts +18 -0
  37. package/dist/types/client/nodes/FileNode.d.ts +5 -0
  38. package/dist/types/client/nodes/GroupNode.d.ts +6 -0
  39. package/dist/types/client/nodes/ImageNode.d.ts +10 -0
  40. package/dist/types/client/nodes/InlineFormatBar.d.ts +7 -0
  41. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +14 -0
  42. package/dist/types/client/nodes/LedgerNode.d.ts +4 -0
  43. package/dist/types/client/nodes/MarkdownNode.d.ts +6 -0
  44. package/dist/types/client/nodes/McpAppNode.d.ts +4 -0
  45. package/dist/types/client/nodes/MdFormatBar.d.ts +8 -0
  46. package/dist/types/client/nodes/PromptNode.d.ts +5 -0
  47. package/dist/types/client/nodes/ResponseNode.d.ts +5 -0
  48. package/dist/types/client/nodes/StatusNode.d.ts +4 -0
  49. package/dist/types/client/nodes/StatusSummary.d.ts +4 -0
  50. package/dist/types/client/nodes/TraceNode.d.ts +4 -0
  51. package/dist/types/client/nodes/WebpageNode.d.ts +5 -0
  52. package/dist/types/client/nodes/image-warnings.d.ts +6 -0
  53. package/dist/types/client/nodes/inline-editor-commands.d.ts +11 -0
  54. package/dist/types/client/nodes/md-format.d.ts +25 -0
  55. package/dist/types/client/state/attention-bridge.d.ts +3 -0
  56. package/dist/types/client/state/attention-store.d.ts +25 -0
  57. package/dist/types/client/state/canvas-store.d.ts +74 -0
  58. package/dist/types/client/state/intent-bridge.d.ts +158 -0
  59. package/dist/types/client/state/sse-bridge.d.ts +5 -0
  60. package/dist/types/client/theme/tokens.d.ts +27 -0
  61. package/dist/types/client/types.d.ts +40 -0
  62. package/dist/types/client/utils/ext-app-tool-result.d.ts +1 -0
  63. package/dist/types/client/utils/placement.d.ts +1 -0
  64. package/dist/types/client/utils/platform.d.ts +2 -0
  65. package/dist/types/json-render/catalog.d.ts +815 -0
  66. package/dist/types/json-render/charts/components.d.ts +54 -0
  67. package/dist/types/json-render/charts/definitions.d.ts +103 -0
  68. package/dist/types/json-render/charts/extra-components.d.ts +58 -0
  69. package/dist/types/json-render/charts/extra-definitions.d.ts +181 -0
  70. package/dist/types/json-render/renderer/index.d.ts +16 -0
  71. package/dist/types/json-render/schema.d.ts +46 -0
  72. package/dist/types/json-render/server.d.ts +55 -0
  73. package/dist/types/mcp/server.d.ts +22 -0
  74. package/dist/types/server/agent-context.d.ts +21 -0
  75. package/dist/types/server/artifact-paths.d.ts +3 -0
  76. package/dist/types/server/canvas-operations.d.ts +154 -0
  77. package/dist/types/server/canvas-provenance.d.ts +13 -0
  78. package/dist/types/server/canvas-schema.d.ts +49 -0
  79. package/dist/types/server/canvas-serialization.d.ts +25 -0
  80. package/dist/types/server/canvas-state.d.ts +174 -0
  81. package/dist/types/server/canvas-validation.d.ts +33 -0
  82. package/dist/types/server/chart-template.d.ts +29 -0
  83. package/dist/types/server/code-graph.d.ts +67 -0
  84. package/dist/types/server/context-cards.d.ts +24 -0
  85. package/dist/types/server/diagram-presets.d.ts +28 -0
  86. package/dist/types/server/ext-app-call-registry.d.ts +16 -0
  87. package/dist/types/server/ext-app-tool-result.d.ts +1 -0
  88. package/dist/types/server/file-watcher.d.ts +16 -0
  89. package/dist/types/server/index.d.ts +243 -0
  90. package/dist/types/server/mcp-app-candidate.d.ts +25 -0
  91. package/dist/types/server/mcp-app-host.d.ts +65 -0
  92. package/dist/types/server/mcp-app-runtime.d.ts +47 -0
  93. package/dist/types/server/mutation-history.d.ts +105 -0
  94. package/dist/types/server/placement.d.ts +37 -0
  95. package/dist/types/server/server.d.ts +103 -0
  96. package/dist/types/server/spatial-analysis.d.ts +87 -0
  97. package/dist/types/server/trace-manager.d.ts +48 -0
  98. package/dist/types/server/web-artifacts.d.ts +50 -0
  99. package/dist/types/server/webpage-node.d.ts +25 -0
  100. package/dist/types/shared/auto-arrange.d.ts +29 -0
  101. package/dist/types/shared/ext-app-tool-result.d.ts +9 -0
  102. package/dist/types/shared/placement.d.ts +26 -0
  103. package/dist/types/shared/semantic-attention.d.ts +97 -0
  104. package/package.json +109 -0
  105. package/skills/data-analysis/SKILL.md +324 -0
  106. package/skills/doc-coauthoring/SKILL.md +375 -0
  107. package/skills/frontend-design/SKILL.md +45 -0
  108. package/skills/json-render-codegen/SKILL.md +112 -0
  109. package/skills/json-render-core/SKILL.md +265 -0
  110. package/skills/json-render-ink/SKILL.md +273 -0
  111. package/skills/json-render-mcp/SKILL.md +132 -0
  112. package/skills/json-render-react/SKILL.md +264 -0
  113. package/skills/json-render-shadcn/SKILL.md +159 -0
  114. package/skills/playwright-cli/SKILL.md +67 -0
  115. package/skills/pmx-canvas/SKILL.md +668 -0
  116. package/skills/pmx-canvas/evals/evals.json +186 -0
  117. package/skills/pmx-canvas-testing/SKILL.md +78 -0
  118. package/skills/published-consumer-e2e/SKILL.md +43 -0
  119. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +241 -0
  120. package/skills/web-artifacts-builder/SKILL.md +80 -0
  121. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +167 -0
  122. package/skills/web-artifacts-builder/scripts/init-artifact.sh +425 -0
  123. package/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  124. package/skills/web-design-guidelines/SKILL.md +39 -0
  125. package/src/cli/agent.ts +2144 -0
  126. package/src/cli/index.ts +622 -0
  127. package/src/cli/watch.ts +88 -0
  128. package/src/client/App.tsx +507 -0
  129. package/src/client/canvas/AttentionHistory.tsx +81 -0
  130. package/src/client/canvas/AttentionToast.tsx +19 -0
  131. package/src/client/canvas/CanvasNode.tsx +363 -0
  132. package/src/client/canvas/CanvasViewport.tsx +590 -0
  133. package/src/client/canvas/CommandPalette.tsx +302 -0
  134. package/src/client/canvas/ContextMenu.tsx +601 -0
  135. package/src/client/canvas/ContextPinBar.tsx +25 -0
  136. package/src/client/canvas/ContextPinHud.tsx +22 -0
  137. package/src/client/canvas/DockedNode.tsx +66 -0
  138. package/src/client/canvas/EdgeLayer.tsx +280 -0
  139. package/src/client/canvas/ExpandedNodeOverlay.tsx +260 -0
  140. package/src/client/canvas/FocusFieldLayer.tsx +107 -0
  141. package/src/client/canvas/Minimap.tsx +301 -0
  142. package/src/client/canvas/SelectionBar.tsx +69 -0
  143. package/src/client/canvas/ShortcutOverlay.tsx +69 -0
  144. package/src/client/canvas/SnapshotPanel.tsx +236 -0
  145. package/src/client/canvas/snap-guides.ts +170 -0
  146. package/src/client/canvas/use-node-drag.ts +51 -0
  147. package/src/client/canvas/use-node-resize.ts +59 -0
  148. package/src/client/canvas/use-pan-zoom.ts +191 -0
  149. package/src/client/ext-app/bridge.ts +542 -0
  150. package/src/client/icons.tsx +424 -0
  151. package/src/client/index.tsx +7 -0
  152. package/src/client/nodes/ContextNode.tsx +412 -0
  153. package/src/client/nodes/ExtAppFrame.tsx +509 -0
  154. package/src/client/nodes/FileNode.tsx +256 -0
  155. package/src/client/nodes/GroupNode.tsx +39 -0
  156. package/src/client/nodes/ImageNode.tsx +160 -0
  157. package/src/client/nodes/InlineFormatBar.tsx +169 -0
  158. package/src/client/nodes/InlineMarkdownEditor.tsx +123 -0
  159. package/src/client/nodes/LedgerNode.tsx +37 -0
  160. package/src/client/nodes/MarkdownNode.tsx +359 -0
  161. package/src/client/nodes/McpAppNode.tsx +85 -0
  162. package/src/client/nodes/MdFormatBar.tsx +109 -0
  163. package/src/client/nodes/PromptNode.tsx +597 -0
  164. package/src/client/nodes/ResponseNode.tsx +153 -0
  165. package/src/client/nodes/StatusNode.tsx +84 -0
  166. package/src/client/nodes/StatusSummary.tsx +38 -0
  167. package/src/client/nodes/TraceNode.tsx +120 -0
  168. package/src/client/nodes/WebpageNode.tsx +288 -0
  169. package/src/client/nodes/image-warnings.ts +95 -0
  170. package/src/client/nodes/inline-editor-commands.ts +37 -0
  171. package/src/client/nodes/md-format.ts +206 -0
  172. package/src/client/state/attention-bridge.ts +328 -0
  173. package/src/client/state/attention-store.ts +73 -0
  174. package/src/client/state/canvas-store.ts +631 -0
  175. package/src/client/state/intent-bridge.ts +315 -0
  176. package/src/client/state/sse-bridge.ts +965 -0
  177. package/src/client/theme/global.css +3173 -0
  178. package/src/client/theme/tokens.ts +72 -0
  179. package/src/client/types-shims.d.ts +5 -0
  180. package/src/client/types.ts +81 -0
  181. package/src/client/utils/ext-app-tool-result.ts +4 -0
  182. package/src/client/utils/placement.ts +4 -0
  183. package/src/client/utils/platform.ts +2 -0
  184. package/src/json-render/catalog.ts +256 -0
  185. package/src/json-render/charts/components.tsx +198 -0
  186. package/src/json-render/charts/definitions.ts +81 -0
  187. package/src/json-render/charts/extra-components.tsx +267 -0
  188. package/src/json-render/charts/extra-definitions.ts +145 -0
  189. package/src/json-render/renderer/index.css +174 -0
  190. package/src/json-render/renderer/index.tsx +86 -0
  191. package/src/json-render/schema.ts +62 -0
  192. package/src/json-render/server.ts +597 -0
  193. package/src/mcp/server.ts +1377 -0
  194. package/src/server/agent-context.ts +242 -0
  195. package/src/server/artifact-paths.ts +17 -0
  196. package/src/server/canvas-operations.ts +1279 -0
  197. package/src/server/canvas-provenance.ts +243 -0
  198. package/src/server/canvas-schema.ts +432 -0
  199. package/src/server/canvas-serialization.ts +95 -0
  200. package/src/server/canvas-state.ts +1134 -0
  201. package/src/server/canvas-validation.ts +114 -0
  202. package/src/server/chart-template.ts +449 -0
  203. package/src/server/code-graph.ts +370 -0
  204. package/src/server/context-cards.ts +31 -0
  205. package/src/server/diagram-presets.ts +71 -0
  206. package/src/server/ext-app-call-registry.ts +77 -0
  207. package/src/server/ext-app-tool-result.ts +4 -0
  208. package/src/server/file-watcher.ts +121 -0
  209. package/src/server/index.ts +647 -0
  210. package/src/server/mcp-app-candidate.ts +174 -0
  211. package/src/server/mcp-app-host.ts +814 -0
  212. package/src/server/mcp-app-runtime.ts +459 -0
  213. package/src/server/mutation-history.ts +350 -0
  214. package/src/server/placement.ts +125 -0
  215. package/src/server/server.ts +3846 -0
  216. package/src/server/spatial-analysis.ts +356 -0
  217. package/src/server/trace-manager.ts +333 -0
  218. package/src/server/web-artifacts/scripts/bundle-artifact.sh +167 -0
  219. package/src/server/web-artifacts/scripts/init-artifact.sh +426 -0
  220. package/src/server/web-artifacts/scripts/shadcn-components.tar.gz +0 -0
  221. package/src/server/web-artifacts.ts +442 -0
  222. package/src/server/webpage-node.ts +328 -0
  223. package/src/shared/auto-arrange.ts +439 -0
  224. package/src/shared/ext-app-tool-result.ts +76 -0
  225. package/src/shared/placement.ts +81 -0
  226. package/src/shared/semantic-attention.ts +598 -0
@@ -0,0 +1,542 @@
1
+ type JsonRpcId = string | number | null;
2
+
3
+ interface JsonRpcBase {
4
+ jsonrpc: '2.0';
5
+ }
6
+
7
+ interface JsonRpcRequestMessage extends JsonRpcBase {
8
+ id: JsonRpcId;
9
+ method: string;
10
+ params?: unknown;
11
+ }
12
+
13
+ interface JsonRpcNotificationMessage extends JsonRpcBase {
14
+ method: string;
15
+ params?: unknown;
16
+ }
17
+
18
+ interface JsonRpcSuccessResponse extends JsonRpcBase {
19
+ id: JsonRpcId;
20
+ result: unknown;
21
+ }
22
+
23
+ interface JsonRpcErrorObject {
24
+ code: number;
25
+ message: string;
26
+ data?: unknown;
27
+ }
28
+
29
+ interface JsonRpcErrorResponse extends JsonRpcBase {
30
+ id: JsonRpcId;
31
+ error: JsonRpcErrorObject;
32
+ }
33
+
34
+ type JsonRpcResponseMessage = JsonRpcSuccessResponse | JsonRpcErrorResponse;
35
+ type JsonRpcMessage =
36
+ | JsonRpcRequestMessage
37
+ | JsonRpcNotificationMessage
38
+ | JsonRpcResponseMessage;
39
+
40
+ type DisplayMode = 'inline' | 'fullscreen' | 'pip';
41
+
42
+ interface ImplementationInfo {
43
+ name: string;
44
+ version: string;
45
+ }
46
+
47
+ type HostCapabilities = Record<string, unknown>;
48
+
49
+ interface HostContext {
50
+ theme?: 'light' | 'dark';
51
+ platform?: string;
52
+ displayMode?: DisplayMode;
53
+ containerDimensions?: {
54
+ maxHeight?: number;
55
+ width?: number;
56
+ height?: number;
57
+ };
58
+ [key: string]: unknown;
59
+ }
60
+
61
+ interface HostOptions {
62
+ hostContext?: HostContext;
63
+ }
64
+
65
+ interface RequestExtra {
66
+ signal: AbortSignal;
67
+ sessionId?: string;
68
+ }
69
+
70
+ interface InitializeParams {
71
+ appCapabilities?: Record<string, unknown>;
72
+ appInfo?: ImplementationInfo;
73
+ protocolVersion?: string;
74
+ }
75
+
76
+ interface InitializeResult {
77
+ protocolVersion: string;
78
+ hostCapabilities: HostCapabilities;
79
+ hostInfo: ImplementationInfo;
80
+ hostContext: HostContext;
81
+ }
82
+
83
+ interface SizeChangedParams {
84
+ width?: number;
85
+ height?: number;
86
+ }
87
+
88
+ interface OpenLinkParams {
89
+ url: string;
90
+ }
91
+
92
+ interface DownloadFileParams {
93
+ contents?: unknown[];
94
+ }
95
+
96
+ interface RequestDisplayModeParams {
97
+ mode: DisplayMode;
98
+ }
99
+
100
+ interface RequestDisplayModeResult {
101
+ mode: DisplayMode;
102
+ }
103
+
104
+ interface ToolCallParams {
105
+ name: string;
106
+ arguments?: Record<string, unknown>;
107
+ }
108
+
109
+ interface ToolInputParams {
110
+ arguments?: Record<string, unknown>;
111
+ }
112
+
113
+ interface ToolCancelledParams {
114
+ reason?: string;
115
+ }
116
+
117
+ interface SandboxReadyParams {
118
+ html?: string;
119
+ sandbox?: string;
120
+ }
121
+
122
+ interface LoggingMessageParams {
123
+ level?: string;
124
+ logger?: string;
125
+ data?: unknown;
126
+ }
127
+
128
+ type RequestHandler<TParams = unknown, TResult = unknown> = (
129
+ params: TParams,
130
+ extra: RequestExtra,
131
+ ) => Promise<TResult> | TResult;
132
+
133
+ type NotificationHandler<TParams = unknown> = (params: TParams) => Promise<void> | void;
134
+
135
+ interface PendingRequest {
136
+ resolve: (value: unknown) => void;
137
+ reject: (reason?: unknown) => void;
138
+ }
139
+
140
+ function isRecord(value: unknown): value is Record<string, unknown> {
141
+ return typeof value === 'object' && value !== null;
142
+ }
143
+
144
+ function hasJsonRpcEnvelope(value: unknown): value is Record<string, unknown> & JsonRpcBase {
145
+ return isRecord(value) && value.jsonrpc === '2.0';
146
+ }
147
+
148
+ function isJsonRpcResponseMessage(value: unknown): value is JsonRpcResponseMessage {
149
+ return hasJsonRpcEnvelope(value) && 'id' in value && ('result' in value || 'error' in value);
150
+ }
151
+
152
+ function isJsonRpcRequestMessage(value: unknown): value is JsonRpcRequestMessage {
153
+ return hasJsonRpcEnvelope(value) && 'id' in value && typeof value.method === 'string';
154
+ }
155
+
156
+ function isJsonRpcNotificationMessage(value: unknown): value is JsonRpcNotificationMessage {
157
+ return hasJsonRpcEnvelope(value) && !('id' in value) && typeof value.method === 'string';
158
+ }
159
+
160
+ function parseJsonRpcMessage(value: unknown): JsonRpcMessage | null {
161
+ if (typeof value === 'string') {
162
+ try {
163
+ return parseJsonRpcMessage(JSON.parse(value));
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+ if (isJsonRpcResponseMessage(value)) return value;
169
+ if (isJsonRpcRequestMessage(value)) return value;
170
+ if (isJsonRpcNotificationMessage(value)) return value;
171
+ return null;
172
+ }
173
+
174
+ function isEqualJsonValue(left: unknown, right: unknown): boolean {
175
+ return JSON.stringify(left) === JSON.stringify(right);
176
+ }
177
+
178
+ function asDisplayMode(value: unknown): DisplayMode {
179
+ return value === 'fullscreen' || value === 'pip' ? value : 'inline';
180
+ }
181
+
182
+ const LATEST_PROTOCOL_VERSION = '2026-01-26';
183
+
184
+ export class PostMessageTransport {
185
+ private messageListener: ((event: MessageEvent) => void) | null = null;
186
+
187
+ onclose?: () => void;
188
+ onerror?: (error: Error) => void;
189
+ onmessage?: (message: JsonRpcMessage, extra?: { sessionId?: string }) => void;
190
+ sessionId?: string;
191
+ setProtocolVersion?: (version: string) => void;
192
+
193
+ constructor(
194
+ private readonly eventTarget: Window | undefined,
195
+ private readonly eventSource: MessageEventSource | null,
196
+ ) {}
197
+
198
+ async start(): Promise<void> {
199
+ if (this.messageListener) return;
200
+ this.messageListener = (event: MessageEvent) => {
201
+ if (event.source !== this.eventSource) return;
202
+ const message = parseJsonRpcMessage(event.data);
203
+ if (!message) return;
204
+ this.onmessage?.(message, this.sessionId ? { sessionId: this.sessionId } : undefined);
205
+ };
206
+ window.addEventListener('message', this.messageListener);
207
+ }
208
+
209
+ async send(message: JsonRpcMessage): Promise<void> {
210
+ if (!this.eventTarget) {
211
+ throw new Error('PostMessageTransport target is unavailable');
212
+ }
213
+ this.eventTarget.postMessage(message, '*');
214
+ }
215
+
216
+ async close(): Promise<void> {
217
+ if (this.messageListener) {
218
+ window.removeEventListener('message', this.messageListener);
219
+ this.messageListener = null;
220
+ }
221
+ this.onclose?.();
222
+ }
223
+ }
224
+
225
+ export class AppBridge {
226
+ private readonly requestHandlers = new Map<string, RequestHandler>();
227
+ private readonly notificationHandlers = new Map<string, NotificationHandler>();
228
+ private readonly pendingRequests = new Map<JsonRpcId, PendingRequest>();
229
+ private transport: PostMessageTransport | null = null;
230
+ private nextRequestId = 1;
231
+ private appCapabilities?: Record<string, unknown>;
232
+ private hostContext: HostContext;
233
+ private appInfo?: ImplementationInfo;
234
+
235
+ onping?: (params: Record<string, never>, extra: RequestExtra) => void;
236
+
237
+ constructor(
238
+ private readonly client: null,
239
+ private readonly hostInfo: ImplementationInfo,
240
+ private readonly capabilities: HostCapabilities,
241
+ options?: HostOptions,
242
+ ) {
243
+ this.client = client;
244
+ this.hostContext = options?.hostContext ?? {};
245
+ }
246
+
247
+ getAppCapabilities(): Record<string, unknown> | undefined {
248
+ return this.appCapabilities;
249
+ }
250
+
251
+ getAppVersion(): ImplementationInfo | undefined {
252
+ return this.appInfo;
253
+ }
254
+
255
+ private setNotificationHandler<TParams>(method: string, callback: NotificationHandler<TParams>): void {
256
+ this.notificationHandlers.set(method, (params) => callback(params as TParams));
257
+ }
258
+
259
+ private setRequestHandler<TParams, TResult>(
260
+ method: string,
261
+ callback: RequestHandler<TParams, TResult>,
262
+ ): void {
263
+ this.requestHandlers.set(method, (params, extra) => callback(params as TParams, extra));
264
+ }
265
+
266
+ set onsizechange(callback: (params: SizeChangedParams) => void) {
267
+ this.setNotificationHandler('ui/notifications/size-changed', callback);
268
+ }
269
+
270
+ set onsandboxready(callback: (params: Record<string, never>) => void) {
271
+ this.setNotificationHandler('ui/notifications/sandbox-proxy-ready', callback);
272
+ }
273
+
274
+ set oninitialized(callback: (params: Record<string, never>) => void) {
275
+ this.setNotificationHandler('ui/notifications/initialized', callback);
276
+ }
277
+
278
+ set onmessage(callback: RequestHandler<Record<string, unknown>, Record<string, unknown>>) {
279
+ this.setRequestHandler('ui/message', callback);
280
+ }
281
+
282
+ set onopenlink(callback: RequestHandler<OpenLinkParams, Record<string, unknown>>) {
283
+ this.setRequestHandler('ui/open-link', callback);
284
+ }
285
+
286
+ set ondownloadfile(callback: RequestHandler<DownloadFileParams, Record<string, unknown>>) {
287
+ this.setRequestHandler('ui/download-file', callback);
288
+ }
289
+
290
+ set onrequestteardown(callback: (params: Record<string, never>) => void) {
291
+ this.setNotificationHandler('ui/notifications/request-teardown', callback);
292
+ }
293
+
294
+ set onrequestdisplaymode(callback: RequestHandler<RequestDisplayModeParams, RequestDisplayModeResult>) {
295
+ this.setRequestHandler('ui/request-display-mode', callback);
296
+ }
297
+
298
+ set onloggingmessage(callback: (params: LoggingMessageParams) => void) {
299
+ this.setNotificationHandler('notifications/message', callback);
300
+ }
301
+
302
+ set onupdatemodelcontext(callback: RequestHandler<Record<string, unknown>, Record<string, unknown>>) {
303
+ this.setRequestHandler('ui/update-model-context', callback);
304
+ }
305
+
306
+ set oncalltool(callback: RequestHandler<ToolCallParams, unknown>) {
307
+ this.setRequestHandler('tools/call', callback);
308
+ }
309
+
310
+ set onlisttools(callback: RequestHandler<Record<string, unknown>, unknown>) {
311
+ this.setRequestHandler('tools/list', callback);
312
+ }
313
+
314
+ set onlistresources(callback: RequestHandler<Record<string, unknown>, unknown>) {
315
+ this.setRequestHandler('resources/list', callback);
316
+ }
317
+
318
+ set onlistresourcetemplates(callback: RequestHandler<Record<string, unknown>, unknown>) {
319
+ this.setRequestHandler('resources/templates/list', callback);
320
+ }
321
+
322
+ set onreadresource(callback: RequestHandler<Record<string, unknown>, unknown>) {
323
+ this.setRequestHandler('resources/read', callback);
324
+ }
325
+
326
+ set onlistprompts(callback: RequestHandler<Record<string, unknown>, unknown>) {
327
+ this.setRequestHandler('prompts/list', callback);
328
+ }
329
+
330
+ getCapabilities(): HostCapabilities {
331
+ return this.capabilities;
332
+ }
333
+
334
+ setHostContext(hostContext: HostContext): void {
335
+ const changed: HostContext = {};
336
+ let hasChanges = false;
337
+
338
+ for (const [key, value] of Object.entries(hostContext)) {
339
+ if (isEqualJsonValue(this.hostContext[key], value)) continue;
340
+ changed[key] = value;
341
+ hasChanges = true;
342
+ }
343
+
344
+ this.hostContext = hostContext;
345
+ if (hasChanges) {
346
+ void this.sendHostContextChange(changed);
347
+ }
348
+ }
349
+
350
+ sendHostContextChange(params: HostContext): Promise<void> {
351
+ return this.notification('ui/notifications/host-context-changed', params);
352
+ }
353
+
354
+ sendToolInput(params: ToolInputParams): Promise<void> {
355
+ return this.notification('ui/notifications/tool-input', params);
356
+ }
357
+
358
+ sendToolInputPartial(params: ToolInputParams): Promise<void> {
359
+ return this.notification('ui/notifications/tool-input-partial', params);
360
+ }
361
+
362
+ sendToolResult(params: unknown): Promise<void> {
363
+ return this.notification('ui/notifications/tool-result', params);
364
+ }
365
+
366
+ sendToolCancelled(params: ToolCancelledParams): Promise<void> {
367
+ return this.notification('ui/notifications/tool-cancelled', params);
368
+ }
369
+
370
+ sendSandboxResourceReady(params: SandboxReadyParams): Promise<void> {
371
+ return this.notification('ui/notifications/sandbox-resource-ready', params);
372
+ }
373
+
374
+ teardownResource(params: Record<string, never>): Promise<Record<string, unknown>> {
375
+ return this.request('ui/resource-teardown', params) as Promise<Record<string, unknown>>;
376
+ }
377
+
378
+ sendResourceTeardown = this.teardownResource.bind(this);
379
+
380
+ sendToolListChanged(params: Record<string, unknown> = {}): Promise<void> {
381
+ return this.notification('notifications/tools/list_changed', params);
382
+ }
383
+
384
+ sendResourceListChanged(params: Record<string, unknown> = {}): Promise<void> {
385
+ return this.notification('notifications/resources/list_changed', params);
386
+ }
387
+
388
+ sendPromptListChanged(params: Record<string, unknown> = {}): Promise<void> {
389
+ return this.notification('notifications/prompts/list_changed', params);
390
+ }
391
+
392
+ async connect(transport: PostMessageTransport): Promise<void> {
393
+ if (this.transport) {
394
+ throw new Error('AppBridge is already connected. Call close() before connecting again.');
395
+ }
396
+
397
+ this.transport = transport;
398
+ this.transport.onmessage = (message, extra) => {
399
+ void this.handleIncomingMessage(message, extra?.sessionId);
400
+ };
401
+ this.transport.onclose = () => {
402
+ if (this.transport === transport) {
403
+ this.transport = null;
404
+ }
405
+ };
406
+ await this.transport.start();
407
+ }
408
+
409
+ private async handleIncomingMessage(message: JsonRpcMessage, sessionId?: string): Promise<void> {
410
+ if (isJsonRpcResponseMessage(message)) {
411
+ const pending = this.pendingRequests.get(message.id);
412
+ if (!pending) return;
413
+ this.pendingRequests.delete(message.id);
414
+ if ('error' in message) {
415
+ pending.reject(new Error(message.error.message));
416
+ } else {
417
+ pending.resolve(message.result);
418
+ }
419
+ return;
420
+ }
421
+
422
+ if (isJsonRpcNotificationMessage(message)) {
423
+ const handler = this.notificationHandlers.get(message.method);
424
+ if (handler) {
425
+ await handler(message.params as never);
426
+ }
427
+ return;
428
+ }
429
+
430
+ if (!isJsonRpcRequestMessage(message)) {
431
+ return;
432
+ }
433
+
434
+ const requestMessage: JsonRpcRequestMessage = message;
435
+
436
+ const extra: RequestExtra = {
437
+ signal: new AbortController().signal,
438
+ sessionId,
439
+ };
440
+
441
+ try {
442
+ const result = await this.handleRequest(requestMessage, extra);
443
+ await this.sendSuccess(requestMessage.id, result);
444
+ } catch (error) {
445
+ const messageText = error instanceof Error ? error.message : String(error);
446
+ await this.sendError(requestMessage.id, -32000, messageText);
447
+ }
448
+ }
449
+
450
+ private async handleRequest(
451
+ message: JsonRpcRequestMessage,
452
+ extra: RequestExtra,
453
+ ): Promise<unknown> {
454
+ if (message.method === 'ui/initialize') {
455
+ return this.handleInitialize(message.params);
456
+ }
457
+
458
+ if (message.method === 'ping') {
459
+ this.onping?.((message.params as Record<string, never> | undefined) ?? {}, extra);
460
+ return {};
461
+ }
462
+
463
+ if (message.method === 'ui/request-display-mode' && !this.requestHandlers.has(message.method)) {
464
+ return {
465
+ mode: asDisplayMode(this.hostContext.displayMode),
466
+ } satisfies RequestDisplayModeResult;
467
+ }
468
+
469
+ const handler = this.requestHandlers.get(message.method);
470
+ if (!handler) {
471
+ throw new Error(`Unsupported ext-app method: ${message.method}`);
472
+ }
473
+
474
+ return handler(message.params as never, extra);
475
+ }
476
+
477
+ private handleInitialize(params: unknown): InitializeResult {
478
+ const safeParams = isRecord(params) ? (params as InitializeParams) : {};
479
+ const requestedVersion =
480
+ typeof safeParams.protocolVersion === 'string' ? safeParams.protocolVersion : LATEST_PROTOCOL_VERSION;
481
+ this.appCapabilities = safeParams.appCapabilities;
482
+ this.appInfo = safeParams.appInfo;
483
+ this.transport?.setProtocolVersion?.(requestedVersion);
484
+
485
+ return {
486
+ protocolVersion: requestedVersion,
487
+ hostCapabilities: this.capabilities,
488
+ hostInfo: this.hostInfo,
489
+ hostContext: this.hostContext,
490
+ };
491
+ }
492
+
493
+ private async sendSuccess(id: JsonRpcId, result: unknown): Promise<void> {
494
+ if (!this.transport) return;
495
+ await this.transport.send({
496
+ jsonrpc: '2.0',
497
+ id,
498
+ result,
499
+ });
500
+ }
501
+
502
+ private async sendError(id: JsonRpcId, code: number, message: string, data?: unknown): Promise<void> {
503
+ if (!this.transport) return;
504
+ await this.transport.send({
505
+ jsonrpc: '2.0',
506
+ id,
507
+ error: {
508
+ code,
509
+ message,
510
+ data,
511
+ },
512
+ });
513
+ }
514
+
515
+ private async notification(method: string, params: unknown): Promise<void> {
516
+ if (!this.transport) {
517
+ throw new Error('AppBridge is not connected');
518
+ }
519
+ await this.transport.send({
520
+ jsonrpc: '2.0',
521
+ method,
522
+ params,
523
+ });
524
+ }
525
+
526
+ private async request(method: string, params: unknown): Promise<unknown> {
527
+ if (!this.transport) {
528
+ throw new Error('AppBridge is not connected');
529
+ }
530
+ const id = this.nextRequestId++;
531
+ const promise = new Promise<unknown>((resolve, reject) => {
532
+ this.pendingRequests.set(id, { resolve, reject });
533
+ });
534
+ await this.transport.send({
535
+ jsonrpc: '2.0',
536
+ id,
537
+ method,
538
+ params,
539
+ });
540
+ return promise;
541
+ }
542
+ }