@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,167 @@
|
|
|
1
|
+
import { cookies, headers } from 'next/headers';
|
|
2
|
+
import aui, { AUITool, AUIContext, z } from './index';
|
|
3
|
+
|
|
4
|
+
// Server-only control system exports
|
|
5
|
+
export {
|
|
6
|
+
serverTools,
|
|
7
|
+
serverControlSystem,
|
|
8
|
+
createServerControlSystem,
|
|
9
|
+
type ServerControlContext
|
|
10
|
+
} from './server-control';
|
|
11
|
+
|
|
12
|
+
export interface ServerToolOptions {
|
|
13
|
+
requireAuth?: boolean;
|
|
14
|
+
rateLimit?: {
|
|
15
|
+
requests: number;
|
|
16
|
+
window: number; // in milliseconds
|
|
17
|
+
};
|
|
18
|
+
middleware?: (ctx: AUIContext) => Promise<void> | void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a server-side AUI context with Next.js integration
|
|
23
|
+
*/
|
|
24
|
+
export async function createServerContext(
|
|
25
|
+
additions?: Partial<AUIContext>
|
|
26
|
+
): Promise<AUIContext> {
|
|
27
|
+
try {
|
|
28
|
+
const cookieStore = await cookies();
|
|
29
|
+
const headersList = await headers();
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
cache: new Map(),
|
|
33
|
+
fetch: globalThis.fetch,
|
|
34
|
+
isServer: true,
|
|
35
|
+
headers: Object.fromEntries(headersList.entries()),
|
|
36
|
+
cookies: Object.fromEntries(
|
|
37
|
+
cookieStore.getAll().map(c => [c.name, c.value])
|
|
38
|
+
),
|
|
39
|
+
...additions,
|
|
40
|
+
};
|
|
41
|
+
} catch {
|
|
42
|
+
// Fallback for non-Next.js environments
|
|
43
|
+
return {
|
|
44
|
+
cache: new Map(),
|
|
45
|
+
fetch: globalThis.fetch,
|
|
46
|
+
isServer: true,
|
|
47
|
+
...additions,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createServerTool<TInput = any, TOutput = any>(
|
|
53
|
+
name: string,
|
|
54
|
+
schema: z.ZodType<TInput>,
|
|
55
|
+
handler: (params: { input: TInput; ctx?: AUIContext }) => Promise<TOutput> | TOutput,
|
|
56
|
+
options?: ServerToolOptions
|
|
57
|
+
): AUITool<TInput, TOutput> {
|
|
58
|
+
const tool = aui
|
|
59
|
+
.tool(name)
|
|
60
|
+
.input(schema)
|
|
61
|
+
.execute(async ({ input, ctx }) => {
|
|
62
|
+
// Apply middleware
|
|
63
|
+
if (options?.middleware) {
|
|
64
|
+
await options.middleware(ctx!);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check auth if required
|
|
68
|
+
if (options?.requireAuth && !ctx?.user) {
|
|
69
|
+
throw new Error('Authentication required');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Execute handler
|
|
73
|
+
return await handler({ input, ctx });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return tool;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function executeServerTool<TInput = any, TOutput = any>(
|
|
80
|
+
toolName: string,
|
|
81
|
+
input: TInput,
|
|
82
|
+
contextAdditions?: Partial<AUIContext>
|
|
83
|
+
): Promise<TOutput> {
|
|
84
|
+
const ctx = await createServerContext(contextAdditions);
|
|
85
|
+
return await aui.execute(toolName, input, ctx);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Server action wrapper for tools
|
|
90
|
+
*/
|
|
91
|
+
export function createServerAction<TInput, TOutput>(
|
|
92
|
+
tool: AUITool<TInput, TOutput>
|
|
93
|
+
) {
|
|
94
|
+
return async (input: TInput): Promise<TOutput> => {
|
|
95
|
+
'use server';
|
|
96
|
+
const ctx = await createServerContext();
|
|
97
|
+
return tool.run(input, ctx);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Batch execute multiple tools on the server
|
|
103
|
+
*/
|
|
104
|
+
export async function batchExecute(
|
|
105
|
+
executions: Array<{ tool: string; input: any }>,
|
|
106
|
+
contextAdditions?: Partial<AUIContext>
|
|
107
|
+
): Promise<Array<{ tool: string; result?: any; error?: string }>> {
|
|
108
|
+
const ctx = await createServerContext(contextAdditions);
|
|
109
|
+
|
|
110
|
+
const results = await Promise.allSettled(
|
|
111
|
+
executions.map(exec => aui.execute(exec.tool, exec.input, ctx))
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return results.map((result, index) => ({
|
|
115
|
+
tool: executions[index].tool,
|
|
116
|
+
...(result.status === 'fulfilled'
|
|
117
|
+
? { result: result.value }
|
|
118
|
+
: { error: result.reason?.message || 'Unknown error' }),
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function registerServerTools(tools: AUITool[]) {
|
|
123
|
+
tools.forEach(tool => {
|
|
124
|
+
if (!aui.has(tool.name)) {
|
|
125
|
+
aui.get(tool.name); // This will register the tool
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function handleToolRequest(
|
|
131
|
+
request: Request
|
|
132
|
+
): Promise<Response> {
|
|
133
|
+
try {
|
|
134
|
+
const { tool: toolName, input, context } = await request.json();
|
|
135
|
+
|
|
136
|
+
const tool = aui.get(toolName);
|
|
137
|
+
if (!tool) {
|
|
138
|
+
return new Response(
|
|
139
|
+
JSON.stringify({ error: `Tool "${toolName}" not found` }),
|
|
140
|
+
{
|
|
141
|
+
status: 404,
|
|
142
|
+
headers: { 'Content-Type': 'application/json' }
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ctx = await createServerContext(context);
|
|
148
|
+
const result = await tool.run(input, ctx);
|
|
149
|
+
|
|
150
|
+
return new Response(
|
|
151
|
+
JSON.stringify({ success: true, data: result }),
|
|
152
|
+
{
|
|
153
|
+
status: 200,
|
|
154
|
+
headers: { 'Content-Type': 'application/json' }
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
159
|
+
return new Response(
|
|
160
|
+
JSON.stringify({ error: message }),
|
|
161
|
+
{
|
|
162
|
+
status: 400,
|
|
163
|
+
headers: { 'Content-Type': 'application/json' }
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AUITool, AUIContext } from './core';
|
|
3
|
+
import { AIControlledTool } from './ai-control';
|
|
4
|
+
import { clientTools, clientControlSystem } from './client-control';
|
|
5
|
+
|
|
6
|
+
export interface ToolMetadata {
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
tags: string[];
|
|
10
|
+
category: 'server' | 'client' | 'hybrid' | 'custom';
|
|
11
|
+
permissions?: string[];
|
|
12
|
+
schema?: z.ZodType<any>;
|
|
13
|
+
examples?: Array<{
|
|
14
|
+
description: string;
|
|
15
|
+
input: any;
|
|
16
|
+
expectedOutput?: any;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ToolRegistry {
|
|
21
|
+
private tools = new Map<string, AUITool | AIControlledTool>();
|
|
22
|
+
private metadata = new Map<string, ToolMetadata>();
|
|
23
|
+
private categories = new Map<string, Set<string>>();
|
|
24
|
+
private tagIndex = new Map<string, Set<string>>();
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this.initializeBuiltInTools();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private initializeBuiltInTools() {
|
|
31
|
+
// Only initialize client tools by default
|
|
32
|
+
// Server tools should be registered separately in server-side code
|
|
33
|
+
Object.entries(clientTools).forEach(([key, tool]) => {
|
|
34
|
+
this.register(tool, {
|
|
35
|
+
name: tool.name,
|
|
36
|
+
description: tool.description,
|
|
37
|
+
tags: tool.tags,
|
|
38
|
+
category: 'client'
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
register(
|
|
44
|
+
tool: AUITool | AIControlledTool,
|
|
45
|
+
metadata?: Partial<ToolMetadata>
|
|
46
|
+
): this {
|
|
47
|
+
const meta: ToolMetadata = {
|
|
48
|
+
name: tool.name,
|
|
49
|
+
description: tool.description,
|
|
50
|
+
tags: tool.tags,
|
|
51
|
+
category: metadata?.category || 'custom',
|
|
52
|
+
permissions: metadata?.permissions,
|
|
53
|
+
schema: tool.schema,
|
|
54
|
+
examples: metadata?.examples,
|
|
55
|
+
...metadata
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.tools.set(tool.name, tool);
|
|
59
|
+
this.metadata.set(tool.name, meta);
|
|
60
|
+
|
|
61
|
+
if (!this.categories.has(meta.category)) {
|
|
62
|
+
this.categories.set(meta.category, new Set());
|
|
63
|
+
}
|
|
64
|
+
this.categories.get(meta.category)!.add(tool.name);
|
|
65
|
+
|
|
66
|
+
meta.tags.forEach(tag => {
|
|
67
|
+
if (!this.tagIndex.has(tag)) {
|
|
68
|
+
this.tagIndex.set(tag, new Set());
|
|
69
|
+
}
|
|
70
|
+
this.tagIndex.get(tag)!.add(tool.name);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
unregister(name: string): boolean {
|
|
77
|
+
const tool = this.tools.get(name);
|
|
78
|
+
if (!tool) return false;
|
|
79
|
+
|
|
80
|
+
const meta = this.metadata.get(name);
|
|
81
|
+
if (meta) {
|
|
82
|
+
this.categories.get(meta.category)?.delete(name);
|
|
83
|
+
meta.tags.forEach(tag => {
|
|
84
|
+
this.tagIndex.get(tag)?.delete(name);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.tools.delete(name);
|
|
89
|
+
this.metadata.delete(name);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get(name: string): AUITool | AIControlledTool | undefined {
|
|
94
|
+
return this.tools.get(name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getMetadata(name: string): ToolMetadata | undefined {
|
|
98
|
+
return this.metadata.get(name);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
findByCategory(category: string): Array<AUITool | AIControlledTool> {
|
|
102
|
+
const names = this.categories.get(category);
|
|
103
|
+
if (!names) return [];
|
|
104
|
+
return Array.from(names)
|
|
105
|
+
.map(name => this.tools.get(name))
|
|
106
|
+
.filter(Boolean) as Array<AUITool | AIControlledTool>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
findByTag(tag: string): Array<AUITool | AIControlledTool> {
|
|
110
|
+
const names = this.tagIndex.get(tag);
|
|
111
|
+
if (!names) return [];
|
|
112
|
+
return Array.from(names)
|
|
113
|
+
.map(name => this.tools.get(name))
|
|
114
|
+
.filter(Boolean) as Array<AUITool | AIControlledTool>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
findByTags(tags: string[]): Array<AUITool | AIControlledTool> {
|
|
118
|
+
const toolSets = tags.map(tag => this.tagIndex.get(tag) || new Set());
|
|
119
|
+
if (toolSets.length === 0) return [];
|
|
120
|
+
|
|
121
|
+
const intersection = toolSets.reduce((acc, set) => {
|
|
122
|
+
return new Set([...acc].filter(x => set.has(x)));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return Array.from(intersection)
|
|
126
|
+
.map(name => this.tools.get(name as string))
|
|
127
|
+
.filter(Boolean) as Array<AUITool | AIControlledTool>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
search(query: string): Array<{
|
|
131
|
+
tool: AUITool | AIControlledTool;
|
|
132
|
+
metadata: ToolMetadata;
|
|
133
|
+
relevance: number;
|
|
134
|
+
}> {
|
|
135
|
+
const results: Array<{
|
|
136
|
+
tool: AUITool | AIControlledTool;
|
|
137
|
+
metadata: ToolMetadata;
|
|
138
|
+
relevance: number;
|
|
139
|
+
}> = [];
|
|
140
|
+
|
|
141
|
+
const lowerQuery = query.toLowerCase();
|
|
142
|
+
|
|
143
|
+
this.tools.forEach((tool, name) => {
|
|
144
|
+
const meta = this.metadata.get(name)!;
|
|
145
|
+
let relevance = 0;
|
|
146
|
+
|
|
147
|
+
if (name.toLowerCase().includes(lowerQuery)) {
|
|
148
|
+
relevance += 10;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (meta.description?.toLowerCase().includes(lowerQuery)) {
|
|
152
|
+
relevance += 5;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
meta.tags.forEach(tag => {
|
|
156
|
+
if (tag.toLowerCase().includes(lowerQuery)) {
|
|
157
|
+
relevance += 3;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (meta.category.toLowerCase().includes(lowerQuery)) {
|
|
162
|
+
relevance += 2;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (relevance > 0) {
|
|
166
|
+
results.push({ tool, metadata: meta, relevance });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return results.sort((a, b) => b.relevance - a.relevance);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async execute(
|
|
174
|
+
name: string,
|
|
175
|
+
input: any,
|
|
176
|
+
context?: AUIContext
|
|
177
|
+
): Promise<any> {
|
|
178
|
+
const tool = this.tools.get(name);
|
|
179
|
+
if (!tool) {
|
|
180
|
+
throw new Error(`Tool "${name}" not found in registry`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const meta = this.metadata.get(name);
|
|
184
|
+
const enhancedContext: AUIContext = {
|
|
185
|
+
cache: context?.cache || new Map(),
|
|
186
|
+
fetch: context?.fetch || globalThis.fetch,
|
|
187
|
+
...context,
|
|
188
|
+
isServer: meta?.category === 'server' ? true :
|
|
189
|
+
meta?.category === 'client' ? false :
|
|
190
|
+
context?.isServer ?? (typeof window === 'undefined')
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return await tool.run(input, enhancedContext);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
list(): ToolMetadata[] {
|
|
197
|
+
return Array.from(this.metadata.values());
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
listByCategory(): Record<string, ToolMetadata[]> {
|
|
201
|
+
const result: Record<string, ToolMetadata[]> = {};
|
|
202
|
+
|
|
203
|
+
this.categories.forEach((tools, category) => {
|
|
204
|
+
result[category] = Array.from(tools)
|
|
205
|
+
.map(name => this.metadata.get(name))
|
|
206
|
+
.filter(Boolean) as ToolMetadata[];
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
exportSchema(): Record<string, any> {
|
|
213
|
+
const schemas: Record<string, any> = {};
|
|
214
|
+
|
|
215
|
+
this.tools.forEach((tool, name) => {
|
|
216
|
+
const meta = this.metadata.get(name)!;
|
|
217
|
+
schemas[name] = {
|
|
218
|
+
name,
|
|
219
|
+
description: meta.description,
|
|
220
|
+
category: meta.category,
|
|
221
|
+
tags: meta.tags,
|
|
222
|
+
inputSchema: tool.schema ? zodToJsonSchema(tool.schema) : null,
|
|
223
|
+
examples: meta.examples
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return schemas;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
importTools(tools: Array<AUITool | AIControlledTool>, category?: 'server' | 'client' | 'hybrid' | 'custom'): void {
|
|
231
|
+
tools.forEach(tool => {
|
|
232
|
+
this.register(tool, { category: category || 'custom' });
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
clear(): void {
|
|
237
|
+
this.tools.clear();
|
|
238
|
+
this.metadata.clear();
|
|
239
|
+
this.categories.clear();
|
|
240
|
+
this.tagIndex.clear();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
getStats(): {
|
|
244
|
+
totalTools: number;
|
|
245
|
+
byCategory: Record<string, number>;
|
|
246
|
+
byTag: Record<string, number>;
|
|
247
|
+
} {
|
|
248
|
+
const byCategory: Record<string, number> = {};
|
|
249
|
+
this.categories.forEach((tools, category) => {
|
|
250
|
+
byCategory[category] = tools.size;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const byTag: Record<string, number> = {};
|
|
254
|
+
this.tagIndex.forEach((tools, tag) => {
|
|
255
|
+
byTag[tag] = tools.size;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
totalTools: this.tools.size,
|
|
260
|
+
byCategory,
|
|
261
|
+
byTag
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function zodToJsonSchema(schema: z.ZodType<any>): any {
|
|
267
|
+
if (schema instanceof z.ZodObject) {
|
|
268
|
+
const shape = schema.shape;
|
|
269
|
+
const properties: Record<string, any> = {};
|
|
270
|
+
const required: string[] = [];
|
|
271
|
+
|
|
272
|
+
Object.entries(shape).forEach(([key, value]: [string, any]) => {
|
|
273
|
+
properties[key] = zodToJsonSchema(value);
|
|
274
|
+
if (!value.isOptional()) {
|
|
275
|
+
required.push(key);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
type: 'object',
|
|
281
|
+
properties,
|
|
282
|
+
required: required.length > 0 ? required : undefined
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (schema instanceof z.ZodString) {
|
|
287
|
+
return { type: 'string' };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (schema instanceof z.ZodNumber) {
|
|
291
|
+
return { type: 'number' };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (schema instanceof z.ZodBoolean) {
|
|
295
|
+
return { type: 'boolean' };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (schema instanceof z.ZodArray) {
|
|
299
|
+
return {
|
|
300
|
+
type: 'array',
|
|
301
|
+
items: zodToJsonSchema(schema.element)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (schema instanceof z.ZodEnum) {
|
|
306
|
+
return {
|
|
307
|
+
type: 'string',
|
|
308
|
+
enum: schema.options
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (schema instanceof z.ZodOptional) {
|
|
313
|
+
return zodToJsonSchema(schema.unwrap());
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return { type: 'any' };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export const globalToolRegistry = new ToolRegistry();
|
|
320
|
+
|
|
321
|
+
export class ToolDiscovery {
|
|
322
|
+
private registry: ToolRegistry;
|
|
323
|
+
|
|
324
|
+
constructor(registry: ToolRegistry = globalToolRegistry) {
|
|
325
|
+
this.registry = registry;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async discoverTools(pattern?: string): Promise<ToolMetadata[]> {
|
|
329
|
+
if (pattern) {
|
|
330
|
+
return this.registry.search(pattern).map(r => r.metadata);
|
|
331
|
+
}
|
|
332
|
+
return this.registry.list();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async getToolCapabilities(name: string): Promise<{
|
|
336
|
+
canExecuteOnServer: boolean;
|
|
337
|
+
canExecuteOnClient: boolean;
|
|
338
|
+
requiresAuth: boolean;
|
|
339
|
+
rateLimit?: { requestsPerMinute?: number; requestsPerHour?: number };
|
|
340
|
+
}> {
|
|
341
|
+
const tool = this.registry.get(name);
|
|
342
|
+
if (!tool) {
|
|
343
|
+
throw new Error(`Tool "${name}" not found`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const meta = this.registry.getMetadata(name);
|
|
347
|
+
const isAIControlled = tool instanceof AIControlledTool;
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
canExecuteOnServer: meta?.category === 'server' || meta?.category === 'hybrid',
|
|
351
|
+
canExecuteOnClient: meta?.category === 'client' || meta?.category === 'hybrid',
|
|
352
|
+
requiresAuth: meta?.permissions?.includes('auth') || false,
|
|
353
|
+
rateLimit: isAIControlled ? (tool as any).controlOptions?.rateLimit : undefined
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async getRecommendedTools(context: {
|
|
358
|
+
task?: string;
|
|
359
|
+
tags?: string[];
|
|
360
|
+
category?: string;
|
|
361
|
+
}): Promise<ToolMetadata[]> {
|
|
362
|
+
let tools: ToolMetadata[] = [];
|
|
363
|
+
|
|
364
|
+
if (context.category) {
|
|
365
|
+
tools = this.registry.findByCategory(context.category)
|
|
366
|
+
.map(t => this.registry.getMetadata(t.name)!);
|
|
367
|
+
} else if (context.tags && context.tags.length > 0) {
|
|
368
|
+
tools = this.registry.findByTags(context.tags)
|
|
369
|
+
.map(t => this.registry.getMetadata(t.name)!);
|
|
370
|
+
} else if (context.task) {
|
|
371
|
+
tools = this.registry.search(context.task).map(r => r.metadata);
|
|
372
|
+
} else {
|
|
373
|
+
tools = this.registry.list();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return tools.slice(0, 10);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export const toolDiscovery = new ToolDiscovery(globalToolRegistry);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import aui, { z } from '../index';
|
|
3
|
+
|
|
4
|
+
export const databaseTool = aui
|
|
5
|
+
.tool('database')
|
|
6
|
+
.input(z.object({
|
|
7
|
+
operation: z.enum(['create', 'read', 'update', 'delete']),
|
|
8
|
+
table: z.string(),
|
|
9
|
+
data: z.any().optional()
|
|
10
|
+
}))
|
|
11
|
+
.execute(async ({ input }) => {
|
|
12
|
+
switch (input.operation) {
|
|
13
|
+
case 'create':
|
|
14
|
+
return { id: Date.now(), ...input.data };
|
|
15
|
+
case 'read':
|
|
16
|
+
return { records: [], count: 0 };
|
|
17
|
+
case 'update':
|
|
18
|
+
return { updated: 1 };
|
|
19
|
+
case 'delete':
|
|
20
|
+
return { deleted: 1 };
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const fileSystemTool = aui
|
|
25
|
+
.tool('fileSystem')
|
|
26
|
+
.input(z.object({
|
|
27
|
+
action: z.enum(['read', 'write', 'list']),
|
|
28
|
+
path: z.string(),
|
|
29
|
+
content: z.string().optional()
|
|
30
|
+
}))
|
|
31
|
+
.execute(async ({ input }) => {
|
|
32
|
+
return { success: true, action: input.action, path: input.path };
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const apiTool = aui
|
|
36
|
+
.tool('api')
|
|
37
|
+
.input(z.object({
|
|
38
|
+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
|
|
39
|
+
endpoint: z.string(),
|
|
40
|
+
body: z.any().optional()
|
|
41
|
+
}))
|
|
42
|
+
.execute(async ({ input }) => {
|
|
43
|
+
return { status: 200, data: { message: 'Success' } };
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const processTool = aui
|
|
47
|
+
.tool('process')
|
|
48
|
+
.input(z.object({
|
|
49
|
+
command: z.string(),
|
|
50
|
+
args: z.array(z.string()).optional()
|
|
51
|
+
}))
|
|
52
|
+
.execute(async ({ input }) => {
|
|
53
|
+
return { output: `Executed: ${input.command}`, exitCode: 0 };
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const stateTool = aui
|
|
57
|
+
.tool('state')
|
|
58
|
+
.input(z.object({
|
|
59
|
+
action: z.enum(['get', 'set', 'update']),
|
|
60
|
+
key: z.string(),
|
|
61
|
+
value: z.any().optional()
|
|
62
|
+
}))
|
|
63
|
+
.execute(async ({ input }) => {
|
|
64
|
+
return { key: input.key, value: input.value || null };
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const notificationTool = aui
|
|
68
|
+
.tool('notification')
|
|
69
|
+
.input(z.object({
|
|
70
|
+
type: z.enum(['info', 'success', 'warning', 'error']),
|
|
71
|
+
message: z.string(),
|
|
72
|
+
duration: z.number().optional()
|
|
73
|
+
}))
|
|
74
|
+
.execute(async ({ input }) => {
|
|
75
|
+
return { sent: true, ...input };
|
|
76
|
+
})
|
|
77
|
+
.render(({ data }) => (
|
|
78
|
+
<div className={`p-4 rounded-lg ${
|
|
79
|
+
data.type === 'error' ? 'bg-red-100' :
|
|
80
|
+
data.type === 'warning' ? 'bg-yellow-100' :
|
|
81
|
+
data.type === 'success' ? 'bg-green-100' :
|
|
82
|
+
'bg-blue-100'
|
|
83
|
+
}`}>
|
|
84
|
+
{data.message}
|
|
85
|
+
</div>
|
|
86
|
+
));
|