@lantos1618/better-ui 0.2.2 → 0.3.1
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 +231 -148
- package/dist/index.d.mts +314 -0
- package/dist/index.d.ts +314 -0
- package/dist/index.js +522 -0
- package/dist/index.mjs +491 -0
- package/package.json +59 -20
- package/lib/aui/README.md +0 -136
- package/lib/aui/__tests__/aui-complete.test.ts +0 -251
- package/lib/aui/__tests__/aui-comprehensive.test.ts +0 -376
- package/lib/aui/__tests__/aui-concise.test.ts +0 -278
- package/lib/aui/__tests__/aui-integration.test.ts +0 -309
- package/lib/aui/__tests__/aui-simple.test.ts +0 -116
- package/lib/aui/__tests__/aui.test.ts +0 -269
- package/lib/aui/__tests__/concise-api.test.ts +0 -165
- package/lib/aui/__tests__/core.test.ts +0 -265
- package/lib/aui/__tests__/simple-api.test.ts +0 -200
- package/lib/aui/ai-assistant.ts +0 -408
- package/lib/aui/ai-control.ts +0 -353
- package/lib/aui/client/use-aui.ts +0 -55
- package/lib/aui/client-control.ts +0 -551
- package/lib/aui/client-executor.ts +0 -417
- package/lib/aui/components/ToolRenderer.tsx +0 -22
- package/lib/aui/core.ts +0 -137
- package/lib/aui/demo.tsx +0 -89
- package/lib/aui/examples/ai-complete-demo.tsx +0 -359
- package/lib/aui/examples/ai-control-demo.tsx +0 -356
- package/lib/aui/examples/ai-control-tools.ts +0 -308
- package/lib/aui/examples/concise-api.tsx +0 -153
- package/lib/aui/examples/index.tsx +0 -163
- package/lib/aui/examples/quick-demo.tsx +0 -91
- package/lib/aui/examples/simple-demo.tsx +0 -71
- package/lib/aui/examples/simple-tools.tsx +0 -160
- package/lib/aui/examples/user-api.tsx +0 -208
- package/lib/aui/examples/user-requested.tsx +0 -174
- package/lib/aui/examples/weather-search-tools.tsx +0 -119
- package/lib/aui/examples.tsx +0 -367
- package/lib/aui/hooks/useAUITool.ts +0 -142
- package/lib/aui/hooks/useAUIToolEnhanced.ts +0 -343
- package/lib/aui/hooks/useAUITools.ts +0 -195
- package/lib/aui/index.ts +0 -156
- package/lib/aui/provider.tsx +0 -45
- package/lib/aui/server-control.ts +0 -386
- package/lib/aui/server-executor.ts +0 -165
- package/lib/aui/server.ts +0 -167
- package/lib/aui/tool-registry.ts +0 -380
- package/lib/aui/tools/advanced-examples.tsx +0 -86
- package/lib/aui/tools/ai-complete.ts +0 -375
- package/lib/aui/tools/api-tools.tsx +0 -230
- package/lib/aui/tools/data-tools.tsx +0 -232
- package/lib/aui/tools/dom-tools.tsx +0 -202
- package/lib/aui/tools/examples.ts +0 -43
- package/lib/aui/tools/file-tools.tsx +0 -202
- package/lib/aui/tools/form-tools.tsx +0 -233
- package/lib/aui/tools/index.ts +0 -8
- package/lib/aui/tools/navigation-tools.tsx +0 -172
- package/lib/aui/tools/notification-tools.ts +0 -213
- package/lib/aui/tools/state-tools.tsx +0 -209
- package/lib/aui/types.ts +0 -47
- package/lib/aui/vercel-ai.ts +0 -100
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
import aui from '../index';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
describe('AUI Comprehensive Tests', () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
aui.clear();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
describe('Core AUI Functionality', () => {
|
|
10
|
-
it('should create a simple tool with execute and render', async () => {
|
|
11
|
-
const tool = aui
|
|
12
|
-
.tool('test-tool')
|
|
13
|
-
.input(z.object({ value: z.number() }))
|
|
14
|
-
.execute(async ({ input }) => ({ result: input.value * 2 }));
|
|
15
|
-
|
|
16
|
-
expect(tool.name).toBe('test-tool');
|
|
17
|
-
|
|
18
|
-
const result = await tool.run({ value: 5 });
|
|
19
|
-
expect(result).toEqual({ result: 10 });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should handle client execution when not on server', async () => {
|
|
23
|
-
const clientFn = jest.fn(async ({ input }) => ({ server: false, client: true, value: input.value }));
|
|
24
|
-
const serverFn = jest.fn(async ({ input }) => ({ server: true, value: input.value }));
|
|
25
|
-
|
|
26
|
-
const tool = aui
|
|
27
|
-
.tool('dual-tool')
|
|
28
|
-
.input(z.object({ value: z.string() }))
|
|
29
|
-
.execute(serverFn)
|
|
30
|
-
.clientExecute(clientFn);
|
|
31
|
-
|
|
32
|
-
// Simulate client environment
|
|
33
|
-
const result = await tool.run(
|
|
34
|
-
{ value: 'test' },
|
|
35
|
-
{
|
|
36
|
-
isServer: false,
|
|
37
|
-
cache: new Map(),
|
|
38
|
-
fetch: global.fetch
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
expect(result).toEqual({ server: false, client: true, value: 'test' });
|
|
43
|
-
expect(clientFn).toHaveBeenCalled();
|
|
44
|
-
expect(serverFn).not.toHaveBeenCalled();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should use server execution when on server', async () => {
|
|
48
|
-
const clientFn = jest.fn(async ({ input }) => ({ server: false, client: true }));
|
|
49
|
-
const serverFn = jest.fn(async ({ input }) => ({ server: true }));
|
|
50
|
-
|
|
51
|
-
const tool = aui
|
|
52
|
-
.tool('server-tool')
|
|
53
|
-
.execute(serverFn)
|
|
54
|
-
.clientExecute(clientFn);
|
|
55
|
-
|
|
56
|
-
const result = await tool.run(
|
|
57
|
-
{},
|
|
58
|
-
{
|
|
59
|
-
isServer: true,
|
|
60
|
-
cache: new Map(),
|
|
61
|
-
fetch: global.fetch
|
|
62
|
-
}
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
expect(result).toEqual({ server: true });
|
|
66
|
-
expect(serverFn).toHaveBeenCalled();
|
|
67
|
-
expect(clientFn).not.toHaveBeenCalled();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should validate input with Zod schema', async () => {
|
|
71
|
-
const tool = aui
|
|
72
|
-
.tool('validated-tool')
|
|
73
|
-
.input(z.object({
|
|
74
|
-
name: z.string().min(3),
|
|
75
|
-
age: z.number().positive()
|
|
76
|
-
}))
|
|
77
|
-
.execute(async ({ input }) => input);
|
|
78
|
-
|
|
79
|
-
await expect(tool.run({ name: 'ab', age: 25 }))
|
|
80
|
-
.rejects.toThrow();
|
|
81
|
-
|
|
82
|
-
await expect(tool.run({ name: 'Alice', age: -5 }))
|
|
83
|
-
.rejects.toThrow();
|
|
84
|
-
|
|
85
|
-
const result = await tool.run({ name: 'Alice', age: 25 });
|
|
86
|
-
expect(result).toEqual({ name: 'Alice', age: 25 });
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should support middleware', async () => {
|
|
90
|
-
const middleware1 = jest.fn(async ({ input, next }) => {
|
|
91
|
-
const result = await next();
|
|
92
|
-
return { ...result, middleware1: true };
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const middleware2 = jest.fn(async ({ input, next }) => {
|
|
96
|
-
const result = await next();
|
|
97
|
-
return { ...result, middleware2: true };
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const tool = aui
|
|
101
|
-
.tool('middleware-tool')
|
|
102
|
-
.execute(async () => ({ base: true }))
|
|
103
|
-
.middleware(middleware1)
|
|
104
|
-
.middleware(middleware2);
|
|
105
|
-
|
|
106
|
-
const result = await tool.run({});
|
|
107
|
-
|
|
108
|
-
expect(result).toEqual({
|
|
109
|
-
base: true,
|
|
110
|
-
middleware1: true,
|
|
111
|
-
middleware2: true
|
|
112
|
-
});
|
|
113
|
-
expect(middleware1).toHaveBeenCalled();
|
|
114
|
-
expect(middleware2).toHaveBeenCalled();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should support tags and descriptions', () => {
|
|
118
|
-
const tool = aui
|
|
119
|
-
.tool('tagged-tool')
|
|
120
|
-
.describe('A tool with tags')
|
|
121
|
-
.tag('ai', 'control', 'frontend');
|
|
122
|
-
|
|
123
|
-
expect(tool.description).toBe('A tool with tags');
|
|
124
|
-
expect(tool.tags).toEqual(['ai', 'control', 'frontend']);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('AUI Instance Management', () => {
|
|
129
|
-
it('should store and retrieve tools', () => {
|
|
130
|
-
const tool1 = aui.tool('tool1');
|
|
131
|
-
const tool2 = aui.tool('tool2');
|
|
132
|
-
|
|
133
|
-
expect(aui.has('tool1')).toBe(true);
|
|
134
|
-
expect(aui.has('tool2')).toBe(true);
|
|
135
|
-
expect(aui.has('tool3')).toBe(false);
|
|
136
|
-
|
|
137
|
-
expect(aui.get('tool1')).toBe(tool1);
|
|
138
|
-
expect(aui.get('tool2')).toBe(tool2);
|
|
139
|
-
expect(aui.get('tool3')).toBeUndefined();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should list all tools', () => {
|
|
143
|
-
aui.tool('a').tag('type1');
|
|
144
|
-
aui.tool('b').tag('type2');
|
|
145
|
-
aui.tool('c').tag('type1', 'type2');
|
|
146
|
-
|
|
147
|
-
const tools = aui.list();
|
|
148
|
-
expect(tools).toHaveLength(3);
|
|
149
|
-
expect(tools.map(t => t.name)).toEqual(['a', 'b', 'c']);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should find tools by tags', () => {
|
|
153
|
-
aui.tool('frontend').tag('ui', 'client');
|
|
154
|
-
aui.tool('backend').tag('api', 'server');
|
|
155
|
-
aui.tool('fullstack').tag('ui', 'api');
|
|
156
|
-
|
|
157
|
-
const uiTools = aui.findByTag('ui');
|
|
158
|
-
expect(uiTools).toHaveLength(2);
|
|
159
|
-
expect(uiTools.map(t => t.name)).toContain('frontend');
|
|
160
|
-
expect(uiTools.map(t => t.name)).toContain('fullstack');
|
|
161
|
-
|
|
162
|
-
const apiTools = aui.findByTags('ui', 'api');
|
|
163
|
-
expect(apiTools).toHaveLength(1);
|
|
164
|
-
expect(apiTools[0].name).toBe('fullstack');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should execute tools by name', async () => {
|
|
168
|
-
aui
|
|
169
|
-
.tool('calculator')
|
|
170
|
-
.input(z.object({ a: z.number(), b: z.number() }))
|
|
171
|
-
.execute(async ({ input }) => ({ sum: input.a + input.b }));
|
|
172
|
-
|
|
173
|
-
const result = await aui.execute('calculator', { a: 3, b: 4 });
|
|
174
|
-
expect(result).toEqual({ sum: 7 });
|
|
175
|
-
|
|
176
|
-
await expect(aui.execute('nonexistent', {}))
|
|
177
|
-
.rejects.toThrow('Tool "nonexistent" not found');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should remove tools', () => {
|
|
181
|
-
aui.tool('temp');
|
|
182
|
-
expect(aui.has('temp')).toBe(true);
|
|
183
|
-
|
|
184
|
-
const removed = aui.remove('temp');
|
|
185
|
-
expect(removed).toBe(true);
|
|
186
|
-
expect(aui.has('temp')).toBe(false);
|
|
187
|
-
|
|
188
|
-
const removedAgain = aui.remove('temp');
|
|
189
|
-
expect(removedAgain).toBe(false);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should clear all tools', () => {
|
|
193
|
-
aui.tool('t1');
|
|
194
|
-
aui.tool('t2');
|
|
195
|
-
aui.tool('t3');
|
|
196
|
-
|
|
197
|
-
expect(aui.list()).toHaveLength(3);
|
|
198
|
-
|
|
199
|
-
aui.clear();
|
|
200
|
-
expect(aui.list()).toHaveLength(0);
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
describe('Context Management', () => {
|
|
205
|
-
it('should create default context', () => {
|
|
206
|
-
const ctx = aui.createContext();
|
|
207
|
-
|
|
208
|
-
expect(ctx.cache).toBeInstanceOf(Map);
|
|
209
|
-
expect(ctx.fetch).toBeDefined();
|
|
210
|
-
expect(ctx.isServer).toBe(true); // In test environment
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should merge context additions', () => {
|
|
214
|
-
const ctx = aui.createContext({
|
|
215
|
-
user: { id: 1, name: 'Test' },
|
|
216
|
-
session: { token: 'abc123' },
|
|
217
|
-
env: { API_KEY: 'secret' }
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
expect(ctx.user).toEqual({ id: 1, name: 'Test' });
|
|
221
|
-
expect(ctx.session).toEqual({ token: 'abc123' });
|
|
222
|
-
expect(ctx.env).toEqual({ API_KEY: 'secret' });
|
|
223
|
-
expect(ctx.cache).toBeInstanceOf(Map);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should use context cache in client execution', async () => {
|
|
227
|
-
const fetchMock = jest.fn(async () => ({ data: 'fresh' }));
|
|
228
|
-
|
|
229
|
-
const tool = aui
|
|
230
|
-
.tool('cached-tool')
|
|
231
|
-
.input(z.object({ key: z.string() }))
|
|
232
|
-
.clientExecute(async ({ input, ctx }) => {
|
|
233
|
-
const cached = ctx.cache.get(input.key);
|
|
234
|
-
if (cached) return cached;
|
|
235
|
-
|
|
236
|
-
const result = await fetchMock();
|
|
237
|
-
ctx.cache.set(input.key, result);
|
|
238
|
-
return result;
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const ctx = {
|
|
242
|
-
cache: new Map(),
|
|
243
|
-
fetch: global.fetch,
|
|
244
|
-
isServer: false
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// First call - should fetch
|
|
248
|
-
const result1 = await tool.run({ key: 'test' }, ctx);
|
|
249
|
-
expect(result1).toEqual({ data: 'fresh' });
|
|
250
|
-
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
251
|
-
|
|
252
|
-
// Second call - should use cache
|
|
253
|
-
const result2 = await tool.run({ key: 'test' }, ctx);
|
|
254
|
-
expect(result2).toEqual({ data: 'fresh' });
|
|
255
|
-
expect(fetchMock).toHaveBeenCalledTimes(1); // Still 1, not called again
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
describe('Tool Configuration', () => {
|
|
260
|
-
it('should return tool configuration', () => {
|
|
261
|
-
const tool = aui
|
|
262
|
-
.tool('config-tool')
|
|
263
|
-
.input(z.object({ value: z.string() }))
|
|
264
|
-
.execute(async ({ input }) => input)
|
|
265
|
-
.describe('A configurable tool')
|
|
266
|
-
.tag('test', 'config');
|
|
267
|
-
|
|
268
|
-
const config = tool.getConfig();
|
|
269
|
-
|
|
270
|
-
expect(config.name).toBe('config-tool');
|
|
271
|
-
expect(config.description).toBe('A configurable tool');
|
|
272
|
-
expect(config.tags).toEqual(['test', 'config']);
|
|
273
|
-
expect(config.inputSchema).toBeDefined();
|
|
274
|
-
expect(config.executeHandler).toBeDefined();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('should serialize tool to JSON', () => {
|
|
278
|
-
const tool = aui
|
|
279
|
-
.tool('json-tool')
|
|
280
|
-
.input(z.object({ id: z.number() }))
|
|
281
|
-
.execute(async () => ({}))
|
|
282
|
-
.clientExecute(async () => ({}))
|
|
283
|
-
.middleware(async ({ next }) => next())
|
|
284
|
-
.describe('JSON serializable tool')
|
|
285
|
-
.tag('json');
|
|
286
|
-
|
|
287
|
-
const json = tool.toJSON();
|
|
288
|
-
|
|
289
|
-
expect(json).toEqual({
|
|
290
|
-
name: 'json-tool',
|
|
291
|
-
description: 'JSON serializable tool',
|
|
292
|
-
tags: ['json'],
|
|
293
|
-
hasInput: true,
|
|
294
|
-
hasExecute: true,
|
|
295
|
-
hasClientExecute: true,
|
|
296
|
-
hasRender: false,
|
|
297
|
-
hasMiddleware: true
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe('Complex Tool Scenarios', () => {
|
|
303
|
-
it('should handle async operations correctly', async () => {
|
|
304
|
-
const delays = [100, 50, 150];
|
|
305
|
-
const results: number[] = [];
|
|
306
|
-
|
|
307
|
-
const tool = aui
|
|
308
|
-
.tool('async-tool')
|
|
309
|
-
.input(z.object({ delay: z.number(), value: z.number() }))
|
|
310
|
-
.execute(async ({ input }) => {
|
|
311
|
-
await new Promise(resolve => setTimeout(resolve, input.delay));
|
|
312
|
-
results.push(input.value);
|
|
313
|
-
return { processed: input.value };
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
// Execute in parallel
|
|
317
|
-
const promises = delays.map((delay, i) =>
|
|
318
|
-
tool.run({ delay, value: i })
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
const responses = await Promise.all(promises);
|
|
322
|
-
|
|
323
|
-
// Results should be in order of completion (50ms, 100ms, 150ms)
|
|
324
|
-
expect(results).toEqual([1, 0, 2]);
|
|
325
|
-
expect(responses).toEqual([
|
|
326
|
-
{ processed: 0 },
|
|
327
|
-
{ processed: 1 },
|
|
328
|
-
{ processed: 2 }
|
|
329
|
-
]);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('should handle errors gracefully', async () => {
|
|
333
|
-
const tool = aui
|
|
334
|
-
.tool('error-tool')
|
|
335
|
-
.input(z.object({ shouldFail: z.boolean() }))
|
|
336
|
-
.execute(async ({ input }) => {
|
|
337
|
-
if (input.shouldFail) {
|
|
338
|
-
throw new Error('Intentional failure');
|
|
339
|
-
}
|
|
340
|
-
return { success: true };
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const success = await tool.run({ shouldFail: false });
|
|
344
|
-
expect(success).toEqual({ success: true });
|
|
345
|
-
|
|
346
|
-
await expect(tool.run({ shouldFail: true }))
|
|
347
|
-
.rejects.toThrow('Intentional failure');
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it('should chain multiple tools together', async () => {
|
|
351
|
-
const preprocessor = aui
|
|
352
|
-
.tool('preprocessor')
|
|
353
|
-
.input(z.object({ text: z.string() }))
|
|
354
|
-
.execute(async ({ input }) => ({
|
|
355
|
-
processed: input.text.toUpperCase()
|
|
356
|
-
}));
|
|
357
|
-
|
|
358
|
-
const analyzer = aui
|
|
359
|
-
.tool('analyzer')
|
|
360
|
-
.input(z.object({ processed: z.string() }))
|
|
361
|
-
.execute(async ({ input }) => ({
|
|
362
|
-
length: input.processed.length,
|
|
363
|
-
words: input.processed.split(' ').length
|
|
364
|
-
}));
|
|
365
|
-
|
|
366
|
-
const input = { text: 'hello world' };
|
|
367
|
-
const preprocessed = await preprocessor.run(input);
|
|
368
|
-
const analyzed = await analyzer.run(preprocessed);
|
|
369
|
-
|
|
370
|
-
expect(analyzed).toEqual({
|
|
371
|
-
length: 11,
|
|
372
|
-
words: 2
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
});
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
-
import aui, { AUITool, z } from '../index';
|
|
3
|
-
|
|
4
|
-
describe('AUI Concise API', () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
aui.clear();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
describe('Simple Tools', () => {
|
|
10
|
-
it('should create a simple tool with just input and execute', async () => {
|
|
11
|
-
const weatherTool = aui
|
|
12
|
-
.tool('weather')
|
|
13
|
-
.input(z.object({ city: z.string() }))
|
|
14
|
-
.execute(async ({ input }) => ({ temp: 72, city: input.city }));
|
|
15
|
-
|
|
16
|
-
const result = await weatherTool.run({ city: 'SF' });
|
|
17
|
-
expect(result).toEqual({ temp: 72, city: 'SF' });
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should work without input schema', async () => {
|
|
21
|
-
const simpleTool = aui
|
|
22
|
-
.tool('simple')
|
|
23
|
-
.execute(async () => ({ success: true }));
|
|
24
|
-
|
|
25
|
-
const result = await simpleTool.run({});
|
|
26
|
-
expect(result).toEqual({ success: true });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should support render method', () => {
|
|
30
|
-
const tool = aui
|
|
31
|
-
.tool('ui')
|
|
32
|
-
.input(z.object({ text: z.string() }))
|
|
33
|
-
.execute(async ({ input }) => ({ message: input.text }))
|
|
34
|
-
.render(({ data }) => ({ type: 'div', props: { children: data.message } } as any));
|
|
35
|
-
|
|
36
|
-
expect(tool.renderer).toBeDefined();
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('Complex Tools with Client Execution', () => {
|
|
41
|
-
it('should use client execution when available and not on server', async () => {
|
|
42
|
-
const searchTool = aui
|
|
43
|
-
.tool('search')
|
|
44
|
-
.input(z.object({ query: z.string() }))
|
|
45
|
-
.execute(async ({ input }) => ({ server: true, query: input.query }))
|
|
46
|
-
.clientExecute(async ({ input }) => ({ server: true, query: input.query }));
|
|
47
|
-
|
|
48
|
-
const clientContext = {
|
|
49
|
-
cache: new Map(),
|
|
50
|
-
fetch: global.fetch,
|
|
51
|
-
isServer: false
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const result = await searchTool.run({ query: 'test' }, clientContext);
|
|
55
|
-
expect(result).toEqual({ server: true, query: 'test' });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should use server execution when on server', async () => {
|
|
59
|
-
const searchTool = aui
|
|
60
|
-
.tool('search')
|
|
61
|
-
.input(z.object({ query: z.string() }))
|
|
62
|
-
.execute(async ({ input }) => ({ server: true, query: input.query }))
|
|
63
|
-
.clientExecute(async ({ input }) => ({ server: false, query: input.query }));
|
|
64
|
-
|
|
65
|
-
const serverContext = {
|
|
66
|
-
cache: new Map(),
|
|
67
|
-
fetch: global.fetch,
|
|
68
|
-
isServer: true
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const result = await searchTool.run({ query: 'test' }, serverContext);
|
|
72
|
-
expect(result).toEqual({ server: true, query: 'test' });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should support caching in client execution', async () => {
|
|
76
|
-
const cache = new Map();
|
|
77
|
-
let fetchCalls = 0;
|
|
78
|
-
|
|
79
|
-
const cachedTool = aui
|
|
80
|
-
.tool('cached')
|
|
81
|
-
.input(z.object({ key: z.string() }))
|
|
82
|
-
.clientExecute(async ({ input, ctx }) => {
|
|
83
|
-
const cached = ctx.cache.get(input.key);
|
|
84
|
-
if (cached) return cached;
|
|
85
|
-
|
|
86
|
-
fetchCalls++;
|
|
87
|
-
const data = { value: `fetched-${input.key}`, calls: fetchCalls };
|
|
88
|
-
ctx.cache.set(input.key, data);
|
|
89
|
-
return data;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const context = { cache, fetch: global.fetch, isServer: false };
|
|
93
|
-
|
|
94
|
-
const result1 = await cachedTool.run({ key: 'test' }, context);
|
|
95
|
-
const result2 = await cachedTool.run({ key: 'test' }, context);
|
|
96
|
-
|
|
97
|
-
expect(result1).toEqual({ value: 'fetched-test', calls: 1 });
|
|
98
|
-
expect(result2).toEqual({ value: 'fetched-test', calls: 1 });
|
|
99
|
-
expect(fetchCalls).toBe(1);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('AI Control Tools', () => {
|
|
104
|
-
it('should create DOM control tool', async () => {
|
|
105
|
-
const domTool = aui
|
|
106
|
-
.tool('dom')
|
|
107
|
-
.input(z.object({
|
|
108
|
-
action: z.enum(['click', 'type', 'scroll']),
|
|
109
|
-
selector: z.string(),
|
|
110
|
-
value: z.string().optional()
|
|
111
|
-
}))
|
|
112
|
-
.clientExecute(async ({ input }) => {
|
|
113
|
-
return { success: true, action: input.action };
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const result = await domTool.run(
|
|
117
|
-
{ action: 'click', selector: '#button' },
|
|
118
|
-
{ cache: new Map(), fetch: global.fetch, isServer: false }
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
expect(result).toEqual({ success: true, action: 'click' });
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should create database control tool', async () => {
|
|
125
|
-
const dbTool = aui
|
|
126
|
-
.tool('database')
|
|
127
|
-
.input(z.object({
|
|
128
|
-
operation: z.enum(['find', 'create', 'update', 'delete']),
|
|
129
|
-
collection: z.string(),
|
|
130
|
-
data: z.any().optional()
|
|
131
|
-
}))
|
|
132
|
-
.execute(async ({ input }) => {
|
|
133
|
-
return {
|
|
134
|
-
operation: input.operation,
|
|
135
|
-
collection: input.collection,
|
|
136
|
-
result: 'mock-data'
|
|
137
|
-
};
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
const result = await dbTool.run({
|
|
141
|
-
operation: 'find',
|
|
142
|
-
collection: 'users'
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
expect(result).toEqual({
|
|
146
|
-
operation: 'find',
|
|
147
|
-
collection: 'users',
|
|
148
|
-
result: 'mock-data'
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
describe('Tool Middleware', () => {
|
|
154
|
-
it('should support middleware', async () => {
|
|
155
|
-
const events: string[] = [];
|
|
156
|
-
|
|
157
|
-
const toolWithMiddleware = aui
|
|
158
|
-
.tool('middleware-test')
|
|
159
|
-
.input(z.object({ value: z.number() }))
|
|
160
|
-
.middleware(async ({ input, next }) => {
|
|
161
|
-
events.push('middleware-1-before');
|
|
162
|
-
input.value = input.value * 2;
|
|
163
|
-
const result = await next();
|
|
164
|
-
events.push('middleware-1-after');
|
|
165
|
-
return result;
|
|
166
|
-
})
|
|
167
|
-
.middleware(async ({ input, next }) => {
|
|
168
|
-
events.push('middleware-2-before');
|
|
169
|
-
input.value = input.value + 1;
|
|
170
|
-
const result = await next();
|
|
171
|
-
events.push('middleware-2-after');
|
|
172
|
-
return result;
|
|
173
|
-
})
|
|
174
|
-
.execute(async ({ input }) => {
|
|
175
|
-
events.push('execute');
|
|
176
|
-
return { finalValue: input.value };
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const result = await toolWithMiddleware.run({ value: 5 });
|
|
180
|
-
|
|
181
|
-
expect(result).toEqual({ finalValue: 11 }); // (5 * 2) + 1
|
|
182
|
-
expect(events).toEqual([
|
|
183
|
-
'middleware-1-before',
|
|
184
|
-
'middleware-2-before',
|
|
185
|
-
'execute',
|
|
186
|
-
'middleware-2-after',
|
|
187
|
-
'middleware-1-after'
|
|
188
|
-
]);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('Tool Registry', () => {
|
|
193
|
-
it('should register and retrieve tools', () => {
|
|
194
|
-
const tool1 = aui.tool('tool1').execute(async () => ({ id: 1 }));
|
|
195
|
-
const tool2 = aui.tool('tool2').execute(async () => ({ id: 2 }));
|
|
196
|
-
|
|
197
|
-
expect(aui.has('tool1')).toBe(true);
|
|
198
|
-
expect(aui.has('tool2')).toBe(true);
|
|
199
|
-
expect(aui.getToolNames()).toContain('tool1');
|
|
200
|
-
expect(aui.getToolNames()).toContain('tool2');
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('should execute tools by name', async () => {
|
|
204
|
-
aui.tool('named')
|
|
205
|
-
.input(z.object({ value: z.string() }))
|
|
206
|
-
.execute(async ({ input }) => ({ result: input.value }));
|
|
207
|
-
|
|
208
|
-
const result = await aui.execute('named', { value: 'test' });
|
|
209
|
-
expect(result).toEqual({ result: 'test' });
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe('Type Safety', () => {
|
|
214
|
-
it('should validate input with Zod schema', async () => {
|
|
215
|
-
const strictTool = aui
|
|
216
|
-
.tool('strict')
|
|
217
|
-
.input(z.object({
|
|
218
|
-
name: z.string().min(1),
|
|
219
|
-
age: z.number().positive()
|
|
220
|
-
}))
|
|
221
|
-
.execute(async ({ input }) => input);
|
|
222
|
-
|
|
223
|
-
await expect(
|
|
224
|
-
strictTool.run({ name: '', age: 25 })
|
|
225
|
-
).rejects.toThrow();
|
|
226
|
-
|
|
227
|
-
await expect(
|
|
228
|
-
strictTool.run({ name: 'John', age: -5 })
|
|
229
|
-
).rejects.toThrow();
|
|
230
|
-
|
|
231
|
-
const validResult = await strictTool.run({ name: 'John', age: 25 });
|
|
232
|
-
expect(validResult).toEqual({ name: 'John', age: 25 });
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('Tool Composition', () => {
|
|
237
|
-
it('should support workflow tools that compose other tools', async () => {
|
|
238
|
-
aui.tool('add')
|
|
239
|
-
.input(z.object({ a: z.number(), b: z.number() }))
|
|
240
|
-
.execute(async ({ input }) => ({ sum: input.a + input.b }));
|
|
241
|
-
|
|
242
|
-
aui.tool('multiply')
|
|
243
|
-
.input(z.object({ a: z.number(), b: z.number() }))
|
|
244
|
-
.execute(async ({ input }) => ({ product: input.a * input.b }));
|
|
245
|
-
|
|
246
|
-
const workflowTool = aui
|
|
247
|
-
.tool('workflow')
|
|
248
|
-
.input(z.object({
|
|
249
|
-
steps: z.array(z.object({
|
|
250
|
-
tool: z.string(),
|
|
251
|
-
input: z.any()
|
|
252
|
-
}))
|
|
253
|
-
}))
|
|
254
|
-
.execute(async ({ input }) => {
|
|
255
|
-
const results = [];
|
|
256
|
-
for (const step of input.steps) {
|
|
257
|
-
const result = await aui.execute(step.tool, step.input);
|
|
258
|
-
results.push(result);
|
|
259
|
-
}
|
|
260
|
-
return { results };
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const result = await workflowTool.run({
|
|
264
|
-
steps: [
|
|
265
|
-
{ tool: 'add', input: { a: 2, b: 3 } },
|
|
266
|
-
{ tool: 'multiply', input: { a: 4, b: 5 } }
|
|
267
|
-
]
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
expect(result).toEqual({
|
|
271
|
-
results: [
|
|
272
|
-
{ sum: 5 },
|
|
273
|
-
{ product: 20 }
|
|
274
|
-
]
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
});
|