@ai11y/agent 0.0.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.
@@ -0,0 +1,235 @@
1
+ import type {
2
+ Ai11yContext,
3
+ Instruction,
4
+ ToolDefinition,
5
+ ToolExecutor,
6
+ } from "@ai11y/core";
7
+
8
+ /**
9
+ * Registry for managing tools that can be called by the LLM agent.
10
+ * This allows extending the agent with custom tools.
11
+ */
12
+ export class ToolRegistry {
13
+ private tools = new Map<
14
+ string,
15
+ { definition: ToolDefinition; executor: ToolExecutor }
16
+ >();
17
+
18
+ /**
19
+ * Register a new tool
20
+ * @returns this for method chaining
21
+ */
22
+ register(definition: ToolDefinition, executor: ToolExecutor): this {
23
+ if (this.tools.has(definition.name)) {
24
+ throw new Error(`Tool "${definition.name}" is already registered`);
25
+ }
26
+ this.tools.set(definition.name, { definition, executor });
27
+ return this;
28
+ }
29
+
30
+ /**
31
+ * Unregister a tool
32
+ */
33
+ unregister(name: string): void {
34
+ this.tools.delete(name);
35
+ }
36
+
37
+ /**
38
+ * Get all registered tool definitions for OpenAI function calling
39
+ */
40
+ getToolDefinitions(): Array<{
41
+ type: "function";
42
+ function: ToolDefinition;
43
+ }> {
44
+ return Array.from(this.tools.values()).map(({ definition }) => ({
45
+ type: "function" as const,
46
+ function: definition,
47
+ }));
48
+ }
49
+
50
+ /**
51
+ * Execute a tool call
52
+ */
53
+ async executeToolCall(
54
+ toolName: string,
55
+ args: Record<string, unknown>,
56
+ context: Ai11yContext,
57
+ ): Promise<unknown> {
58
+ const tool = this.tools.get(toolName);
59
+ if (!tool) {
60
+ throw new Error(`Tool "${toolName}" is not registered`);
61
+ }
62
+ return tool.executor(args, context);
63
+ }
64
+
65
+ /**
66
+ * Convert OpenAI tool call to our Instruction format
67
+ */
68
+ convertToolCall(toolCall: {
69
+ type: "function";
70
+ function: { name: string; arguments: string };
71
+ }): Instruction | null {
72
+ const tool = this.tools.get(toolCall.function.name);
73
+ if (!tool) {
74
+ return null;
75
+ }
76
+
77
+ const args = JSON.parse(toolCall.function.arguments);
78
+
79
+ if (toolCall.function.name === "navigate") {
80
+ return { action: "navigate", route: args.route };
81
+ }
82
+ if (toolCall.function.name === "click") {
83
+ return { action: "click", id: args.markerId };
84
+ }
85
+ if (toolCall.function.name === "highlight") {
86
+ return { action: "highlight", id: args.markerId };
87
+ }
88
+ if (toolCall.function.name === "scroll") {
89
+ return { action: "scroll", id: args.markerId };
90
+ }
91
+ if (toolCall.function.name === "fillInput") {
92
+ return {
93
+ action: "fillInput",
94
+ id: args.markerId,
95
+ value: args.value,
96
+ };
97
+ }
98
+
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * Check if a tool is registered
104
+ */
105
+ hasTool(name: string): boolean {
106
+ return this.tools.has(name);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Default tool registry with built-in tools
112
+ */
113
+ export function createDefaultToolRegistry(): ToolRegistry {
114
+ const registry = new ToolRegistry();
115
+
116
+ registry.register(
117
+ {
118
+ name: "click",
119
+ description:
120
+ "Click an interactive element (link, button, etc.) by its marker ID. When markerId is in inViewMarkerIds use only 'click'. When markerId is NOT in inViewMarkerIds you MUST call 'scroll' first (same response) then 'click'—both calls required. Never omit the click when the user asked to click.",
121
+ parameters: {
122
+ type: "object",
123
+ properties: {
124
+ markerId: {
125
+ type: "string",
126
+ description:
127
+ "The ID of the marker to click. If NOT in inViewMarkerIds, you must call 'scroll' with this markerId first, then 'click' with this same markerId—two tool calls.",
128
+ },
129
+ },
130
+ required: ["markerId"],
131
+ },
132
+ },
133
+ async (args) => {
134
+ return { success: true, markerId: args.markerId };
135
+ },
136
+ );
137
+
138
+ registry.register(
139
+ {
140
+ name: "scroll",
141
+ description:
142
+ "Scroll to a UI element by its marker ID to bring it into view. Use when user only wants to see/navigate (single scroll). When user asked to CLICK or interact (increment, press, submit, fill) and the element is not in view: you MUST call scroll first AND then call 'click' (or fillInput etc.) in the same response—never scroll only when they asked for an action. For relative scrolling ('scroll to next'/'previous'): skip markers in inViewMarkerIds.",
143
+ parameters: {
144
+ type: "object",
145
+ properties: {
146
+ markerId: {
147
+ type: "string",
148
+ description:
149
+ "The ID of the marker to scroll to. If user asked to click/interact, you must also call the action tool (click, fillInput) after this scroll in the same response.",
150
+ },
151
+ },
152
+ required: ["markerId"],
153
+ },
154
+ },
155
+ async (args) => {
156
+ return { success: true, markerId: args.markerId };
157
+ },
158
+ );
159
+
160
+ registry.register(
161
+ {
162
+ name: "highlight",
163
+ description:
164
+ "Highlight a UI element by its marker ID to draw the user's attention. This will also scroll the element into view.",
165
+ parameters: {
166
+ type: "object",
167
+ properties: {
168
+ markerId: {
169
+ type: "string",
170
+ description: "The ID of the marker to highlight",
171
+ },
172
+ },
173
+ required: ["markerId"],
174
+ },
175
+ },
176
+ async (args) => {
177
+ return { success: true, markerId: args.markerId };
178
+ },
179
+ );
180
+
181
+ registry.register(
182
+ {
183
+ name: "navigate",
184
+ description:
185
+ "Navigate to a different route/page using a route path (e.g., '/billing', '/integrations'). Use only when 'navigate to [X]' refers to a route path, not a UI element. If X matches a marker, use 'scroll' instead (navigate to element = scroll to it).",
186
+ parameters: {
187
+ type: "object",
188
+ properties: {
189
+ route: {
190
+ type: "string",
191
+ description:
192
+ "The route path to navigate to (e.g., '/billing', '/integrations', '/').",
193
+ },
194
+ },
195
+ required: ["route"],
196
+ },
197
+ },
198
+ async (args) => {
199
+ return { success: true, route: args.route };
200
+ },
201
+ );
202
+
203
+ registry.register(
204
+ {
205
+ name: "fillInput",
206
+ description:
207
+ "Fill a form field (input, textarea, or select) with a value by its marker ID. Emits native browser events to trigger React onChange handlers and form validation. Use when the user wants to enter text into a form field or select an option from a dropdown. For select elements, the value should match one of the available option values. IMPORTANT: Do NOT fill password fields - politely inform the user that sending passwords over the wire is a security risk and they should enter it manually.",
208
+ parameters: {
209
+ type: "object",
210
+ properties: {
211
+ markerId: {
212
+ type: "string",
213
+ description:
214
+ "The ID of the marker for the input/textarea/select element to fill",
215
+ },
216
+ value: {
217
+ type: "string",
218
+ description:
219
+ "The value to fill the field with. For select elements, this must match one of the available option values.",
220
+ },
221
+ },
222
+ required: ["markerId", "value"],
223
+ },
224
+ },
225
+ async (args) => {
226
+ return {
227
+ success: true,
228
+ markerId: args.markerId,
229
+ value: args.value,
230
+ };
231
+ },
232
+ );
233
+
234
+ return registry;
235
+ }
package/src/types.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Server configuration
3
+ */
4
+ export interface ServerConfig {
5
+ provider: "openai";
6
+ apiKey: string;
7
+ model?: string;
8
+ temperature?: number;
9
+ baseURL?: string;
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist",
4
+ "rootDir": "./src",
5
+ "declaration": true,
6
+ "declarationMap": true,
7
+ "sourceMap": true,
8
+ "module": "NodeNext",
9
+ "moduleResolution": "nodenext",
10
+ "target": "ES2022",
11
+ "lib": ["ES2022"],
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "types": ["node"]
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts", "src/fastify.ts"],
5
+ format: ["esm"],
6
+ dts: true,
7
+ unbundle: true,
8
+ sourcemap: true,
9
+ clean: true,
10
+ outDir: "dist",
11
+ });