@lantos1618/better-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +190 -0
- package/lib/aui/README.md +136 -0
- package/lib/aui/__tests__/aui-complete.test.ts +251 -0
- package/lib/aui/__tests__/aui-comprehensive.test.ts +376 -0
- package/lib/aui/__tests__/aui-concise.test.ts +278 -0
- package/lib/aui/__tests__/aui-integration.test.ts +309 -0
- package/lib/aui/__tests__/aui-simple.test.ts +116 -0
- package/lib/aui/__tests__/aui.test.ts +269 -0
- package/lib/aui/__tests__/concise-api.test.ts +165 -0
- package/lib/aui/__tests__/core.test.ts +265 -0
- package/lib/aui/__tests__/simple-api.test.ts +200 -0
- package/lib/aui/ai-assistant.ts +408 -0
- package/lib/aui/ai-control.ts +353 -0
- package/lib/aui/client/use-aui.ts +55 -0
- package/lib/aui/client-control.ts +551 -0
- package/lib/aui/client-executor.ts +417 -0
- package/lib/aui/components/ToolRenderer.tsx +22 -0
- package/lib/aui/core.ts +137 -0
- package/lib/aui/demo.tsx +89 -0
- package/lib/aui/examples/ai-complete-demo.tsx +359 -0
- package/lib/aui/examples/ai-control-demo.tsx +356 -0
- package/lib/aui/examples/ai-control-tools.ts +308 -0
- package/lib/aui/examples/concise-api.tsx +153 -0
- package/lib/aui/examples/index.tsx +163 -0
- package/lib/aui/examples/quick-demo.tsx +91 -0
- package/lib/aui/examples/simple-demo.tsx +71 -0
- package/lib/aui/examples/simple-tools.tsx +160 -0
- package/lib/aui/examples/user-api.tsx +208 -0
- package/lib/aui/examples/user-requested.tsx +174 -0
- package/lib/aui/examples/weather-search-tools.tsx +119 -0
- package/lib/aui/examples.tsx +367 -0
- package/lib/aui/hooks/useAUITool.ts +142 -0
- package/lib/aui/hooks/useAUIToolEnhanced.ts +343 -0
- package/lib/aui/hooks/useAUITools.ts +195 -0
- package/lib/aui/index.ts +156 -0
- package/lib/aui/provider.tsx +45 -0
- package/lib/aui/server-control.ts +386 -0
- package/lib/aui/server-executor.ts +165 -0
- package/lib/aui/server.ts +167 -0
- package/lib/aui/tool-registry.ts +380 -0
- package/lib/aui/tools/advanced-examples.tsx +86 -0
- package/lib/aui/tools/ai-complete.ts +375 -0
- package/lib/aui/tools/api-tools.tsx +230 -0
- package/lib/aui/tools/data-tools.tsx +232 -0
- package/lib/aui/tools/dom-tools.tsx +202 -0
- package/lib/aui/tools/examples.ts +43 -0
- package/lib/aui/tools/file-tools.tsx +202 -0
- package/lib/aui/tools/form-tools.tsx +233 -0
- package/lib/aui/tools/index.ts +8 -0
- package/lib/aui/tools/navigation-tools.tsx +172 -0
- package/lib/aui/tools/notification-tools.ts +213 -0
- package/lib/aui/tools/state-tools.tsx +209 -0
- package/lib/aui/types.ts +47 -0
- package/lib/aui/vercel-ai.ts +100 -0
- package/package.json +51 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useMemo, ReactNode } from 'react';
|
|
4
|
+
import aui, { AUI, AUIContext } from './index';
|
|
5
|
+
|
|
6
|
+
interface AUIProviderProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
instance?: AUI;
|
|
9
|
+
context?: Partial<AUIContext>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const AUIReactContext = createContext<{
|
|
13
|
+
aui: AUI;
|
|
14
|
+
context: AUIContext;
|
|
15
|
+
} | null>(null);
|
|
16
|
+
|
|
17
|
+
export function AUIProvider({
|
|
18
|
+
children,
|
|
19
|
+
instance = aui,
|
|
20
|
+
context: contextOverrides = {}
|
|
21
|
+
}: AUIProviderProps) {
|
|
22
|
+
const value = useMemo(() => ({
|
|
23
|
+
aui: instance,
|
|
24
|
+
context: instance.createContext(contextOverrides)
|
|
25
|
+
}), [instance, contextOverrides]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<AUIReactContext.Provider value={value}>
|
|
29
|
+
{children}
|
|
30
|
+
</AUIReactContext.Provider>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useAUIContext() {
|
|
35
|
+
const context = useContext(AUIReactContext);
|
|
36
|
+
if (!context) {
|
|
37
|
+
throw new Error('useAUIContext must be used within AUIProvider');
|
|
38
|
+
}
|
|
39
|
+
return context;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useAUIInstance() {
|
|
43
|
+
const { aui } = useAUIContext();
|
|
44
|
+
return aui;
|
|
45
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createAITool, AIControlledTool } from './ai-control';
|
|
3
|
+
import { AUIContext } from './core';
|
|
4
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { exec } from 'child_process';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
export interface ServerControlContext extends AUIContext {
|
|
13
|
+
req?: NextRequest;
|
|
14
|
+
res?: NextResponse;
|
|
15
|
+
db?: any;
|
|
16
|
+
redis?: any;
|
|
17
|
+
prisma?: any;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const serverTools = {
|
|
21
|
+
fileOperations: createAITool('server-file-ops', {
|
|
22
|
+
permissions: {
|
|
23
|
+
allowServerExecution: true,
|
|
24
|
+
allowFileSystemAccess: true
|
|
25
|
+
},
|
|
26
|
+
audit: true,
|
|
27
|
+
sandbox: true
|
|
28
|
+
})
|
|
29
|
+
.input(z.object({
|
|
30
|
+
operation: z.enum(['read', 'write', 'append', 'delete', 'exists', 'mkdir', 'list']),
|
|
31
|
+
path: z.string(),
|
|
32
|
+
content: z.string().optional(),
|
|
33
|
+
encoding: z.enum(['utf8', 'base64', 'binary']).default('utf8').optional()
|
|
34
|
+
}))
|
|
35
|
+
.describe('Perform file system operations on the server')
|
|
36
|
+
.tag('server', 'filesystem', 'io')
|
|
37
|
+
.execute(async ({ input }) => {
|
|
38
|
+
const safePath = path.resolve(process.cwd(), input.path);
|
|
39
|
+
|
|
40
|
+
if (!safePath.startsWith(process.cwd())) {
|
|
41
|
+
throw new Error('Path traversal attempt blocked');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
switch (input.operation) {
|
|
45
|
+
case 'read':
|
|
46
|
+
const content = await fs.readFile(safePath, input.encoding || 'utf8');
|
|
47
|
+
return { content, path: safePath };
|
|
48
|
+
|
|
49
|
+
case 'write':
|
|
50
|
+
if (!input.content) throw new Error('Content required for write operation');
|
|
51
|
+
await fs.writeFile(safePath, input.content, input.encoding || 'utf8');
|
|
52
|
+
return { success: true, path: safePath };
|
|
53
|
+
|
|
54
|
+
case 'append':
|
|
55
|
+
if (!input.content) throw new Error('Content required for append operation');
|
|
56
|
+
await fs.appendFile(safePath, input.content, input.encoding || 'utf8');
|
|
57
|
+
return { success: true, path: safePath };
|
|
58
|
+
|
|
59
|
+
case 'delete':
|
|
60
|
+
await fs.unlink(safePath);
|
|
61
|
+
return { success: true, deleted: safePath };
|
|
62
|
+
|
|
63
|
+
case 'exists':
|
|
64
|
+
const exists = await fs.access(safePath).then(() => true).catch(() => false);
|
|
65
|
+
return { exists, path: safePath };
|
|
66
|
+
|
|
67
|
+
case 'mkdir':
|
|
68
|
+
await fs.mkdir(safePath, { recursive: true });
|
|
69
|
+
return { success: true, created: safePath };
|
|
70
|
+
|
|
71
|
+
case 'list':
|
|
72
|
+
const items = await fs.readdir(safePath, { withFileTypes: true });
|
|
73
|
+
return {
|
|
74
|
+
path: safePath,
|
|
75
|
+
items: items.map(item => ({
|
|
76
|
+
name: item.name,
|
|
77
|
+
type: item.isDirectory() ? 'directory' : 'file'
|
|
78
|
+
}))
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
default:
|
|
82
|
+
throw new Error('Unknown operation');
|
|
83
|
+
}
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
processExecution: createAITool('server-process', {
|
|
87
|
+
permissions: {
|
|
88
|
+
allowServerExecution: true
|
|
89
|
+
},
|
|
90
|
+
audit: true,
|
|
91
|
+
sandbox: true,
|
|
92
|
+
rateLimit: {
|
|
93
|
+
requestsPerMinute: 10,
|
|
94
|
+
requestsPerHour: 100
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
.input(z.object({
|
|
98
|
+
command: z.string(),
|
|
99
|
+
args: z.array(z.string()).optional(),
|
|
100
|
+
cwd: z.string().optional(),
|
|
101
|
+
env: z.record(z.string()).optional(),
|
|
102
|
+
timeout: z.number().default(30000).optional()
|
|
103
|
+
}))
|
|
104
|
+
.describe('Execute system commands on the server')
|
|
105
|
+
.tag('server', 'process', 'system')
|
|
106
|
+
.execute(async ({ input }) => {
|
|
107
|
+
const allowedCommands = ['ls', 'pwd', 'echo', 'node', 'npm', 'yarn', 'pnpm'];
|
|
108
|
+
const cmd = input.command.split(' ')[0];
|
|
109
|
+
|
|
110
|
+
if (!allowedCommands.includes(cmd)) {
|
|
111
|
+
throw new Error(`Command "${cmd}" is not allowed`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const fullCommand = input.args
|
|
115
|
+
? `${input.command} ${input.args.join(' ')}`
|
|
116
|
+
: input.command;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const { stdout, stderr } = await execAsync(fullCommand, {
|
|
120
|
+
cwd: input.cwd || process.cwd(),
|
|
121
|
+
env: { ...process.env, ...input.env },
|
|
122
|
+
timeout: input.timeout
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
stdout,
|
|
127
|
+
stderr,
|
|
128
|
+
exitCode: 0,
|
|
129
|
+
command: fullCommand
|
|
130
|
+
};
|
|
131
|
+
} catch (error: any) {
|
|
132
|
+
return {
|
|
133
|
+
stdout: error.stdout || '',
|
|
134
|
+
stderr: error.stderr || error.message,
|
|
135
|
+
exitCode: error.code || 1,
|
|
136
|
+
command: fullCommand,
|
|
137
|
+
error: error.message
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}),
|
|
141
|
+
|
|
142
|
+
databaseQuery: createAITool('server-database', {
|
|
143
|
+
permissions: {
|
|
144
|
+
allowServerExecution: true,
|
|
145
|
+
allowDatabaseAccess: true
|
|
146
|
+
},
|
|
147
|
+
audit: true,
|
|
148
|
+
rateLimit: {
|
|
149
|
+
requestsPerMinute: 50,
|
|
150
|
+
requestsPerHour: 1000
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
.input(z.object({
|
|
154
|
+
operation: z.enum(['find', 'findOne', 'create', 'update', 'delete', 'count', 'aggregate']),
|
|
155
|
+
model: z.string(),
|
|
156
|
+
where: z.record(z.any()).optional(),
|
|
157
|
+
data: z.record(z.any()).optional(),
|
|
158
|
+
select: z.record(z.boolean()).optional(),
|
|
159
|
+
include: z.record(z.any()).optional(),
|
|
160
|
+
orderBy: z.record(z.enum(['asc', 'desc'])).optional(),
|
|
161
|
+
take: z.number().optional(),
|
|
162
|
+
skip: z.number().optional()
|
|
163
|
+
}))
|
|
164
|
+
.describe('Execute database operations via Prisma or other ORMs')
|
|
165
|
+
.tag('server', 'database', 'orm')
|
|
166
|
+
.execute(async ({ input, ctx }) => {
|
|
167
|
+
const serverCtx = ctx as ServerControlContext;
|
|
168
|
+
|
|
169
|
+
if (!serverCtx.prisma) {
|
|
170
|
+
throw new Error('Database connection not available');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const model = serverCtx.prisma[input.model];
|
|
174
|
+
if (!model) {
|
|
175
|
+
throw new Error(`Model "${input.model}" not found`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const query: any = {};
|
|
179
|
+
if (input.where) query.where = input.where;
|
|
180
|
+
if (input.data) query.data = input.data;
|
|
181
|
+
if (input.select) query.select = input.select;
|
|
182
|
+
if (input.include) query.include = input.include;
|
|
183
|
+
if (input.orderBy) query.orderBy = input.orderBy;
|
|
184
|
+
if (input.take) query.take = input.take;
|
|
185
|
+
if (input.skip) query.skip = input.skip;
|
|
186
|
+
|
|
187
|
+
const result = await model[input.operation](query);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
operation: input.operation,
|
|
191
|
+
model: input.model,
|
|
192
|
+
result,
|
|
193
|
+
count: Array.isArray(result) ? result.length : 1
|
|
194
|
+
};
|
|
195
|
+
}),
|
|
196
|
+
|
|
197
|
+
cacheControl: createAITool('server-cache', {
|
|
198
|
+
permissions: {
|
|
199
|
+
allowServerExecution: true
|
|
200
|
+
},
|
|
201
|
+
audit: true
|
|
202
|
+
})
|
|
203
|
+
.input(z.object({
|
|
204
|
+
operation: z.enum(['get', 'set', 'delete', 'clear', 'has']),
|
|
205
|
+
key: z.string(),
|
|
206
|
+
value: z.any().optional(),
|
|
207
|
+
ttl: z.number().optional()
|
|
208
|
+
}))
|
|
209
|
+
.describe('Control server-side caching')
|
|
210
|
+
.tag('server', 'cache', 'performance')
|
|
211
|
+
.execute(async ({ input, ctx }) => {
|
|
212
|
+
const serverCtx = ctx as ServerControlContext;
|
|
213
|
+
const cache = serverCtx.cache || new Map();
|
|
214
|
+
|
|
215
|
+
switch (input.operation) {
|
|
216
|
+
case 'get':
|
|
217
|
+
return { value: cache.get(input.key), key: input.key };
|
|
218
|
+
|
|
219
|
+
case 'set':
|
|
220
|
+
cache.set(input.key, input.value);
|
|
221
|
+
if (input.ttl) {
|
|
222
|
+
setTimeout(() => cache.delete(input.key), input.ttl * 1000);
|
|
223
|
+
}
|
|
224
|
+
return { success: true, key: input.key };
|
|
225
|
+
|
|
226
|
+
case 'delete':
|
|
227
|
+
const deleted = cache.delete(input.key);
|
|
228
|
+
return { success: deleted, key: input.key };
|
|
229
|
+
|
|
230
|
+
case 'clear':
|
|
231
|
+
cache.clear();
|
|
232
|
+
return { success: true, cleared: true };
|
|
233
|
+
|
|
234
|
+
case 'has':
|
|
235
|
+
return { exists: cache.has(input.key), key: input.key };
|
|
236
|
+
|
|
237
|
+
default:
|
|
238
|
+
throw new Error('Unknown cache operation');
|
|
239
|
+
}
|
|
240
|
+
}),
|
|
241
|
+
|
|
242
|
+
apiRoute: createAITool('server-api-route', {
|
|
243
|
+
permissions: {
|
|
244
|
+
allowServerExecution: true,
|
|
245
|
+
allowNetworkRequests: true
|
|
246
|
+
},
|
|
247
|
+
audit: true
|
|
248
|
+
})
|
|
249
|
+
.input(z.object({
|
|
250
|
+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
|
|
251
|
+
path: z.string(),
|
|
252
|
+
headers: z.record(z.string()).optional(),
|
|
253
|
+
body: z.any().optional(),
|
|
254
|
+
query: z.record(z.string()).optional()
|
|
255
|
+
}))
|
|
256
|
+
.describe('Create or modify API routes')
|
|
257
|
+
.tag('server', 'api', 'routing')
|
|
258
|
+
.execute(async ({ input }) => {
|
|
259
|
+
return {
|
|
260
|
+
method: input.method,
|
|
261
|
+
path: input.path,
|
|
262
|
+
status: 200,
|
|
263
|
+
response: {
|
|
264
|
+
message: 'Route handler executed',
|
|
265
|
+
data: input.body
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}),
|
|
269
|
+
|
|
270
|
+
environmentControl: createAITool('server-env', {
|
|
271
|
+
permissions: {
|
|
272
|
+
allowServerExecution: true
|
|
273
|
+
},
|
|
274
|
+
audit: true
|
|
275
|
+
})
|
|
276
|
+
.input(z.object({
|
|
277
|
+
operation: z.enum(['get', 'set', 'list']),
|
|
278
|
+
key: z.string().optional(),
|
|
279
|
+
value: z.string().optional()
|
|
280
|
+
}))
|
|
281
|
+
.describe('Manage environment variables')
|
|
282
|
+
.tag('server', 'environment', 'config')
|
|
283
|
+
.execute(async ({ input }) => {
|
|
284
|
+
switch (input.operation) {
|
|
285
|
+
case 'get':
|
|
286
|
+
if (!input.key) throw new Error('Key required for get operation');
|
|
287
|
+
return { key: input.key, value: process.env[input.key] };
|
|
288
|
+
|
|
289
|
+
case 'set':
|
|
290
|
+
if (!input.key || !input.value) {
|
|
291
|
+
throw new Error('Key and value required for set operation');
|
|
292
|
+
}
|
|
293
|
+
process.env[input.key] = input.value;
|
|
294
|
+
return { success: true, key: input.key };
|
|
295
|
+
|
|
296
|
+
case 'list':
|
|
297
|
+
const safeKeys = Object.keys(process.env).filter(
|
|
298
|
+
key => !key.includes('SECRET') && !key.includes('KEY') && !key.includes('TOKEN')
|
|
299
|
+
);
|
|
300
|
+
return {
|
|
301
|
+
keys: safeKeys,
|
|
302
|
+
count: safeKeys.length
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
default:
|
|
306
|
+
throw new Error('Unknown environment operation');
|
|
307
|
+
}
|
|
308
|
+
}),
|
|
309
|
+
|
|
310
|
+
logAnalysis: createAITool('server-logs', {
|
|
311
|
+
permissions: {
|
|
312
|
+
allowServerExecution: true,
|
|
313
|
+
allowFileSystemAccess: true
|
|
314
|
+
},
|
|
315
|
+
audit: true
|
|
316
|
+
})
|
|
317
|
+
.input(z.object({
|
|
318
|
+
source: z.enum(['console', 'file', 'database']),
|
|
319
|
+
level: z.enum(['error', 'warn', 'info', 'debug']).optional(),
|
|
320
|
+
startTime: z.string().optional(),
|
|
321
|
+
endTime: z.string().optional(),
|
|
322
|
+
limit: z.number().default(100).optional(),
|
|
323
|
+
filter: z.string().optional()
|
|
324
|
+
}))
|
|
325
|
+
.describe('Analyze and retrieve server logs')
|
|
326
|
+
.tag('server', 'logging', 'monitoring')
|
|
327
|
+
.execute(async ({ input }) => {
|
|
328
|
+
return {
|
|
329
|
+
source: input.source,
|
|
330
|
+
logs: [],
|
|
331
|
+
count: 0,
|
|
332
|
+
timeRange: {
|
|
333
|
+
start: input.startTime,
|
|
334
|
+
end: input.endTime
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
})
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export function createServerControlSystem() {
|
|
341
|
+
const tools = new Map<string, AIControlledTool>();
|
|
342
|
+
|
|
343
|
+
Object.values(serverTools).forEach(tool => {
|
|
344
|
+
tools.set(tool.name, tool);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
tools,
|
|
349
|
+
|
|
350
|
+
async executeServerTool(
|
|
351
|
+
name: string,
|
|
352
|
+
input: any,
|
|
353
|
+
context?: ServerControlContext
|
|
354
|
+
) {
|
|
355
|
+
const tool = tools.get(name);
|
|
356
|
+
if (!tool) {
|
|
357
|
+
throw new Error(`Server tool "${name}" not found`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const serverContext: ServerControlContext = {
|
|
361
|
+
...context,
|
|
362
|
+
isServer: true,
|
|
363
|
+
cache: context?.cache || new Map(),
|
|
364
|
+
fetch: fetch
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
return await tool.run(input, serverContext);
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
registerServerTool(tool: AIControlledTool) {
|
|
371
|
+
tools.set(tool.name, tool);
|
|
372
|
+
return this;
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
listServerTools() {
|
|
376
|
+
return Array.from(tools.values()).map(tool => ({
|
|
377
|
+
name: tool.name,
|
|
378
|
+
description: tool.description,
|
|
379
|
+
tags: tool.tags,
|
|
380
|
+
schema: tool.schema
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export const serverControlSystem = createServerControlSystem();
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { AUITool, AUIContext } from './core';
|
|
2
|
+
import aui from './index';
|
|
3
|
+
|
|
4
|
+
export interface ServerExecutorOptions {
|
|
5
|
+
// Security options
|
|
6
|
+
allowedTools?: string[];
|
|
7
|
+
blockedTools?: string[];
|
|
8
|
+
maxExecutionTime?: number;
|
|
9
|
+
|
|
10
|
+
// Context options
|
|
11
|
+
defaultContext?: Partial<AUIContext>;
|
|
12
|
+
|
|
13
|
+
// Logging
|
|
14
|
+
logExecution?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ServerExecutor {
|
|
18
|
+
private options: ServerExecutorOptions;
|
|
19
|
+
|
|
20
|
+
constructor(options: ServerExecutorOptions = {}) {
|
|
21
|
+
this.options = {
|
|
22
|
+
maxExecutionTime: 30000, // 30 seconds default
|
|
23
|
+
logExecution: true,
|
|
24
|
+
...options
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async execute<TInput = any, TOutput = any>(
|
|
29
|
+
toolName: string,
|
|
30
|
+
input: TInput,
|
|
31
|
+
context?: Partial<AUIContext>
|
|
32
|
+
): Promise<TOutput> {
|
|
33
|
+
// Security checks
|
|
34
|
+
if (this.options.blockedTools?.includes(toolName)) {
|
|
35
|
+
throw new Error(`Tool "${toolName}" is blocked`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (this.options.allowedTools && !this.options.allowedTools.includes(toolName)) {
|
|
39
|
+
throw new Error(`Tool "${toolName}" is not allowed`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Get the tool
|
|
43
|
+
const tool = aui.get(toolName);
|
|
44
|
+
if (!tool) {
|
|
45
|
+
throw new Error(`Tool "${toolName}" not found`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Create server context
|
|
49
|
+
const serverContext = aui.createContext({
|
|
50
|
+
isServer: true,
|
|
51
|
+
...this.options.defaultContext,
|
|
52
|
+
...context
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Log execution if enabled
|
|
56
|
+
if (this.options.logExecution) {
|
|
57
|
+
console.log(`[AUI] Executing tool: ${toolName}`, { input });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Execute with timeout
|
|
61
|
+
let timeoutId: NodeJS.Timeout | undefined;
|
|
62
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
63
|
+
timeoutId = setTimeout(() => {
|
|
64
|
+
reject(new Error(`Tool execution timeout (${this.options.maxExecutionTime}ms)`));
|
|
65
|
+
}, this.options.maxExecutionTime!);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = await Promise.race([
|
|
70
|
+
tool.run(input, serverContext),
|
|
71
|
+
timeoutPromise
|
|
72
|
+
]) as TOutput;
|
|
73
|
+
|
|
74
|
+
// Clear the timeout after successful execution
|
|
75
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
76
|
+
|
|
77
|
+
if (this.options.logExecution) {
|
|
78
|
+
console.log(`[AUI] Tool executed successfully: ${toolName}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// Clear the timeout on error as well
|
|
84
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
85
|
+
|
|
86
|
+
if (this.options.logExecution) {
|
|
87
|
+
console.error(`[AUI] Tool execution failed: ${toolName}`, error);
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async executeBatch(
|
|
94
|
+
executions: Array<{ tool: string; input: any }>
|
|
95
|
+
): Promise<Array<{ tool: string; result?: any; error?: string }>> {
|
|
96
|
+
const results = await Promise.allSettled(
|
|
97
|
+
executions.map(({ tool, input }) =>
|
|
98
|
+
this.execute(tool, input)
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return results.map((result, index) => ({
|
|
103
|
+
tool: executions[index].tool,
|
|
104
|
+
...(result.status === 'fulfilled'
|
|
105
|
+
? { result: result.value }
|
|
106
|
+
: { error: result.reason?.message || 'Unknown error' }
|
|
107
|
+
)
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Validate tool input without executing
|
|
112
|
+
async validate(toolName: string, input: any): Promise<boolean> {
|
|
113
|
+
const tool = aui.get(toolName);
|
|
114
|
+
if (!tool) {
|
|
115
|
+
throw new Error(`Tool "${toolName}" not found`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const config = tool.getConfig();
|
|
119
|
+
if (!config.inputSchema) {
|
|
120
|
+
return true; // No schema means any input is valid
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
config.inputSchema.parse(input);
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get tool capabilities for AI
|
|
132
|
+
getCapabilities(): Array<{
|
|
133
|
+
name: string;
|
|
134
|
+
description?: string;
|
|
135
|
+
hasInput: boolean;
|
|
136
|
+
hasServerExecution: boolean;
|
|
137
|
+
hasClientExecution: boolean;
|
|
138
|
+
tags: string[];
|
|
139
|
+
}> {
|
|
140
|
+
return aui.getTools().map((tool: any) => {
|
|
141
|
+
const config = tool.getConfig();
|
|
142
|
+
return {
|
|
143
|
+
name: tool.name,
|
|
144
|
+
description: tool.description,
|
|
145
|
+
hasInput: !!config.inputSchema,
|
|
146
|
+
hasServerExecution: !!config.executeHandler,
|
|
147
|
+
hasClientExecution: !!config.clientHandler,
|
|
148
|
+
tags: tool.tags
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Default server executor instance
|
|
155
|
+
export const serverExecutor = new ServerExecutor();
|
|
156
|
+
|
|
157
|
+
// Helper function for API routes
|
|
158
|
+
export async function executeServerTool<TInput = any, TOutput = any>(
|
|
159
|
+
toolName: string,
|
|
160
|
+
input: TInput,
|
|
161
|
+
options?: ServerExecutorOptions
|
|
162
|
+
): Promise<TOutput> {
|
|
163
|
+
const executor = options ? new ServerExecutor(options) : serverExecutor;
|
|
164
|
+
return executor.execute(toolName, input);
|
|
165
|
+
}
|