@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,31 @@
1
+
2
+ > @ai11y/agent@0.0.1 build /home/runner/work/ai11y/ai11y/packages/agent
3
+ > tsdown
4
+
5
+ ℹ tsdown v0.18.1 powered by rolldown v1.0.0-beta.55
6
+ ℹ config file: /home/runner/work/ai11y/ai11y/packages/agent/tsdown.config.ts (unrun)
7
+ ℹ entry: src/index.ts, src/fastify.ts
8
+ ℹ tsconfig: tsconfig.json
9
+ ℹ Build start
10
+ ℹ dist/fastify.mjs  2.08 kB │ gzip: 0.94 kB
11
+ ℹ dist/index.mjs  0.24 kB │ gzip: 0.14 kB
12
+ ℹ dist/agent.mjs.map 20.59 kB │ gzip: 6.54 kB
13
+ ℹ dist/agent.mjs 12.04 kB │ gzip: 4.18 kB
14
+ ℹ dist/tool-registry.mjs.map  9.73 kB │ gzip: 2.75 kB
15
+ ℹ dist/tool-registry.mjs  5.89 kB │ gzip: 1.91 kB
16
+ ℹ dist/fastify.mjs.map  3.50 kB │ gzip: 1.43 kB
17
+ ℹ dist/llm-provider.mjs.map  1.02 kB │ gzip: 0.56 kB
18
+ ℹ dist/llm-provider.mjs  0.47 kB │ gzip: 0.31 kB
19
+ ℹ dist/tool-registry.d.mts.map  0.36 kB │ gzip: 0.25 kB
20
+ ℹ dist/fastify.d.mts.map  0.28 kB │ gzip: 0.20 kB
21
+ ℹ dist/agent.d.mts.map  0.23 kB │ gzip: 0.18 kB
22
+ ℹ dist/llm-provider.d.mts.map  0.16 kB │ gzip: 0.14 kB
23
+ ℹ dist/types.d.mts.map  0.13 kB │ gzip: 0.12 kB
24
+ ℹ dist/fastify.d.mts  1.57 kB │ gzip: 0.71 kB
25
+ ℹ dist/index.d.mts  0.30 kB │ gzip: 0.16 kB
26
+ ℹ dist/tool-registry.d.mts  1.35 kB │ gzip: 0.61 kB
27
+ ℹ dist/agent.d.mts  0.42 kB │ gzip: 0.26 kB
28
+ ℹ dist/llm-provider.d.mts  0.36 kB │ gzip: 0.25 kB
29
+ ℹ dist/types.d.mts  0.26 kB │ gzip: 0.20 kB
30
+ ℹ 20 files, total: 60.98 kB
31
+ ✔ Build complete in 3596ms
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # @ai11y/agent
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#10](https://github.com/maerzhase/ai11y/pull/10)
8
+ [`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d)
9
+ Thanks [@maerzhase](https://github.com/maerzhase)! - Docs and demo
10
+ improvements: naming (ai11y ≠ a11y), Describe/Plan/Act boundaries, UI context
11
+ payload viewer, annotation guidance, Why not ARIA, Security & privacy,
12
+ multi-step demo
13
+
14
+ - [#10](https://github.com/maerzhase/ai11y/pull/10)
15
+ [`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d)
16
+ Thanks [@maerzhase](https://github.com/maerzhase)! - Add custom state
17
+ management and privacy protection features
18
+ - **Custom State API**: `setState()` now merges with existing state (like
19
+ React's setState), allowing multiple components to independently manage
20
+ state keys. Added `clearState()` helper for resetting state.
21
+ - **Privacy Protection**: Automatically redact sensitive input values
22
+ (passwords, hidden fields, credit card fields) from UI context. Values are
23
+ replaced with `[REDACTED]` to prevent exposure to AI agents.
24
+ - **Sensitive Marker Support**: Added `data-ai-sensitive` attribute and
25
+ `sensitive` prop to Marker component for explicitly marking sensitive
26
+ fields.
27
+ - **Agent Improvements**: Enhanced agent prompts with security rules to
28
+ prevent filling password fields and improved pronoun resolution for better
29
+ context tracking in conversations.
30
+
31
+ - Updated dependencies
32
+ [[`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d),
33
+ [`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d)]:
34
+ - @ai11y/core@0.0.1
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @ai11y/agent
2
+
3
+ Runs the **plan** step for ai11y on the server (LLM + tools). Securely handles
4
+ LLM API calls and provides extensible tool support.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pnpm add @ai11y/agent
10
+ ```
11
+
12
+ ## Quick Start
13
+
14
+ ### Standalone
15
+
16
+ Use `runAgent` with a config and tool registry; wire it to your own HTTP (or
17
+ other) transport:
18
+
19
+ ```ts
20
+ import type { AgentRequest } from "@ai11y/core";
21
+ import { runAgent, createDefaultToolRegistry } from "@ai11y/agent";
22
+ import type { ServerConfig } from "@ai11y/agent";
23
+
24
+ const config: ServerConfig = {
25
+ apiKey: process.env.OPENAI_API_KEY!,
26
+ model: "gpt-4o-mini",
27
+ };
28
+
29
+ const toolRegistry = createDefaultToolRegistry();
30
+
31
+ async function handleRequest(request: AgentRequest) {
32
+ const response = await runAgent(request, config, toolRegistry);
33
+ return response;
34
+ }
35
+ ```
36
+
37
+ ### With Fastify
38
+
39
+ Register the plugin to expose the agent endpoint:
40
+
41
+ ```ts
42
+ import Fastify from "fastify";
43
+ import { ai11yPlugin } from "@ai11y/agent/fastify";
44
+
45
+ const fastify = Fastify();
46
+
47
+ await fastify.register(ai11yPlugin, {
48
+ config: {
49
+ apiKey: process.env.OPENAI_API_KEY!,
50
+ model: "gpt-4o-mini",
51
+ baseURL: "https://api.openai.com/v1",
52
+ },
53
+ });
54
+
55
+ await fastify.listen({ port: 3000 });
56
+ ```
57
+
58
+ The plugin registers `POST /ai11y/agent` and `GET /ai11y/health`. See
59
+ `apps/server/` for a full example.
60
+
61
+ ## Extending with Custom Tools
62
+
63
+ You can extend the agent with custom tools using the `ToolRegistry`:
64
+
65
+ ```ts
66
+ import type { Ai11yContext, ToolDefinition, ToolExecutor } from "@ai11y/core";
67
+ import { ai11yPlugin, createToolRegistry } from "@ai11y/agent/fastify";
68
+
69
+ const registry = createToolRegistry();
70
+
71
+ registry.register(
72
+ {
73
+ name: "send_email",
74
+ description: "Send an email notification",
75
+ parameters: {
76
+ type: "object",
77
+ properties: {
78
+ to: { type: "string", description: "Recipient email address" },
79
+ subject: { type: "string", description: "Email subject" },
80
+ body: { type: "string", description: "Email body" },
81
+ },
82
+ required: ["to", "subject", "body"],
83
+ },
84
+ },
85
+ async (args, context) => {
86
+ console.log("Sending email:", args);
87
+ return { success: true, messageId: "123" };
88
+ },
89
+ );
90
+
91
+ await fastify.register(ai11yPlugin, {
92
+ config: {
93
+ apiKey: process.env.OPENAI_API_KEY!,
94
+ },
95
+ toolRegistry: registry,
96
+ });
97
+ ```
98
+
99
+ For API details and types, see the generated docs.
@@ -0,0 +1,13 @@
1
+ import { ToolRegistry } from "./tool-registry.mjs";
2
+ import { ServerConfig } from "./types.mjs";
3
+ import { AgentRequest, AgentResponse } from "@ai11y/core";
4
+
5
+ //#region src/agent.d.ts
6
+
7
+ /**
8
+ * Run the LLM agent on the server
9
+ */
10
+ declare function runAgent(request: AgentRequest, config: ServerConfig, toolRegistry?: ToolRegistry): Promise<AgentResponse>;
11
+ //#endregion
12
+ export { runAgent };
13
+ //# sourceMappingURL=agent.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.mts","names":[],"sources":["../src/agent.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA+NA;AACU,iBADY,QAAA,CACZ,OAAA,EAAA,YAAA,EAAA,MAAA,EACD,YADC,EAAA,YAAA,CAAA,EAEK,YAFL,CAAA,EAGP,OAHO,CAGC,aAHD,CAAA"}
package/dist/agent.mjs ADDED
@@ -0,0 +1,221 @@
1
+ import { createLLM } from "./llm-provider.mjs";
2
+ import { createDefaultToolRegistry } from "./tool-registry.mjs";
3
+ import { DynamicStructuredTool } from "@langchain/core/tools";
4
+ import { z } from "zod";
5
+
6
+ //#region src/agent.ts
7
+ /**
8
+ * Format context for the LLM prompt
9
+ */
10
+ function formatContextForPrompt(context) {
11
+ const parts = [];
12
+ if (context.route) parts.push(`Current route: ${context.route}`);
13
+ if (context.error) {
14
+ const error = context.error.error;
15
+ parts.push(`\n! Last error: ${error.message}${context.error.meta?.markerId ? ` (related to marker: ${context.error.meta.markerId})` : ""}`);
16
+ parts.push("The user may want to retry the failed action. Look for markers related to the error.");
17
+ }
18
+ if (context.state && Object.keys(context.state).length > 0) parts.push(`\nApplication state: ${JSON.stringify(context.state)}`);
19
+ if (context.markers.length > 0) {
20
+ parts.push("\nAvailable UI elements (markers):");
21
+ for (const marker of context.markers) {
22
+ const inViewStatus = context.inViewMarkerIds?.includes(marker.id) ?? false ? " [IN VIEW]" : "";
23
+ let markerLine = ` - ${marker.label} (ID: ${marker.id}, Type: ${marker.elementType})${inViewStatus}: ${marker.intent}`;
24
+ if (marker.value !== void 0) markerLine += `\n Current value: "${marker.value}"`;
25
+ if (marker.options !== void 0) {
26
+ const optionsList = marker.options.map((opt) => `${opt.label} (${opt.value})`).join(", ");
27
+ markerLine += `\n Available options: ${optionsList}`;
28
+ }
29
+ if (marker.selectedOptions !== void 0 && marker.selectedOptions.length > 0) markerLine += `\n Selected: ${marker.selectedOptions.join(", ")}`;
30
+ parts.push(markerLine);
31
+ }
32
+ } else parts.push("\nNo UI elements are currently available.");
33
+ if (context.inViewMarkerIds && context.inViewMarkerIds.length > 0) parts.push(`\nVisible markers: ${context.inViewMarkerIds.join(", ")}`);
34
+ return parts.join("\n");
35
+ }
36
+ /**
37
+ * Convert tool registry to LangChain tools
38
+ */
39
+ function createLangChainTools(toolRegistry, context) {
40
+ const tools = [];
41
+ const toolDefinitions = toolRegistry.getToolDefinitions();
42
+ for (const toolDef of toolDefinitions) {
43
+ const def = toolDef.function;
44
+ const zodSchema = {};
45
+ for (const [key, param] of Object.entries(def.parameters.properties)) zodSchema[key] = (param.type === "string" ? z.string() : param.type === "number" ? z.number() : param.type === "boolean" ? z.boolean() : z.unknown()).describe(param.description);
46
+ const schema = z.object(zodSchema);
47
+ const tool = new DynamicStructuredTool({
48
+ name: def.name,
49
+ description: def.description,
50
+ schema,
51
+ func: async (args) => {
52
+ const result = await toolRegistry.executeToolCall(def.name, args, context);
53
+ return JSON.stringify(result);
54
+ }
55
+ });
56
+ tools.push(tool);
57
+ }
58
+ return tools;
59
+ }
60
+ /**
61
+ * Check if user input matches any marker and return matching marker info
62
+ * Also checks if user is referring to a UI element (button, link, etc.) vs just a route
63
+ */
64
+ function findMatchingMarker(input, markers) {
65
+ const lowerInput = input.toLowerCase().trim();
66
+ if (!(lowerInput.includes("go to") || lowerInput.includes("navigate to") || lowerInput.includes("open") || lowerInput.includes("take me to"))) return null;
67
+ const isElementReference = [
68
+ "button",
69
+ "link",
70
+ "nav button",
71
+ "navigation button",
72
+ "nav link",
73
+ "navigation link",
74
+ "element",
75
+ "item",
76
+ "tab"
77
+ ].some((keyword) => lowerInput.includes(keyword));
78
+ const searchText = lowerInput.replace(/go to|navigate to|open|take me to/g, "").trim();
79
+ const matchingMarker = markers.find((m) => {
80
+ return `${m.label} ${m.intent}`.toLowerCase().includes(searchText) || lowerInput.includes(m.label.toLowerCase()) || lowerInput.includes(m.intent.toLowerCase());
81
+ });
82
+ return matchingMarker ? {
83
+ marker: matchingMarker,
84
+ searchText,
85
+ isElementReference
86
+ } : null;
87
+ }
88
+ /**
89
+ * Type guard to check if response has tool calls
90
+ */
91
+ function hasToolCalls(response) {
92
+ const kwargs = response.additional_kwargs;
93
+ return typeof response === "object" && response !== null && ("tool_calls" in response || "additional_kwargs" in response && typeof kwargs === "object" && kwargs !== null && "tool_calls" in kwargs);
94
+ }
95
+ /**
96
+ * Run the LLM agent on the server
97
+ */
98
+ async function runAgent(request, config, toolRegistry = createDefaultToolRegistry()) {
99
+ const llm = await createLLM(config);
100
+ const markerMatch = findMatchingMarker(request.input, request.context.markers);
101
+ const contextPrompt = formatContextForPrompt(request.context);
102
+ const langchainTools = createLangChainTools(toolRegistry, request.context);
103
+ const llmWithTools = langchainTools.length > 0 && typeof llm.bindTools === "function" ? llm.bindTools(langchainTools) : llm;
104
+ let recentMarkerContext = "";
105
+ if (request.messages && request.messages.length > 0) {
106
+ const lastFewMessages = request.messages.slice(-4);
107
+ for (const msg of lastFewMessages) for (const marker of request.context.markers) if (msg.content.toLowerCase().includes(marker.label.toLowerCase()) || msg.content.toLowerCase().includes(marker.id.toLowerCase())) {
108
+ recentMarkerContext += `\nRecently discussed: ${marker.label} (${marker.id}) - ${marker.intent}`;
109
+ break;
110
+ }
111
+ }
112
+ let markerMatchGuidance = "";
113
+ if (markerMatch) {
114
+ const marker = markerMatch.marker;
115
+ const isInView = request.context.inViewMarkerIds?.includes(marker.id) ?? false;
116
+ const isLink = marker.elementType === "a";
117
+ if (isLink && isInView) markerMatchGuidance = `\n\n⚠️ Match found: "${marker.label}" (${marker.id}) is a visible link. Use 'click' tool.`;
118
+ else if (isLink && !isInView) markerMatchGuidance = `\n\n🚨 Match found: "${marker.label}" (${marker.id}) is a link NOT in view. You MUST call BOTH 'scroll' and 'click' (in that order)—two tool calls. Do not call only scroll.`;
119
+ else if (!isInView) markerMatchGuidance = `\n\n🚨 Match found: "${marker.label}" (${marker.id}) is NOT in view. You MUST call BOTH 'scroll' and 'click' (or the appropriate action) in that order—two tool calls. Do not call only scroll.`;
120
+ else markerMatchGuidance = `\n\nMatch found: "${marker.label}" (${marker.id}) is an element. Use 'scroll' tool.`;
121
+ }
122
+ const conversationMessages = [{
123
+ role: "system",
124
+ content: `You are a helpful AI agent embedded in a web application. Help users navigate, interact with UI elements, and resolve errors.
125
+
126
+ ${contextPrompt}${recentMarkerContext}${markerMatchGuidance}
127
+
128
+ Reading marker values:
129
+ - You can read current values from markers in the context above
130
+ - For input/textarea elements: The "Current value" field shows what's currently entered (password fields show "[REDACTED]" for privacy)
131
+ - For select elements: The "Available options" shows all choices, and "Selected" shows what's currently selected
132
+ - When users ask "what's in [field]" or "what's the value of [marker]", read the value from the context and respond with it
133
+ - You don't need a tool to read values - they're already in the context provided above
134
+
135
+ Security rules:
136
+ - NEVER fill password fields - if a user asks to fill a password, politely explain that sending passwords is a security risk and they should enter it manually
137
+ - Password field values are redacted as "[REDACTED]" in the context for privacy protection
138
+
139
+ Pronoun resolution:
140
+ - When the user says "it", "that", "this", they're referring to the most recently discussed marker
141
+ - Look at the recent conversation history (shown above as "Recently discussed") to identify which specific marker was discussed
142
+ - Prefer specific interactive elements (buttons, inputs, links) over parent sections (those with "section" or "slide" in the label/id) when resolving pronouns
143
+ - Example: If user asked about "password input" and then says "highlight it", use the password input marker (e.g. fill_demo_password), NOT the parent section marker (e.g. slide_fill_input)
144
+
145
+ Navigation rules:
146
+ - "navigate to [element]" = scroll to that element (use 'scroll' tool)
147
+ - "navigate to [route]" = route navigation (use 'navigate' tool with route path)
148
+ - If marker matches and is in inViewMarkerIds + elementType='a' → use 'click'
149
+ - If no marker matches → use 'navigate' with route path
150
+ - For affirmative responses after discussing a marker, interact with that marker using the appropriate tool.
151
+
152
+ Scroll-then-act rule (CRITICAL):
153
+ - When the user wants to INTERACT with an element (click, press, increment, submit, fill, etc.) and the target marker is NOT in inViewMarkerIds: you MUST emit TWO tool calls in order—(1) 'scroll' to that marker, (2) the action ('click', 'fillInput', etc.). Emitting only 'scroll' is WRONG; the user asked for an action, so you must call both scroll and the action.
154
+ - Prefer the specific interactive element for the action (e.g. the button click_demo_increment for "increment counter"), not a parent section (e.g. slide_click). Emit one scroll to the exact target element, then the action.
155
+ - If the user only wants to see or navigate to an element (no click/fill), use a single 'scroll' to that element.
156
+
157
+ Relative scrolling rules (for "scroll to next" or "scroll to previous"):
158
+ - Markers are listed in document order (top to bottom) in the markers array
159
+ - CRITICAL: Always skip markers that are in inViewMarkerIds - only scroll to markers NOT currently in view
160
+ - For "scroll to next": Find the first marker in the markers array that comes after any currently visible markers and is NOT in inViewMarkerIds
161
+ - For "scroll to previous": Find the first marker in the markers array that comes before any currently visible markers and is NOT in inViewMarkerIds
162
+ - This prevents getting stuck on sections already in view
163
+
164
+ Multiple actions rules:
165
+ - When user says "all" (e.g., "highlight all badges", "click all buttons"), generate MULTIPLE tool calls - one for each matching marker
166
+ - When user specifies a count (e.g., "click 10 times", "increment counter 5 times"), generate MULTIPLE tool calls - repeat the action the specified number of times
167
+ - CRITICAL: You can and should call the same tool multiple times in a single response when the user requests multiple actions
168
+ - For "highlight all [type]" or "click all [type]": Find all markers matching the type and generate one tool call per marker
169
+ - For "[action] [count] times": Generate [count] identical tool calls`
170
+ }];
171
+ if (request.messages && request.messages.length > 0) {
172
+ const recentMessages = request.messages.slice(-10);
173
+ for (const msg of recentMessages) conversationMessages.push({
174
+ role: msg.role,
175
+ content: msg.content
176
+ });
177
+ }
178
+ conversationMessages.push({
179
+ role: "user",
180
+ content: request.input
181
+ });
182
+ const response = await llmWithTools.invoke(conversationMessages);
183
+ const instructions = [];
184
+ let reply = "";
185
+ if (response.content) if (typeof response.content === "string") reply = response.content;
186
+ else if (Array.isArray(response.content)) reply = response.content.map((c) => {
187
+ if (typeof c === "string") return c;
188
+ if (c && typeof c === "object" && "text" in c) return c.text;
189
+ return "";
190
+ }).join("");
191
+ else reply = String(response.content);
192
+ const toolCallObjects = hasToolCalls(response) ? response.tool_calls || response.additional_kwargs?.tool_calls || [] : [];
193
+ for (const toolCall of toolCallObjects) {
194
+ const toolName = toolCall.name || toolCall.function?.name;
195
+ const toolArgs = toolCall.args || (toolCall.function?.arguments ? typeof toolCall.function.arguments === "string" ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments : {});
196
+ if (toolName) {
197
+ const converted = toolRegistry.convertToolCall({
198
+ type: "function",
199
+ function: {
200
+ name: toolName,
201
+ arguments: JSON.stringify(toolArgs)
202
+ }
203
+ });
204
+ if (converted) instructions.push(converted);
205
+ else try {
206
+ const result = await toolRegistry.executeToolCall(toolName, toolArgs, request.context);
207
+ if (result && typeof result === "object" && "action" in result) instructions.push(result);
208
+ } catch (error) {
209
+ console.error(`Error executing tool ${toolName}:`, error);
210
+ }
211
+ }
212
+ }
213
+ return {
214
+ reply: reply || "I'm here to help!",
215
+ instructions: instructions.length > 0 ? instructions : void 0
216
+ };
217
+ }
218
+
219
+ //#endregion
220
+ export { runAgent };
221
+ //# sourceMappingURL=agent.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.mjs","names":["parts: string[]","tools: DynamicStructuredTool[]","zodSchema: Record<string, z.ZodTypeAny>","conversationMessages: Array<{ role: string; content: string }>","instructions: Instruction[]","toolCallObjects: Array<{\n\t\tname?: string;\n\t\targs?: Record<string, unknown>;\n\t\tfunction?: {\n\t\t\tname?: string;\n\t\t\targuments?: string | Record<string, unknown>;\n\t\t};\n\t}>"],"sources":["../src/agent.ts"],"sourcesContent":["import type { AgentRequest, AgentResponse, Instruction } from \"@ai11y/core\";\nimport { DynamicStructuredTool } from \"@langchain/core/tools\";\nimport { z } from \"zod\";\nimport { createLLM } from \"./llm-provider.js\";\nimport {\n\tcreateDefaultToolRegistry,\n\ttype ToolRegistry,\n} from \"./tool-registry.js\";\nimport type { ServerConfig } from \"./types.js\";\n\n/**\n * Format context for the LLM prompt\n */\nfunction formatContextForPrompt(context: AgentRequest[\"context\"]): string {\n\tconst parts: string[] = [];\n\n\tif (context.route) {\n\t\tparts.push(`Current route: ${context.route}`);\n\t}\n\n\tif (context.error) {\n\t\tconst error = context.error.error;\n\t\tparts.push(\n\t\t\t`\\n! Last error: ${error.message}${context.error.meta?.markerId ? ` (related to marker: ${context.error.meta.markerId})` : \"\"}`,\n\t\t);\n\t\tparts.push(\n\t\t\t\"The user may want to retry the failed action. Look for markers related to the error.\",\n\t\t);\n\t}\n\n\tif (context.state && Object.keys(context.state).length > 0) {\n\t\tparts.push(`\\nApplication state: ${JSON.stringify(context.state)}`);\n\t}\n\n\tif (context.markers.length > 0) {\n\t\tparts.push(\"\\nAvailable UI elements (markers):\");\n\t\tfor (const marker of context.markers) {\n\t\t\tconst isInView = context.inViewMarkerIds?.includes(marker.id) ?? false;\n\t\t\tconst inViewStatus = isInView ? \" [IN VIEW]\" : \"\";\n\t\t\tlet markerLine = ` - ${marker.label} (ID: ${marker.id}, Type: ${marker.elementType})${inViewStatus}: ${marker.intent}`;\n\n\t\t\tif (marker.value !== undefined) {\n\t\t\t\tmarkerLine += `\\n Current value: \"${marker.value}\"`;\n\t\t\t}\n\n\t\t\tif (marker.options !== undefined) {\n\t\t\t\tconst optionsList = marker.options\n\t\t\t\t\t.map((opt) => `${opt.label} (${opt.value})`)\n\t\t\t\t\t.join(\", \");\n\t\t\t\tmarkerLine += `\\n Available options: ${optionsList}`;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tmarker.selectedOptions !== undefined &&\n\t\t\t\tmarker.selectedOptions.length > 0\n\t\t\t) {\n\t\t\t\tmarkerLine += `\\n Selected: ${marker.selectedOptions.join(\", \")}`;\n\t\t\t}\n\n\t\t\tparts.push(markerLine);\n\t\t}\n\t} else {\n\t\tparts.push(\"\\nNo UI elements are currently available.\");\n\t}\n\n\tif (context.inViewMarkerIds && context.inViewMarkerIds.length > 0) {\n\t\tparts.push(`\\nVisible markers: ${context.inViewMarkerIds.join(\", \")}`);\n\t}\n\n\treturn parts.join(\"\\n\");\n}\n\n/**\n * Convert tool registry to LangChain tools\n */\nfunction createLangChainTools(\n\ttoolRegistry: ToolRegistry,\n\tcontext: AgentRequest[\"context\"],\n): DynamicStructuredTool[] {\n\tconst tools: DynamicStructuredTool[] = [];\n\tconst toolDefinitions = toolRegistry.getToolDefinitions();\n\n\tfor (const toolDef of toolDefinitions) {\n\t\tconst def = toolDef.function;\n\n\t\tconst zodSchema: Record<string, z.ZodTypeAny> = {};\n\t\tfor (const [key, param] of Object.entries(\n\t\t\tdef.parameters.properties,\n\t\t) as Array<[string, { type: string; description: string }]>) {\n\t\t\tconst zodType =\n\t\t\t\tparam.type === \"string\"\n\t\t\t\t\t? z.string()\n\t\t\t\t\t: param.type === \"number\"\n\t\t\t\t\t\t? z.number()\n\t\t\t\t\t\t: param.type === \"boolean\"\n\t\t\t\t\t\t\t? z.boolean()\n\t\t\t\t\t\t\t: z.unknown();\n\t\t\tzodSchema[key] = zodType.describe(param.description);\n\t\t}\n\n\t\tconst schema = z.object(zodSchema);\n\n\t\tconst tool = new DynamicStructuredTool({\n\t\t\tname: def.name,\n\t\t\tdescription: def.description,\n\t\t\tschema,\n\t\t\tfunc: async (args: Record<string, unknown>) => {\n\t\t\t\tconst result = await toolRegistry.executeToolCall(\n\t\t\t\t\tdef.name,\n\t\t\t\t\targs,\n\t\t\t\t\tcontext,\n\t\t\t\t);\n\t\t\t\treturn JSON.stringify(result);\n\t\t\t},\n\t\t});\n\n\t\ttools.push(tool);\n\t}\n\n\treturn tools;\n}\n\n/**\n * Check if user input matches any marker and return matching marker info\n * Also checks if user is referring to a UI element (button, link, etc.) vs just a route\n */\nfunction findMatchingMarker(\n\tinput: string,\n\tmarkers: AgentRequest[\"context\"][\"markers\"],\n): {\n\tmarker: AgentRequest[\"context\"][\"markers\"][0];\n\tsearchText: string;\n\tisElementReference: boolean;\n} | null {\n\tconst lowerInput = input.toLowerCase().trim();\n\n\tconst hasNavigationLanguage =\n\t\tlowerInput.includes(\"go to\") ||\n\t\tlowerInput.includes(\"navigate to\") ||\n\t\tlowerInput.includes(\"open\") ||\n\t\tlowerInput.includes(\"take me to\");\n\n\tif (!hasNavigationLanguage) {\n\t\treturn null;\n\t}\n\n\tconst elementKeywords = [\n\t\t\"button\",\n\t\t\"link\",\n\t\t\"nav button\",\n\t\t\"navigation button\",\n\t\t\"nav link\",\n\t\t\"navigation link\",\n\t\t\"element\",\n\t\t\"item\",\n\t\t\"tab\",\n\t];\n\tconst isElementReference = elementKeywords.some((keyword) =>\n\t\tlowerInput.includes(keyword),\n\t);\n\n\tconst searchText = lowerInput\n\t\t.replace(/go to|navigate to|open|take me to/g, \"\")\n\t\t.trim();\n\n\tconst matchingMarker = markers.find((m) => {\n\t\tconst markerText = `${m.label} ${m.intent}`.toLowerCase();\n\t\treturn (\n\t\t\tmarkerText.includes(searchText) ||\n\t\t\tlowerInput.includes(m.label.toLowerCase()) ||\n\t\t\tlowerInput.includes(m.intent.toLowerCase())\n\t\t);\n\t});\n\n\treturn matchingMarker\n\t\t? { marker: matchingMarker, searchText, isElementReference }\n\t\t: null;\n}\n\n/**\n * LangChain response with tool calls\n */\ninterface LangChainResponseWithTools {\n\ttool_calls?: Array<{\n\t\tname?: string;\n\t\targs?: Record<string, unknown>;\n\t\tfunction?: {\n\t\t\tname?: string;\n\t\t\targuments?: string | Record<string, unknown>;\n\t\t};\n\t}>;\n\tadditional_kwargs?: {\n\t\ttool_calls?: Array<{\n\t\t\tname?: string;\n\t\t\targs?: Record<string, unknown>;\n\t\t\tfunction?: {\n\t\t\t\tname?: string;\n\t\t\t\targuments?: string | Record<string, unknown>;\n\t\t\t};\n\t\t}>;\n\t};\n}\n\n/**\n * Type guard to check if response has tool calls\n */\nfunction hasToolCalls(\n\tresponse: unknown,\n): response is LangChainResponseWithTools {\n\tconst kwargs = (response as LangChainResponseWithTools).additional_kwargs;\n\treturn (\n\t\ttypeof response === \"object\" &&\n\t\tresponse !== null &&\n\t\t(\"tool_calls\" in response ||\n\t\t\t(\"additional_kwargs\" in response &&\n\t\t\t\ttypeof kwargs === \"object\" &&\n\t\t\t\tkwargs !== null &&\n\t\t\t\t\"tool_calls\" in kwargs))\n\t);\n}\n\n/**\n * Run the LLM agent on the server\n */\nexport async function runAgent(\n\trequest: AgentRequest,\n\tconfig: ServerConfig,\n\ttoolRegistry: ToolRegistry = createDefaultToolRegistry(),\n): Promise<AgentResponse> {\n\tconst llm = await createLLM(config);\n\n\tconst markerMatch = findMatchingMarker(\n\t\trequest.input,\n\t\trequest.context.markers,\n\t);\n\n\tconst contextPrompt = formatContextForPrompt(request.context);\n\tconst langchainTools = createLangChainTools(toolRegistry, request.context);\n\n\tconst llmWithTools =\n\t\tlangchainTools.length > 0 && typeof llm.bindTools === \"function\"\n\t\t\t? llm.bindTools(langchainTools)\n\t\t\t: llm;\n\n\tlet recentMarkerContext = \"\";\n\tif (request.messages && request.messages.length > 0) {\n\t\tconst lastFewMessages = request.messages.slice(-4);\n\t\tfor (const msg of lastFewMessages) {\n\t\t\tfor (const marker of request.context.markers) {\n\t\t\t\tif (\n\t\t\t\t\tmsg.content.toLowerCase().includes(marker.label.toLowerCase()) ||\n\t\t\t\t\tmsg.content.toLowerCase().includes(marker.id.toLowerCase())\n\t\t\t\t) {\n\t\t\t\t\trecentMarkerContext += `\\nRecently discussed: ${marker.label} (${marker.id}) - ${marker.intent}`;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet markerMatchGuidance = \"\";\n\tif (markerMatch) {\n\t\tconst marker = markerMatch.marker;\n\t\tconst isInView =\n\t\t\trequest.context.inViewMarkerIds?.includes(marker.id) ?? false;\n\t\tconst isLink = marker.elementType === \"a\";\n\n\t\tif (isLink && isInView) {\n\t\t\tmarkerMatchGuidance = `\\n\\n⚠️ Match found: \"${marker.label}\" (${marker.id}) is a visible link. Use 'click' tool.`;\n\t\t} else if (isLink && !isInView) {\n\t\t\tmarkerMatchGuidance = `\\n\\n🚨 Match found: \"${marker.label}\" (${marker.id}) is a link NOT in view. You MUST call BOTH 'scroll' and 'click' (in that order)—two tool calls. Do not call only scroll.`;\n\t\t} else if (!isInView) {\n\t\t\tmarkerMatchGuidance = `\\n\\n🚨 Match found: \"${marker.label}\" (${marker.id}) is NOT in view. You MUST call BOTH 'scroll' and 'click' (or the appropriate action) in that order—two tool calls. Do not call only scroll.`;\n\t\t} else {\n\t\t\tmarkerMatchGuidance = `\\n\\nMatch found: \"${marker.label}\" (${marker.id}) is an element. Use 'scroll' tool.`;\n\t\t}\n\t}\n\n\tconst systemPrompt = `You are a helpful AI agent embedded in a web application. Help users navigate, interact with UI elements, and resolve errors.\n\n${contextPrompt}${recentMarkerContext}${markerMatchGuidance}\n\nReading marker values:\n- You can read current values from markers in the context above\n- For input/textarea elements: The \"Current value\" field shows what's currently entered (password fields show \"[REDACTED]\" for privacy)\n- For select elements: The \"Available options\" shows all choices, and \"Selected\" shows what's currently selected\n- When users ask \"what's in [field]\" or \"what's the value of [marker]\", read the value from the context and respond with it\n- You don't need a tool to read values - they're already in the context provided above\n\nSecurity rules:\n- NEVER fill password fields - if a user asks to fill a password, politely explain that sending passwords is a security risk and they should enter it manually\n- Password field values are redacted as \"[REDACTED]\" in the context for privacy protection\n\nPronoun resolution:\n- When the user says \"it\", \"that\", \"this\", they're referring to the most recently discussed marker\n- Look at the recent conversation history (shown above as \"Recently discussed\") to identify which specific marker was discussed\n- Prefer specific interactive elements (buttons, inputs, links) over parent sections (those with \"section\" or \"slide\" in the label/id) when resolving pronouns\n- Example: If user asked about \"password input\" and then says \"highlight it\", use the password input marker (e.g. fill_demo_password), NOT the parent section marker (e.g. slide_fill_input)\n\nNavigation rules:\n- \"navigate to [element]\" = scroll to that element (use 'scroll' tool)\n- \"navigate to [route]\" = route navigation (use 'navigate' tool with route path)\n- If marker matches and is in inViewMarkerIds + elementType='a' → use 'click'\n- If no marker matches → use 'navigate' with route path\n- For affirmative responses after discussing a marker, interact with that marker using the appropriate tool.\n\nScroll-then-act rule (CRITICAL):\n- When the user wants to INTERACT with an element (click, press, increment, submit, fill, etc.) and the target marker is NOT in inViewMarkerIds: you MUST emit TWO tool calls in order—(1) 'scroll' to that marker, (2) the action ('click', 'fillInput', etc.). Emitting only 'scroll' is WRONG; the user asked for an action, so you must call both scroll and the action.\n- Prefer the specific interactive element for the action (e.g. the button click_demo_increment for \"increment counter\"), not a parent section (e.g. slide_click). Emit one scroll to the exact target element, then the action.\n- If the user only wants to see or navigate to an element (no click/fill), use a single 'scroll' to that element.\n\nRelative scrolling rules (for \"scroll to next\" or \"scroll to previous\"):\n- Markers are listed in document order (top to bottom) in the markers array\n- CRITICAL: Always skip markers that are in inViewMarkerIds - only scroll to markers NOT currently in view\n- For \"scroll to next\": Find the first marker in the markers array that comes after any currently visible markers and is NOT in inViewMarkerIds\n- For \"scroll to previous\": Find the first marker in the markers array that comes before any currently visible markers and is NOT in inViewMarkerIds\n- This prevents getting stuck on sections already in view\n\nMultiple actions rules:\n- When user says \"all\" (e.g., \"highlight all badges\", \"click all buttons\"), generate MULTIPLE tool calls - one for each matching marker\n- When user specifies a count (e.g., \"click 10 times\", \"increment counter 5 times\"), generate MULTIPLE tool calls - repeat the action the specified number of times\n- CRITICAL: You can and should call the same tool multiple times in a single response when the user requests multiple actions\n- For \"highlight all [type]\" or \"click all [type]\": Find all markers matching the type and generate one tool call per marker\n- For \"[action] [count] times\": Generate [count] identical tool calls`;\n\n\tconst conversationMessages: Array<{ role: string; content: string }> = [\n\t\t{ role: \"system\", content: systemPrompt },\n\t];\n\n\tif (request.messages && request.messages.length > 0) {\n\t\tconst recentMessages = request.messages.slice(-10);\n\t\tfor (const msg of recentMessages) {\n\t\t\tconversationMessages.push({\n\t\t\t\trole: msg.role,\n\t\t\t\tcontent: msg.content,\n\t\t\t});\n\t\t}\n\t}\n\n\tconversationMessages.push({ role: \"user\", content: request.input });\n\n\tconst response = await llmWithTools.invoke(conversationMessages);\n\n\tconst instructions: Instruction[] = [];\n\tlet reply = \"\";\n\n\tif (response.content) {\n\t\tif (typeof response.content === \"string\") {\n\t\t\treply = response.content;\n\t\t} else if (Array.isArray(response.content)) {\n\t\t\treply = response.content\n\t\t\t\t.map((c) => {\n\t\t\t\t\tif (typeof c === \"string\") return c;\n\t\t\t\t\tif (c && typeof c === \"object\" && \"text\" in c) return c.text;\n\t\t\t\t\treturn \"\";\n\t\t\t\t})\n\t\t\t\t.join(\"\");\n\t\t} else {\n\t\t\treply = String(response.content);\n\t\t}\n\t}\n\n\t// LangChain stores tool calls in response.tool_calls or response.additional_kwargs.tool_calls\n\tconst toolCallObjects: Array<{\n\t\tname?: string;\n\t\targs?: Record<string, unknown>;\n\t\tfunction?: {\n\t\t\tname?: string;\n\t\t\targuments?: string | Record<string, unknown>;\n\t\t};\n\t}> = hasToolCalls(response)\n\t\t? response.tool_calls || response.additional_kwargs?.tool_calls || []\n\t\t: [];\n\n\tfor (const toolCall of toolCallObjects) {\n\t\tconst toolName = toolCall.name || toolCall.function?.name;\n\t\tconst toolArgs =\n\t\t\ttoolCall.args ||\n\t\t\t(toolCall.function?.arguments\n\t\t\t\t? typeof toolCall.function.arguments === \"string\"\n\t\t\t\t\t? JSON.parse(toolCall.function.arguments)\n\t\t\t\t\t: toolCall.function.arguments\n\t\t\t\t: {});\n\n\t\tif (toolName) {\n\t\t\tconst converted = toolRegistry.convertToolCall({\n\t\t\t\ttype: \"function\",\n\t\t\t\tfunction: {\n\t\t\t\t\tname: toolName,\n\t\t\t\t\targuments: JSON.stringify(toolArgs),\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tif (converted) {\n\t\t\t\tinstructions.push(converted);\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await toolRegistry.executeToolCall(\n\t\t\t\t\t\ttoolName,\n\t\t\t\t\t\ttoolArgs,\n\t\t\t\t\t\trequest.context,\n\t\t\t\t\t);\n\t\t\t\t\tif (result && typeof result === \"object\" && \"action\" in result) {\n\t\t\t\t\t\tinstructions.push(result as Instruction);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(`Error executing tool ${toolName}:`, error);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\treply: reply || \"I'm here to help!\",\n\t\tinstructions: instructions.length > 0 ? instructions : undefined,\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import { ToolRegistry } from "./tool-registry.mjs";
2
+ import { ServerConfig } from "./types.mjs";
3
+ import { FastifyInstance, FastifyPluginOptions } from "fastify";
4
+
5
+ //#region src/fastify.d.ts
6
+ interface FastifyAi11yOptions extends FastifyPluginOptions {
7
+ config: ServerConfig;
8
+ toolRegistry?: ToolRegistry;
9
+ }
10
+ /**
11
+ * Fastify plugin for ai11y server
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import Fastify from 'fastify';
16
+ * import { ai11yPlugin } from '@ai11y/agent/fastify';
17
+ *
18
+ * const fastify = Fastify();
19
+ *
20
+ * await fastify.register(ai11yPlugin, {
21
+ * config: {
22
+ * provider: 'openai',
23
+ * apiKey: process.env.OPENAI_API_KEY!,
24
+ * model: 'gpt-4o-mini'
25
+ * }
26
+ * });
27
+ *
28
+ * await fastify.listen({ port: 3000 });
29
+ * ```
30
+ */
31
+ declare function ai11yPlugin(fastify: FastifyInstance, options: FastifyAi11yOptions): Promise<void>;
32
+ /**
33
+ * Helper function to create a tool registry with custom tools.
34
+ * Returns a ToolRegistry instance that supports method chaining.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const registry = createToolRegistry()
39
+ * .register({
40
+ * name: "custom_action",
41
+ * description: "Perform a custom action",
42
+ * parameters: {
43
+ * type: "object",
44
+ * properties: {
45
+ * param: { type: "string", description: "A parameter" }
46
+ * },
47
+ * required: ["param"]
48
+ * }
49
+ * }, async (args) => {
50
+ * // Execute custom logic
51
+ * return { success: true };
52
+ * });
53
+ * ```
54
+ */
55
+ declare function createToolRegistry(): ToolRegistry;
56
+ //#endregion
57
+ export { type ServerConfig, ai11yPlugin, createToolRegistry };
58
+ //# sourceMappingURL=fastify.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.d.mts","names":[],"sources":["../src/fastify.ts"],"sourcesContent":[],"mappings":";;;;;UAcU,mBAAA,SAA4B;UAC7B;EADC,YAAA,CAAA,EAEM,YAFc;;;;;AA0B9B;;;;;AAkEA;;;;;;;;;;;;;iBAlEsB,WAAA,UACZ,0BACA,sBAAmB;;;;;;;;;;;;;;;;;;;;;;;;iBAgEb,kBAAA,CAAA,GAAsB"}
@@ -0,0 +1,82 @@
1
+ import { createDefaultToolRegistry } from "./tool-registry.mjs";
2
+ import { runAgent } from "./agent.mjs";
3
+
4
+ //#region src/fastify.ts
5
+ /**
6
+ * Fastify plugin for ai11y server
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import Fastify from 'fastify';
11
+ * import { ai11yPlugin } from '@ai11y/agent/fastify';
12
+ *
13
+ * const fastify = Fastify();
14
+ *
15
+ * await fastify.register(ai11yPlugin, {
16
+ * config: {
17
+ * provider: 'openai',
18
+ * apiKey: process.env.OPENAI_API_KEY!,
19
+ * model: 'gpt-4o-mini'
20
+ * }
21
+ * });
22
+ *
23
+ * await fastify.listen({ port: 3000 });
24
+ * ```
25
+ */
26
+ async function ai11yPlugin(fastify, options) {
27
+ const { config, toolRegistry = createDefaultToolRegistry() } = options;
28
+ if (!config.apiKey) throw new Error("API key is required");
29
+ /**
30
+ * POST /ai11y/agent
31
+ * Main endpoint for agent requests
32
+ */
33
+ fastify.post("/ai11y/agent", async (request, reply) => {
34
+ try {
35
+ const response = await runAgent(request.body, config, toolRegistry);
36
+ return reply.send(response);
37
+ } catch (error) {
38
+ fastify.log.error(error, "Error processing agent request");
39
+ return reply.status(500).send({
40
+ error: "Failed to process agent request",
41
+ message: error instanceof Error ? error.message : "Unknown error"
42
+ });
43
+ }
44
+ });
45
+ /**
46
+ * GET /ai11y/health
47
+ * Health check endpoint
48
+ */
49
+ fastify.get("/ai11y/health", async (_request, reply) => {
50
+ return reply.send({ status: "ok" });
51
+ });
52
+ }
53
+ /**
54
+ * Helper function to create a tool registry with custom tools.
55
+ * Returns a ToolRegistry instance that supports method chaining.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const registry = createToolRegistry()
60
+ * .register({
61
+ * name: "custom_action",
62
+ * description: "Perform a custom action",
63
+ * parameters: {
64
+ * type: "object",
65
+ * properties: {
66
+ * param: { type: "string", description: "A parameter" }
67
+ * },
68
+ * required: ["param"]
69
+ * }
70
+ * }, async (args) => {
71
+ * // Execute custom logic
72
+ * return { success: true };
73
+ * });
74
+ * ```
75
+ */
76
+ function createToolRegistry() {
77
+ return createDefaultToolRegistry();
78
+ }
79
+
80
+ //#endregion
81
+ export { ai11yPlugin, createToolRegistry };
82
+ //# sourceMappingURL=fastify.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.mjs","names":[],"sources":["../src/fastify.ts"],"sourcesContent":["import type { AgentRequest } from \"@ai11y/core\";\nimport type {\n\tFastifyInstance,\n\tFastifyPluginOptions,\n\tFastifyReply,\n\tFastifyRequest,\n} from \"fastify\";\nimport { runAgent } from \"./agent.js\";\nimport {\n\tcreateDefaultToolRegistry,\n\ttype ToolRegistry,\n} from \"./tool-registry.js\";\nimport type { ServerConfig } from \"./types.js\";\n\ninterface FastifyAi11yOptions extends FastifyPluginOptions {\n\tconfig: ServerConfig;\n\ttoolRegistry?: ToolRegistry;\n}\n\n/**\n * Fastify plugin for ai11y server\n *\n * @example\n * ```ts\n * import Fastify from 'fastify';\n * import { ai11yPlugin } from '@ai11y/agent/fastify';\n *\n * const fastify = Fastify();\n *\n * await fastify.register(ai11yPlugin, {\n * config: {\n * provider: 'openai',\n * apiKey: process.env.OPENAI_API_KEY!,\n * model: 'gpt-4o-mini'\n * }\n * });\n *\n * await fastify.listen({ port: 3000 });\n * ```\n */\nexport async function ai11yPlugin(\n\tfastify: FastifyInstance,\n\toptions: FastifyAi11yOptions,\n) {\n\tconst { config, toolRegistry = createDefaultToolRegistry() } = options;\n\n\t// Validate config\n\tif (!config.apiKey) {\n\t\tthrow new Error(\"API key is required\");\n\t}\n\n\t/**\n\t * POST /ai11y/agent\n\t * Main endpoint for agent requests\n\t */\n\tfastify.post<{ Body: AgentRequest }>(\n\t\t\"/ai11y/agent\",\n\t\tasync (\n\t\t\trequest: FastifyRequest<{ Body: AgentRequest }>,\n\t\t\treply: FastifyReply,\n\t\t) => {\n\t\t\ttry {\n\t\t\t\tconst response = await runAgent(request.body, config, toolRegistry);\n\t\t\t\treturn reply.send(response);\n\t\t\t} catch (error) {\n\t\t\t\tfastify.log.error(error, \"Error processing agent request\");\n\t\t\t\treturn reply.status(500).send({\n\t\t\t\t\terror: \"Failed to process agent request\",\n\t\t\t\t\tmessage: error instanceof Error ? error.message : \"Unknown error\",\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t);\n\n\t/**\n\t * GET /ai11y/health\n\t * Health check endpoint\n\t */\n\tfastify.get(\"/ai11y/health\", async (_request, reply) => {\n\t\treturn reply.send({ status: \"ok\" });\n\t});\n}\n\n/**\n * Helper function to create a tool registry with custom tools.\n * Returns a ToolRegistry instance that supports method chaining.\n *\n * @example\n * ```ts\n * const registry = createToolRegistry()\n * .register({\n * name: \"custom_action\",\n * description: \"Perform a custom action\",\n * parameters: {\n * type: \"object\",\n * properties: {\n * param: { type: \"string\", description: \"A parameter\" }\n * },\n * required: [\"param\"]\n * }\n * }, async (args) => {\n * // Execute custom logic\n * return { success: true };\n * });\n * ```\n */\nexport function createToolRegistry(): ToolRegistry {\n\treturn createDefaultToolRegistry();\n}\n\nexport type { ServerConfig } from \"./types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,eAAsB,YACrB,SACA,SACC;CACD,MAAM,EAAE,QAAQ,eAAe,2BAA2B,KAAK;AAG/D,KAAI,CAAC,OAAO,OACX,OAAM,IAAI,MAAM,sBAAsB;;;;;AAOvC,SAAQ,KACP,gBACA,OACC,SACA,UACI;AACJ,MAAI;GACH,MAAM,WAAW,MAAM,SAAS,QAAQ,MAAM,QAAQ,aAAa;AACnE,UAAO,MAAM,KAAK,SAAS;WACnB,OAAO;AACf,WAAQ,IAAI,MAAM,OAAO,iCAAiC;AAC1D,UAAO,MAAM,OAAO,IAAI,CAAC,KAAK;IAC7B,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;IAClD,CAAC;;GAGJ;;;;;AAMD,SAAQ,IAAI,iBAAiB,OAAO,UAAU,UAAU;AACvD,SAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,CAAC;GAClC;;;;;;;;;;;;;;;;;;;;;;;;;AA0BH,SAAgB,qBAAmC;AAClD,QAAO,2BAA2B"}
@@ -0,0 +1,5 @@
1
+ import { ToolRegistry, createDefaultToolRegistry } from "./tool-registry.mjs";
2
+ import { ServerConfig } from "./types.mjs";
3
+ import { runAgent } from "./agent.mjs";
4
+ import { createLLM } from "./llm-provider.mjs";
5
+ export { type ServerConfig, ToolRegistry, createDefaultToolRegistry, createLLM, runAgent };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { createLLM } from "./llm-provider.mjs";
2
+ import { ToolRegistry, createDefaultToolRegistry } from "./tool-registry.mjs";
3
+ import { runAgent } from "./agent.mjs";
4
+
5
+ export { ToolRegistry, createDefaultToolRegistry, createLLM, runAgent };
@@ -0,0 +1,12 @@
1
+ import { ServerConfig } from "./types.mjs";
2
+ import { BaseChatModel } from "@langchain/core/language_models/chat_models";
3
+
4
+ //#region src/llm-provider.d.ts
5
+
6
+ /**
7
+ * Create a LangChain LLM instance for OpenAI
8
+ */
9
+ declare function createLLM(config: ServerConfig): Promise<BaseChatModel>;
10
+ //#endregion
11
+ export { createLLM };
12
+ //# sourceMappingURL=llm-provider.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider.d.mts","names":[],"sources":["../src/llm-provider.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBAWsB,SAAA,SAAkB,eAAe,QAAQ"}