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
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineProps Utility
|
|
3
|
+
*
|
|
4
|
+
* Hydrates Lit component properties directly from the Catalog JSON.
|
|
5
|
+
* This is the core of the "Dry" Law - Schema-First Components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PropertyDeclaration } from 'lit';
|
|
9
|
+
import type { CatalogDefinition, PropertyDefinition } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert JSON Schema type to Lit property type
|
|
13
|
+
*/
|
|
14
|
+
function schemaTypeToLitType(schemaProp: PropertyDefinition): PropertyDeclaration {
|
|
15
|
+
let type: typeof String | typeof Number | typeof Boolean | typeof Array | typeof Object = String;
|
|
16
|
+
|
|
17
|
+
switch (schemaProp.type) {
|
|
18
|
+
case 'string':
|
|
19
|
+
type = String;
|
|
20
|
+
break;
|
|
21
|
+
case 'number':
|
|
22
|
+
case 'integer':
|
|
23
|
+
type = Number;
|
|
24
|
+
break;
|
|
25
|
+
case 'boolean':
|
|
26
|
+
type = Boolean;
|
|
27
|
+
break;
|
|
28
|
+
case 'array':
|
|
29
|
+
type = Array;
|
|
30
|
+
break;
|
|
31
|
+
case 'object':
|
|
32
|
+
type = Object;
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
type = String;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
type,
|
|
40
|
+
reflect: schemaProp.reflect ?? false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate Lit property declarations from a catalog component definition
|
|
46
|
+
*
|
|
47
|
+
* @param catalog - The catalog JSON object
|
|
48
|
+
* @param componentName - Name of the component in the catalog
|
|
49
|
+
* @returns Property declarations for use with Lit's static properties
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import catalog from './catalog.json';
|
|
54
|
+
*
|
|
55
|
+
* export class FreesailText extends LitElement {
|
|
56
|
+
* static properties = defineProps(catalog, 'Text');
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function defineProps(
|
|
61
|
+
catalog: CatalogDefinition,
|
|
62
|
+
componentName: string
|
|
63
|
+
): Record<string, PropertyDeclaration> {
|
|
64
|
+
const componentDef = catalog.components?.[componentName];
|
|
65
|
+
|
|
66
|
+
if (!componentDef) {
|
|
67
|
+
console.warn(`[defineProps] Component "${componentName}" not found in catalog`);
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const properties: Record<string, PropertyDeclaration> = {};
|
|
72
|
+
|
|
73
|
+
// Process component properties
|
|
74
|
+
if (componentDef.properties) {
|
|
75
|
+
for (const [propName, propDef] of Object.entries(componentDef.properties)) {
|
|
76
|
+
properties[propName] = schemaTypeToLitType(propDef);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return properties;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get default values for a component from the catalog
|
|
85
|
+
*/
|
|
86
|
+
export function getDefaults(
|
|
87
|
+
catalog: CatalogDefinition,
|
|
88
|
+
componentName: string
|
|
89
|
+
): Record<string, unknown> {
|
|
90
|
+
const componentDef = catalog.components?.[componentName];
|
|
91
|
+
|
|
92
|
+
if (!componentDef) {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const defaults: Record<string, unknown> = {};
|
|
97
|
+
|
|
98
|
+
if (componentDef.properties) {
|
|
99
|
+
for (const [propName, propDef] of Object.entries(componentDef.properties)) {
|
|
100
|
+
if (propDef.default !== undefined) {
|
|
101
|
+
defaults[propName] = propDef.default;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return defaults;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate a component's properties against the catalog schema
|
|
111
|
+
*/
|
|
112
|
+
export function validateProps(
|
|
113
|
+
catalog: CatalogDefinition,
|
|
114
|
+
componentName: string,
|
|
115
|
+
props: Record<string, unknown>
|
|
116
|
+
): { valid: boolean; errors: string[] } {
|
|
117
|
+
const componentDef = catalog.components?.[componentName];
|
|
118
|
+
const errors: string[] = [];
|
|
119
|
+
|
|
120
|
+
if (!componentDef) {
|
|
121
|
+
errors.push(`Component "${componentName}" not found in catalog`);
|
|
122
|
+
return { valid: false, errors };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check required properties
|
|
126
|
+
if (componentDef.required) {
|
|
127
|
+
for (const required of componentDef.required) {
|
|
128
|
+
if (props[required] === undefined || props[required] === null) {
|
|
129
|
+
errors.push(`Missing required property: ${required}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Type check properties
|
|
135
|
+
if (componentDef.properties) {
|
|
136
|
+
for (const [propName, propValue] of Object.entries(props)) {
|
|
137
|
+
const propDef = componentDef.properties[propName];
|
|
138
|
+
|
|
139
|
+
if (!propDef) {
|
|
140
|
+
// Allow additional properties unless explicitly forbidden
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const actualType = Array.isArray(propValue) ? 'array' : typeof propValue;
|
|
145
|
+
const expectedType = propDef.type;
|
|
146
|
+
|
|
147
|
+
if (actualType !== expectedType &&
|
|
148
|
+
!(expectedType === 'integer' && actualType === 'number')) {
|
|
149
|
+
errors.push(
|
|
150
|
+
`Property "${propName}" expected ${expectedType}, got ${actualType}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { valid: errors.length === 0, errors };
|
|
157
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages the registration and retrieval of Web Components
|
|
5
|
+
* based on catalog definitions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { LitElement } from 'lit';
|
|
9
|
+
import type { CatalogDefinition } from '../types.js';
|
|
10
|
+
|
|
11
|
+
type ComponentConstructor = typeof LitElement;
|
|
12
|
+
|
|
13
|
+
interface RegisteredCatalog {
|
|
14
|
+
definition: CatalogDefinition;
|
|
15
|
+
components: Map<string, ComponentConstructor>;
|
|
16
|
+
prefix: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ComponentRegistry manages Web Component registration by catalog.
|
|
21
|
+
* It ensures components are properly registered with unique prefixes
|
|
22
|
+
* and handles dynamic component resolution.
|
|
23
|
+
*/
|
|
24
|
+
export class ComponentRegistry {
|
|
25
|
+
private catalogs: Map<string, RegisteredCatalog> = new Map();
|
|
26
|
+
private globalComponents: Map<string, ComponentConstructor> = new Map();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Register a catalog and its components
|
|
30
|
+
*/
|
|
31
|
+
registerCatalog(
|
|
32
|
+
catalogId: string,
|
|
33
|
+
definition: CatalogDefinition,
|
|
34
|
+
components: Map<string, ComponentConstructor>,
|
|
35
|
+
prefix: string = 'fs'
|
|
36
|
+
): void {
|
|
37
|
+
// Validate catalog
|
|
38
|
+
if (!definition.id || !definition.version) {
|
|
39
|
+
console.warn(`[Registry] Invalid catalog definition for ${catalogId}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Store catalog
|
|
43
|
+
this.catalogs.set(catalogId, {
|
|
44
|
+
definition,
|
|
45
|
+
components,
|
|
46
|
+
prefix,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Register each component as a custom element
|
|
50
|
+
for (const [componentName, ComponentClass] of components) {
|
|
51
|
+
const tagName = this.getTagName(prefix, componentName);
|
|
52
|
+
|
|
53
|
+
if (!customElements.get(tagName)) {
|
|
54
|
+
try {
|
|
55
|
+
customElements.define(tagName, ComponentClass);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`[Registry] Failed to register ${tagName}:`, error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Also store in global lookup
|
|
62
|
+
this.globalComponents.set(`${catalogId}:${componentName}`, ComponentClass);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a component class by catalog and name
|
|
68
|
+
*/
|
|
69
|
+
getComponent(catalogId: string, componentName: string): ComponentConstructor | undefined {
|
|
70
|
+
const catalog = this.catalogs.get(catalogId);
|
|
71
|
+
return catalog?.components.get(componentName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the HTML tag name for a component
|
|
76
|
+
*/
|
|
77
|
+
getTagName(prefix: string, componentName: string): string {
|
|
78
|
+
// Convert PascalCase to kebab-case
|
|
79
|
+
const kebab = componentName
|
|
80
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
81
|
+
.toLowerCase();
|
|
82
|
+
return `${prefix}-${kebab}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get tag name for a component in a specific catalog
|
|
87
|
+
*/
|
|
88
|
+
getTagNameForCatalog(catalogId: string, componentName: string): string | undefined {
|
|
89
|
+
const catalog = this.catalogs.get(catalogId);
|
|
90
|
+
if (!catalog) return undefined;
|
|
91
|
+
return this.getTagName(catalog.prefix, componentName);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if a catalog is registered
|
|
96
|
+
*/
|
|
97
|
+
hasCatalog(catalogId: string): boolean {
|
|
98
|
+
return this.catalogs.has(catalogId);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get catalog definition
|
|
103
|
+
*/
|
|
104
|
+
getCatalogDefinition(catalogId: string): CatalogDefinition | undefined {
|
|
105
|
+
return this.catalogs.get(catalogId)?.definition;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get all registered catalog IDs
|
|
110
|
+
*/
|
|
111
|
+
getCatalogIds(): string[] {
|
|
112
|
+
return Array.from(this.catalogs.keys());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get all component names in a catalog
|
|
117
|
+
*/
|
|
118
|
+
getComponentNames(catalogId: string): string[] {
|
|
119
|
+
const catalog = this.catalogs.get(catalogId);
|
|
120
|
+
return catalog ? Array.from(catalog.components.keys()) : [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Unregister a catalog (components remain in DOM but won't be found)
|
|
125
|
+
*/
|
|
126
|
+
unregisterCatalog(catalogId: string): void {
|
|
127
|
+
const catalog = this.catalogs.get(catalogId);
|
|
128
|
+
if (catalog) {
|
|
129
|
+
// Remove from global lookup
|
|
130
|
+
for (const componentName of catalog.components.keys()) {
|
|
131
|
+
this.globalComponents.delete(`${catalogId}:${componentName}`);
|
|
132
|
+
}
|
|
133
|
+
this.catalogs.delete(catalogId);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Singleton instance
|
|
139
|
+
export const registry = new ComponentRegistry();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"composite": true,
|
|
7
|
+
"resolveJsonModule": true
|
|
8
|
+
},
|
|
9
|
+
"include": ["src/**/*", "src/**/*.json"],
|
|
10
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@freesail/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Freesail Server - SSE Stream Engine & Framework Adapters",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./adapters/langchain": {
|
|
15
|
+
"types": "./dist/adapters/langchain.d.ts",
|
|
16
|
+
"import": "./dist/adapters/langchain.js"
|
|
17
|
+
},
|
|
18
|
+
"./adapters/openai": {
|
|
19
|
+
"types": "./dist/adapters/openai.d.ts",
|
|
20
|
+
"import": "./dist/adapters/openai.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"dev": "tsc --watch",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest",
|
|
31
|
+
"lint": "eslint src --ext .ts"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@freesail/core": "^0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.10.0",
|
|
38
|
+
"typescript": "^5.3.3",
|
|
39
|
+
"vitest": "^1.2.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"langchain": ">=0.1.0",
|
|
43
|
+
"openai": ">=4.0.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"langchain": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"openai": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"sse",
|
|
55
|
+
"stream",
|
|
56
|
+
"langchain",
|
|
57
|
+
"openai",
|
|
58
|
+
"a2ux"
|
|
59
|
+
],
|
|
60
|
+
"license": "MIT"
|
|
61
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createLangChainTools, createLangChainRenderUITool, createLangChainUpdateDataTool } from './langchain.js';
|
|
2
|
+
export type { LangChainAdapterOptions } from './langchain.js';
|
|
3
|
+
|
|
4
|
+
export { OpenAIAdapter, createOpenAIAdapter, processOpenAIResponse } from './openai.js';
|
|
5
|
+
export type { OpenAIAdapterOptions, ToolCallResult } from './openai.js';
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangChain Adapter
|
|
3
|
+
*
|
|
4
|
+
* Converts Freesail tools to LangChain DynamicStructuredTool format.
|
|
5
|
+
* This adapter allows using Freesail with LangChain agents.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This adapter requires 'langchain' as a peer dependency.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ToolSchema } from '../catalog-loader.js';
|
|
11
|
+
import type { FreesailStream } from '../stream.js';
|
|
12
|
+
import { toolCallToMessages, updateDataToMessage } from '../catalog-loader.js';
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Types (Minimal LangChain interfaces to avoid direct import)
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
interface LangChainToolInput {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
schema: unknown; // Zod schema
|
|
22
|
+
func: (input: unknown) => Promise<string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ZodObjectLike {
|
|
26
|
+
parse: (input: unknown) => unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// LangChain Adapter
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
export interface LangChainAdapterOptions {
|
|
34
|
+
/** The SSE stream to send messages to */
|
|
35
|
+
stream: FreesailStream;
|
|
36
|
+
/** Catalog ID being used */
|
|
37
|
+
catalogId: string;
|
|
38
|
+
/** Callback after UI is rendered */
|
|
39
|
+
onRenderUI?: (surfaceId: string) => void;
|
|
40
|
+
/** Callback after data is updated */
|
|
41
|
+
onUpdateData?: (surfaceId: string) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a LangChain DynamicStructuredTool configuration for render_ui
|
|
46
|
+
*/
|
|
47
|
+
export function createLangChainRenderUITool(
|
|
48
|
+
toolSchema: ToolSchema,
|
|
49
|
+
options: LangChainAdapterOptions,
|
|
50
|
+
z: { object: Function; string: Function; array: Function; any: Function }
|
|
51
|
+
): LangChainToolInput {
|
|
52
|
+
const { stream, catalogId, onRenderUI } = options;
|
|
53
|
+
|
|
54
|
+
// Build Zod schema from tool schema
|
|
55
|
+
const zodSchema = buildZodSchema(toolSchema.parameters, z);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
name: toolSchema.name,
|
|
59
|
+
description: toolSchema.description,
|
|
60
|
+
schema: zodSchema,
|
|
61
|
+
func: async (input: unknown) => {
|
|
62
|
+
const { surfaceId, components } = input as {
|
|
63
|
+
surfaceId: string;
|
|
64
|
+
components: Array<{ id: string; component: string; [key: string]: unknown }>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Convert to A2UX messages and send
|
|
68
|
+
const messages = toolCallToMessages({ surfaceId, components }, catalogId);
|
|
69
|
+
|
|
70
|
+
for (const message of messages) {
|
|
71
|
+
stream.send(message);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onRenderUI?.(surfaceId);
|
|
75
|
+
|
|
76
|
+
return `UI surface "${surfaceId}" created with ${components.length} components.`;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Creates a LangChain DynamicStructuredTool configuration for update_data
|
|
83
|
+
*/
|
|
84
|
+
export function createLangChainUpdateDataTool(
|
|
85
|
+
toolSchema: ToolSchema,
|
|
86
|
+
options: LangChainAdapterOptions,
|
|
87
|
+
z: { object: Function; string: Function; optional: Function; any: Function; enum: Function }
|
|
88
|
+
): LangChainToolInput {
|
|
89
|
+
const { stream, onUpdateData } = options;
|
|
90
|
+
|
|
91
|
+
// Build Zod schema
|
|
92
|
+
const zodSchema = z.object({
|
|
93
|
+
surfaceId: z.string(),
|
|
94
|
+
path: z.string().optional(),
|
|
95
|
+
value: z.any(),
|
|
96
|
+
operation: z.enum(['add', 'replace', 'remove']).optional(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
name: toolSchema.name,
|
|
101
|
+
description: toolSchema.description,
|
|
102
|
+
schema: zodSchema,
|
|
103
|
+
func: async (input: unknown) => {
|
|
104
|
+
const toolCall = input as {
|
|
105
|
+
surfaceId: string;
|
|
106
|
+
path?: string;
|
|
107
|
+
value: unknown;
|
|
108
|
+
operation?: string;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const message = updateDataToMessage(toolCall);
|
|
112
|
+
stream.send(message);
|
|
113
|
+
|
|
114
|
+
onUpdateData?.(toolCall.surfaceId);
|
|
115
|
+
|
|
116
|
+
return `Data updated at path "${toolCall.path || '/'}" in surface "${toolCall.surfaceId}".`;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create all Freesail tools for LangChain
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { z } from 'zod';
|
|
127
|
+
* import { DynamicStructuredTool } from 'langchain/tools';
|
|
128
|
+
*
|
|
129
|
+
* const toolConfigs = createLangChainTools(converter, catalogId, { stream, catalogId }, z);
|
|
130
|
+
* const tools = toolConfigs.map(config => new DynamicStructuredTool(config));
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export function createLangChainTools(
|
|
134
|
+
converter: { generateRenderUITool: (id: string) => ToolSchema; generateUpdateDataTool: () => ToolSchema },
|
|
135
|
+
catalogId: string,
|
|
136
|
+
options: LangChainAdapterOptions,
|
|
137
|
+
z: unknown // Zod instance
|
|
138
|
+
): LangChainToolInput[] {
|
|
139
|
+
const zod = z as { object: Function; string: Function; array: Function; any: Function; optional: Function; enum: Function };
|
|
140
|
+
|
|
141
|
+
return [
|
|
142
|
+
createLangChainRenderUITool(converter.generateRenderUITool(catalogId), options, zod),
|
|
143
|
+
createLangChainUpdateDataTool(converter.generateUpdateDataTool(), options, zod),
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// =============================================================================
|
|
148
|
+
// Helpers
|
|
149
|
+
// =============================================================================
|
|
150
|
+
|
|
151
|
+
function buildZodSchema(
|
|
152
|
+
schema: ToolSchema['parameters'],
|
|
153
|
+
z: { object: Function; string: Function; array: Function; any: Function; enum?: Function }
|
|
154
|
+
): ZodObjectLike {
|
|
155
|
+
const properties: Record<string, unknown> = {};
|
|
156
|
+
|
|
157
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
158
|
+
const prop = value as { type: string; enum?: string[]; items?: unknown };
|
|
159
|
+
|
|
160
|
+
if (prop.type === 'string') {
|
|
161
|
+
properties[key] = (prop.enum && z.enum) ? z.enum(prop.enum as [string, ...string[]]) : z.string();
|
|
162
|
+
} else if (prop.type === 'array') {
|
|
163
|
+
properties[key] = z.array(z.any());
|
|
164
|
+
} else {
|
|
165
|
+
properties[key] = z.any();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Make optional if not required
|
|
169
|
+
if (!schema.required.includes(key)) {
|
|
170
|
+
properties[key] = (properties[key] as { optional?: Function }).optional?.() ?? properties[key];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return z.object(properties) as ZodObjectLike;
|
|
175
|
+
}
|