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,270 @@
1
+ /**
2
+ * JSON Stream Parser
3
+ *
4
+ * Handles parsing of JSON data from SSE streams,
5
+ * including incremental/chunked JSON parsing.
6
+ */
7
+
8
+ import type { A2UXMessage } from './types.js';
9
+
10
+ // =============================================================================
11
+ // Types
12
+ // =============================================================================
13
+
14
+ export interface ParseResult {
15
+ success: boolean;
16
+ message?: A2UXMessage;
17
+ error?: string;
18
+ remaining?: string;
19
+ }
20
+
21
+ export interface ParserOptions {
22
+ /** Enable lenient parsing mode */
23
+ lenient?: boolean;
24
+ /** Maximum buffer size in characters */
25
+ maxBufferSize?: number;
26
+ }
27
+
28
+ // =============================================================================
29
+ // JSON Stream Parser
30
+ // =============================================================================
31
+
32
+ /**
33
+ * JSONStreamParser handles incremental JSON parsing from SSE streams.
34
+ * It buffers partial JSON and emits complete messages.
35
+ */
36
+ export class JSONStreamParser {
37
+ private buffer: string = '';
38
+ private maxBufferSize: number;
39
+ private lenient: boolean;
40
+
41
+ constructor(options: ParserOptions = {}) {
42
+ this.maxBufferSize = options.maxBufferSize ?? 1024 * 1024; // 1MB default
43
+ this.lenient = options.lenient ?? false;
44
+ }
45
+
46
+ /**
47
+ * Feed data into the parser and get any complete messages
48
+ */
49
+ feed(chunk: string): A2UXMessage[] {
50
+ this.buffer += chunk;
51
+
52
+ // Check buffer overflow
53
+ if (this.buffer.length > this.maxBufferSize) {
54
+ console.warn('[JSONStreamParser] Buffer overflow, clearing buffer');
55
+ this.buffer = '';
56
+ return [];
57
+ }
58
+
59
+ return this.extractMessages();
60
+ }
61
+
62
+ /**
63
+ * Parse a single complete JSON string
64
+ */
65
+ parse(jsonString: string): ParseResult {
66
+ try {
67
+ const message = JSON.parse(jsonString) as A2UXMessage;
68
+ return { success: true, message };
69
+ } catch (error) {
70
+ const err = error instanceof Error ? error : new Error(String(error));
71
+ return {
72
+ success: false,
73
+ error: err.message,
74
+ remaining: jsonString
75
+ };
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Reset the parser state
81
+ */
82
+ reset(): void {
83
+ this.buffer = '';
84
+ }
85
+
86
+ /**
87
+ * Get current buffer content (for debugging)
88
+ */
89
+ getBuffer(): string {
90
+ return this.buffer;
91
+ }
92
+
93
+ // ===========================================================================
94
+ // Private Methods
95
+ // ===========================================================================
96
+
97
+ private extractMessages(): A2UXMessage[] {
98
+ const messages: A2UXMessage[] = [];
99
+
100
+ // Try to extract complete JSON objects
101
+ let startIndex = 0;
102
+ let depth = 0;
103
+ let inString = false;
104
+ let escapeNext = false;
105
+
106
+ for (let i = 0; i < this.buffer.length; i++) {
107
+ const char = this.buffer[i];
108
+
109
+ if (escapeNext) {
110
+ escapeNext = false;
111
+ continue;
112
+ }
113
+
114
+ if (char === '\\' && inString) {
115
+ escapeNext = true;
116
+ continue;
117
+ }
118
+
119
+ if (char === '"') {
120
+ inString = !inString;
121
+ continue;
122
+ }
123
+
124
+ if (inString) continue;
125
+
126
+ if (char === '{') {
127
+ if (depth === 0) {
128
+ startIndex = i;
129
+ }
130
+ depth++;
131
+ } else if (char === '}') {
132
+ depth--;
133
+ if (depth === 0) {
134
+ // Found complete JSON object
135
+ const jsonString = this.buffer.slice(startIndex, i + 1);
136
+ const result = this.parse(jsonString);
137
+
138
+ if (result.success && result.message) {
139
+ messages.push(result.message);
140
+ } else if (!this.lenient) {
141
+ console.warn('[JSONStreamParser] Failed to parse:', result.error);
142
+ }
143
+
144
+ startIndex = i + 1;
145
+ }
146
+ }
147
+ }
148
+
149
+ // Keep remaining incomplete data in buffer
150
+ this.buffer = this.buffer.slice(startIndex);
151
+
152
+ return messages;
153
+ }
154
+ }
155
+
156
+ // =============================================================================
157
+ // SSE Parser
158
+ // =============================================================================
159
+
160
+ /**
161
+ * SSEParser handles Server-Sent Events format parsing
162
+ */
163
+ export class SSEParser {
164
+ private jsonParser: JSONStreamParser;
165
+ private lineBuffer: string = '';
166
+
167
+ constructor(options: ParserOptions = {}) {
168
+ this.jsonParser = new JSONStreamParser(options);
169
+ }
170
+
171
+ /**
172
+ * Feed raw SSE data and extract A2UX messages
173
+ */
174
+ feed(chunk: string): A2UXMessage[] {
175
+ const messages: A2UXMessage[] = [];
176
+ this.lineBuffer += chunk;
177
+
178
+ // Split by SSE line endings
179
+ const lines = this.lineBuffer.split(/\r?\n/);
180
+
181
+ // Keep the last incomplete line in buffer
182
+ this.lineBuffer = lines.pop() ?? '';
183
+
184
+ for (const line of lines) {
185
+ const trimmed = line.trim();
186
+
187
+ // Skip empty lines and comments
188
+ if (!trimmed || trimmed.startsWith(':')) {
189
+ continue;
190
+ }
191
+
192
+ // Parse SSE data field
193
+ if (trimmed.startsWith('data:')) {
194
+ const data = trimmed.slice(5).trim();
195
+
196
+ // Skip [DONE] marker
197
+ if (data === '[DONE]') {
198
+ continue;
199
+ }
200
+
201
+ const parsed = this.jsonParser.feed(data);
202
+ messages.push(...parsed);
203
+ }
204
+ }
205
+
206
+ return messages;
207
+ }
208
+
209
+ /**
210
+ * Reset the parser state
211
+ */
212
+ reset(): void {
213
+ this.jsonParser.reset();
214
+ this.lineBuffer = '';
215
+ }
216
+ }
217
+
218
+ // =============================================================================
219
+ // Template Expression Parser
220
+ // =============================================================================
221
+
222
+ /**
223
+ * Parses and evaluates template expressions like ${user.name}
224
+ */
225
+ export function parseTemplateExpression(
226
+ template: string,
227
+ dataModel: Record<string, unknown>
228
+ ): string {
229
+ return template.replace(/\$\{([^}]+)\}/g, (_, expression) => {
230
+ try {
231
+ const value = evaluateExpression(expression.trim(), dataModel);
232
+ return String(value ?? '');
233
+ } catch {
234
+ return '';
235
+ }
236
+ });
237
+ }
238
+
239
+ /**
240
+ * Evaluates a simple path expression against a data model
241
+ */
242
+ function evaluateExpression(
243
+ expression: string,
244
+ dataModel: Record<string, unknown>
245
+ ): unknown {
246
+ const parts = expression.split('.');
247
+ let current: unknown = dataModel;
248
+
249
+ for (const part of parts) {
250
+ if (current === null || current === undefined) {
251
+ return undefined;
252
+ }
253
+
254
+ // Handle array index access like items[0]
255
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/);
256
+ if (arrayMatch) {
257
+ const [, key, index] = arrayMatch;
258
+ current = (current as Record<string, unknown>)[key];
259
+ if (Array.isArray(current)) {
260
+ current = current[parseInt(index, 10)];
261
+ } else {
262
+ return undefined;
263
+ }
264
+ } else {
265
+ current = (current as Record<string, unknown>)[part];
266
+ }
267
+ }
268
+
269
+ return current;
270
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * A2UX Protocol Handler
3
+ *
4
+ * Core protocol listener that processes incoming A2UX messages
5
+ * and dispatches them to appropriate handlers.
6
+ */
7
+
8
+ import type {
9
+ A2UXMessage,
10
+ CreateSurfaceMessage,
11
+ UpdateComponentsMessage,
12
+ UpdateDataModelMessage,
13
+ DeleteSurfaceMessage,
14
+ WatchSurfaceMessage,
15
+ UnwatchSurfaceMessage,
16
+ } from './types.js';
17
+
18
+ import {
19
+ isCreateSurface,
20
+ isUpdateComponents,
21
+ isUpdateDataModel,
22
+ isDeleteSurface,
23
+ isWatchSurface,
24
+ isUnwatchSurface,
25
+ getMessageType,
26
+ } from './types.js';
27
+
28
+ import { SurfaceStore } from './store';
29
+
30
+ // =============================================================================
31
+ // Event Types
32
+ // =============================================================================
33
+
34
+ export interface ProtocolEventMap {
35
+ 'createSurface': CreateSurfaceMessage['createSurface'];
36
+ 'updateComponents': UpdateComponentsMessage['updateComponents'];
37
+ 'updateDataModel': UpdateDataModelMessage['updateDataModel'];
38
+ 'deleteSurface': DeleteSurfaceMessage['deleteSurface'];
39
+ 'watchSurface': WatchSurfaceMessage['watchSurface'];
40
+ 'unwatchSurface': UnwatchSurfaceMessage['unwatchSurface'];
41
+ 'error': { message: string; originalError?: Error };
42
+ 'connected': { url: string };
43
+ 'disconnected': { reason: string };
44
+ }
45
+
46
+ export type ProtocolEventHandler<K extends keyof ProtocolEventMap> =
47
+ (data: ProtocolEventMap[K]) => void;
48
+
49
+ // =============================================================================
50
+ // Protocol Handler
51
+ // =============================================================================
52
+
53
+ export interface ProtocolHandlerOptions {
54
+ /** Surface store for state management */
55
+ store?: SurfaceStore;
56
+ /** Enable debug logging */
57
+ debug?: boolean;
58
+ }
59
+
60
+ /**
61
+ * A2UXProtocolHandler processes incoming A2UX messages and manages
62
+ * protocol state. It acts as the central message dispatcher.
63
+ */
64
+ export class A2UXProtocolHandler {
65
+ private store: SurfaceStore;
66
+ private debug: boolean;
67
+ private listeners: Map<keyof ProtocolEventMap, Set<ProtocolEventHandler<keyof ProtocolEventMap>>> = new Map();
68
+ private watchTimers: Map<string, ReturnType<typeof setInterval>> = new Map();
69
+
70
+ constructor(options: ProtocolHandlerOptions = {}) {
71
+ this.store = options.store ?? new SurfaceStore();
72
+ this.debug = options.debug ?? false;
73
+ }
74
+
75
+ /**
76
+ * Process an incoming A2UX message
77
+ */
78
+ processMessage(message: A2UXMessage): void {
79
+ try {
80
+ const messageType = getMessageType(message);
81
+ this.log(`Processing message: ${messageType}`);
82
+
83
+ if (isCreateSurface(message)) {
84
+ this.handleCreateSurface(message.createSurface);
85
+ } else if (isUpdateComponents(message)) {
86
+ this.handleUpdateComponents(message.updateComponents);
87
+ } else if (isUpdateDataModel(message)) {
88
+ this.handleUpdateDataModel(message.updateDataModel);
89
+ } else if (isDeleteSurface(message)) {
90
+ this.handleDeleteSurface(message.deleteSurface);
91
+ } else if (isWatchSurface(message)) {
92
+ this.handleWatchSurface(message.watchSurface);
93
+ } else if (isUnwatchSurface(message)) {
94
+ this.handleUnwatchSurface(message.unwatchSurface);
95
+ } else {
96
+ this.log(`Unknown message type: ${messageType}`, 'warn');
97
+ }
98
+ } catch (error) {
99
+ const err = error instanceof Error ? error : new Error(String(error));
100
+ this.emit('error', { message: err.message, originalError: err });
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Process a raw JSON string message
106
+ */
107
+ processRawMessage(jsonString: string): void {
108
+ try {
109
+ const message = JSON.parse(jsonString) as A2UXMessage;
110
+ this.processMessage(message);
111
+ } catch (error) {
112
+ const err = error instanceof Error ? error : new Error(String(error));
113
+ this.emit('error', {
114
+ message: `Failed to parse message: ${err.message}`,
115
+ originalError: err
116
+ });
117
+ }
118
+ }
119
+
120
+ // ===========================================================================
121
+ // Message Handlers
122
+ // ===========================================================================
123
+
124
+ private handleCreateSurface(payload: CreateSurfaceMessage['createSurface']): void {
125
+ const { surfaceId, catalogId } = payload;
126
+ this.store.createSurface(surfaceId, catalogId);
127
+ this.emit('createSurface', payload);
128
+ }
129
+
130
+ private handleUpdateComponents(payload: UpdateComponentsMessage['updateComponents']): void {
131
+ const { surfaceId, components } = payload;
132
+ this.store.updateComponents(surfaceId, components);
133
+ this.emit('updateComponents', payload);
134
+ }
135
+
136
+ private handleUpdateDataModel(payload: UpdateDataModelMessage['updateDataModel']): void {
137
+ const { surfaceId, path, op, value } = payload;
138
+ this.store.updateDataModel(surfaceId, path ?? '/', op ?? 'replace', value);
139
+ this.emit('updateDataModel', payload);
140
+ }
141
+
142
+ private handleDeleteSurface(payload: DeleteSurfaceMessage['deleteSurface']): void {
143
+ const { surfaceId } = payload;
144
+
145
+ // Clear any active watchers
146
+ this.clearWatcher(surfaceId);
147
+
148
+ this.store.deleteSurface(surfaceId);
149
+ this.emit('deleteSurface', payload);
150
+ }
151
+
152
+ private handleWatchSurface(payload: WatchSurfaceMessage['watchSurface']): void {
153
+ const { surfaceId, interval = 10, expiresIn = 300 } = payload;
154
+
155
+ // Clear any existing watcher
156
+ this.clearWatcher(surfaceId);
157
+
158
+ // Set up new watcher
159
+ const timer = setInterval(() => {
160
+ this.store.triggerWatchCallback(surfaceId);
161
+ }, interval * 1000);
162
+
163
+ this.watchTimers.set(surfaceId, timer);
164
+
165
+ // Set expiration
166
+ setTimeout(() => {
167
+ this.clearWatcher(surfaceId);
168
+ }, expiresIn * 1000);
169
+
170
+ this.emit('watchSurface', payload);
171
+ }
172
+
173
+ private handleUnwatchSurface(payload: UnwatchSurfaceMessage['unwatchSurface']): void {
174
+ const { surfaceId } = payload;
175
+ this.clearWatcher(surfaceId);
176
+ this.emit('unwatchSurface', payload);
177
+ }
178
+
179
+ private clearWatcher(surfaceId: string): void {
180
+ const timer = this.watchTimers.get(surfaceId);
181
+ if (timer) {
182
+ clearInterval(timer);
183
+ this.watchTimers.delete(surfaceId);
184
+ }
185
+ }
186
+
187
+ // ===========================================================================
188
+ // Event Emitter
189
+ // ===========================================================================
190
+
191
+ on<K extends keyof ProtocolEventMap>(
192
+ event: K,
193
+ handler: ProtocolEventHandler<K>
194
+ ): () => void {
195
+ if (!this.listeners.has(event)) {
196
+ this.listeners.set(event, new Set());
197
+ }
198
+ this.listeners.get(event)!.add(handler as ProtocolEventHandler<keyof ProtocolEventMap>);
199
+
200
+ // Return unsubscribe function
201
+ return () => this.off(event, handler);
202
+ }
203
+
204
+ off<K extends keyof ProtocolEventMap>(
205
+ event: K,
206
+ handler: ProtocolEventHandler<K>
207
+ ): void {
208
+ this.listeners.get(event)?.delete(handler as ProtocolEventHandler<keyof ProtocolEventMap>);
209
+ }
210
+
211
+ private emit<K extends keyof ProtocolEventMap>(event: K, data: ProtocolEventMap[K]): void {
212
+ this.listeners.get(event)?.forEach(handler => {
213
+ try {
214
+ (handler as ProtocolEventHandler<K>)(data);
215
+ } catch (error) {
216
+ console.error(`Error in event handler for ${event}:`, error);
217
+ }
218
+ });
219
+ }
220
+
221
+ // ===========================================================================
222
+ // Accessors
223
+ // ===========================================================================
224
+
225
+ getStore(): SurfaceStore {
226
+ return this.store;
227
+ }
228
+
229
+ // ===========================================================================
230
+ // Cleanup
231
+ // ===========================================================================
232
+
233
+ destroy(): void {
234
+ // Clear all watchers
235
+ this.watchTimers.forEach((timer) => clearInterval(timer));
236
+ this.watchTimers.clear();
237
+
238
+ // Clear all listeners
239
+ this.listeners.clear();
240
+
241
+ // Clear store
242
+ this.store.clear();
243
+ }
244
+
245
+ // ===========================================================================
246
+ // Debug
247
+ // ===========================================================================
248
+
249
+ private log(message: string, level: 'log' | 'warn' | 'error' = 'log'): void {
250
+ if (this.debug) {
251
+ console[level](`[A2UX Protocol] ${message}`);
252
+ }
253
+ }
254
+ }