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,403 @@
1
+ /**
2
+ * Standard Functions Library
3
+ *
4
+ * A2UI Standard Functions for client-side data logic.
5
+ * Used in template expressions like ${formatCurrency(price, 'USD')}.
6
+ */
7
+
8
+ // =============================================================================
9
+ // Currency Formatting
10
+ // =============================================================================
11
+
12
+ export interface CurrencyOptions {
13
+ locale?: string;
14
+ minimumFractionDigits?: number;
15
+ maximumFractionDigits?: number;
16
+ }
17
+
18
+ /**
19
+ * Format a number as currency
20
+ */
21
+ export function formatCurrency(
22
+ value: number,
23
+ currency: string = 'USD',
24
+ options: CurrencyOptions = {}
25
+ ): string {
26
+ const {
27
+ locale = 'en-US',
28
+ minimumFractionDigits = 2,
29
+ maximumFractionDigits = 2,
30
+ } = options;
31
+
32
+ return new Intl.NumberFormat(locale, {
33
+ style: 'currency',
34
+ currency,
35
+ minimumFractionDigits,
36
+ maximumFractionDigits,
37
+ }).format(value);
38
+ }
39
+
40
+ // =============================================================================
41
+ // Number Formatting
42
+ // =============================================================================
43
+
44
+ export interface NumberOptions {
45
+ locale?: string;
46
+ minimumFractionDigits?: number;
47
+ maximumFractionDigits?: number;
48
+ useGrouping?: boolean;
49
+ }
50
+
51
+ /**
52
+ * Format a number with locale-specific formatting
53
+ */
54
+ export function formatNumber(
55
+ value: number,
56
+ options: NumberOptions = {}
57
+ ): string {
58
+ const {
59
+ locale = 'en-US',
60
+ minimumFractionDigits = 0,
61
+ maximumFractionDigits = 2,
62
+ useGrouping = true,
63
+ } = options;
64
+
65
+ return new Intl.NumberFormat(locale, {
66
+ minimumFractionDigits,
67
+ maximumFractionDigits,
68
+ useGrouping,
69
+ }).format(value);
70
+ }
71
+
72
+ /**
73
+ * Format a number as a percentage
74
+ */
75
+ export function formatPercent(
76
+ value: number,
77
+ options: { locale?: string; decimals?: number } = {}
78
+ ): string {
79
+ const { locale = 'en-US', decimals = 0 } = options;
80
+
81
+ return new Intl.NumberFormat(locale, {
82
+ style: 'percent',
83
+ minimumFractionDigits: decimals,
84
+ maximumFractionDigits: decimals,
85
+ }).format(value);
86
+ }
87
+
88
+ /**
89
+ * Format bytes into human-readable size
90
+ */
91
+ export function formatBytes(bytes: number, decimals: number = 2): string {
92
+ if (bytes === 0) return '0 Bytes';
93
+
94
+ const k = 1024;
95
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
96
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
97
+
98
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
99
+ }
100
+
101
+ // =============================================================================
102
+ // Date Formatting
103
+ // =============================================================================
104
+
105
+ export type DateFormatStyle = 'short' | 'medium' | 'long' | 'full';
106
+
107
+ export interface DateOptions {
108
+ locale?: string;
109
+ dateStyle?: DateFormatStyle;
110
+ timeStyle?: DateFormatStyle;
111
+ }
112
+
113
+ /**
114
+ * Format a date/time value
115
+ */
116
+ export function formatDate(
117
+ value: Date | string | number,
118
+ options: DateOptions = {}
119
+ ): string {
120
+ const date = value instanceof Date ? value : new Date(value);
121
+ const { locale = 'en-US', dateStyle = 'medium', timeStyle } = options;
122
+
123
+ return new Intl.DateTimeFormat(locale, {
124
+ dateStyle,
125
+ timeStyle,
126
+ } as Intl.DateTimeFormatOptions).format(date);
127
+ }
128
+
129
+ /**
130
+ * Format a date as relative time (e.g., "2 hours ago")
131
+ */
132
+ export function formatRelativeTime(
133
+ value: Date | string | number,
134
+ locale: string = 'en-US'
135
+ ): string {
136
+ const date = value instanceof Date ? value : new Date(value);
137
+ const now = new Date();
138
+ const diffMs = date.getTime() - now.getTime();
139
+ const diffSecs = Math.round(diffMs / 1000);
140
+ const diffMins = Math.round(diffSecs / 60);
141
+ const diffHours = Math.round(diffMins / 60);
142
+ const diffDays = Math.round(diffHours / 24);
143
+
144
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
145
+
146
+ if (Math.abs(diffSecs) < 60) {
147
+ return rtf.format(diffSecs, 'second');
148
+ } else if (Math.abs(diffMins) < 60) {
149
+ return rtf.format(diffMins, 'minute');
150
+ } else if (Math.abs(diffHours) < 24) {
151
+ return rtf.format(diffHours, 'hour');
152
+ } else {
153
+ return rtf.format(diffDays, 'day');
154
+ }
155
+ }
156
+
157
+ // =============================================================================
158
+ // String Formatting
159
+ // =============================================================================
160
+
161
+ /**
162
+ * Pluralize a word based on count
163
+ */
164
+ export function pluralize(
165
+ count: number,
166
+ singular: string,
167
+ plural?: string
168
+ ): string {
169
+ if (count === 1) return singular;
170
+ return plural ?? `${singular}s`;
171
+ }
172
+
173
+ /**
174
+ * Truncate text with ellipsis
175
+ */
176
+ export function truncate(
177
+ text: string,
178
+ maxLength: number,
179
+ suffix: string = '...'
180
+ ): string {
181
+ if (text.length <= maxLength) return text;
182
+ return text.slice(0, maxLength - suffix.length) + suffix;
183
+ }
184
+
185
+ /**
186
+ * Capitalize the first letter of a string
187
+ */
188
+ export function capitalize(text: string): string {
189
+ if (!text) return '';
190
+ return text.charAt(0).toUpperCase() + text.slice(1);
191
+ }
192
+
193
+ /**
194
+ * Convert a string to title case
195
+ */
196
+ export function titleCase(text: string): string {
197
+ return text
198
+ .toLowerCase()
199
+ .split(' ')
200
+ .map(word => capitalize(word))
201
+ .join(' ');
202
+ }
203
+
204
+ /**
205
+ * Format a string using named placeholders
206
+ */
207
+ export function format(
208
+ template: string,
209
+ values: Record<string, unknown>
210
+ ): string {
211
+ return template.replace(/\{(\w+)\}/g, (_, key) => {
212
+ return key in values ? String(values[key]) : `{${key}}`;
213
+ });
214
+ }
215
+
216
+ // =============================================================================
217
+ // Regex Functions
218
+ // =============================================================================
219
+
220
+ /**
221
+ * Test if a string matches a pattern
222
+ */
223
+ export function regexTest(
224
+ text: string,
225
+ pattern: string,
226
+ flags: string = ''
227
+ ): boolean {
228
+ const regex = new RegExp(pattern, flags);
229
+ return regex.test(text);
230
+ }
231
+
232
+ /**
233
+ * Extract matches from a string
234
+ */
235
+ export function regexMatch(
236
+ text: string,
237
+ pattern: string,
238
+ flags: string = ''
239
+ ): string[] {
240
+ const regex = new RegExp(pattern, flags);
241
+ const match = text.match(regex);
242
+ return match ? Array.from(match) : [];
243
+ }
244
+
245
+ /**
246
+ * Replace text using regex
247
+ */
248
+ export function regexReplace(
249
+ text: string,
250
+ pattern: string,
251
+ replacement: string,
252
+ flags: string = 'g'
253
+ ): string {
254
+ const regex = new RegExp(pattern, flags);
255
+ return text.replace(regex, replacement);
256
+ }
257
+
258
+ // =============================================================================
259
+ // Validation Functions
260
+ // =============================================================================
261
+
262
+ /**
263
+ * Check if a value is empty
264
+ */
265
+ export function isEmpty(value: unknown): boolean {
266
+ if (value === null || value === undefined) return true;
267
+ if (typeof value === 'string') return value.trim() === '';
268
+ if (Array.isArray(value)) return value.length === 0;
269
+ if (typeof value === 'object') return Object.keys(value).length === 0;
270
+ return false;
271
+ }
272
+
273
+ /**
274
+ * Check if a string is a valid email
275
+ */
276
+ export function isEmail(value: string): boolean {
277
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
278
+ return emailRegex.test(value);
279
+ }
280
+
281
+ /**
282
+ * Check if a string is a valid URL
283
+ */
284
+ export function isUrl(value: string): boolean {
285
+ try {
286
+ new URL(value);
287
+ return true;
288
+ } catch {
289
+ return false;
290
+ }
291
+ }
292
+
293
+ // =============================================================================
294
+ // Array Functions
295
+ // =============================================================================
296
+
297
+ /**
298
+ * Get the first N items from an array
299
+ */
300
+ export function take<T>(array: T[], count: number): T[] {
301
+ return array.slice(0, count);
302
+ }
303
+
304
+ /**
305
+ * Get the last N items from an array
306
+ */
307
+ export function takeLast<T>(array: T[], count: number): T[] {
308
+ return array.slice(-count);
309
+ }
310
+
311
+ /**
312
+ * Join array items with a separator
313
+ */
314
+ export function join(
315
+ array: unknown[],
316
+ separator: string = ', ',
317
+ lastSeparator?: string
318
+ ): string {
319
+ if (array.length === 0) return '';
320
+ if (array.length === 1) return String(array[0]);
321
+
322
+ if (lastSeparator) {
323
+ const items = array.map(String);
324
+ const last = items.pop();
325
+ return `${items.join(separator)}${lastSeparator}${last}`;
326
+ }
327
+
328
+ return array.map(String).join(separator);
329
+ }
330
+
331
+ // =============================================================================
332
+ // Conditional Functions
333
+ // =============================================================================
334
+
335
+ /**
336
+ * Return value based on condition
337
+ */
338
+ export function ifElse<T>(
339
+ condition: boolean,
340
+ trueValue: T,
341
+ falseValue: T
342
+ ): T {
343
+ return condition ? trueValue : falseValue;
344
+ }
345
+
346
+ /**
347
+ * Return first non-null/undefined value
348
+ */
349
+ export function coalesce<T>(...values: (T | null | undefined)[]): T | undefined {
350
+ for (const value of values) {
351
+ if (value !== null && value !== undefined) return value;
352
+ }
353
+ return undefined;
354
+ }
355
+
356
+ // =============================================================================
357
+ // Function Registry
358
+ // =============================================================================
359
+
360
+ /**
361
+ * Standard function registry for template expression evaluation
362
+ */
363
+ export const standardFunctions = {
364
+ // Currency
365
+ formatCurrency,
366
+
367
+ // Numbers
368
+ formatNumber,
369
+ formatPercent,
370
+ formatBytes,
371
+
372
+ // Dates
373
+ formatDate,
374
+ formatRelativeTime,
375
+
376
+ // Strings
377
+ pluralize,
378
+ truncate,
379
+ capitalize,
380
+ titleCase,
381
+ format,
382
+
383
+ // Regex
384
+ regexTest,
385
+ regexMatch,
386
+ regexReplace,
387
+
388
+ // Validation
389
+ isEmpty,
390
+ isEmail,
391
+ isUrl,
392
+
393
+ // Arrays
394
+ take,
395
+ takeLast,
396
+ join,
397
+
398
+ // Conditionals
399
+ ifElse,
400
+ coalesce,
401
+ };
402
+
403
+ export type StandardFunctions = typeof standardFunctions;
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Freesail Core SDK
3
+ *
4
+ * The brain of the Freesail SDK - handles A2UX protocol,
5
+ * transport, state management, and standard functions.
6
+ */
7
+
8
+ // Types
9
+ export type {
10
+ A2UXComponent,
11
+ A2UXMessage,
12
+ ServerToClientMessage,
13
+ ClientToServerMessage,
14
+ CreateSurfaceMessage,
15
+ UpdateComponentsMessage,
16
+ UpdateDataModelMessage,
17
+ DeleteSurfaceMessage,
18
+ WatchSurfaceMessage,
19
+ UnwatchSurfaceMessage,
20
+ UserActionMessage,
21
+ WatchSurfaceResponseMessage,
22
+ } from './types.js';
23
+
24
+ export {
25
+ isCreateSurface,
26
+ isUpdateComponents,
27
+ isUpdateDataModel,
28
+ isDeleteSurface,
29
+ isWatchSurface,
30
+ isUnwatchSurface,
31
+ isUserAction,
32
+ isWatchSurfaceResponse,
33
+ getMessageType,
34
+ getSurfaceId,
35
+ } from './types.js';
36
+
37
+ // Protocol Handler
38
+ export type {
39
+ ProtocolEventMap,
40
+ ProtocolEventHandler,
41
+ ProtocolHandlerOptions,
42
+ } from './protocol.js';
43
+
44
+ export { A2UXProtocolHandler } from './protocol.js';
45
+
46
+ // Parser
47
+ export type {
48
+ ParseResult,
49
+ ParserOptions,
50
+ } from './parser.js';
51
+
52
+ export {
53
+ JSONStreamParser,
54
+ SSEParser,
55
+ parseTemplateExpression,
56
+ } from './parser.js';
57
+
58
+ // Transport
59
+ export type {
60
+ TransportOptions,
61
+ TransportEventMap,
62
+ TransportEventHandler,
63
+ ConnectionState,
64
+ } from './transport.js';
65
+
66
+ export { FreesailTransport } from './transport.js';
67
+
68
+ // Store
69
+ export type {
70
+ Surface,
71
+ StoreEventMap,
72
+ StoreEventHandler,
73
+ } from './store.js';
74
+
75
+ export { SurfaceStore } from './store.js';
76
+
77
+ // Functions
78
+ export {
79
+ formatCurrency,
80
+ formatNumber,
81
+ formatPercent,
82
+ formatBytes,
83
+ formatDate,
84
+ formatRelativeTime,
85
+ pluralize,
86
+ truncate,
87
+ capitalize,
88
+ titleCase,
89
+ format,
90
+ regexTest,
91
+ regexMatch,
92
+ regexReplace,
93
+ isEmpty,
94
+ isEmail,
95
+ isUrl,
96
+ take,
97
+ takeLast,
98
+ join,
99
+ ifElse,
100
+ coalesce,
101
+ standardFunctions,
102
+ } from './functions.js';
103
+
104
+ export type { StandardFunctions, CurrencyOptions, NumberOptions, DateOptions } from './functions.js';
105
+
106
+ // =============================================================================
107
+ // Convenience: Freesail Client
108
+ // =============================================================================
109
+
110
+ import { FreesailTransport, type TransportOptions } from './transport.js';
111
+ import { A2UXProtocolHandler, type ProtocolHandlerOptions } from './protocol.js';
112
+ import { SurfaceStore } from './store.js';
113
+ import type { A2UXMessage, UserActionMessage, WatchSurfaceResponseMessage } from './types.js';
114
+
115
+ export interface FreesailClientOptions extends TransportOptions {
116
+ /** Protocol handler options */
117
+ protocol?: Omit<ProtocolHandlerOptions, 'store'>;
118
+ }
119
+
120
+ /**
121
+ * FreesailClient is the main entry point for client applications.
122
+ * It combines transport, protocol handling, and state management.
123
+ */
124
+ export class FreesailClient {
125
+ private transport: FreesailTransport;
126
+ private protocol: A2UXProtocolHandler;
127
+ private store: SurfaceStore;
128
+
129
+ constructor(options: FreesailClientOptions) {
130
+ this.store = new SurfaceStore();
131
+
132
+ this.protocol = new A2UXProtocolHandler({
133
+ ...options.protocol,
134
+ store: this.store,
135
+ });
136
+
137
+ this.transport = new FreesailTransport(options);
138
+
139
+ // Wire up transport messages to protocol handler
140
+ this.transport.on('message', (message: A2UXMessage) => {
141
+ this.protocol.processMessage(message);
142
+ });
143
+
144
+ // Wire up watch callbacks to transport
145
+ this.store.on('watchCallback', (response: WatchSurfaceResponseMessage) => {
146
+ this.transport.sendWatchResponse(response);
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Connect to the server
152
+ */
153
+ async connect(): Promise<void> {
154
+ await this.transport.connect();
155
+ }
156
+
157
+ /**
158
+ * Disconnect from the server
159
+ */
160
+ disconnect(): void {
161
+ this.transport.disconnect();
162
+ this.protocol.destroy();
163
+ }
164
+
165
+ /**
166
+ * Send a user action
167
+ */
168
+ async sendAction(
169
+ surfaceId: string,
170
+ action: string,
171
+ context?: Record<string, unknown>
172
+ ): Promise<void> {
173
+ // Use full context from store if not provided
174
+ const fullContext = context ?? this.store.getDataModel(surfaceId) ?? {};
175
+
176
+ const message: UserActionMessage = {
177
+ userAction: {
178
+ surfaceId,
179
+ action,
180
+ context: fullContext,
181
+ }
182
+ };
183
+
184
+ await this.transport.sendAction(message);
185
+ }
186
+
187
+ /**
188
+ * Get the surface store
189
+ */
190
+ getStore(): SurfaceStore {
191
+ return this.store;
192
+ }
193
+
194
+ /**
195
+ * Get the protocol handler
196
+ */
197
+ getProtocol(): A2UXProtocolHandler {
198
+ return this.protocol;
199
+ }
200
+
201
+ /**
202
+ * Get the transport
203
+ */
204
+ getTransport(): FreesailTransport {
205
+ return this.transport;
206
+ }
207
+
208
+ /**
209
+ * Check if connected
210
+ */
211
+ get isConnected(): boolean {
212
+ return this.transport.isConnected;
213
+ }
214
+ }