@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.
Files changed (59) hide show
  1. package/README.md +231 -148
  2. package/dist/index.d.mts +314 -0
  3. package/dist/index.d.ts +314 -0
  4. package/dist/index.js +522 -0
  5. package/dist/index.mjs +491 -0
  6. package/package.json +59 -20
  7. package/lib/aui/README.md +0 -136
  8. package/lib/aui/__tests__/aui-complete.test.ts +0 -251
  9. package/lib/aui/__tests__/aui-comprehensive.test.ts +0 -376
  10. package/lib/aui/__tests__/aui-concise.test.ts +0 -278
  11. package/lib/aui/__tests__/aui-integration.test.ts +0 -309
  12. package/lib/aui/__tests__/aui-simple.test.ts +0 -116
  13. package/lib/aui/__tests__/aui.test.ts +0 -269
  14. package/lib/aui/__tests__/concise-api.test.ts +0 -165
  15. package/lib/aui/__tests__/core.test.ts +0 -265
  16. package/lib/aui/__tests__/simple-api.test.ts +0 -200
  17. package/lib/aui/ai-assistant.ts +0 -408
  18. package/lib/aui/ai-control.ts +0 -353
  19. package/lib/aui/client/use-aui.ts +0 -55
  20. package/lib/aui/client-control.ts +0 -551
  21. package/lib/aui/client-executor.ts +0 -417
  22. package/lib/aui/components/ToolRenderer.tsx +0 -22
  23. package/lib/aui/core.ts +0 -137
  24. package/lib/aui/demo.tsx +0 -89
  25. package/lib/aui/examples/ai-complete-demo.tsx +0 -359
  26. package/lib/aui/examples/ai-control-demo.tsx +0 -356
  27. package/lib/aui/examples/ai-control-tools.ts +0 -308
  28. package/lib/aui/examples/concise-api.tsx +0 -153
  29. package/lib/aui/examples/index.tsx +0 -163
  30. package/lib/aui/examples/quick-demo.tsx +0 -91
  31. package/lib/aui/examples/simple-demo.tsx +0 -71
  32. package/lib/aui/examples/simple-tools.tsx +0 -160
  33. package/lib/aui/examples/user-api.tsx +0 -208
  34. package/lib/aui/examples/user-requested.tsx +0 -174
  35. package/lib/aui/examples/weather-search-tools.tsx +0 -119
  36. package/lib/aui/examples.tsx +0 -367
  37. package/lib/aui/hooks/useAUITool.ts +0 -142
  38. package/lib/aui/hooks/useAUIToolEnhanced.ts +0 -343
  39. package/lib/aui/hooks/useAUITools.ts +0 -195
  40. package/lib/aui/index.ts +0 -156
  41. package/lib/aui/provider.tsx +0 -45
  42. package/lib/aui/server-control.ts +0 -386
  43. package/lib/aui/server-executor.ts +0 -165
  44. package/lib/aui/server.ts +0 -167
  45. package/lib/aui/tool-registry.ts +0 -380
  46. package/lib/aui/tools/advanced-examples.tsx +0 -86
  47. package/lib/aui/tools/ai-complete.ts +0 -375
  48. package/lib/aui/tools/api-tools.tsx +0 -230
  49. package/lib/aui/tools/data-tools.tsx +0 -232
  50. package/lib/aui/tools/dom-tools.tsx +0 -202
  51. package/lib/aui/tools/examples.ts +0 -43
  52. package/lib/aui/tools/file-tools.tsx +0 -202
  53. package/lib/aui/tools/form-tools.tsx +0 -233
  54. package/lib/aui/tools/index.ts +0 -8
  55. package/lib/aui/tools/navigation-tools.tsx +0 -172
  56. package/lib/aui/tools/notification-tools.ts +0 -213
  57. package/lib/aui/tools/state-tools.tsx +0 -209
  58. package/lib/aui/types.ts +0 -47
  59. 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
- });