@lantos1618/better-ui 0.5.0 → 0.6.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 CHANGED
@@ -52,6 +52,7 @@ npm install @lantos1618/better-ui zod
52
52
  |---------|------|
53
53
  | **View integration** | Tools render their own results — no other framework does this |
54
54
  | **MCP server** | Expose any tool registry to Claude Desktop, Cursor, VS Code |
55
+ | **AG-UI protocol** | Compatible with CopilotKit, LangChain, Google ADK frontends |
55
56
  | **Multi-provider** | OpenAI, Anthropic, Google Gemini, OpenRouter |
56
57
  | **Streaming views** | Progressive partial data rendering |
57
58
  | **Drop-in chat** | `<Chat />` component with automatic tool view rendering |
@@ -404,6 +405,26 @@ zodToJsonSchema(z.object({
404
405
 
405
406
  ---
406
407
 
408
+ ## AG-UI Protocol
409
+
410
+ Expose your tools via the [AG-UI (Agent-User Interaction Protocol)](https://docs.ag-ui.com) — compatible with CopilotKit, LangChain, Google ADK, and any AG-UI frontend.
411
+
412
+ ```typescript
413
+ import { createAGUIServer } from '@lantos1618/better-ui/agui';
414
+
415
+ const server = createAGUIServer({
416
+ name: 'my-tools',
417
+ tools: { weather, search },
418
+ });
419
+
420
+ // Next.js route handler — returns SSE event stream
421
+ export const POST = server.handler();
422
+ ```
423
+
424
+ The handler emits standard AG-UI events (`RUN_STARTED`, `TOOL_CALL_START`, `TOOL_CALL_ARGS`, `TOOL_CALL_RESULT`, `TOOL_CALL_END`, `RUN_FINISHED`) over Server-Sent Events.
425
+
426
+ ---
427
+
407
428
  ## Providers
408
429
 
409
430
  ```typescript
@@ -532,8 +553,10 @@ src/
532
553
  types.ts PersistenceAdapter interface
533
554
  memory.ts In-memory adapter
534
555
  mcp/
535
- server.ts MCP server (stdio + HTTP)
556
+ server.ts MCP server (stdio + HTTP + SSE)
536
557
  schema.ts Zod → JSON Schema converter
558
+ agui/
559
+ server.ts AG-UI protocol server (SSE)
537
560
  examples/
538
561
  nextjs-demo/ Full Next.js demo app
539
562
  vite-demo/ Vite + Express demo app
@@ -545,7 +568,7 @@ examples/
545
568
  ```bash
546
569
  npm install
547
570
  npm run build # Build library
548
- npm test # Run tests (163 tests)
571
+ npm test # Run tests (226 tests)
549
572
  npm run type-check # TypeScript check
550
573
  ```
551
574
 
@@ -0,0 +1,116 @@
1
+ import { T as Tool, c as ToolContext } from '../tool-Ca2x-VNK.mjs';
2
+ import 'zod';
3
+ import 'react';
4
+
5
+ /**
6
+ * AG-UI (Agent-User Interaction Protocol) Server for Better UI
7
+ *
8
+ * Implements the AG-UI protocol, allowing Better UI tools to be used with
9
+ * CopilotKit, LangChain, and any AG-UI compatible frontend.
10
+ *
11
+ * Protocol: Server-Sent Events (SSE) over HTTP
12
+ *
13
+ * @see https://docs.ag-ui.com
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { createAGUIServer } from '@lantos1618/better-ui/agui';
18
+ *
19
+ * const server = createAGUIServer({
20
+ * name: 'my-tools',
21
+ * tools: { weather: weatherTool, search: searchTool },
22
+ * });
23
+ *
24
+ * // Next.js route handler
25
+ * export const POST = server.handler();
26
+ * ```
27
+ */
28
+
29
+ type AGUIEventType = 'RUN_STARTED' | 'RUN_FINISHED' | 'RUN_ERROR' | 'STEP_STARTED' | 'STEP_FINISHED' | 'TEXT_MESSAGE_START' | 'TEXT_MESSAGE_CONTENT' | 'TEXT_MESSAGE_END' | 'TOOL_CALL_START' | 'TOOL_CALL_ARGS' | 'TOOL_CALL_END' | 'TOOL_CALL_RESULT' | 'STATE_SNAPSHOT' | 'STATE_DELTA' | 'CUSTOM' | 'RAW';
30
+ interface AGUIEvent {
31
+ type: AGUIEventType;
32
+ timestamp?: number;
33
+ [key: string]: unknown;
34
+ }
35
+ interface RunAgentInput {
36
+ threadId: string;
37
+ runId: string;
38
+ /** Tool calls to execute */
39
+ tools?: Array<{
40
+ name: string;
41
+ description?: string;
42
+ }>;
43
+ /** Messages context */
44
+ messages?: Array<{
45
+ role: string;
46
+ content: string;
47
+ }>;
48
+ /** Tool call to execute (for direct tool invocation) */
49
+ toolCall?: {
50
+ id: string;
51
+ name: string;
52
+ args: Record<string, unknown>;
53
+ };
54
+ /** State context from the frontend */
55
+ state?: Record<string, unknown>;
56
+ }
57
+ interface AGUIServerConfig {
58
+ /** Server name */
59
+ name: string;
60
+ /** Tool registry — keys are tool names */
61
+ tools: Record<string, Tool>;
62
+ /** Optional context passed to every tool execution */
63
+ context?: Partial<ToolContext>;
64
+ /** Called on errors */
65
+ onError?: (error: Error) => void;
66
+ }
67
+ declare class AGUIServer {
68
+ private config;
69
+ constructor(config: AGUIServerConfig);
70
+ /** Get available tools in AG-UI format */
71
+ listTools(): Array<{
72
+ name: string;
73
+ description: string;
74
+ parameters: Record<string, unknown>;
75
+ }>;
76
+ /**
77
+ * Create an HTTP handler that implements the AG-UI protocol.
78
+ * Returns an SSE stream of AG-UI events.
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * // Next.js: app/api/agui/route.ts
83
+ * export const POST = server.handler();
84
+ *
85
+ * // Express:
86
+ * app.post('/api/agui', (req, res) => server.handler()(req));
87
+ * ```
88
+ */
89
+ handler(): (req: Request) => Promise<Response>;
90
+ /**
91
+ * Execute a tool call and emit AG-UI events.
92
+ */
93
+ private executeToolCall;
94
+ /**
95
+ * Minimal Zod-to-JSON-Schema for AG-UI tool parameters.
96
+ * Delegates to the MCP schema converter if available, otherwise provides a basic fallback.
97
+ */
98
+ private zodToJsonSchema;
99
+ }
100
+ /**
101
+ * Create an AG-UI server from a Better UI tool registry.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const server = createAGUIServer({
106
+ * name: 'my-app',
107
+ * tools: { weather: weatherTool, search: searchTool },
108
+ * });
109
+ *
110
+ * // Use as Next.js route handler
111
+ * export const POST = server.handler();
112
+ * ```
113
+ */
114
+ declare function createAGUIServer(config: AGUIServerConfig): AGUIServer;
115
+
116
+ export { type AGUIEvent, type AGUIEventType, AGUIServer, type AGUIServerConfig, type RunAgentInput, createAGUIServer };
@@ -0,0 +1,116 @@
1
+ import { T as Tool, c as ToolContext } from '../tool-Ca2x-VNK.js';
2
+ import 'zod';
3
+ import 'react';
4
+
5
+ /**
6
+ * AG-UI (Agent-User Interaction Protocol) Server for Better UI
7
+ *
8
+ * Implements the AG-UI protocol, allowing Better UI tools to be used with
9
+ * CopilotKit, LangChain, and any AG-UI compatible frontend.
10
+ *
11
+ * Protocol: Server-Sent Events (SSE) over HTTP
12
+ *
13
+ * @see https://docs.ag-ui.com
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { createAGUIServer } from '@lantos1618/better-ui/agui';
18
+ *
19
+ * const server = createAGUIServer({
20
+ * name: 'my-tools',
21
+ * tools: { weather: weatherTool, search: searchTool },
22
+ * });
23
+ *
24
+ * // Next.js route handler
25
+ * export const POST = server.handler();
26
+ * ```
27
+ */
28
+
29
+ type AGUIEventType = 'RUN_STARTED' | 'RUN_FINISHED' | 'RUN_ERROR' | 'STEP_STARTED' | 'STEP_FINISHED' | 'TEXT_MESSAGE_START' | 'TEXT_MESSAGE_CONTENT' | 'TEXT_MESSAGE_END' | 'TOOL_CALL_START' | 'TOOL_CALL_ARGS' | 'TOOL_CALL_END' | 'TOOL_CALL_RESULT' | 'STATE_SNAPSHOT' | 'STATE_DELTA' | 'CUSTOM' | 'RAW';
30
+ interface AGUIEvent {
31
+ type: AGUIEventType;
32
+ timestamp?: number;
33
+ [key: string]: unknown;
34
+ }
35
+ interface RunAgentInput {
36
+ threadId: string;
37
+ runId: string;
38
+ /** Tool calls to execute */
39
+ tools?: Array<{
40
+ name: string;
41
+ description?: string;
42
+ }>;
43
+ /** Messages context */
44
+ messages?: Array<{
45
+ role: string;
46
+ content: string;
47
+ }>;
48
+ /** Tool call to execute (for direct tool invocation) */
49
+ toolCall?: {
50
+ id: string;
51
+ name: string;
52
+ args: Record<string, unknown>;
53
+ };
54
+ /** State context from the frontend */
55
+ state?: Record<string, unknown>;
56
+ }
57
+ interface AGUIServerConfig {
58
+ /** Server name */
59
+ name: string;
60
+ /** Tool registry — keys are tool names */
61
+ tools: Record<string, Tool>;
62
+ /** Optional context passed to every tool execution */
63
+ context?: Partial<ToolContext>;
64
+ /** Called on errors */
65
+ onError?: (error: Error) => void;
66
+ }
67
+ declare class AGUIServer {
68
+ private config;
69
+ constructor(config: AGUIServerConfig);
70
+ /** Get available tools in AG-UI format */
71
+ listTools(): Array<{
72
+ name: string;
73
+ description: string;
74
+ parameters: Record<string, unknown>;
75
+ }>;
76
+ /**
77
+ * Create an HTTP handler that implements the AG-UI protocol.
78
+ * Returns an SSE stream of AG-UI events.
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * // Next.js: app/api/agui/route.ts
83
+ * export const POST = server.handler();
84
+ *
85
+ * // Express:
86
+ * app.post('/api/agui', (req, res) => server.handler()(req));
87
+ * ```
88
+ */
89
+ handler(): (req: Request) => Promise<Response>;
90
+ /**
91
+ * Execute a tool call and emit AG-UI events.
92
+ */
93
+ private executeToolCall;
94
+ /**
95
+ * Minimal Zod-to-JSON-Schema for AG-UI tool parameters.
96
+ * Delegates to the MCP schema converter if available, otherwise provides a basic fallback.
97
+ */
98
+ private zodToJsonSchema;
99
+ }
100
+ /**
101
+ * Create an AG-UI server from a Better UI tool registry.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const server = createAGUIServer({
106
+ * name: 'my-app',
107
+ * tools: { weather: weatherTool, search: searchTool },
108
+ * });
109
+ *
110
+ * // Use as Next.js route handler
111
+ * export const POST = server.handler();
112
+ * ```
113
+ */
114
+ declare function createAGUIServer(config: AGUIServerConfig): AGUIServer;
115
+
116
+ export { type AGUIEvent, type AGUIEventType, AGUIServer, type AGUIServerConfig, type RunAgentInput, createAGUIServer };
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/mcp/schema.ts
24
+ var schema_exports = {};
25
+ __export(schema_exports, {
26
+ zodToJsonSchema: () => zodToJsonSchema
27
+ });
28
+ function zodToJsonSchema(schema) {
29
+ return convert(schema);
30
+ }
31
+ function convert(schema) {
32
+ const def = schema._def;
33
+ const typeName = def?.typeName;
34
+ switch (typeName) {
35
+ case "ZodString":
36
+ return convertString(def);
37
+ case "ZodNumber":
38
+ return convertNumber(def);
39
+ case "ZodBoolean":
40
+ return { type: "boolean" };
41
+ case "ZodNull":
42
+ return { type: "null" };
43
+ case "ZodLiteral":
44
+ return { enum: [def.value] };
45
+ case "ZodEnum":
46
+ return { type: "string", enum: def.values };
47
+ case "ZodNativeEnum":
48
+ return { enum: Object.values(def.values) };
49
+ case "ZodObject":
50
+ return convertObject(def);
51
+ case "ZodArray":
52
+ return convertArray(def);
53
+ case "ZodOptional":
54
+ return convert(def.innerType);
55
+ case "ZodNullable": {
56
+ const inner = convert(def.innerType);
57
+ return { anyOf: [inner, { type: "null" }] };
58
+ }
59
+ case "ZodDefault":
60
+ return { ...convert(def.innerType), default: def.defaultValue() };
61
+ case "ZodUnion":
62
+ return { anyOf: def.options.map((o) => convert(o)) };
63
+ case "ZodDiscriminatedUnion":
64
+ return { oneOf: [...def.options.values()].map((o) => convert(o)) };
65
+ case "ZodRecord":
66
+ return {
67
+ type: "object",
68
+ additionalProperties: convert(def.valueType)
69
+ };
70
+ case "ZodTuple": {
71
+ const items = def.items.map((item) => convert(item));
72
+ return { type: "array", items: items.length === 1 ? items[0] : void 0, prefixItems: items };
73
+ }
74
+ case "ZodEffects":
75
+ return convert(def.schema);
76
+ case "ZodPipeline":
77
+ return convert(def.in);
78
+ case "ZodLazy":
79
+ return convert(def.getter());
80
+ case "ZodAny":
81
+ return {};
82
+ case "ZodUnknown":
83
+ return {};
84
+ default:
85
+ return {};
86
+ }
87
+ }
88
+ function convertString(def) {
89
+ const schema = { type: "string" };
90
+ if (def.checks) {
91
+ for (const check of def.checks) {
92
+ switch (check.kind) {
93
+ case "min":
94
+ schema.minLength = check.value;
95
+ break;
96
+ case "max":
97
+ schema.maxLength = check.value;
98
+ break;
99
+ case "email":
100
+ schema.format = "email";
101
+ break;
102
+ case "url":
103
+ schema.format = "uri";
104
+ break;
105
+ case "uuid":
106
+ schema.format = "uuid";
107
+ break;
108
+ case "regex":
109
+ schema.pattern = check.regex.source;
110
+ break;
111
+ }
112
+ }
113
+ }
114
+ if (def.description) schema.description = def.description;
115
+ return schema;
116
+ }
117
+ function convertNumber(def) {
118
+ const schema = { type: "number" };
119
+ if (def.checks) {
120
+ for (const check of def.checks) {
121
+ switch (check.kind) {
122
+ case "min":
123
+ schema.minimum = check.value;
124
+ if (check.inclusive === false) schema.exclusiveMinimum = check.value;
125
+ break;
126
+ case "max":
127
+ schema.maximum = check.value;
128
+ if (check.inclusive === false) schema.exclusiveMaximum = check.value;
129
+ break;
130
+ case "int":
131
+ schema.type = "integer";
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ if (def.description) schema.description = def.description;
137
+ return schema;
138
+ }
139
+ function convertObject(def) {
140
+ const shape = def.shape();
141
+ const properties = {};
142
+ const required = [];
143
+ for (const [key, value] of Object.entries(shape)) {
144
+ properties[key] = convert(value);
145
+ const fieldDef = value._def;
146
+ const isOptional = fieldDef?.typeName === "ZodOptional" || fieldDef?.typeName === "ZodDefault";
147
+ if (!isOptional) {
148
+ required.push(key);
149
+ }
150
+ }
151
+ const schema = { type: "object", properties };
152
+ if (required.length > 0) schema.required = required;
153
+ if (def.description) schema.description = def.description;
154
+ return schema;
155
+ }
156
+ function convertArray(def) {
157
+ const schema = {
158
+ type: "array",
159
+ items: convert(def.type)
160
+ };
161
+ if (def.minLength) schema.minItems = def.minLength.value;
162
+ if (def.maxLength) schema.maxItems = def.maxLength.value;
163
+ if (def.description) schema.description = def.description;
164
+ return schema;
165
+ }
166
+ var init_schema = __esm({
167
+ "src/mcp/schema.ts"() {
168
+ "use strict";
169
+ }
170
+ });
171
+
172
+ // src/agui/index.ts
173
+ var agui_exports = {};
174
+ __export(agui_exports, {
175
+ AGUIServer: () => AGUIServer,
176
+ createAGUIServer: () => createAGUIServer
177
+ });
178
+ module.exports = __toCommonJS(agui_exports);
179
+
180
+ // src/agui/server.ts
181
+ var AGUIServer = class {
182
+ constructor(config) {
183
+ this.config = config;
184
+ }
185
+ /** Get available tools in AG-UI format */
186
+ listTools() {
187
+ return Object.values(this.config.tools).map((tool) => ({
188
+ name: tool.name,
189
+ description: tool.description || tool.name,
190
+ parameters: this.zodToJsonSchema(tool.inputSchema)
191
+ }));
192
+ }
193
+ /**
194
+ * Create an HTTP handler that implements the AG-UI protocol.
195
+ * Returns an SSE stream of AG-UI events.
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * // Next.js: app/api/agui/route.ts
200
+ * export const POST = server.handler();
201
+ *
202
+ * // Express:
203
+ * app.post('/api/agui', (req, res) => server.handler()(req));
204
+ * ```
205
+ */
206
+ handler() {
207
+ return async (req) => {
208
+ let input;
209
+ try {
210
+ input = await req.json();
211
+ } catch {
212
+ return new Response("Invalid JSON", { status: 400 });
213
+ }
214
+ const { threadId, runId, toolCall } = input;
215
+ if (!threadId || !runId) {
216
+ return new Response("Missing threadId or runId", { status: 400 });
217
+ }
218
+ const encoder = new TextEncoder();
219
+ const self = this;
220
+ const stream = new ReadableStream({
221
+ async start(controller) {
222
+ const emit = (event) => {
223
+ const data = JSON.stringify({ ...event, timestamp: event.timestamp ?? Date.now() });
224
+ controller.enqueue(encoder.encode(`data: ${data}
225
+
226
+ `));
227
+ };
228
+ try {
229
+ emit({ type: "RUN_STARTED", threadId, runId });
230
+ if (toolCall) {
231
+ await self.executeToolCall(toolCall, emit);
232
+ } else {
233
+ const tools = self.listTools();
234
+ const messageId = `msg_${runId}`;
235
+ emit({ type: "TEXT_MESSAGE_START", messageId, role: "assistant" });
236
+ emit({
237
+ type: "TEXT_MESSAGE_CONTENT",
238
+ messageId,
239
+ delta: `Available tools: ${tools.map((t) => t.name).join(", ")}`
240
+ });
241
+ emit({ type: "TEXT_MESSAGE_END", messageId });
242
+ }
243
+ emit({ type: "RUN_FINISHED", threadId, runId });
244
+ } catch (err) {
245
+ emit({
246
+ type: "RUN_ERROR",
247
+ threadId,
248
+ runId,
249
+ message: err instanceof Error ? err.message : "Unknown error"
250
+ });
251
+ self.config.onError?.(err instanceof Error ? err : new Error(String(err)));
252
+ }
253
+ controller.close();
254
+ }
255
+ });
256
+ return new Response(stream, {
257
+ headers: {
258
+ "Content-Type": "text/event-stream",
259
+ "Cache-Control": "no-cache",
260
+ "Connection": "keep-alive"
261
+ }
262
+ });
263
+ };
264
+ }
265
+ /**
266
+ * Execute a tool call and emit AG-UI events.
267
+ */
268
+ async executeToolCall(toolCall, emit) {
269
+ const { id, name, args } = toolCall;
270
+ if (!Object.prototype.hasOwnProperty.call(this.config.tools, name)) {
271
+ emit({
272
+ type: "TOOL_CALL_START",
273
+ toolCallId: id,
274
+ toolCallName: name
275
+ });
276
+ emit({
277
+ type: "TOOL_CALL_END",
278
+ toolCallId: id
279
+ });
280
+ throw new Error(`Unknown tool: ${name}`);
281
+ }
282
+ const tool = this.config.tools[name];
283
+ emit({
284
+ type: "TOOL_CALL_START",
285
+ toolCallId: id,
286
+ toolCallName: name
287
+ });
288
+ emit({
289
+ type: "TOOL_CALL_ARGS",
290
+ toolCallId: id,
291
+ delta: JSON.stringify(args)
292
+ });
293
+ const result = await tool.run(args, {
294
+ isServer: true,
295
+ ...this.config.context
296
+ });
297
+ const resultText = typeof result === "string" ? result : JSON.stringify(result);
298
+ emit({
299
+ type: "TOOL_CALL_RESULT",
300
+ toolCallId: id,
301
+ result: resultText
302
+ });
303
+ emit({
304
+ type: "TOOL_CALL_END",
305
+ toolCallId: id
306
+ });
307
+ }
308
+ /**
309
+ * Minimal Zod-to-JSON-Schema for AG-UI tool parameters.
310
+ * Delegates to the MCP schema converter if available, otherwise provides a basic fallback.
311
+ */
312
+ zodToJsonSchema(schema) {
313
+ try {
314
+ const { zodToJsonSchema: zodToJsonSchema2 } = (init_schema(), __toCommonJS(schema_exports));
315
+ return zodToJsonSchema2(schema);
316
+ } catch {
317
+ return { type: "object", properties: {} };
318
+ }
319
+ }
320
+ };
321
+ function createAGUIServer(config) {
322
+ return new AGUIServer(config);
323
+ }
324
+ // Annotate the CommonJS export names for ESM import in node:
325
+ 0 && (module.exports = {
326
+ AGUIServer,
327
+ createAGUIServer
328
+ });