freesail 0.0.1 → 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 (54) hide show
  1. package/README.md +190 -5
  2. package/docs/A2UX_Protocol.md +183 -0
  3. package/docs/Agents.md +218 -0
  4. package/docs/Architecture.md +285 -0
  5. package/docs/CatalogReference.md +377 -0
  6. package/docs/GettingStarted.md +230 -0
  7. package/examples/demo/package.json +21 -0
  8. package/examples/demo/public/index.html +381 -0
  9. package/examples/demo/server.js +253 -0
  10. package/package.json +38 -5
  11. package/packages/core/package.json +48 -0
  12. package/packages/core/src/functions.ts +403 -0
  13. package/packages/core/src/index.ts +214 -0
  14. package/packages/core/src/parser.ts +270 -0
  15. package/packages/core/src/protocol.ts +254 -0
  16. package/packages/core/src/store.ts +452 -0
  17. package/packages/core/src/transport.ts +439 -0
  18. package/packages/core/src/types.ts +209 -0
  19. package/packages/core/tsconfig.json +10 -0
  20. package/packages/lit-ui/package.json +44 -0
  21. package/packages/lit-ui/src/catalogs/standard/catalog.json +405 -0
  22. package/packages/lit-ui/src/catalogs/standard/elements/Badge.ts +96 -0
  23. package/packages/lit-ui/src/catalogs/standard/elements/Button.ts +147 -0
  24. package/packages/lit-ui/src/catalogs/standard/elements/Card.ts +78 -0
  25. package/packages/lit-ui/src/catalogs/standard/elements/Checkbox.ts +94 -0
  26. package/packages/lit-ui/src/catalogs/standard/elements/Column.ts +66 -0
  27. package/packages/lit-ui/src/catalogs/standard/elements/Divider.ts +59 -0
  28. package/packages/lit-ui/src/catalogs/standard/elements/Image.ts +54 -0
  29. package/packages/lit-ui/src/catalogs/standard/elements/Input.ts +125 -0
  30. package/packages/lit-ui/src/catalogs/standard/elements/Progress.ts +79 -0
  31. package/packages/lit-ui/src/catalogs/standard/elements/Row.ts +68 -0
  32. package/packages/lit-ui/src/catalogs/standard/elements/Select.ts +110 -0
  33. package/packages/lit-ui/src/catalogs/standard/elements/Spacer.ts +37 -0
  34. package/packages/lit-ui/src/catalogs/standard/elements/Spinner.ts +76 -0
  35. package/packages/lit-ui/src/catalogs/standard/elements/Text.ts +86 -0
  36. package/packages/lit-ui/src/catalogs/standard/elements/index.ts +18 -0
  37. package/packages/lit-ui/src/catalogs/standard/index.ts +17 -0
  38. package/packages/lit-ui/src/index.ts +84 -0
  39. package/packages/lit-ui/src/renderer.ts +211 -0
  40. package/packages/lit-ui/src/types.ts +49 -0
  41. package/packages/lit-ui/src/utils/define-props.ts +157 -0
  42. package/packages/lit-ui/src/utils/index.ts +2 -0
  43. package/packages/lit-ui/src/utils/registry.ts +139 -0
  44. package/packages/lit-ui/tsconfig.json +11 -0
  45. package/packages/server/package.json +61 -0
  46. package/packages/server/src/adapters/index.ts +5 -0
  47. package/packages/server/src/adapters/langchain.ts +175 -0
  48. package/packages/server/src/adapters/openai.ts +209 -0
  49. package/packages/server/src/catalog-loader.ts +311 -0
  50. package/packages/server/src/index.ts +142 -0
  51. package/packages/server/src/stream.ts +329 -0
  52. package/packages/server/tsconfig.json +11 -0
  53. package/tsconfig.base.json +23 -0
  54. package/index.js +0 -3
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Transport Layer
3
+ *
4
+ * Handles SSE connections with automatic reconnection,
5
+ * action queueing, and offline support.
6
+ */
7
+
8
+ import type { A2UXMessage, UserActionMessage, WatchSurfaceResponseMessage } from './types.js';
9
+ import { SSEParser } from './parser.js';
10
+
11
+ // =============================================================================
12
+ // Types
13
+ // =============================================================================
14
+
15
+ export interface TransportOptions {
16
+ /** Base URL for the SSE endpoint */
17
+ url: string;
18
+ /** Custom headers for requests */
19
+ headers?: Record<string, string>;
20
+ /** Enable automatic reconnection (default: true) */
21
+ autoReconnect?: boolean;
22
+ /** Initial reconnect delay in ms (default: 1000) */
23
+ reconnectDelay?: number;
24
+ /** Maximum reconnect delay in ms (default: 30000) */
25
+ maxReconnectDelay?: number;
26
+ /** Maximum reconnection attempts (default: Infinity) */
27
+ maxReconnectAttempts?: number;
28
+ /** Enable offline action queueing (default: true) */
29
+ offlineQueue?: boolean;
30
+ /** Debug mode */
31
+ debug?: boolean;
32
+ }
33
+
34
+ export interface TransportEventMap {
35
+ 'message': A2UXMessage;
36
+ 'connected': { url: string };
37
+ 'disconnected': { reason: string };
38
+ 'reconnecting': { attempt: number; delay: number };
39
+ 'error': { message: string; originalError?: Error };
40
+ 'queuedAction': { action: UserActionMessage | WatchSurfaceResponseMessage };
41
+ 'flushedActions': { count: number };
42
+ }
43
+
44
+ export type TransportEventHandler<K extends keyof TransportEventMap> =
45
+ (data: TransportEventMap[K]) => void;
46
+
47
+ export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
48
+
49
+ // =============================================================================
50
+ // IndexedDB Action Queue (for offline support)
51
+ // =============================================================================
52
+
53
+ const DB_NAME = 'freesail_action_queue';
54
+ const STORE_NAME = 'actions';
55
+
56
+ class ActionQueue {
57
+ private db: IDBDatabase | null = null;
58
+ private memoryQueue: (UserActionMessage | WatchSurfaceResponseMessage)[] = [];
59
+
60
+ async init(): Promise<void> {
61
+ if (typeof indexedDB === 'undefined') {
62
+ // Fall back to memory queue in non-browser environments
63
+ return;
64
+ }
65
+
66
+ return new Promise((resolve, reject) => {
67
+ const request = indexedDB.open(DB_NAME, 1);
68
+
69
+ request.onerror = () => reject(request.error);
70
+
71
+ request.onsuccess = () => {
72
+ this.db = request.result;
73
+ resolve();
74
+ };
75
+
76
+ request.onupgradeneeded = (event) => {
77
+ const db = (event.target as IDBOpenDBRequest).result;
78
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
79
+ db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
80
+ }
81
+ };
82
+ });
83
+ }
84
+
85
+ async enqueue(action: UserActionMessage | WatchSurfaceResponseMessage): Promise<void> {
86
+ if (!this.db) {
87
+ this.memoryQueue.push(action);
88
+ return;
89
+ }
90
+
91
+ return new Promise((resolve, reject) => {
92
+ const transaction = this.db!.transaction([STORE_NAME], 'readwrite');
93
+ const store = transaction.objectStore(STORE_NAME);
94
+ const request = store.add({ action, timestamp: Date.now() });
95
+
96
+ request.onsuccess = () => resolve();
97
+ request.onerror = () => reject(request.error);
98
+ });
99
+ }
100
+
101
+ async dequeueAll(): Promise<(UserActionMessage | WatchSurfaceResponseMessage)[]> {
102
+ if (!this.db) {
103
+ const actions = [...this.memoryQueue];
104
+ this.memoryQueue = [];
105
+ return actions;
106
+ }
107
+
108
+ return new Promise((resolve, reject) => {
109
+ const transaction = this.db!.transaction([STORE_NAME], 'readwrite');
110
+ const store = transaction.objectStore(STORE_NAME);
111
+ const request = store.getAll();
112
+
113
+ request.onsuccess = () => {
114
+ const items = request.result as { id: number; action: UserActionMessage | WatchSurfaceResponseMessage }[];
115
+ const actions = items.map(item => item.action);
116
+
117
+ // Clear the store
118
+ const clearRequest = store.clear();
119
+ clearRequest.onsuccess = () => resolve(actions);
120
+ clearRequest.onerror = () => reject(clearRequest.error);
121
+ };
122
+
123
+ request.onerror = () => reject(request.error);
124
+ });
125
+ }
126
+
127
+ async getCount(): Promise<number> {
128
+ if (!this.db) {
129
+ return this.memoryQueue.length;
130
+ }
131
+
132
+ return new Promise((resolve, reject) => {
133
+ const transaction = this.db!.transaction([STORE_NAME], 'readonly');
134
+ const store = transaction.objectStore(STORE_NAME);
135
+ const request = store.count();
136
+
137
+ request.onsuccess = () => resolve(request.result);
138
+ request.onerror = () => reject(request.error);
139
+ });
140
+ }
141
+ }
142
+
143
+ // =============================================================================
144
+ // SSE Transport
145
+ // =============================================================================
146
+
147
+ /**
148
+ * FreesailTransport manages SSE connections with resilience features:
149
+ * - Automatic reconnection with exponential backoff
150
+ * - Offline action queueing via IndexedDB
151
+ * - Connection state management
152
+ */
153
+ export class FreesailTransport {
154
+ private options: Required<TransportOptions>;
155
+ private eventSource: EventSource | null = null;
156
+ private parser: SSEParser;
157
+ private actionQueue: ActionQueue;
158
+ private listeners: Map<keyof TransportEventMap, Set<TransportEventHandler<keyof TransportEventMap>>> = new Map();
159
+
160
+ private _state: ConnectionState = 'disconnected';
161
+ private reconnectAttempt: number = 0;
162
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
163
+
164
+ constructor(options: TransportOptions) {
165
+ this.options = {
166
+ url: options.url,
167
+ headers: options.headers ?? {},
168
+ autoReconnect: options.autoReconnect ?? true,
169
+ reconnectDelay: options.reconnectDelay ?? 1000,
170
+ maxReconnectDelay: options.maxReconnectDelay ?? 30000,
171
+ maxReconnectAttempts: options.maxReconnectAttempts ?? Infinity,
172
+ offlineQueue: options.offlineQueue ?? true,
173
+ debug: options.debug ?? false,
174
+ };
175
+
176
+ this.parser = new SSEParser();
177
+ this.actionQueue = new ActionQueue();
178
+ }
179
+
180
+ // ===========================================================================
181
+ // Connection Management
182
+ // ===========================================================================
183
+
184
+ /**
185
+ * Connect to the SSE endpoint
186
+ */
187
+ async connect(): Promise<void> {
188
+ if (this._state === 'connected' || this._state === 'connecting') {
189
+ return;
190
+ }
191
+
192
+ // Initialize action queue
193
+ if (this.options.offlineQueue) {
194
+ await this.actionQueue.init();
195
+ }
196
+
197
+ this.setState('connecting');
198
+ this.createEventSource();
199
+ }
200
+
201
+ /**
202
+ * Disconnect from the SSE endpoint
203
+ */
204
+ disconnect(): void {
205
+ this.cancelReconnect();
206
+ this.closeEventSource();
207
+ this.setState('disconnected');
208
+ this.emit('disconnected', { reason: 'manual' });
209
+ }
210
+
211
+ /**
212
+ * Get current connection state
213
+ */
214
+ get state(): ConnectionState {
215
+ return this._state;
216
+ }
217
+
218
+ /**
219
+ * Check if connected
220
+ */
221
+ get isConnected(): boolean {
222
+ return this._state === 'connected';
223
+ }
224
+
225
+ // ===========================================================================
226
+ // Sending Messages
227
+ // ===========================================================================
228
+
229
+ /**
230
+ * Send a user action to the server
231
+ */
232
+ async sendAction(action: UserActionMessage): Promise<void> {
233
+ await this.sendMessage(action);
234
+ }
235
+
236
+ /**
237
+ * Send a watch surface response
238
+ */
239
+ async sendWatchResponse(response: WatchSurfaceResponseMessage): Promise<void> {
240
+ await this.sendMessage(response);
241
+ }
242
+
243
+ private async sendMessage(message: UserActionMessage | WatchSurfaceResponseMessage): Promise<void> {
244
+ if (!this.isConnected) {
245
+ if (this.options.offlineQueue) {
246
+ await this.actionQueue.enqueue(message);
247
+ this.emit('queuedAction', { action: message });
248
+ this.log(`Action queued for later: ${JSON.stringify(message)}`);
249
+ } else {
250
+ throw new Error('Not connected and offline queue is disabled');
251
+ }
252
+ return;
253
+ }
254
+
255
+ try {
256
+ const response = await fetch(this.options.url, {
257
+ method: 'POST',
258
+ headers: {
259
+ 'Content-Type': 'application/json',
260
+ ...this.options.headers,
261
+ },
262
+ body: JSON.stringify(message),
263
+ });
264
+
265
+ if (!response.ok) {
266
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
267
+ }
268
+ } catch (error) {
269
+ const err = error instanceof Error ? error : new Error(String(error));
270
+
271
+ // Queue for retry if offline
272
+ if (this.options.offlineQueue) {
273
+ await this.actionQueue.enqueue(message);
274
+ this.emit('queuedAction', { action: message });
275
+ }
276
+
277
+ this.emit('error', { message: err.message, originalError: err });
278
+ throw err;
279
+ }
280
+ }
281
+
282
+ // ===========================================================================
283
+ // Event Handling
284
+ // ===========================================================================
285
+
286
+ on<K extends keyof TransportEventMap>(
287
+ event: K,
288
+ handler: TransportEventHandler<K>
289
+ ): () => void {
290
+ if (!this.listeners.has(event)) {
291
+ this.listeners.set(event, new Set());
292
+ }
293
+ this.listeners.get(event)!.add(handler as TransportEventHandler<keyof TransportEventMap>);
294
+
295
+ return () => this.off(event, handler);
296
+ }
297
+
298
+ off<K extends keyof TransportEventMap>(
299
+ event: K,
300
+ handler: TransportEventHandler<K>
301
+ ): void {
302
+ this.listeners.get(event)?.delete(handler as TransportEventHandler<keyof TransportEventMap>);
303
+ }
304
+
305
+ private emit<K extends keyof TransportEventMap>(event: K, data: TransportEventMap[K]): void {
306
+ this.listeners.get(event)?.forEach(handler => {
307
+ try {
308
+ (handler as TransportEventHandler<K>)(data);
309
+ } catch (error) {
310
+ console.error(`Error in transport event handler for ${event}:`, error);
311
+ }
312
+ });
313
+ }
314
+
315
+ // ===========================================================================
316
+ // Private: EventSource Management
317
+ // ===========================================================================
318
+
319
+ private createEventSource(): void {
320
+ try {
321
+ this.eventSource = new EventSource(this.options.url);
322
+
323
+ this.eventSource.onopen = () => {
324
+ this.setState('connected');
325
+ this.reconnectAttempt = 0;
326
+ this.emit('connected', { url: this.options.url });
327
+ this.log('Connected to SSE endpoint');
328
+
329
+ // Flush queued actions
330
+ this.flushQueue();
331
+ };
332
+
333
+ this.eventSource.onmessage = (event) => {
334
+ const messages = this.parser.feed(event.data);
335
+ for (const message of messages) {
336
+ this.emit('message', message);
337
+ }
338
+ };
339
+
340
+ this.eventSource.onerror = () => {
341
+ this.closeEventSource();
342
+
343
+ if (this.options.autoReconnect &&
344
+ this.reconnectAttempt < this.options.maxReconnectAttempts) {
345
+ this.scheduleReconnect();
346
+ } else {
347
+ this.setState('disconnected');
348
+ this.emit('disconnected', { reason: 'error' });
349
+ }
350
+ };
351
+ } catch (error) {
352
+ const err = error instanceof Error ? error : new Error(String(error));
353
+ this.emit('error', { message: err.message, originalError: err });
354
+ this.setState('disconnected');
355
+ }
356
+ }
357
+
358
+ private closeEventSource(): void {
359
+ if (this.eventSource) {
360
+ this.eventSource.close();
361
+ this.eventSource = null;
362
+ }
363
+ this.parser.reset();
364
+ }
365
+
366
+ // ===========================================================================
367
+ // Private: Reconnection Logic
368
+ // ===========================================================================
369
+
370
+ private scheduleReconnect(): void {
371
+ this.setState('reconnecting');
372
+ this.reconnectAttempt++;
373
+
374
+ // Exponential backoff with jitter
375
+ const baseDelay = Math.min(
376
+ this.options.reconnectDelay * Math.pow(2, this.reconnectAttempt - 1),
377
+ this.options.maxReconnectDelay
378
+ );
379
+ const jitter = Math.random() * 0.3 * baseDelay;
380
+ const delay = baseDelay + jitter;
381
+
382
+ this.emit('reconnecting', { attempt: this.reconnectAttempt, delay });
383
+ this.log(`Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempt})`);
384
+
385
+ this.reconnectTimer = setTimeout(() => {
386
+ this.createEventSource();
387
+ }, delay);
388
+ }
389
+
390
+ private cancelReconnect(): void {
391
+ if (this.reconnectTimer) {
392
+ clearTimeout(this.reconnectTimer);
393
+ this.reconnectTimer = null;
394
+ }
395
+ }
396
+
397
+ // ===========================================================================
398
+ // Private: Queue Management
399
+ // ===========================================================================
400
+
401
+ private async flushQueue(): Promise<void> {
402
+ if (!this.options.offlineQueue) return;
403
+
404
+ try {
405
+ const actions = await this.actionQueue.dequeueAll();
406
+
407
+ if (actions.length > 0) {
408
+ this.log(`Flushing ${actions.length} queued actions`);
409
+
410
+ for (const action of actions) {
411
+ try {
412
+ await this.sendMessage(action);
413
+ } catch (error) {
414
+ // Re-queue on failure
415
+ this.log(`Failed to flush action, re-queueing: ${error}`);
416
+ }
417
+ }
418
+
419
+ this.emit('flushedActions', { count: actions.length });
420
+ }
421
+ } catch (error) {
422
+ console.error('Failed to flush action queue:', error);
423
+ }
424
+ }
425
+
426
+ // ===========================================================================
427
+ // Private: State & Logging
428
+ // ===========================================================================
429
+
430
+ private setState(state: ConnectionState): void {
431
+ this._state = state;
432
+ }
433
+
434
+ private log(message: string): void {
435
+ if (this.options.debug) {
436
+ console.log(`[FreesailTransport] ${message}`);
437
+ }
438
+ }
439
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * A2UX Protocol Types
3
+ *
4
+ * Defines the complete type system for the A2UX protocol,
5
+ * an extension of Google A2UI for generative UI communication.
6
+ */
7
+
8
+ // =============================================================================
9
+ // Component Types
10
+ // =============================================================================
11
+
12
+ /**
13
+ * Base component definition used in updateComponents messages
14
+ */
15
+ export interface A2UXComponent {
16
+ /** Unique identifier for this component within the surface */
17
+ id: string;
18
+ /** The component type from the catalog (e.g., 'Text', 'Button', 'Column') */
19
+ component: string;
20
+ /** Child component IDs (for container components) */
21
+ children?: string[];
22
+ /** Any additional properties defined in the catalog */
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ // =============================================================================
27
+ // Server to Client Messages (Downstream)
28
+ // =============================================================================
29
+
30
+ /**
31
+ * createSurface - Initializes a UI container and loads a specific Catalog
32
+ */
33
+ export interface CreateSurfaceMessage {
34
+ createSurface: {
35
+ /** Unique identifier for the UI surface */
36
+ surfaceId: string;
37
+ /** Catalog identifier for component definitions */
38
+ catalogId: string;
39
+ };
40
+ }
41
+
42
+ /**
43
+ * updateComponents - Streams structural UI definitions
44
+ */
45
+ export interface UpdateComponentsMessage {
46
+ updateComponents: {
47
+ /** Target surface ID */
48
+ surfaceId: string;
49
+ /** Array of component definitions (flat adjacency list) */
50
+ components: A2UXComponent[];
51
+ };
52
+ }
53
+
54
+ /**
55
+ * updateDataModel - Pushes data/state changes to the UI
56
+ */
57
+ export interface UpdateDataModelMessage {
58
+ updateDataModel: {
59
+ /** Target surface ID */
60
+ surfaceId: string;
61
+ /** JSON Pointer path for partial updates (optional) */
62
+ path?: string;
63
+ /** Operation type: 'add', 'replace', or 'remove' */
64
+ op?: 'add' | 'replace' | 'remove';
65
+ /** The data value (required for 'add' and 'replace') */
66
+ value?: unknown;
67
+ };
68
+ }
69
+
70
+ /**
71
+ * deleteSurface - Removes a surface and all its components
72
+ */
73
+ export interface DeleteSurfaceMessage {
74
+ deleteSurface: {
75
+ /** Surface ID to delete */
76
+ surfaceId: string;
77
+ };
78
+ }
79
+
80
+ /**
81
+ * watchSurface - Configures automatic state reporting
82
+ */
83
+ export interface WatchSurfaceMessage {
84
+ watchSurface: {
85
+ /** Target surface ID */
86
+ surfaceId: string;
87
+ /** Reporting interval in seconds (default: 10) */
88
+ interval?: number;
89
+ /** Watch expiration in seconds (default: 300) */
90
+ expiresIn?: number;
91
+ };
92
+ }
93
+
94
+ /**
95
+ * unwatchSurface - Removes a surface watcher
96
+ */
97
+ export interface UnwatchSurfaceMessage {
98
+ unwatchSurface: {
99
+ /** Surface ID to unwatch */
100
+ surfaceId: string;
101
+ };
102
+ }
103
+
104
+ // =============================================================================
105
+ // Client to Server Messages (Upstream)
106
+ // =============================================================================
107
+
108
+ /**
109
+ * userAction - Reports user interactions to the server
110
+ */
111
+ export interface UserActionMessage {
112
+ userAction: {
113
+ /** Source surface ID */
114
+ surfaceId: string;
115
+ /** Semantic action label (e.g., 'submit_form', 'button_click') */
116
+ action: string;
117
+ /** Full context/state relevant to the action */
118
+ context: Record<string, unknown>;
119
+ };
120
+ }
121
+
122
+ /**
123
+ * watchSurfaceResponse - Automatic state report from watch
124
+ */
125
+ export interface WatchSurfaceResponseMessage {
126
+ watchSurfaceResponse: {
127
+ /** Source surface ID */
128
+ surfaceId: string;
129
+ /** Current data model state (JSON Pointer paths as keys) */
130
+ data: Record<string, unknown>;
131
+ };
132
+ }
133
+
134
+ // =============================================================================
135
+ // Union Types
136
+ // =============================================================================
137
+
138
+ /** All server-to-client message types */
139
+ export type ServerToClientMessage =
140
+ | CreateSurfaceMessage
141
+ | UpdateComponentsMessage
142
+ | UpdateDataModelMessage
143
+ | DeleteSurfaceMessage
144
+ | WatchSurfaceMessage
145
+ | UnwatchSurfaceMessage;
146
+
147
+ /** All client-to-server message types */
148
+ export type ClientToServerMessage =
149
+ | UserActionMessage
150
+ | WatchSurfaceResponseMessage;
151
+
152
+ /** Any A2UX protocol message */
153
+ export type A2UXMessage = ServerToClientMessage | ClientToServerMessage;
154
+
155
+ // =============================================================================
156
+ // Type Guards
157
+ // =============================================================================
158
+
159
+ export function isCreateSurface(msg: A2UXMessage): msg is CreateSurfaceMessage {
160
+ return 'createSurface' in msg;
161
+ }
162
+
163
+ export function isUpdateComponents(msg: A2UXMessage): msg is UpdateComponentsMessage {
164
+ return 'updateComponents' in msg;
165
+ }
166
+
167
+ export function isUpdateDataModel(msg: A2UXMessage): msg is UpdateDataModelMessage {
168
+ return 'updateDataModel' in msg;
169
+ }
170
+
171
+ export function isDeleteSurface(msg: A2UXMessage): msg is DeleteSurfaceMessage {
172
+ return 'deleteSurface' in msg;
173
+ }
174
+
175
+ export function isWatchSurface(msg: A2UXMessage): msg is WatchSurfaceMessage {
176
+ return 'watchSurface' in msg;
177
+ }
178
+
179
+ export function isUnwatchSurface(msg: A2UXMessage): msg is UnwatchSurfaceMessage {
180
+ return 'unwatchSurface' in msg;
181
+ }
182
+
183
+ export function isUserAction(msg: A2UXMessage): msg is UserActionMessage {
184
+ return 'userAction' in msg;
185
+ }
186
+
187
+ export function isWatchSurfaceResponse(msg: A2UXMessage): msg is WatchSurfaceResponseMessage {
188
+ return 'watchSurfaceResponse' in msg;
189
+ }
190
+
191
+ // =============================================================================
192
+ // Message Utilities
193
+ // =============================================================================
194
+
195
+ /**
196
+ * Gets the message type from an A2UX message
197
+ */
198
+ export function getMessageType(msg: A2UXMessage): string {
199
+ return Object.keys(msg)[0];
200
+ }
201
+
202
+ /**
203
+ * Gets the surfaceId from any A2UX message
204
+ */
205
+ export function getSurfaceId(msg: A2UXMessage): string {
206
+ const key = getMessageType(msg);
207
+ const payload = (msg as unknown as Record<string, { surfaceId: string }>)[key];
208
+ return payload.surfaceId;
209
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true
7
+ },
8
+ "include": ["src/**/*"],
9
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
10
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@freesail/lit-ui",
3
+ "version": "0.1.0",
4
+ "description": "Freesail Lit UI - Web Components for A2UX Protocol",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./standard": {
15
+ "types": "./dist/catalogs/standard/index.d.ts",
16
+ "import": "./dist/catalogs/standard/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "dev": "tsc --watch",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "lint": "eslint src --ext .ts"
28
+ },
29
+ "dependencies": {
30
+ "@freesail/core": "^0.1.0",
31
+ "lit": "^3.1.0"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^5.3.3",
35
+ "vitest": "^1.2.0"
36
+ },
37
+ "keywords": [
38
+ "web-components",
39
+ "lit",
40
+ "a2ux",
41
+ "generative-ui"
42
+ ],
43
+ "license": "MIT"
44
+ }