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,329 @@
1
+ /**
2
+ * SSE Stream Implementation
3
+ *
4
+ * Manages Server-Sent Events connections with backpressure handling.
5
+ */
6
+
7
+ import type { ServerResponse } from 'http';
8
+ import type { ServerToClientMessage } from '@freesail/core';
9
+
10
+ export interface StreamOptions {
11
+ /** Response object to write to */
12
+ response: ServerResponse;
13
+ /** Session/connection identifier */
14
+ sessionId?: string;
15
+ /** Keep-alive interval in ms (default: 15000) */
16
+ keepAliveInterval?: number;
17
+ /** Enable debug logging */
18
+ debug?: boolean;
19
+ }
20
+
21
+ export interface StreamStats {
22
+ messagesSent: number;
23
+ bytesWritten: number;
24
+ createdAt: number;
25
+ lastMessageAt: number | null;
26
+ }
27
+
28
+ /**
29
+ * FreesailStream manages an SSE connection with a client.
30
+ * It handles message serialization, backpressure, and keep-alive.
31
+ */
32
+ export class FreesailStream {
33
+ private res: ServerResponse;
34
+ private sessionId: string;
35
+ private keepAliveTimer: ReturnType<typeof setInterval> | null = null;
36
+ private debug: boolean;
37
+ private _closed: boolean = false;
38
+ private _stats: StreamStats;
39
+
40
+ constructor(options: StreamOptions) {
41
+ this.res = options.response;
42
+ this.sessionId = options.sessionId ?? this.generateId();
43
+ this.debug = options.debug ?? false;
44
+
45
+ this._stats = {
46
+ messagesSent: 0,
47
+ bytesWritten: 0,
48
+ createdAt: Date.now(),
49
+ lastMessageAt: null,
50
+ };
51
+
52
+ // Initialize SSE headers
53
+ this.initializeSSE();
54
+
55
+ // Start keep-alive
56
+ if (options.keepAliveInterval !== 0) {
57
+ this.startKeepAlive(options.keepAliveInterval ?? 15000);
58
+ }
59
+
60
+ // Handle connection close
61
+ this.res.on('close', () => {
62
+ this.close();
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Send an A2UX message to the client
68
+ */
69
+ send(message: ServerToClientMessage): boolean {
70
+ if (this._closed) {
71
+ this.log('Cannot send to closed stream');
72
+ return false;
73
+ }
74
+
75
+ try {
76
+ const data = JSON.stringify(message);
77
+ const payload = `data: ${data}\n\n`;
78
+
79
+ this.res.write(payload);
80
+
81
+ this._stats.messagesSent++;
82
+ this._stats.bytesWritten += payload.length;
83
+ this._stats.lastMessageAt = Date.now();
84
+
85
+ this.log(`Sent: ${Object.keys(message)[0]}`);
86
+ return true;
87
+ } catch (error) {
88
+ this.log(`Send error: ${error}`);
89
+ return false;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Send a createSurface message
95
+ */
96
+ createSurface(surfaceId: string, catalogId: string): boolean {
97
+ return this.send({
98
+ createSurface: { surfaceId, catalogId }
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Send an updateComponents message
104
+ */
105
+ updateComponents(
106
+ surfaceId: string,
107
+ components: ServerToClientMessage extends { updateComponents: infer U }
108
+ ? U extends { components: infer C } ? C : never
109
+ : never
110
+ ): boolean {
111
+ return this.send({
112
+ updateComponents: { surfaceId, components }
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Send an updateDataModel message
118
+ */
119
+ updateDataModel(
120
+ surfaceId: string,
121
+ value: unknown,
122
+ path?: string,
123
+ op?: 'add' | 'replace' | 'remove'
124
+ ): boolean {
125
+ return this.send({
126
+ updateDataModel: {
127
+ surfaceId,
128
+ path: path ?? '/',
129
+ op: op ?? 'replace',
130
+ value
131
+ }
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Send a deleteSurface message
137
+ */
138
+ deleteSurface(surfaceId: string): boolean {
139
+ return this.send({
140
+ deleteSurface: { surfaceId }
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Send a watchSurface message
146
+ */
147
+ watchSurface(surfaceId: string, interval?: number, expiresIn?: number): boolean {
148
+ return this.send({
149
+ watchSurface: { surfaceId, interval, expiresIn }
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Send an unwatchSurface message
155
+ */
156
+ unwatchSurface(surfaceId: string): boolean {
157
+ return this.send({
158
+ unwatchSurface: { surfaceId }
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Send raw data (for custom events)
164
+ */
165
+ sendRaw(event: string, data: unknown): boolean {
166
+ if (this._closed) return false;
167
+
168
+ try {
169
+ const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
170
+ this.res.write(payload);
171
+ this._stats.bytesWritten += payload.length;
172
+ return true;
173
+ } catch {
174
+ return false;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Send done signal and close stream
180
+ */
181
+ done(): void {
182
+ if (this._closed) return;
183
+
184
+ this.res.write('data: [DONE]\n\n');
185
+ this.close();
186
+ }
187
+
188
+ /**
189
+ * Close the stream
190
+ */
191
+ close(): void {
192
+ if (this._closed) return;
193
+
194
+ this._closed = true;
195
+ this.stopKeepAlive();
196
+
197
+ if (!this.res.writableEnded) {
198
+ this.res.end();
199
+ }
200
+
201
+ this.log('Stream closed');
202
+ }
203
+
204
+ /**
205
+ * Check if stream is closed
206
+ */
207
+ get closed(): boolean {
208
+ return this._closed;
209
+ }
210
+
211
+ /**
212
+ * Get session ID
213
+ */
214
+ get id(): string {
215
+ return this.sessionId;
216
+ }
217
+
218
+ /**
219
+ * Get stream statistics
220
+ */
221
+ get stats(): StreamStats {
222
+ return { ...this._stats };
223
+ }
224
+
225
+ // ===========================================================================
226
+ // Private Methods
227
+ // ===========================================================================
228
+
229
+ private initializeSSE(): void {
230
+ this.res.setHeader('Content-Type', 'text/event-stream');
231
+ this.res.setHeader('Cache-Control', 'no-cache');
232
+ this.res.setHeader('Connection', 'keep-alive');
233
+ this.res.setHeader('X-Accel-Buffering', 'no'); // Disable nginx buffering
234
+ this.res.flushHeaders();
235
+
236
+ this.log('SSE initialized');
237
+ }
238
+
239
+ private startKeepAlive(interval: number): void {
240
+ this.keepAliveTimer = setInterval(() => {
241
+ if (!this._closed) {
242
+ this.res.write(': keep-alive\n\n');
243
+ }
244
+ }, interval);
245
+ }
246
+
247
+ private stopKeepAlive(): void {
248
+ if (this.keepAliveTimer) {
249
+ clearInterval(this.keepAliveTimer);
250
+ this.keepAliveTimer = null;
251
+ }
252
+ }
253
+
254
+ private generateId(): string {
255
+ return `fs_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
256
+ }
257
+
258
+ private log(message: string): void {
259
+ if (this.debug) {
260
+ console.log(`[FreesailStream:${this.sessionId}] ${message}`);
261
+ }
262
+ }
263
+ }
264
+
265
+ // =============================================================================
266
+ // Stream Store (for managing multiple connections)
267
+ // =============================================================================
268
+
269
+ /**
270
+ * StreamStore manages multiple SSE connections
271
+ */
272
+ export class StreamStore {
273
+ private streams: Map<string, FreesailStream> = new Map();
274
+
275
+ /**
276
+ * Create and register a new stream
277
+ */
278
+ create(options: StreamOptions): FreesailStream {
279
+ const stream = new FreesailStream(options);
280
+ this.streams.set(stream.id, stream);
281
+
282
+ // Auto-remove on close
283
+ options.response.on('close', () => {
284
+ this.streams.delete(stream.id);
285
+ });
286
+
287
+ return stream;
288
+ }
289
+
290
+ /**
291
+ * Get a stream by ID
292
+ */
293
+ get(streamId: string): FreesailStream | undefined {
294
+ return this.streams.get(streamId);
295
+ }
296
+
297
+ /**
298
+ * Get all active streams
299
+ */
300
+ getAll(): FreesailStream[] {
301
+ return Array.from(this.streams.values());
302
+ }
303
+
304
+ /**
305
+ * Get stream count
306
+ */
307
+ get count(): number {
308
+ return this.streams.size;
309
+ }
310
+
311
+ /**
312
+ * Broadcast message to all streams
313
+ */
314
+ broadcast(message: ServerToClientMessage): void {
315
+ for (const stream of this.streams.values()) {
316
+ stream.send(message);
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Close all streams
322
+ */
323
+ closeAll(): void {
324
+ for (const stream of this.streams.values()) {
325
+ stream.close();
326
+ }
327
+ this.streams.clear();
328
+ }
329
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true,
7
+ "lib": ["ES2022"]
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
11
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "noImplicitAny": true,
16
+ "noUnusedLocals": true,
17
+ "noUnusedParameters": true,
18
+ "noImplicitReturns": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+ "experimentalDecorators": true,
21
+ "useDefineForClassFields": false
22
+ }
23
+ }
package/index.js DELETED
@@ -1,3 +0,0 @@
1
- // index.js
2
- console.log("Freesail is currently in Alpha. Please check back later.");
3
- module.exports = {};