@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,309 +0,0 @@
|
|
|
1
|
-
import aui from '../index';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { ServerExecutor } from '../server-executor';
|
|
4
|
-
|
|
5
|
-
describe('AUI Integration Tests', () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
aui.clear();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
describe('Simple Tool Creation', () => {
|
|
11
|
-
it('should create a simple tool with just input and execute', async () => {
|
|
12
|
-
const tool = aui
|
|
13
|
-
.tool('simple')
|
|
14
|
-
.input(z.object({ value: z.number() }))
|
|
15
|
-
.execute(async ({ input }) => ({ result: input.value * 2 }));
|
|
16
|
-
|
|
17
|
-
const result = await tool.run({ value: 5 });
|
|
18
|
-
expect(result).toEqual({ result: 10 });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should work without input schema', async () => {
|
|
22
|
-
const tool = aui
|
|
23
|
-
.tool('noSchema')
|
|
24
|
-
.execute(async ({ input }) => ({ received: input }));
|
|
25
|
-
|
|
26
|
-
const result = await tool.run({ anything: 'works' });
|
|
27
|
-
expect(result).toEqual({ received: { anything: 'works' } });
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('Client and Server Execution', () => {
|
|
32
|
-
it('should use server execution by default', async () => {
|
|
33
|
-
const serverFn = jest.fn(async ({ input }) => ({ server: true, value: input }));
|
|
34
|
-
const clientFn = jest.fn(async ({ input }) => ({ server: false, client: true, value: input }));
|
|
35
|
-
|
|
36
|
-
const tool = aui
|
|
37
|
-
.tool('dual')
|
|
38
|
-
.execute(serverFn)
|
|
39
|
-
.clientExecute(clientFn);
|
|
40
|
-
|
|
41
|
-
// Default context is server
|
|
42
|
-
const result = await tool.run({ test: 1 });
|
|
43
|
-
|
|
44
|
-
expect(serverFn).toHaveBeenCalled();
|
|
45
|
-
expect(clientFn).not.toHaveBeenCalled();
|
|
46
|
-
expect(result).toEqual({ server: true, value: { test: 1 } });
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should use client execution when isServer is false', async () => {
|
|
50
|
-
const serverFn = jest.fn(async ({ input }) => ({ server: true }));
|
|
51
|
-
const clientFn = jest.fn(async ({ input }) => ({ server: false, client: true }));
|
|
52
|
-
|
|
53
|
-
const tool = aui
|
|
54
|
-
.tool('dual')
|
|
55
|
-
.execute(serverFn)
|
|
56
|
-
.clientExecute(clientFn);
|
|
57
|
-
|
|
58
|
-
const result = await tool.run({}, { isServer: false, cache: new Map(), fetch });
|
|
59
|
-
|
|
60
|
-
expect(serverFn).not.toHaveBeenCalled();
|
|
61
|
-
expect(clientFn).toHaveBeenCalled();
|
|
62
|
-
expect(result).toEqual({ server: false, client: true });
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('Context and Caching', () => {
|
|
67
|
-
it('should pass context to execution handlers', async () => {
|
|
68
|
-
const tool = aui
|
|
69
|
-
.tool('contextTest')
|
|
70
|
-
.execute(async ({ input, ctx }) => ({
|
|
71
|
-
hasCache: ctx?.cache instanceof Map,
|
|
72
|
-
hasFetch: typeof ctx?.fetch === 'function',
|
|
73
|
-
isServer: ctx?.isServer
|
|
74
|
-
}));
|
|
75
|
-
|
|
76
|
-
const result = await tool.run({});
|
|
77
|
-
expect(result.hasCache).toBe(true);
|
|
78
|
-
expect(result.hasFetch).toBe(true);
|
|
79
|
-
expect(result.isServer).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should support caching in client execution', async () => {
|
|
83
|
-
const cache = new Map();
|
|
84
|
-
let callCount = 0;
|
|
85
|
-
|
|
86
|
-
const tool = aui
|
|
87
|
-
.tool('cached')
|
|
88
|
-
.input(z.object({ key: z.string() }))
|
|
89
|
-
.clientExecute(async ({ input, ctx }) => {
|
|
90
|
-
if (ctx.cache.has(input.key)) {
|
|
91
|
-
return ctx.cache.get(input.key);
|
|
92
|
-
}
|
|
93
|
-
callCount++;
|
|
94
|
-
const result = { value: callCount, key: input.key };
|
|
95
|
-
ctx.cache.set(input.key, result);
|
|
96
|
-
return result;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const ctx = { cache, fetch, isServer: false };
|
|
100
|
-
|
|
101
|
-
const result1 = await tool.run({ key: 'test' }, ctx);
|
|
102
|
-
const result2 = await tool.run({ key: 'test' }, ctx);
|
|
103
|
-
const result3 = await tool.run({ key: 'other' }, ctx);
|
|
104
|
-
|
|
105
|
-
expect(result1).toEqual({ value: 1, key: 'test' });
|
|
106
|
-
expect(result2).toEqual({ value: 1, key: 'test' }); // Cached
|
|
107
|
-
expect(result3).toEqual({ value: 2, key: 'other' });
|
|
108
|
-
expect(callCount).toBe(2);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('Middleware', () => {
|
|
113
|
-
it('should execute middleware in order', async () => {
|
|
114
|
-
const order: string[] = [];
|
|
115
|
-
|
|
116
|
-
const tool = aui
|
|
117
|
-
.tool('middleware')
|
|
118
|
-
.execute(async ({ input }) => {
|
|
119
|
-
order.push('execute');
|
|
120
|
-
return { result: input };
|
|
121
|
-
})
|
|
122
|
-
.middleware(async ({ input, next }) => {
|
|
123
|
-
order.push('middleware1-before');
|
|
124
|
-
const result = await next();
|
|
125
|
-
order.push('middleware1-after');
|
|
126
|
-
return result;
|
|
127
|
-
})
|
|
128
|
-
.middleware(async ({ input, next }) => {
|
|
129
|
-
order.push('middleware2-before');
|
|
130
|
-
const result = await next();
|
|
131
|
-
order.push('middleware2-after');
|
|
132
|
-
return result;
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
await tool.run({ test: 1 });
|
|
136
|
-
|
|
137
|
-
expect(order).toEqual([
|
|
138
|
-
'middleware1-before',
|
|
139
|
-
'middleware2-before',
|
|
140
|
-
'execute',
|
|
141
|
-
'middleware2-after',
|
|
142
|
-
'middleware1-after'
|
|
143
|
-
]);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should allow middleware to modify results', async () => {
|
|
147
|
-
const tool = aui
|
|
148
|
-
.tool('transform')
|
|
149
|
-
.execute(async ({ input }) => ({ value: input.value }))
|
|
150
|
-
.middleware(async ({ input, next }) => {
|
|
151
|
-
const result = await next();
|
|
152
|
-
return { ...result, modified: true };
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const result = await tool.run({ value: 10 });
|
|
156
|
-
expect(result).toEqual({ value: 10, modified: true });
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe('Tool Registry', () => {
|
|
161
|
-
it('should register and retrieve tools', () => {
|
|
162
|
-
const tool1 = aui.tool('tool1');
|
|
163
|
-
const tool2 = aui.tool('tool2');
|
|
164
|
-
|
|
165
|
-
expect(aui.get('tool1')).toBe(tool1);
|
|
166
|
-
expect(aui.get('tool2')).toBe(tool2);
|
|
167
|
-
expect(aui.get('nonexistent')).toBeUndefined();
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should list all tools', () => {
|
|
171
|
-
aui.tool('a');
|
|
172
|
-
aui.tool('b');
|
|
173
|
-
aui.tool('c');
|
|
174
|
-
|
|
175
|
-
const tools = aui.getTools();
|
|
176
|
-
expect(tools).toHaveLength(3);
|
|
177
|
-
expect(aui.getToolNames()).toEqual(['a', 'b', 'c']);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should support tags and filtering', () => {
|
|
181
|
-
aui.tool('weather').tag('api', 'external');
|
|
182
|
-
aui.tool('database').tag('backend', 'internal');
|
|
183
|
-
aui.tool('ui').tag('frontend', 'internal');
|
|
184
|
-
|
|
185
|
-
expect(aui.findByTag('internal')).toHaveLength(2);
|
|
186
|
-
expect(aui.findByTag('api')).toHaveLength(1);
|
|
187
|
-
expect(aui.findByTags('backend', 'internal')).toHaveLength(1);
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
describe('Server Executor', () => {
|
|
192
|
-
it('should execute tools on server with security checks', async () => {
|
|
193
|
-
aui.tool('allowed')
|
|
194
|
-
.execute(async () => ({ success: true }));
|
|
195
|
-
|
|
196
|
-
aui.tool('blocked')
|
|
197
|
-
.execute(async () => ({ success: false }));
|
|
198
|
-
|
|
199
|
-
const executor = new ServerExecutor({
|
|
200
|
-
allowedTools: ['allowed'],
|
|
201
|
-
blockedTools: ['blocked']
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const result = await executor.execute('allowed', {});
|
|
205
|
-
expect(result).toEqual({ success: true });
|
|
206
|
-
|
|
207
|
-
await expect(executor.execute('blocked', {}))
|
|
208
|
-
.rejects.toThrow('Tool "blocked" is blocked');
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('should handle batch executions', async () => {
|
|
212
|
-
aui.tool('add')
|
|
213
|
-
.input(z.object({ a: z.number(), b: z.number() }))
|
|
214
|
-
.execute(async ({ input }) => input.a + input.b);
|
|
215
|
-
|
|
216
|
-
aui.tool('multiply')
|
|
217
|
-
.input(z.object({ a: z.number(), b: z.number() }))
|
|
218
|
-
.execute(async ({ input }) => input.a * input.b);
|
|
219
|
-
|
|
220
|
-
const executor = new ServerExecutor();
|
|
221
|
-
const results = await executor.executeBatch([
|
|
222
|
-
{ tool: 'add', input: { a: 2, b: 3 } },
|
|
223
|
-
{ tool: 'multiply', input: { a: 4, b: 5 } },
|
|
224
|
-
{ tool: 'nonexistent', input: {} }
|
|
225
|
-
]);
|
|
226
|
-
|
|
227
|
-
expect(results[0]).toEqual({ tool: 'add', result: 5 });
|
|
228
|
-
expect(results[1]).toEqual({ tool: 'multiply', result: 20 });
|
|
229
|
-
expect(results[2].tool).toBe('nonexistent');
|
|
230
|
-
expect(results[2].error).toBeDefined();
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should validate input without execution', async () => {
|
|
234
|
-
aui.tool('validated')
|
|
235
|
-
.input(z.object({
|
|
236
|
-
name: z.string().min(3),
|
|
237
|
-
age: z.number().positive()
|
|
238
|
-
}))
|
|
239
|
-
.execute(async ({ input }) => input);
|
|
240
|
-
|
|
241
|
-
const executor = new ServerExecutor();
|
|
242
|
-
|
|
243
|
-
expect(await executor.validate('validated', { name: 'John', age: 25 }))
|
|
244
|
-
.toBe(true);
|
|
245
|
-
|
|
246
|
-
expect(await executor.validate('validated', { name: 'Jo', age: 25 }))
|
|
247
|
-
.toBe(false);
|
|
248
|
-
|
|
249
|
-
expect(await executor.validate('validated', { name: 'John', age: -5 }))
|
|
250
|
-
.toBe(false);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
describe('Error Handling', () => {
|
|
255
|
-
it('should handle validation errors', async () => {
|
|
256
|
-
const tool = aui
|
|
257
|
-
.tool('strict')
|
|
258
|
-
.input(z.object({ required: z.string() }))
|
|
259
|
-
.execute(async ({ input }) => input);
|
|
260
|
-
|
|
261
|
-
await expect(tool.run({ wrong: 'field' } as any))
|
|
262
|
-
.rejects.toThrow();
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('should handle execution errors', async () => {
|
|
266
|
-
const tool = aui
|
|
267
|
-
.tool('error')
|
|
268
|
-
.execute(async () => {
|
|
269
|
-
throw new Error('Execution failed');
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
await expect(tool.run({}))
|
|
273
|
-
.rejects.toThrow('Execution failed');
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('should handle missing execute handler', async () => {
|
|
277
|
-
const tool = aui.tool('incomplete');
|
|
278
|
-
|
|
279
|
-
await expect(tool.run({}))
|
|
280
|
-
.rejects.toThrow('Tool incomplete has no execute handler');
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
describe('Tool Metadata', () => {
|
|
285
|
-
it('should export tool metadata as JSON', () => {
|
|
286
|
-
const tool = aui
|
|
287
|
-
.tool('metadata')
|
|
288
|
-
.describe('A test tool')
|
|
289
|
-
.tag('test', 'example')
|
|
290
|
-
.input(z.object({ test: z.string() }))
|
|
291
|
-
.execute(async ({ input }) => input)
|
|
292
|
-
.clientExecute(async ({ input }) => input)
|
|
293
|
-
.middleware(async ({ next }) => next());
|
|
294
|
-
|
|
295
|
-
const json = tool.toJSON();
|
|
296
|
-
|
|
297
|
-
expect(json).toEqual({
|
|
298
|
-
name: 'metadata',
|
|
299
|
-
description: 'A test tool',
|
|
300
|
-
tags: ['test', 'example'],
|
|
301
|
-
hasInput: true,
|
|
302
|
-
hasExecute: true,
|
|
303
|
-
hasClientExecute: true,
|
|
304
|
-
hasRender: false,
|
|
305
|
-
hasMiddleware: true
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import aui from '../index';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
describe('AUI Simple API', () => {
|
|
5
|
-
it('creates a simple tool with just 2 methods', () => {
|
|
6
|
-
const simpleTool = aui
|
|
7
|
-
.tool('weather')
|
|
8
|
-
.input(z.object({ city: z.string() }))
|
|
9
|
-
.execute(async ({ input }) => ({ temp: 72, city: input.city }));
|
|
10
|
-
|
|
11
|
-
expect(simpleTool.name).toBe('weather');
|
|
12
|
-
expect(simpleTool.schema).toBeDefined();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('executes a simple tool', async () => {
|
|
16
|
-
const simpleTool = aui
|
|
17
|
-
.tool('weather')
|
|
18
|
-
.input(z.object({ city: z.string() }))
|
|
19
|
-
.execute(async ({ input }) => ({ temp: 72, city: input.city }));
|
|
20
|
-
|
|
21
|
-
const result = await simpleTool.run({ city: 'NYC' });
|
|
22
|
-
expect(result).toEqual({ temp: 72, city: 'NYC' });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('creates a complex tool with client execution', () => {
|
|
26
|
-
const complexTool = aui
|
|
27
|
-
.tool('search')
|
|
28
|
-
.input(z.object({ query: z.string() }))
|
|
29
|
-
.execute(async ({ input }) => [`Result for ${input.query}`])
|
|
30
|
-
.clientExecute(async ({ input, ctx }) => {
|
|
31
|
-
const cached = ctx.cache.get(input.query);
|
|
32
|
-
return cached || [`Cached result for ${input.query}`];
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
expect(complexTool.name).toBe('search');
|
|
36
|
-
const config = complexTool.getConfig();
|
|
37
|
-
expect(config.clientHandler).toBeDefined();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('supports middleware', async () => {
|
|
41
|
-
let middlewareCalled = false;
|
|
42
|
-
|
|
43
|
-
const tool = aui
|
|
44
|
-
.tool('test')
|
|
45
|
-
.middleware(async ({ input, next }) => {
|
|
46
|
-
middlewareCalled = true;
|
|
47
|
-
return next();
|
|
48
|
-
})
|
|
49
|
-
.execute(async () => 'done');
|
|
50
|
-
|
|
51
|
-
await tool.run({});
|
|
52
|
-
expect(middlewareCalled).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('validates input with zod schema', async () => {
|
|
56
|
-
const tool = aui
|
|
57
|
-
.tool('validated')
|
|
58
|
-
.input(z.object({
|
|
59
|
-
email: z.string().email()
|
|
60
|
-
}))
|
|
61
|
-
.execute(async ({ input }) => input);
|
|
62
|
-
|
|
63
|
-
await expect(tool.run({ email: 'invalid' })).rejects.toThrow();
|
|
64
|
-
|
|
65
|
-
const result = await tool.run({ email: 'test@example.com' });
|
|
66
|
-
expect(result.email).toBe('test@example.com');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('uses client handler when not on server', async () => {
|
|
70
|
-
const tool = aui
|
|
71
|
-
.tool('hybrid')
|
|
72
|
-
.execute(async () => 'server result')
|
|
73
|
-
.clientExecute(async () => 'client result');
|
|
74
|
-
|
|
75
|
-
const serverResult = await tool.run({}, {
|
|
76
|
-
isServer: true,
|
|
77
|
-
cache: new Map(),
|
|
78
|
-
fetch: globalThis.fetch
|
|
79
|
-
});
|
|
80
|
-
expect(serverResult).toBe('server result');
|
|
81
|
-
|
|
82
|
-
const clientResult = await tool.run({}, {
|
|
83
|
-
isServer: false,
|
|
84
|
-
cache: new Map(),
|
|
85
|
-
fetch: globalThis.fetch
|
|
86
|
-
});
|
|
87
|
-
expect(clientResult).toBe('client result');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('supports tags and description', () => {
|
|
91
|
-
const tool = aui
|
|
92
|
-
.tool('tagged')
|
|
93
|
-
.tag('user', 'mutation')
|
|
94
|
-
.describe('A tagged tool')
|
|
95
|
-
.execute(async () => null);
|
|
96
|
-
|
|
97
|
-
expect(tool.tags).toContain('user');
|
|
98
|
-
expect(tool.tags).toContain('mutation');
|
|
99
|
-
expect(tool.description).toBe('A tagged tool');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('finds tools by tags', () => {
|
|
103
|
-
aui.clear(); // Clear any existing tools
|
|
104
|
-
|
|
105
|
-
aui.tool('tool1').tag('api').execute(async () => null);
|
|
106
|
-
aui.tool('tool2').tag('api', 'user').execute(async () => null);
|
|
107
|
-
aui.tool('tool3').tag('user').execute(async () => null);
|
|
108
|
-
|
|
109
|
-
const apiTools = aui.findByTag('api');
|
|
110
|
-
expect(apiTools).toHaveLength(2);
|
|
111
|
-
|
|
112
|
-
const userApiTools = aui.findByTags('api', 'user');
|
|
113
|
-
expect(userApiTools).toHaveLength(1);
|
|
114
|
-
expect(userApiTools[0].name).toBe('tool2');
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import aui, { AUI, AUITool } from '../index';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
describe('AUI System', () => {
|
|
5
|
-
let testAUI: AUI;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
testAUI = new AUI();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
describe('Tool Creation', () => {
|
|
12
|
-
it('should create a simple tool with execute and render', () => {
|
|
13
|
-
const tool = testAUI
|
|
14
|
-
.tool('test')
|
|
15
|
-
.input(z.object({ value: z.number() }))
|
|
16
|
-
.execute(async ({ input }) => ({ result: input.value * 2 }));
|
|
17
|
-
|
|
18
|
-
expect(tool.name).toBe('test');
|
|
19
|
-
expect(tool).toBeInstanceOf(AUITool);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should chain methods fluently', () => {
|
|
23
|
-
const tool = testAUI
|
|
24
|
-
.tool('chain-test')
|
|
25
|
-
.input(z.object({ text: z.string() }))
|
|
26
|
-
.execute(async ({ input }) => ({ message: input.text }))
|
|
27
|
-
.describe('A test tool')
|
|
28
|
-
.tag('test', 'example');
|
|
29
|
-
|
|
30
|
-
expect(tool.name).toBe('chain-test');
|
|
31
|
-
expect(tool.description).toBe('A test tool');
|
|
32
|
-
expect(tool.tags).toContain('test');
|
|
33
|
-
expect(tool.tags).toContain('example');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should support clientExecute for client-side optimization', () => {
|
|
37
|
-
const tool = testAUI
|
|
38
|
-
.tool('client-tool')
|
|
39
|
-
.input(z.object({ id: z.string() }))
|
|
40
|
-
.execute(async ({ input }) => ({ id: input.id, source: 'server' }))
|
|
41
|
-
.clientExecute(async ({ input, ctx }) => {
|
|
42
|
-
const cached = ctx.cache.get(input.id);
|
|
43
|
-
return cached || { id: input.id, source: 'client' };
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
expect(tool.name).toBe('client-tool');
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('Tool Execution', () => {
|
|
51
|
-
it('should execute a tool with valid input', async () => {
|
|
52
|
-
const tool = testAUI
|
|
53
|
-
.tool('calc')
|
|
54
|
-
.input(z.object({ a: z.number(), b: z.number() }))
|
|
55
|
-
.execute(async ({ input }) => ({ sum: input.a + input.b }));
|
|
56
|
-
|
|
57
|
-
const result = await tool.run({ a: 5, b: 3 });
|
|
58
|
-
expect(result).toEqual({ sum: 8 });
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should validate input with Zod schema', async () => {
|
|
62
|
-
const tool = testAUI
|
|
63
|
-
.tool('strict')
|
|
64
|
-
.input(z.object({
|
|
65
|
-
email: z.string().email(),
|
|
66
|
-
age: z.number().min(0).max(120)
|
|
67
|
-
}))
|
|
68
|
-
.execute(async ({ input }) => ({ valid: true }));
|
|
69
|
-
|
|
70
|
-
await expect(
|
|
71
|
-
tool.run({ email: 'invalid', age: 25 })
|
|
72
|
-
).rejects.toThrow();
|
|
73
|
-
|
|
74
|
-
const result = await tool.run({ email: 'test@example.com', age: 25 });
|
|
75
|
-
expect(result).toEqual({ valid: true });
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should use clientExecute when not on server', async () => {
|
|
79
|
-
const tool = testAUI
|
|
80
|
-
.tool('hybrid')
|
|
81
|
-
.input(z.object({ key: z.string() }))
|
|
82
|
-
.execute(async ({ input }) => ({ value: 'server', key: input.key }))
|
|
83
|
-
.clientExecute(async ({ input }) => ({ value: 'client', key: input.key }));
|
|
84
|
-
|
|
85
|
-
const clientCtx = {
|
|
86
|
-
cache: new Map(),
|
|
87
|
-
fetch: jest.fn(),
|
|
88
|
-
isServer: false
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const result = await tool.run({ key: 'test' }, clientCtx);
|
|
92
|
-
expect(result).toEqual({ value: 'client', key: 'test' });
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should apply middleware in order', async () => {
|
|
96
|
-
const order: number[] = [];
|
|
97
|
-
|
|
98
|
-
const tool = testAUI
|
|
99
|
-
.tool('middleware-test')
|
|
100
|
-
.input(z.object({ value: z.number() }))
|
|
101
|
-
.middleware(async ({ input, next }) => {
|
|
102
|
-
order.push(1);
|
|
103
|
-
const result = await next();
|
|
104
|
-
order.push(3);
|
|
105
|
-
return result;
|
|
106
|
-
})
|
|
107
|
-
.middleware(async ({ input, next }) => {
|
|
108
|
-
order.push(2);
|
|
109
|
-
return next();
|
|
110
|
-
})
|
|
111
|
-
.execute(async ({ input }) => {
|
|
112
|
-
return { value: input.value * 2 };
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const result = await tool.run({ value: 5 });
|
|
116
|
-
expect(result).toEqual({ value: 10 });
|
|
117
|
-
expect(order).toEqual([1, 2, 3]);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe('Tool Registry', () => {
|
|
122
|
-
it('should register and retrieve tools', () => {
|
|
123
|
-
const tool1 = testAUI.tool('tool1');
|
|
124
|
-
const tool2 = testAUI.tool('tool2');
|
|
125
|
-
|
|
126
|
-
expect(testAUI.get('tool1')).toBe(tool1);
|
|
127
|
-
expect(testAUI.get('tool2')).toBe(tool2);
|
|
128
|
-
expect(testAUI.has('tool1')).toBe(true);
|
|
129
|
-
expect(testAUI.has('nonexistent')).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should list all tools', () => {
|
|
133
|
-
testAUI.tool('a').tag('type1');
|
|
134
|
-
testAUI.tool('b').tag('type2');
|
|
135
|
-
testAUI.tool('c').tag('type1', 'type2');
|
|
136
|
-
|
|
137
|
-
const tools = testAUI.list();
|
|
138
|
-
expect(tools).toHaveLength(3);
|
|
139
|
-
expect(testAUI.getToolNames()).toEqual(['a', 'b', 'c']);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should find tools by tags', () => {
|
|
143
|
-
testAUI.tool('auth').tag('security', 'user');
|
|
144
|
-
testAUI.tool('db').tag('data', 'storage');
|
|
145
|
-
testAUI.tool('api').tag('network', 'data');
|
|
146
|
-
|
|
147
|
-
const dataTools = testAUI.findByTag('data');
|
|
148
|
-
expect(dataTools).toHaveLength(2);
|
|
149
|
-
|
|
150
|
-
const securityTools = testAUI.findByTag('security');
|
|
151
|
-
expect(securityTools).toHaveLength(1);
|
|
152
|
-
expect(securityTools[0].name).toBe('auth');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should clear and remove tools', () => {
|
|
156
|
-
testAUI.tool('temp1');
|
|
157
|
-
testAUI.tool('temp2');
|
|
158
|
-
|
|
159
|
-
expect(testAUI.has('temp1')).toBe(true);
|
|
160
|
-
testAUI.remove('temp1');
|
|
161
|
-
expect(testAUI.has('temp1')).toBe(false);
|
|
162
|
-
|
|
163
|
-
testAUI.clear();
|
|
164
|
-
expect(testAUI.list()).toHaveLength(0);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('Context Management', () => {
|
|
169
|
-
it('should create default context', () => {
|
|
170
|
-
const ctx = testAUI.createContext();
|
|
171
|
-
|
|
172
|
-
expect(ctx.cache).toBeInstanceOf(Map);
|
|
173
|
-
expect(ctx.fetch).toBeDefined();
|
|
174
|
-
expect(ctx.isServer).toBe(typeof window === 'undefined');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('should merge context additions', () => {
|
|
178
|
-
const ctx = testAUI.createContext({
|
|
179
|
-
user: { id: '123', name: 'Test' },
|
|
180
|
-
session: { token: 'abc' }
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
expect(ctx.user).toEqual({ id: '123', name: 'Test' });
|
|
184
|
-
expect(ctx.session).toEqual({ token: 'abc' });
|
|
185
|
-
expect(ctx.cache).toBeInstanceOf(Map);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should cache results in context', async () => {
|
|
189
|
-
let callCount = 0;
|
|
190
|
-
|
|
191
|
-
const tool = testAUI
|
|
192
|
-
.tool('cacheable')
|
|
193
|
-
.input(z.object({ id: z.string() }))
|
|
194
|
-
.clientExecute(async ({ input, ctx }) => {
|
|
195
|
-
const cached = ctx.cache.get(input.id);
|
|
196
|
-
if (cached) return cached;
|
|
197
|
-
|
|
198
|
-
callCount++;
|
|
199
|
-
const result = { id: input.id, data: 'fetched' };
|
|
200
|
-
ctx.cache.set(input.id, result);
|
|
201
|
-
return result;
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const ctx = testAUI.createContext();
|
|
205
|
-
ctx.isServer = false; // Force client execution
|
|
206
|
-
|
|
207
|
-
const result1 = await tool.run({ id: 'test' }, ctx);
|
|
208
|
-
const result2 = await tool.run({ id: 'test' }, ctx);
|
|
209
|
-
|
|
210
|
-
expect(callCount).toBe(1);
|
|
211
|
-
expect(result1).toEqual(result2);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe('Error Handling', () => {
|
|
216
|
-
it('should throw error when tool not found', async () => {
|
|
217
|
-
await expect(
|
|
218
|
-
testAUI.execute('nonexistent', {})
|
|
219
|
-
).rejects.toThrow('Tool "nonexistent" not found');
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('should throw error when execute handler missing', async () => {
|
|
223
|
-
const tool = testAUI
|
|
224
|
-
.tool('incomplete')
|
|
225
|
-
.input(z.object({ value: z.number() }));
|
|
226
|
-
|
|
227
|
-
await expect(
|
|
228
|
-
tool.run({ value: 5 })
|
|
229
|
-
).rejects.toThrow('Tool incomplete has no execute handler');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('should handle errors in execute handler', async () => {
|
|
233
|
-
const tool = testAUI
|
|
234
|
-
.tool('error-tool')
|
|
235
|
-
.execute(async () => {
|
|
236
|
-
throw new Error('Execution failed');
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
await expect(tool.run({})).rejects.toThrow('Execution failed');
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('Tool Serialization', () => {
|
|
244
|
-
it('should serialize tool to JSON', () => {
|
|
245
|
-
const tool = testAUI
|
|
246
|
-
.tool('serializable')
|
|
247
|
-
.input(z.object({ test: z.string() }))
|
|
248
|
-
.execute(async () => ({ result: 'ok' }))
|
|
249
|
-
.clientExecute(async () => ({ result: 'client' }))
|
|
250
|
-
.describe('A serializable tool')
|
|
251
|
-
.tag('test', 'json');
|
|
252
|
-
|
|
253
|
-
tool.render(() => null as any);
|
|
254
|
-
|
|
255
|
-
const json = tool.toJSON();
|
|
256
|
-
|
|
257
|
-
expect(json).toEqual({
|
|
258
|
-
name: 'serializable',
|
|
259
|
-
description: 'A serializable tool',
|
|
260
|
-
tags: ['test', 'json'],
|
|
261
|
-
hasInput: true,
|
|
262
|
-
hasExecute: true,
|
|
263
|
-
hasClientExecute: true,
|
|
264
|
-
hasRender: true,
|
|
265
|
-
hasMiddleware: false
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
});
|