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,452 @@
1
+ /**
2
+ * Surface Store
3
+ *
4
+ * Reactive state management for A2UX surfaces.
5
+ * Manages component trees, data models, and subscriptions.
6
+ */
7
+
8
+ import type { A2UXComponent, WatchSurfaceResponseMessage } from './types.js';
9
+
10
+ // =============================================================================
11
+ // Types
12
+ // =============================================================================
13
+
14
+ export interface Surface {
15
+ /** Surface unique identifier */
16
+ surfaceId: string;
17
+ /** Catalog ID for component definitions */
18
+ catalogId: string;
19
+ /** Component tree (flat adjacency list) */
20
+ components: Map<string, A2UXComponent>;
21
+ /** Data model state */
22
+ dataModel: Record<string, unknown>;
23
+ /** Creation timestamp */
24
+ createdAt: number;
25
+ /** Last update timestamp */
26
+ updatedAt: number;
27
+ }
28
+
29
+ export interface StoreEventMap {
30
+ 'surfaceCreated': { surfaceId: string; catalogId: string };
31
+ 'surfaceDeleted': { surfaceId: string };
32
+ 'componentsUpdated': { surfaceId: string; componentIds: string[] };
33
+ 'dataModelUpdated': { surfaceId: string; path: string; value: unknown };
34
+ 'watchCallback': WatchSurfaceResponseMessage;
35
+ }
36
+
37
+ export type StoreEventHandler<K extends keyof StoreEventMap> =
38
+ (data: StoreEventMap[K]) => void;
39
+
40
+ // =============================================================================
41
+ // JSON Pointer Utilities
42
+ // =============================================================================
43
+
44
+ /**
45
+ * Parse a JSON Pointer path into an array of keys
46
+ */
47
+ function parseJsonPointer(pointer: string): string[] {
48
+ if (!pointer || pointer === '/') return [];
49
+
50
+ return pointer
51
+ .split('/')
52
+ .slice(1) // Remove leading empty string from initial /
53
+ .map(segment => segment.replace(/~1/g, '/').replace(/~0/g, '~'));
54
+ }
55
+
56
+ /**
57
+ * Get a value from an object using a JSON Pointer path
58
+ */
59
+ function getByPointer(obj: Record<string, unknown>, pointer: string): unknown {
60
+ const keys = parseJsonPointer(pointer);
61
+ let current: unknown = obj;
62
+
63
+ for (const key of keys) {
64
+ if (current === null || current === undefined) return undefined;
65
+ current = (current as Record<string, unknown>)[key];
66
+ }
67
+
68
+ return current;
69
+ }
70
+
71
+ /**
72
+ * Set a value in an object using a JSON Pointer path
73
+ */
74
+ function setByPointer(
75
+ obj: Record<string, unknown>,
76
+ pointer: string,
77
+ value: unknown
78
+ ): void {
79
+ const keys = parseJsonPointer(pointer);
80
+
81
+ if (keys.length === 0) {
82
+ // Replace entire object
83
+ Object.keys(obj).forEach(key => delete obj[key]);
84
+ Object.assign(obj, value);
85
+ return;
86
+ }
87
+
88
+ let current: Record<string, unknown> = obj;
89
+
90
+ for (let i = 0; i < keys.length - 1; i++) {
91
+ const key = keys[i];
92
+ if (!(key in current) || current[key] === null || typeof current[key] !== 'object') {
93
+ current[key] = {};
94
+ }
95
+ current = current[key] as Record<string, unknown>;
96
+ }
97
+
98
+ current[keys[keys.length - 1]] = value;
99
+ }
100
+
101
+ /**
102
+ * Remove a value from an object using a JSON Pointer path
103
+ */
104
+ function removeByPointer(obj: Record<string, unknown>, pointer: string): void {
105
+ const keys = parseJsonPointer(pointer);
106
+
107
+ if (keys.length === 0) {
108
+ Object.keys(obj).forEach(key => delete obj[key]);
109
+ return;
110
+ }
111
+
112
+ let current: Record<string, unknown> = obj;
113
+
114
+ for (let i = 0; i < keys.length - 1; i++) {
115
+ const key = keys[i];
116
+ if (!(key in current)) return;
117
+ current = current[key] as Record<string, unknown>;
118
+ }
119
+
120
+ delete current[keys[keys.length - 1]];
121
+ }
122
+
123
+ // =============================================================================
124
+ // Surface Store
125
+ // =============================================================================
126
+
127
+ /**
128
+ * SurfaceStore manages the state of all A2UX surfaces.
129
+ * It provides reactive subscriptions for state changes.
130
+ */
131
+ export class SurfaceStore {
132
+ private surfaces: Map<string, Surface> = new Map();
133
+ private listeners: Map<keyof StoreEventMap, Set<StoreEventHandler<keyof StoreEventMap>>> = new Map();
134
+ private watchCallbacks: Map<string, () => void> = new Map();
135
+
136
+ // ===========================================================================
137
+ // Surface Management
138
+ // ===========================================================================
139
+
140
+ /**
141
+ * Create a new surface
142
+ */
143
+ createSurface(surfaceId: string, catalogId: string): Surface {
144
+ const surface: Surface = {
145
+ surfaceId,
146
+ catalogId,
147
+ components: new Map(),
148
+ dataModel: {},
149
+ createdAt: Date.now(),
150
+ updatedAt: Date.now(),
151
+ };
152
+
153
+ this.surfaces.set(surfaceId, surface);
154
+ this.emit('surfaceCreated', { surfaceId, catalogId });
155
+
156
+ return surface;
157
+ }
158
+
159
+ /**
160
+ * Get a surface by ID
161
+ */
162
+ getSurface(surfaceId: string): Surface | undefined {
163
+ return this.surfaces.get(surfaceId);
164
+ }
165
+
166
+ /**
167
+ * Check if a surface exists
168
+ */
169
+ hasSurface(surfaceId: string): boolean {
170
+ return this.surfaces.has(surfaceId);
171
+ }
172
+
173
+ /**
174
+ * Get all surface IDs
175
+ */
176
+ getSurfaceIds(): string[] {
177
+ return Array.from(this.surfaces.keys());
178
+ }
179
+
180
+ /**
181
+ * Delete a surface
182
+ */
183
+ deleteSurface(surfaceId: string): boolean {
184
+ const deleted = this.surfaces.delete(surfaceId);
185
+ if (deleted) {
186
+ this.watchCallbacks.delete(surfaceId);
187
+ this.emit('surfaceDeleted', { surfaceId });
188
+ }
189
+ return deleted;
190
+ }
191
+
192
+ /**
193
+ * Clear all surfaces
194
+ */
195
+ clear(): void {
196
+ const surfaceIds = this.getSurfaceIds();
197
+ surfaceIds.forEach(id => this.deleteSurface(id));
198
+ }
199
+
200
+ // ===========================================================================
201
+ // Component Management
202
+ // ===========================================================================
203
+
204
+ /**
205
+ * Update components in a surface
206
+ */
207
+ updateComponents(surfaceId: string, components: A2UXComponent[]): void {
208
+ const surface = this.surfaces.get(surfaceId);
209
+ if (!surface) {
210
+ console.warn(`[SurfaceStore] Surface not found: ${surfaceId}`);
211
+ return;
212
+ }
213
+
214
+ const updatedIds: string[] = [];
215
+
216
+ for (const component of components) {
217
+ surface.components.set(component.id, component);
218
+ updatedIds.push(component.id);
219
+ }
220
+
221
+ surface.updatedAt = Date.now();
222
+ this.emit('componentsUpdated', { surfaceId, componentIds: updatedIds });
223
+ }
224
+
225
+ /**
226
+ * Get a component by ID
227
+ */
228
+ getComponent(surfaceId: string, componentId: string): A2UXComponent | undefined {
229
+ return this.surfaces.get(surfaceId)?.components.get(componentId);
230
+ }
231
+
232
+ /**
233
+ * Get all components in a surface
234
+ */
235
+ getComponents(surfaceId: string): A2UXComponent[] {
236
+ const surface = this.surfaces.get(surfaceId);
237
+ return surface ? Array.from(surface.components.values()) : [];
238
+ }
239
+
240
+ /**
241
+ * Get the root component of a surface
242
+ */
243
+ getRootComponent(surfaceId: string): A2UXComponent | undefined {
244
+ return this.getComponent(surfaceId, 'root');
245
+ }
246
+
247
+ /**
248
+ * Build component tree from flat list
249
+ */
250
+ getComponentTree(surfaceId: string): A2UXComponent | null {
251
+ const root = this.getRootComponent(surfaceId);
252
+ if (!root) return null;
253
+
254
+ const buildTree = (component: A2UXComponent): A2UXComponent => {
255
+ const result = { ...component };
256
+
257
+ if (component.children && Array.isArray(component.children)) {
258
+ result.children = component.children.map(childId => {
259
+ const child = this.getComponent(surfaceId, childId as string);
260
+ return child ? buildTree(child) : childId;
261
+ }) as unknown as string[];
262
+ }
263
+
264
+ return result;
265
+ };
266
+
267
+ return buildTree(root);
268
+ }
269
+
270
+ // ===========================================================================
271
+ // Data Model Management
272
+ // ===========================================================================
273
+
274
+ /**
275
+ * Update the data model of a surface
276
+ */
277
+ updateDataModel(
278
+ surfaceId: string,
279
+ path: string = '/',
280
+ op: 'add' | 'replace' | 'remove' = 'replace',
281
+ value?: unknown
282
+ ): void {
283
+ const surface = this.surfaces.get(surfaceId);
284
+ if (!surface) {
285
+ console.warn(`[SurfaceStore] Surface not found: ${surfaceId}`);
286
+ return;
287
+ }
288
+
289
+ switch (op) {
290
+ case 'add':
291
+ case 'replace':
292
+ setByPointer(surface.dataModel, path, value);
293
+ break;
294
+ case 'remove':
295
+ removeByPointer(surface.dataModel, path);
296
+ break;
297
+ }
298
+
299
+ surface.updatedAt = Date.now();
300
+ this.emit('dataModelUpdated', { surfaceId, path, value });
301
+ }
302
+
303
+ /**
304
+ * Get a value from the data model using JSON Pointer
305
+ */
306
+ getDataValue(surfaceId: string, path: string): unknown {
307
+ const surface = this.surfaces.get(surfaceId);
308
+ if (!surface) return undefined;
309
+ return getByPointer(surface.dataModel, path);
310
+ }
311
+
312
+ /**
313
+ * Get the entire data model
314
+ */
315
+ getDataModel(surfaceId: string): Record<string, unknown> | undefined {
316
+ return this.surfaces.get(surfaceId)?.dataModel;
317
+ }
318
+
319
+ /**
320
+ * Get flattened data model with JSON Pointer paths as keys
321
+ */
322
+ getFlatDataModel(surfaceId: string): Record<string, unknown> {
323
+ const surface = this.surfaces.get(surfaceId);
324
+ if (!surface) return {};
325
+
326
+ const result: Record<string, unknown> = {};
327
+
328
+ const flatten = (obj: Record<string, unknown>, prefix: string = ''): void => {
329
+ for (const [key, value] of Object.entries(obj)) {
330
+ const path = `${prefix}/${key}`;
331
+
332
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
333
+ flatten(value as Record<string, unknown>, path);
334
+ } else {
335
+ result[path] = value;
336
+ }
337
+ }
338
+ };
339
+
340
+ flatten(surface.dataModel);
341
+ return result;
342
+ }
343
+
344
+ // ===========================================================================
345
+ // Watch Callbacks
346
+ // ===========================================================================
347
+
348
+ /**
349
+ * Register a callback for watch responses
350
+ */
351
+ setWatchCallback(surfaceId: string, callback: () => void): void {
352
+ this.watchCallbacks.set(surfaceId, callback);
353
+ }
354
+
355
+ /**
356
+ * Trigger the watch callback for a surface
357
+ */
358
+ triggerWatchCallback(surfaceId: string): void {
359
+ const callback = this.watchCallbacks.get(surfaceId);
360
+ if (callback) {
361
+ callback();
362
+ }
363
+
364
+ // Also emit a watchCallback event with the current state
365
+ const flatData = this.getFlatDataModel(surfaceId);
366
+ this.emit('watchCallback', {
367
+ watchSurfaceResponse: {
368
+ surfaceId,
369
+ data: flatData,
370
+ }
371
+ });
372
+ }
373
+
374
+ // ===========================================================================
375
+ // Event Handling
376
+ // ===========================================================================
377
+
378
+ on<K extends keyof StoreEventMap>(
379
+ event: K,
380
+ handler: StoreEventHandler<K>
381
+ ): () => void {
382
+ if (!this.listeners.has(event)) {
383
+ this.listeners.set(event, new Set());
384
+ }
385
+ this.listeners.get(event)!.add(handler as StoreEventHandler<keyof StoreEventMap>);
386
+
387
+ return () => this.off(event, handler);
388
+ }
389
+
390
+ off<K extends keyof StoreEventMap>(
391
+ event: K,
392
+ handler: StoreEventHandler<K>
393
+ ): void {
394
+ this.listeners.get(event)?.delete(handler as StoreEventHandler<keyof StoreEventMap>);
395
+ }
396
+
397
+ private emit<K extends keyof StoreEventMap>(event: K, data: StoreEventMap[K]): void {
398
+ this.listeners.get(event)?.forEach(handler => {
399
+ try {
400
+ (handler as StoreEventHandler<K>)(data);
401
+ } catch (error) {
402
+ console.error(`Error in store event handler for ${event}:`, error);
403
+ }
404
+ });
405
+ }
406
+
407
+ // ===========================================================================
408
+ // Subscriptions (Reactive)
409
+ // ===========================================================================
410
+
411
+ /**
412
+ * Subscribe to changes in a specific surface
413
+ */
414
+ subscribeSurface(
415
+ surfaceId: string,
416
+ callback: (surface: Surface) => void
417
+ ): () => void {
418
+ const unsubComponents = this.on('componentsUpdated', ({ surfaceId: id }) => {
419
+ if (id === surfaceId) {
420
+ const surface = this.getSurface(surfaceId);
421
+ if (surface) callback(surface);
422
+ }
423
+ });
424
+
425
+ const unsubData = this.on('dataModelUpdated', ({ surfaceId: id }) => {
426
+ if (id === surfaceId) {
427
+ const surface = this.getSurface(surfaceId);
428
+ if (surface) callback(surface);
429
+ }
430
+ });
431
+
432
+ return () => {
433
+ unsubComponents();
434
+ unsubData();
435
+ };
436
+ }
437
+
438
+ /**
439
+ * Subscribe to a specific data path in a surface
440
+ */
441
+ subscribeData(
442
+ surfaceId: string,
443
+ path: string,
444
+ callback: (value: unknown) => void
445
+ ): () => void {
446
+ return this.on('dataModelUpdated', ({ surfaceId: id, path: updatedPath }) => {
447
+ if (id === surfaceId && (updatedPath === path || updatedPath.startsWith(path))) {
448
+ callback(this.getDataValue(surfaceId, path));
449
+ }
450
+ });
451
+ }
452
+ }