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.
- package/README.md +190 -5
- package/docs/A2UX_Protocol.md +183 -0
- package/docs/Agents.md +218 -0
- package/docs/Architecture.md +285 -0
- package/docs/CatalogReference.md +377 -0
- package/docs/GettingStarted.md +230 -0
- package/examples/demo/package.json +21 -0
- package/examples/demo/public/index.html +381 -0
- package/examples/demo/server.js +253 -0
- package/package.json +38 -5
- package/packages/core/package.json +48 -0
- package/packages/core/src/functions.ts +403 -0
- package/packages/core/src/index.ts +214 -0
- package/packages/core/src/parser.ts +270 -0
- package/packages/core/src/protocol.ts +254 -0
- package/packages/core/src/store.ts +452 -0
- package/packages/core/src/transport.ts +439 -0
- package/packages/core/src/types.ts +209 -0
- package/packages/core/tsconfig.json +10 -0
- package/packages/lit-ui/package.json +44 -0
- package/packages/lit-ui/src/catalogs/standard/catalog.json +405 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Badge.ts +96 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Button.ts +147 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Card.ts +78 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Checkbox.ts +94 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Column.ts +66 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Divider.ts +59 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Image.ts +54 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Input.ts +125 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Progress.ts +79 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Row.ts +68 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Select.ts +110 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Spacer.ts +37 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Spinner.ts +76 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Text.ts +86 -0
- package/packages/lit-ui/src/catalogs/standard/elements/index.ts +18 -0
- package/packages/lit-ui/src/catalogs/standard/index.ts +17 -0
- package/packages/lit-ui/src/index.ts +84 -0
- package/packages/lit-ui/src/renderer.ts +211 -0
- package/packages/lit-ui/src/types.ts +49 -0
- package/packages/lit-ui/src/utils/define-props.ts +157 -0
- package/packages/lit-ui/src/utils/index.ts +2 -0
- package/packages/lit-ui/src/utils/registry.ts +139 -0
- package/packages/lit-ui/tsconfig.json +11 -0
- package/packages/server/package.json +61 -0
- package/packages/server/src/adapters/index.ts +5 -0
- package/packages/server/src/adapters/langchain.ts +175 -0
- package/packages/server/src/adapters/openai.ts +209 -0
- package/packages/server/src/catalog-loader.ts +311 -0
- package/packages/server/src/index.ts +142 -0
- package/packages/server/src/stream.ts +329 -0
- package/packages/server/tsconfig.json +11 -0
- package/tsconfig.base.json +23 -0
- package/index.js +0 -3
package/README.md
CHANGED
|
@@ -1,7 +1,192 @@
|
|
|
1
|
-
# Freesail
|
|
1
|
+
# Freesail SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Industrial Strength Generative UI SDK** - Decouple AI Agents from the Frontend
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Freesail is a **Headless**, **Direct-Transport**, and **Schema-Driven** SDK that enables ANY AI Agent framework to drive ANY UI through a standardized JSON protocol (A2UX) over HTTP SSE.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────┐ SSE Stream ┌─────────────────┐
|
|
13
|
+
│ AI Agent │ ───────────────► │ Frontend │
|
|
14
|
+
│ (Any LLM) │ A2UX Protocol │ (Any Framework)│
|
|
15
|
+
│ │ ◄─────────────── │ │
|
|
16
|
+
│ LangChain │ User Actions │ React/Lit/Vue │
|
|
17
|
+
│ OpenAI │ │ Angular/Vanilla│
|
|
18
|
+
│ LlamaIndex │ │ │
|
|
19
|
+
└─────────────────┘ └─────────────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Key Features
|
|
23
|
+
|
|
24
|
+
- 🔌 **Framework Agnostic** - Works with LangChain, LlamaIndex, OpenAI, or vanilla Node.js
|
|
25
|
+
- 🎨 **UI Agnostic** - Drive React, Angular, Vue, or Web Components
|
|
26
|
+
- 📡 **SSE Transport** - Resilient streaming with auto-reconnect and offline support
|
|
27
|
+
- 📋 **Schema-Driven** - Component catalogs are the single source of truth
|
|
28
|
+
- 🔒 **Enterprise Ready** - Built for self-hosted / air-gapped deployments
|
|
29
|
+
|
|
30
|
+
## Packages
|
|
31
|
+
|
|
32
|
+
| Package | Description |
|
|
33
|
+
|---------|-------------|
|
|
34
|
+
| `@freesail/core` | A2UX Protocol parser, transport layer, state management |
|
|
35
|
+
| `@freesail/lit-ui` | Lit-based Web Components with standard catalog |
|
|
36
|
+
| `@freesail/server` | SSE stream engine and framework adapters |
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install @freesail/core @freesail/server
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Server Setup (Express)
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import express from 'express';
|
|
50
|
+
import { createSSEHandler, CatalogToToolConverter } from '@freesail/server';
|
|
51
|
+
|
|
52
|
+
const app = express();
|
|
53
|
+
|
|
54
|
+
app.get('/api/stream', createSSEHandler(async (stream) => {
|
|
55
|
+
// Create a UI surface
|
|
56
|
+
stream.createSurface('main', 'standard_catalog_v1');
|
|
57
|
+
|
|
58
|
+
// Send components
|
|
59
|
+
stream.send({
|
|
60
|
+
updateComponents: {
|
|
61
|
+
surfaceId: 'main',
|
|
62
|
+
components: [
|
|
63
|
+
{ id: 'root', component: 'Column', children: ['greeting'] },
|
|
64
|
+
{ id: 'greeting', component: 'Text', text: 'Hello from AI!' }
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
app.listen(3000);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Client Setup
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<script type="module">
|
|
77
|
+
import { FreesailClient } from '@freesail/core';
|
|
78
|
+
import { registerStandardCatalog, createSurfaceRenderer } from '@freesail/lit-ui';
|
|
79
|
+
|
|
80
|
+
// Register UI components
|
|
81
|
+
registerStandardCatalog();
|
|
82
|
+
|
|
83
|
+
// Connect to server
|
|
84
|
+
const client = new FreesailClient({ url: '/api/stream' });
|
|
85
|
+
await client.connect();
|
|
86
|
+
|
|
87
|
+
// Create renderer
|
|
88
|
+
const renderer = createSurfaceRenderer({
|
|
89
|
+
container: document.getElementById('app'),
|
|
90
|
+
surfaceId: 'main',
|
|
91
|
+
store: client.getStore()
|
|
92
|
+
});
|
|
93
|
+
</script>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## The A2UX Protocol
|
|
97
|
+
|
|
98
|
+
Freesail uses the **A2UX Protocol**, an extension of Google's A2UI specification for agent-to-UI communication.
|
|
99
|
+
|
|
100
|
+
### Server → Client Messages
|
|
101
|
+
|
|
102
|
+
| Message | Purpose |
|
|
103
|
+
|---------|---------|
|
|
104
|
+
| `createSurface` | Initialize a UI container with a component catalog |
|
|
105
|
+
| `updateComponents` | Stream component structure (flat adjacency list) |
|
|
106
|
+
| `updateDataModel` | Push data changes without re-rendering |
|
|
107
|
+
| `deleteSurface` | Remove a surface |
|
|
108
|
+
| `watchSurface` | Request periodic state updates |
|
|
109
|
+
|
|
110
|
+
### Client → Server Messages
|
|
111
|
+
|
|
112
|
+
| Message | Purpose |
|
|
113
|
+
|---------|---------|
|
|
114
|
+
| `userAction` | Report user interactions with full context |
|
|
115
|
+
| `watchSurfaceResponse` | Periodic state snapshot |
|
|
116
|
+
|
|
117
|
+
## Architecture
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
/freesail
|
|
121
|
+
├── /packages
|
|
122
|
+
│ ├── /core # Protocol, Transport, State
|
|
123
|
+
│ ├── /lit-ui # Web Components
|
|
124
|
+
│ └── /server # SSE Engine, Adapters
|
|
125
|
+
├── /examples
|
|
126
|
+
│ └── /demo # Demo application
|
|
127
|
+
└── /docs # Documentation
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Framework Adapters
|
|
131
|
+
|
|
132
|
+
### LangChain
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { z } from 'zod';
|
|
136
|
+
import { DynamicStructuredTool } from 'langchain/tools';
|
|
137
|
+
import { createLangChainTools } from '@freesail/server/adapters/langchain';
|
|
138
|
+
|
|
139
|
+
const toolConfigs = createLangChainTools(converter, catalogId, { stream, catalogId }, z);
|
|
140
|
+
const tools = toolConfigs.map(config => new DynamicStructuredTool(config));
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### OpenAI
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { createOpenAIAdapter, processOpenAIResponse } from '@freesail/server/adapters/openai';
|
|
147
|
+
|
|
148
|
+
const adapter = createOpenAIAdapter({ stream, catalogId, converter });
|
|
149
|
+
|
|
150
|
+
const response = await openai.chat.completions.create({
|
|
151
|
+
model: 'gpt-4',
|
|
152
|
+
messages,
|
|
153
|
+
tools: adapter.getTools(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const results = processOpenAIResponse(response, adapter);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Standard Catalog Components
|
|
160
|
+
|
|
161
|
+
The standard catalog includes cross-compatible A2UI components:
|
|
162
|
+
|
|
163
|
+
- **Layout**: `Column`, `Row`, `Card`, `Spacer`, `Divider`
|
|
164
|
+
- **Text**: `Text` (with variants h1-h6, body, caption)
|
|
165
|
+
- **Input**: `Input`, `Select`, `Checkbox`
|
|
166
|
+
- **Feedback**: `Button`, `Badge`, `Spinner`, `Progress`
|
|
167
|
+
- **Media**: `Image`
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Install dependencies
|
|
173
|
+
npm install
|
|
174
|
+
|
|
175
|
+
# Build all packages
|
|
176
|
+
npm run build
|
|
177
|
+
|
|
178
|
+
# Run demo
|
|
179
|
+
cd examples/demo
|
|
180
|
+
npm run dev
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Design Principles
|
|
184
|
+
|
|
185
|
+
1. **The Contract is the Code** - No duplicated logic between schema and implementation
|
|
186
|
+
2. **Schema-First** - Component catalogs are the single source of truth
|
|
187
|
+
3. **Orchestrator Agnostic** - Core logic never imports framework-specific code
|
|
188
|
+
4. **Stateless Agent** - Client sends full context with every action
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT © Freesail Team
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
The A2UX protocol is a standardized JSON schema that decouples AI agents from the frontend code, allowing them to render interfaces remotely. It extends the Google A2UI protocol.
|
|
2
|
+
|
|
3
|
+
A2UX supports the below Server to Client Messages:
|
|
4
|
+
|
|
5
|
+
1. createSurface: Initializes a UI container and loads a specific "Catalog" (vocabulary of allowed components). Sending a createSurface message allows the UI to start rendering the UI container so the user knows that the LLM is working behind the scenes to render the UI.
|
|
6
|
+
|
|
7
|
+
Properties:
|
|
8
|
+
|
|
9
|
+
- surfaceId (string, required): The unique identifier for the UI surface to
|
|
10
|
+
be rendered. It should be unique to the session.
|
|
11
|
+
|
|
12
|
+
- catalogId (string, required): A string that uniquely identifies the component catalog used for this surface.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"createSurface": {
|
|
18
|
+
"surfaceId": "user_profile_card",
|
|
19
|
+
"catalogId":"https://thendral.ai/standard_catalog_v1.json"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
2. updateComponents: Streams the structural definition of the UI to add or update in a surface.
|
|
24
|
+
|
|
25
|
+
Properties:
|
|
26
|
+
|
|
27
|
+
- surfaceId (string, required): The unique identifier for the UI surface to be updated.
|
|
28
|
+
|
|
29
|
+
- components (array, required): A list of component objects. The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"updateComponents": {
|
|
35
|
+
"surfaceId": "user_profile_card",
|
|
36
|
+
"components": [
|
|
37
|
+
{
|
|
38
|
+
"id": "root",
|
|
39
|
+
"component": "Column",
|
|
40
|
+
"children": ["user_name", "user_title"]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "user_name",
|
|
44
|
+
"component": "Text",
|
|
45
|
+
"text": "John Doe"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "user_title",
|
|
49
|
+
"component": "Text",
|
|
50
|
+
"text": "Software Engineer"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
3. updateDataModel: This message is used to send or update the data that populates the UI components (e.g., to update temperature to 25C). It allows the server to change the UI's content without resending the entire component structure.
|
|
57
|
+
|
|
58
|
+
Properties:
|
|
59
|
+
|
|
60
|
+
- surfaceId (string, required): The unique identifier for the UI surface this data model update applies to.
|
|
61
|
+
|
|
62
|
+
- path (string, optional): A JSON Pointer to a specific location within the data model (e.g., /user/name ). If omitted or set to / , the entire data model for the surface will be replaced.
|
|
63
|
+
|
|
64
|
+
- op (string, optional): The operation to perform on the data model. Must be 'add', 'replace' or remove'. If omitted, defaults to 'replace'.
|
|
65
|
+
|
|
66
|
+
- value (object): The data to be updated in the data model. This can be any valid JSON object. Required if op is 'add' or 'replace'. Not allowed if op is 'remove'.
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"updateDataModel": {
|
|
73
|
+
"surfaceId": "user_profile_card",
|
|
74
|
+
"path": "/user",
|
|
75
|
+
"op": "replace",
|
|
76
|
+
"value": {
|
|
77
|
+
"name": "Jane Doe",
|
|
78
|
+
"title": "Software Engineer"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
4. deleteSurface : This message instructs the client to remove a surface and all its associated components and data from the UI.
|
|
84
|
+
|
|
85
|
+
Properties:
|
|
86
|
+
|
|
87
|
+
- surfaceId (string, required): The unique identifier for the UI surface to be deleted.
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"deleteSurface": {
|
|
93
|
+
"surfaceId": "user_profile_card"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
5. watchSurface: Sometimes the agent may want to know about the current state of the surface. The watchSurface message is used to watch the surface at set intervals for a set period of time. The client will monitor the surface and send the current state of the surface to the server. This mechanism provides automatic updates to the agent without relying on user action.
|
|
98
|
+
|
|
99
|
+
Properties:
|
|
100
|
+
|
|
101
|
+
- surfaceId (string, required): The unique identifier of the UI surface where this watcher should be attached.
|
|
102
|
+
|
|
103
|
+
- interval (number, optional): Time in seconds to wait before sending a surfaceState message to the server. This avoids frequent updates. The default value is 10 seconds.
|
|
104
|
+
|
|
105
|
+
- expiresIn (number, optional): Time in seconds when the client should remove the watch. The default value is 300 seconds.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"watchSurface": {
|
|
112
|
+
"surfaceId": "user_profile_card",
|
|
113
|
+
"interval": 30,
|
|
114
|
+
"expiresIn": 100
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
6. unwatchSurface: Removes a watch that was previously set.
|
|
120
|
+
|
|
121
|
+
Properties:
|
|
122
|
+
|
|
123
|
+
- surfaceId (string, required): The unique identifier for the UI surface for which the watch should be stopped.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"unwatchSurface": {
|
|
130
|
+
"surfaceId": "user_profile_card"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
### Client to Server messages
|
|
135
|
+
|
|
136
|
+
6. userAction: This message is sent from the client (UI) to the server (agent) to report a user action. It serves as the primary way the agent learns about the user's intent.
|
|
137
|
+
|
|
138
|
+
Properties:
|
|
139
|
+
|
|
140
|
+
- surfaceId (string, required): The unique identifier for the UI surface where the action originated.
|
|
141
|
+
|
|
142
|
+
- action (string, required): A semantic label identifying what happened. For standard actions, it describes the intent (e.g., submit_form, Maps_next).
|
|
143
|
+
|
|
144
|
+
- context (object, required): A JSON object that contains the data relevant to the action.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"userAction": {
|
|
151
|
+
"surfaceId": "user_profile_card",
|
|
152
|
+
"action": "save_profile_click",
|
|
153
|
+
"context": {
|
|
154
|
+
"name": "Jane Doe",
|
|
155
|
+
"title": "Senior Engineer",
|
|
156
|
+
"email": "jane@example.com",
|
|
157
|
+
"newsletter_opt_in": true
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
7. watchSurfaceResponse: This message is sent from the client (UI) to the agent to report the current state of the surface data model, in response to a watchSurface request. It serves as a way for the agent to get information about the state of the suface data automatically.
|
|
163
|
+
|
|
164
|
+
Properties:
|
|
165
|
+
|
|
166
|
+
- surfaceId (string, required): The unique identifier for the UI surface.
|
|
167
|
+
|
|
168
|
+
- data (object, required): A JSON object that contains the data relevant to the action.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"watchSurfaceResponse": {
|
|
175
|
+
"surfaceId": "user_profile_card",
|
|
176
|
+
"data": {
|
|
177
|
+
"/user/name": "Jack Ryan",
|
|
178
|
+
"/user/email": "jack@example.com",
|
|
179
|
+
"/user/postcode": "W1T 2EW"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
package/docs/Agents.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# **Agent Configuration: Freesail Architect**
|
|
2
|
+
|
|
3
|
+
## **1\. Project Introduction**
|
|
4
|
+
|
|
5
|
+
**Freesail** is an "Industrial Strength" Generative UI SDK designed to decouple AI Agents from the Frontend.
|
|
6
|
+
|
|
7
|
+
Unlike frameworks that rely on heavy React wrappers or standardized agent protocols, Freesail is **Headless**, **Direct-Transport**, and **Schema-Driven**.
|
|
8
|
+
|
|
9
|
+
* **The Goal:** Allow **ANY Agent Framework** (LangChain, LlamaIndex, Vercel AI, or Vanilla Node.js) to drive any UI (Legacy, Angular, React, Vanilla) using a standardized stream of JSON events over HTTP SSE.
|
|
10
|
+
* **The "Moat":** We provide the resilient streaming runtime, state management, and protocol handling that enterprise clients require for self-hosted / air-gapped applications.
|
|
11
|
+
|
|
12
|
+
## **2\. Role & Vision**
|
|
13
|
+
|
|
14
|
+
You are the **Lead Architect for Freesail**.
|
|
15
|
+
|
|
16
|
+
* **Philosophy:** "The Contract is the Code." We do not duplicate logic. If it exists in the JSON Schema, it exists in the App.
|
|
17
|
+
* **Mission:** Build a system where the **Component Catalog** (standard\_catalog.json \+ custom catalogs) is the single source of truth for both the **AI's System Prompt** (Server) and the **UI's Properties** (Client).
|
|
18
|
+
* **Strategy:** Prioritize "Below the Radar" tech (Web Components, SSE, Node.js) to ensure maximum compatibility and zero vendor lock-in.
|
|
19
|
+
|
|
20
|
+
## **3\. Tech Stack & Constraints**
|
|
21
|
+
|
|
22
|
+
| Layer | Choice | Rationale |
|
|
23
|
+
| :---- | :---- | :---- |
|
|
24
|
+
| **Frontend** | **Lit** (Web Components) | Native browser support, Shadow DOM isolation, works everywhere. |
|
|
25
|
+
| **Backend** | **Node.js** (Express) | Custom runtime for managing SSE streams and Agent state. |
|
|
26
|
+
| **Transport** | **Freesail Stream** | Raw Server-Sent Events (SSE) carrying A2UX payloads. |
|
|
27
|
+
| **Orchestrator** | **Agnostic** (Pluggable) | Supports LangChain, LlamaIndex, or raw OpenAI via **Adapters**. |
|
|
28
|
+
| **Protocol** | **A2UX** (A2UI-Extended) | Strict JSON schema for UI definition (createSurface, updateComponents) and Interaction (userAction). |
|
|
29
|
+
| **Std Lib** | **A2UI Standard** | We implement the standard Row, Column, Text, Input components to ensure cross-compatibility. |
|
|
30
|
+
|
|
31
|
+
### **⛔ Strict Anti-Patterns**
|
|
32
|
+
|
|
33
|
+
* **NO Framework Lock-in:** Core logic must never import langchain or llamaindex directly. Use dependency injection or adapters.
|
|
34
|
+
* **NO Duplicate Property Definitions:** Do not define properties in TypeScript if they are already in the loaded catalog. Use the defineProps() helper.
|
|
35
|
+
* **NO Raw HTML Injection:** Never use innerHTML. All rendering must go through the Registry.
|
|
36
|
+
* **NO "Delta" State Patching on Client:** The client blindly accepts state replacements. The Agent is the source of truth.
|
|
37
|
+
|
|
38
|
+
## **4\. Architecture Standards (The "Golden Stack")**
|
|
39
|
+
|
|
40
|
+
1. **The Source of Truth (Catalogs):** Catalogs are self-contained modules located in packages/lit-ui/src/catalogs/\<name\>/. Each folder contains the JSON definition and the TypeScript elements implementation.
|
|
41
|
+
2. **The Context Injector (Server):** Reads the **selected** Catalog(s) at runtime and converts them into **Standard JSON Schema Tools** compatible with any LLM.
|
|
42
|
+
3. **The Renderer (Lit):** Dynamically loads component definitions from the catalogId specified in createSurface.
|
|
43
|
+
4. **The Stream Engine:** A dedicated StreamStore class that manages SSE connections (res.write) and handles backpressure.
|
|
44
|
+
|
|
45
|
+
## **5\. Implementation Rules**
|
|
46
|
+
|
|
47
|
+
### **Rule \#1: Schema-First Components (The "Dry" Law)**
|
|
48
|
+
|
|
49
|
+
* **Pattern:** Lit components must hydrate their properties directly from the Catalog JSON located **in the same folder**.
|
|
50
|
+
* **Requirement:** Never manually write @property decorators for data fields. Use the defineProps utility.
|
|
51
|
+
|
|
52
|
+
// GOOD: Colocated Import
|
|
53
|
+
import catalog from '../catalog.json';
|
|
54
|
+
|
|
55
|
+
export class FreesailText extends LitElement {
|
|
56
|
+
// Auto-generates properties based on the JSON definition
|
|
57
|
+
static properties \= defineProps(catalog, 'Text');
|
|
58
|
+
render() { return html\`\<span\>${this.text}\</span\>\`; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
### **Rule \#2: Multi-Catalog Support**
|
|
62
|
+
|
|
63
|
+
* **Requirement:** The system must support loading different catalogs for different surfaces.
|
|
64
|
+
* **Mechanism:**
|
|
65
|
+
* **Server:** The Agent decides which catalog to use (e.g., finance\_v1) and sends it in createSurface(catalogId="finance\_v1").
|
|
66
|
+
* **Client:** The Registry maps catalogId to a specific bundle of Web Components.
|
|
67
|
+
* **Validation:** The System Prompt generator must only include components from the *active* catalog to prevent hallucinations.
|
|
68
|
+
|
|
69
|
+
### **Rule \#3: Orchestrator Agnosticism (The Adapter Pattern)**
|
|
70
|
+
|
|
71
|
+
* **Pattern:** Core logic is pure TypeScript. Framework specifics are Adapters.
|
|
72
|
+
* **Requirement:**
|
|
73
|
+
* **Core:** CatalogToToolConverter takes a catalog and outputs a standard JSON Schema Tool definition ({ name: "render\_ui", parameters: ... }).
|
|
74
|
+
* **Adapters:** Create thin wrappers in server/src/adapters/ for specific frameworks:
|
|
75
|
+
* LangChainAdapter: Converts the standard tool to a DynamicStructuredTool.
|
|
76
|
+
* LlamaIndexAdapter: Converts the standard tool to a FunctionTool.
|
|
77
|
+
* OpenAIAdapter: Converts the standard tool to an OpenAI API tools array.
|
|
78
|
+
|
|
79
|
+
### **Rule \#4: Client-Side Logic (The Function Registry)**
|
|
80
|
+
|
|
81
|
+
* **Pattern:** Do not force the Agent to format data. Use A2UI Standard Functions for **Data Logic**.
|
|
82
|
+
* **Requirement:** Implement the standard function list (formatCurrency, pluralize, regex) in @freesail/core.
|
|
83
|
+
* **Execution:** The Parser must detect template strings ${...} and execute the logic locally against the Data Model.
|
|
84
|
+
|
|
85
|
+
### **Rule \#5: The "Full Context" Action Protocol**
|
|
86
|
+
|
|
87
|
+
* **Pattern:** Stateless Communication.
|
|
88
|
+
* **Requirement:** When a user action occurs, the client must gather the **entire state object** for that surfaceId and send it to the agent via the context property.
|
|
89
|
+
* **Why:** The Agent is stateless. It relies on the context payload to know the current state.
|
|
90
|
+
|
|
91
|
+
### **Rule \#6: The "Build & Validate" Loop (CRITICAL)**
|
|
92
|
+
|
|
93
|
+
* **Pattern:** Never assume code works. Prove it.
|
|
94
|
+
* **Requirement:** After generating any code (Client or Server):
|
|
95
|
+
1. **Run Build:** Execute npm run build in the relevant package.
|
|
96
|
+
2. **Fix Errors:** If the build fails, read the error log, fix the code, and retry.
|
|
97
|
+
3. **Validate Schema:** Ensure generated JSON payloads match standard\_catalog.json exactly.
|
|
98
|
+
|
|
99
|
+
### **Rule \#7: Transport Resilience (The "Iron Pipe")**
|
|
100
|
+
|
|
101
|
+
* **Pattern:** Robust Retry & Reconnect (SDK Responsibility).
|
|
102
|
+
* **Requirement:** The @freesail/core SDK handles network faults transparently.
|
|
103
|
+
* **SSE Reconnection:** Automatically reconnect the event stream with exponential backoff if the connection drops.
|
|
104
|
+
* **Action Queueing:** If the user performs an action (userAction) while offline, queue it in indexedDB and flush when online.
|
|
105
|
+
* **Idempotency:** The Server must handle duplicate userAction messages gracefully.
|
|
106
|
+
|
|
107
|
+
## **6\. The A2UX Protocol Specification**
|
|
108
|
+
|
|
109
|
+
We use the **A2UX Protocol**, an extension of Google A2UI v0.9.
|
|
110
|
+
|
|
111
|
+
### **Downstream (Server → Client)**
|
|
112
|
+
|
|
113
|
+
| Message | Purpose | Schema Key |
|
|
114
|
+
| :---- | :---- | :---- |
|
|
115
|
+
| **createSurface** | Init UI container & select Catalog. | createSurface |
|
|
116
|
+
| **updateComponents** | Stream DOM structure. | updateComponents |
|
|
117
|
+
| **updateDataModel** | Push state changes. | updateDataModel |
|
|
118
|
+
| **deleteSurface** | Cleanup/Destroy. | deleteSurface |
|
|
119
|
+
| **watchSurface** | Config client triggers. | watchSurface |
|
|
120
|
+
| **unwatchSurface** | Remove triggers. | unwatchSurface |
|
|
121
|
+
|
|
122
|
+
**Schema: watchSurface**
|
|
123
|
+
|
|
124
|
+
Configures the client to monitor the surface and report state automatically.
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
"watchSurface": {
|
|
128
|
+
"surfaceId": "user\_profile\_card",
|
|
129
|
+
"interval": 30, // seconds
|
|
130
|
+
"expiresIn": 100 // seconds
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
### **Upstream (Client → Server)**
|
|
135
|
+
|
|
136
|
+
| Message | Purpose | Schema Key |
|
|
137
|
+
| :---- | :---- | :---- |
|
|
138
|
+
| **userAction** | Explicit User Intent. | userAction |
|
|
139
|
+
| **watchSurfaceResponse** | Passive State Event. | watchSurfaceResponse |
|
|
140
|
+
|
|
141
|
+
**Schema: userAction**
|
|
142
|
+
|
|
143
|
+
Note: usage of context to carry the data model.
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
"userAction": {
|
|
147
|
+
"surfaceId": "user\_profile\_card",
|
|
148
|
+
"action": "save\_profile\_click",
|
|
149
|
+
"context": {
|
|
150
|
+
"name": "Jane Doe",
|
|
151
|
+
"title": "Senior Engineer",
|
|
152
|
+
"email": "jane@example.com",
|
|
153
|
+
"newsletter\_opt\_in": true
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
**Schema: watchSurfaceResponse**
|
|
159
|
+
|
|
160
|
+
Note: usage of data to carry the state.
|
|
161
|
+
|
|
162
|
+
{
|
|
163
|
+
"watchSurfaceResponse": {
|
|
164
|
+
"surfaceId": "user\_profile\_card",
|
|
165
|
+
"data": {
|
|
166
|
+
"/user/name": "Jack Ryan",
|
|
167
|
+
"/user/email": "jack@example.com",
|
|
168
|
+
"/user/postcode": "W1T 2EW"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
## **7\. Directory Structure**
|
|
174
|
+
|
|
175
|
+
Maintain this structure to enforce the **Vertical Slice** pattern where catalogs and their elements are colocated, and the Server logic is decoupled from specific frameworks.
|
|
176
|
+
|
|
177
|
+
/freesail
|
|
178
|
+
├── /packages
|
|
179
|
+
│ ├── /core \# @freesail/core (The Brain \- Protocol Parser & Store)
|
|
180
|
+
│ │ ├── src/
|
|
181
|
+
│ │ │ ├── protocol.ts \# A2UX Listener
|
|
182
|
+
│ │ │ ├── parser.ts \# JSON Stream Parser
|
|
183
|
+
│ │ │ ├── transport.ts \# ⭐️ Network Retry & Queue Logic
|
|
184
|
+
│ │ │ ├── functions.ts \# Standard Lib (formatCurrency, regex)
|
|
185
|
+
│ │ │ └── store.ts \# Reactive Data Model
|
|
186
|
+
│ │
|
|
187
|
+
│ ├── /lit-ui \# @freesail/lit (The Face \- Web Components)
|
|
188
|
+
│ │ ├── src/
|
|
189
|
+
│ │ │ ├── utils/ \# defineProps helper
|
|
190
|
+
│ │ │ ├── catalogs/ \# ⭐️ COLOCATED CATALOGS
|
|
191
|
+
│ │ │ │ ├── standard/
|
|
192
|
+
│ │ │ │ │ ├── catalog.json \# Standard A2UI Definition
|
|
193
|
+
│ │ │ │ │ └── elements/ \# Implementation
|
|
194
|
+
│ │ │ │ │ ├── Text.ts
|
|
195
|
+
│ │ │ │ │ └── Button.ts
|
|
196
|
+
│ │ │ │ │
|
|
197
|
+
│ │ │ │ └── finance/
|
|
198
|
+
│ │ │ │ ├── catalog.json \# Finance Domain Definition
|
|
199
|
+
│ │ │ │ └── elements/ \# Implementation
|
|
200
|
+
│ │ │ │ ├── Ticker.ts
|
|
201
|
+
│ │ │ │ └── Chart.ts
|
|
202
|
+
│ │
|
|
203
|
+
│ └── /server \# @freesail/server (The Orchestrator)
|
|
204
|
+
│ ├── src/
|
|
205
|
+
│ │ ├── stream.ts \# SSE Implementation (res.write)
|
|
206
|
+
│ │ ├── catalog-loader.ts \# Generic JSON Schema Generator
|
|
207
|
+
│ │ └── adapters/ \# ⭐️ FRAMEWORK ADAPTERS
|
|
208
|
+
│ │ ├── langchain.ts \# LangChain Tool Wrapper
|
|
209
|
+
│ │ ├── llamaindex.ts \# LlamaIndex Tool Wrapper
|
|
210
|
+
│ │ └── openai.ts \# OpenAI Function Definition
|
|
211
|
+
│
|
|
212
|
+
├── /docs \# ⭐️ DEVELOPER GUIDES
|
|
213
|
+
│ ├── GettingStarted.md
|
|
214
|
+
│ ├── Architecture.md
|
|
215
|
+
│ └── CatalogReference.md
|
|
216
|
+
│
|
|
217
|
+
└── /examples
|
|
218
|
+
└── /langchain-demo \# Proof of "Any-Stack" with LangChain
|