@falai/agent 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +42 -34
  2. package/dist/cjs/src/core/Agent.d.ts +19 -5
  3. package/dist/cjs/src/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/src/core/Agent.js +79 -35
  5. package/dist/cjs/src/core/Agent.js.map +1 -1
  6. package/dist/cjs/src/core/ResponseModal.d.ts +9 -3
  7. package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -1
  8. package/dist/cjs/src/core/ResponseModal.js +121 -55
  9. package/dist/cjs/src/core/ResponseModal.js.map +1 -1
  10. package/dist/cjs/src/core/ResponsePipeline.d.ts +8 -4
  11. package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -1
  12. package/dist/cjs/src/core/ResponsePipeline.js +47 -19
  13. package/dist/cjs/src/core/ResponsePipeline.js.map +1 -1
  14. package/dist/cjs/src/core/Route.d.ts +12 -5
  15. package/dist/cjs/src/core/Route.d.ts.map +1 -1
  16. package/dist/cjs/src/core/Route.js +26 -5
  17. package/dist/cjs/src/core/Route.js.map +1 -1
  18. package/dist/cjs/src/core/RoutingEngine.d.ts +5 -0
  19. package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -1
  20. package/dist/cjs/src/core/RoutingEngine.js +37 -25
  21. package/dist/cjs/src/core/RoutingEngine.js.map +1 -1
  22. package/dist/cjs/src/core/SessionManager.d.ts +9 -1
  23. package/dist/cjs/src/core/SessionManager.d.ts.map +1 -1
  24. package/dist/cjs/src/core/SessionManager.js +27 -5
  25. package/dist/cjs/src/core/SessionManager.js.map +1 -1
  26. package/dist/cjs/src/core/Step.d.ts +60 -7
  27. package/dist/cjs/src/core/Step.d.ts.map +1 -1
  28. package/dist/cjs/src/core/Step.js +151 -4
  29. package/dist/cjs/src/core/Step.js.map +1 -1
  30. package/dist/cjs/src/core/ToolManager.d.ts +234 -0
  31. package/dist/cjs/src/core/ToolManager.d.ts.map +1 -0
  32. package/dist/cjs/src/core/ToolManager.js +1117 -0
  33. package/dist/cjs/src/core/ToolManager.js.map +1 -0
  34. package/dist/cjs/src/index.d.ts +2 -3
  35. package/dist/cjs/src/index.d.ts.map +1 -1
  36. package/dist/cjs/src/index.js +5 -3
  37. package/dist/cjs/src/index.js.map +1 -1
  38. package/dist/cjs/src/types/agent.d.ts +1 -1
  39. package/dist/cjs/src/types/agent.d.ts.map +1 -1
  40. package/dist/cjs/src/types/index.d.ts +3 -2
  41. package/dist/cjs/src/types/index.d.ts.map +1 -1
  42. package/dist/cjs/src/types/index.js +3 -1
  43. package/dist/cjs/src/types/index.js.map +1 -1
  44. package/dist/cjs/src/types/route.d.ts +6 -4
  45. package/dist/cjs/src/types/route.d.ts.map +1 -1
  46. package/dist/cjs/src/types/tool.d.ts +84 -14
  47. package/dist/cjs/src/types/tool.d.ts.map +1 -1
  48. package/dist/cjs/src/types/tool.js +13 -0
  49. package/dist/cjs/src/types/tool.js.map +1 -1
  50. package/dist/src/core/Agent.d.ts +19 -5
  51. package/dist/src/core/Agent.d.ts.map +1 -1
  52. package/dist/src/core/Agent.js +79 -35
  53. package/dist/src/core/Agent.js.map +1 -1
  54. package/dist/src/core/ResponseModal.d.ts +9 -3
  55. package/dist/src/core/ResponseModal.d.ts.map +1 -1
  56. package/dist/src/core/ResponseModal.js +121 -55
  57. package/dist/src/core/ResponseModal.js.map +1 -1
  58. package/dist/src/core/ResponsePipeline.d.ts +8 -4
  59. package/dist/src/core/ResponsePipeline.d.ts.map +1 -1
  60. package/dist/src/core/ResponsePipeline.js +47 -19
  61. package/dist/src/core/ResponsePipeline.js.map +1 -1
  62. package/dist/src/core/Route.d.ts +12 -5
  63. package/dist/src/core/Route.d.ts.map +1 -1
  64. package/dist/src/core/Route.js +26 -5
  65. package/dist/src/core/Route.js.map +1 -1
  66. package/dist/src/core/RoutingEngine.d.ts +5 -0
  67. package/dist/src/core/RoutingEngine.d.ts.map +1 -1
  68. package/dist/src/core/RoutingEngine.js +37 -25
  69. package/dist/src/core/RoutingEngine.js.map +1 -1
  70. package/dist/src/core/SessionManager.d.ts +9 -1
  71. package/dist/src/core/SessionManager.d.ts.map +1 -1
  72. package/dist/src/core/SessionManager.js +27 -5
  73. package/dist/src/core/SessionManager.js.map +1 -1
  74. package/dist/src/core/Step.d.ts +60 -7
  75. package/dist/src/core/Step.d.ts.map +1 -1
  76. package/dist/src/core/Step.js +151 -4
  77. package/dist/src/core/Step.js.map +1 -1
  78. package/dist/src/core/ToolManager.d.ts +234 -0
  79. package/dist/src/core/ToolManager.d.ts.map +1 -0
  80. package/dist/src/core/ToolManager.js +1111 -0
  81. package/dist/src/core/ToolManager.js.map +1 -0
  82. package/dist/src/index.d.ts +2 -3
  83. package/dist/src/index.d.ts.map +1 -1
  84. package/dist/src/index.js +1 -1
  85. package/dist/src/index.js.map +1 -1
  86. package/dist/src/types/agent.d.ts +1 -1
  87. package/dist/src/types/agent.d.ts.map +1 -1
  88. package/dist/src/types/index.d.ts +3 -2
  89. package/dist/src/types/index.d.ts.map +1 -1
  90. package/dist/src/types/index.js +1 -0
  91. package/dist/src/types/index.js.map +1 -1
  92. package/dist/src/types/route.d.ts +6 -4
  93. package/dist/src/types/route.d.ts.map +1 -1
  94. package/dist/src/types/tool.d.ts +84 -14
  95. package/dist/src/types/tool.d.ts.map +1 -1
  96. package/dist/src/types/tool.js +12 -1
  97. package/dist/src/types/tool.js.map +1 -1
  98. package/docs/CONTRIBUTING.md +40 -0
  99. package/docs/README.md +12 -5
  100. package/docs/api/README.md +75 -45
  101. package/docs/api/overview.md +74 -32
  102. package/docs/core/agent/session-management.md +152 -5
  103. package/docs/core/ai-integration/response-processing.md +115 -4
  104. package/docs/core/conversation-flows/routes.md +130 -0
  105. package/docs/core/error-handling.md +638 -0
  106. package/docs/core/tools/tool-definition.md +684 -60
  107. package/docs/core/tools/tool-scoping.md +244 -53
  108. package/docs/guides/error-handling-patterns.md +578 -0
  109. package/docs/guides/getting-started/README.md +139 -28
  110. package/examples/advanced-patterns/knowledge-based-agent.ts +6 -6
  111. package/examples/advanced-patterns/persistent-onboarding.ts +30 -43
  112. package/examples/ai-providers/anthropic-integration.ts +9 -5
  113. package/examples/ai-providers/openai-integration.ts +11 -7
  114. package/examples/core-concepts/basic-agent.ts +106 -67
  115. package/examples/core-concepts/schema-driven-extraction.ts +10 -7
  116. package/examples/core-concepts/session-management.ts +71 -18
  117. package/examples/integrations/healthcare-integration.ts +15 -29
  118. package/examples/integrations/server-session-management.ts +3 -3
  119. package/examples/persistence/memory-sessions.ts +3 -3
  120. package/examples/tools/basic-tools.ts +293 -89
  121. package/examples/tools/data-enrichment-tools.ts +185 -75
  122. package/package.json +1 -1
  123. package/src/core/Agent.ts +98 -44
  124. package/src/core/ResponseModal.ts +148 -72
  125. package/src/core/ResponsePipeline.ts +82 -56
  126. package/src/core/Route.ts +39 -12
  127. package/src/core/RoutingEngine.ts +46 -42
  128. package/src/core/SessionManager.ts +39 -7
  129. package/src/core/Step.ts +198 -20
  130. package/src/core/ToolManager.ts +1394 -0
  131. package/src/index.ts +8 -3
  132. package/src/types/agent.ts +1 -1
  133. package/src/types/index.ts +13 -2
  134. package/src/types/route.ts +6 -4
  135. package/src/types/tool.ts +116 -25
  136. package/dist/cjs/src/core/ToolExecutor.d.ts +0 -45
  137. package/dist/cjs/src/core/ToolExecutor.d.ts.map +0 -1
  138. package/dist/cjs/src/core/ToolExecutor.js +0 -84
  139. package/dist/cjs/src/core/ToolExecutor.js.map +0 -1
  140. package/dist/src/core/ToolExecutor.d.ts +0 -45
  141. package/dist/src/core/ToolExecutor.d.ts.map +0 -1
  142. package/dist/src/core/ToolExecutor.js +0 -80
  143. package/dist/src/core/ToolExecutor.js.map +0 -1
  144. package/docs/core/tools/tool-execution.md +0 -815
  145. package/src/core/ToolExecutor.ts +0 -126
@@ -1,19 +1,22 @@
1
1
  /**
2
- * Example: Modifying Collected data with Tools and Hooks
2
+ * Example: Modifying Collected data with Tools and Hooks using ToolManager
3
3
  *
4
4
  * This demonstrates:
5
5
  * 1. Schema-first data extraction with JSON Schema
6
- * 2. Tools that modify collected data (validation, enrichment)
6
+ * 2. Simplified tools that modify collected data (validation, enrichment)
7
7
  * 3. Lifecycle hooks for data validation and business logic
8
8
  * 4. Data-driven step flows with code-based logic (skipIf, requires)
9
9
  * 5. Three-phase pipeline: PREPARATION → ROUTING → RESPONSE
10
+ * 6. NEW: ToolManager pattern helpers for common data operations
10
11
  */
11
12
 
12
13
  import {
13
14
  Agent,
14
15
  END_ROUTE,
15
16
  OpenAIProvider,
16
- type Tool,
17
+ Tool,
18
+ ToolContext,
19
+ ValidationError,
17
20
  } from "../../src";
18
21
 
19
22
  // ==============================================================================
@@ -44,60 +47,49 @@ interface FlightData {
44
47
  // TOOLS: Data Enrichment (PREPARATION Phase)
45
48
  // ==============================================================================
46
49
 
47
- // Tool 1: Convert city names to airport codes
48
- const enrichDestinationTool: Tool<FlightBookingContext, FlightData, [], void> =
49
- {
50
- id: "enrich_destination",
51
- name: "Destination Code Lookup",
52
- description: "Convert city names to IATA airport codes",
53
- parameters: {
54
- type: "object",
55
- properties: {},
56
- },
57
- handler: ({ data }: { data?: Partial<FlightData> }) => {
58
- const destination = (data as Partial<FlightData>)?.destination;
59
-
60
- if (!destination) {
61
- return { data: undefined };
62
- }
63
-
64
- // Simulate airport code lookup
65
- const airportCodes: Record<string, string> = {
66
- Paris: "CDG",
67
- London: "LHR",
68
- "New York": "JFK",
69
- Tokyo: "NRT",
70
- "Los Angeles": "LAX",
71
- };
72
-
73
- const destinationCode = airportCodes[destination];
74
-
75
- console.log(`[Tool] Enriched: ${destination} → ${destinationCode}`);
76
-
77
- return {
78
- data: undefined,
79
- dataUpdate: {
80
- destinationCode,
81
- } as Partial<FlightData>,
82
- };
83
- },
84
- };
50
+ // Tool 1: Convert city names to airport codes using ToolManager pattern helper
51
+ const enrichDestinationConfig = {
52
+ id: "enrich_destination",
53
+ name: "Destination Code Lookup",
54
+ description: "Convert city names to IATA airport codes",
55
+ fields: ["destination"] as const,
56
+ enricher: async (context: FlightBookingContext, data: Pick<FlightData, "destination">) => {
57
+ const destination = data.destination;
58
+
59
+ if (!destination) {
60
+ return {};
61
+ }
62
+
63
+ // Simulate airport code lookup
64
+ const airportCodes: Record<string, string> = {
65
+ Paris: "CDG",
66
+ London: "LHR",
67
+ "New York": "JFK",
68
+ Tokyo: "NRT",
69
+ "Los Angeles": "LAX",
70
+ };
85
71
 
86
- // Tool 2: Parse and validate dates
87
- const validateDateTool: Tool<FlightBookingContext, FlightData, [], void> = {
72
+ const destinationCode = airportCodes[destination];
73
+
74
+ console.log(`[Tool] Enriched: ${destination} → ${destinationCode}`);
75
+
76
+ return {
77
+ destinationCode,
78
+ } as Partial<FlightData>;
79
+ },
80
+ };
81
+
82
+ // Tool 2: Parse and validate dates using ToolManager pattern helper
83
+ const validateDateConfig = {
88
84
  id: "validate_date",
89
85
  name: "Date Parser & Validator",
90
- description:
91
- "Parse relative dates (today, tomorrow) to ISO format and validate",
92
- parameters: {
93
- type: "object",
94
- properties: {},
95
- },
96
- handler: ({ data }: { data?: Partial<FlightData> }) => {
97
- const departureDate = (data as Partial<FlightData>)?.departureDate;
86
+ description: "Parse relative dates (today, tomorrow) to ISO format and validate",
87
+ fields: ["departureDate"] as const,
88
+ enricher: async (context: FlightBookingContext, data: Pick<FlightData, "departureDate">) => {
89
+ const departureDate = data.departureDate;
98
90
 
99
91
  if (!departureDate) {
100
- return { data: undefined };
92
+ return {};
101
93
  }
102
94
 
103
95
  let parsedDate: string;
@@ -130,16 +122,13 @@ const validateDateTool: Tool<FlightBookingContext, FlightData, [], void> = {
130
122
  console.log(`[Tool] Parsed date: ${departureDate} → ${parsedDate}`);
131
123
 
132
124
  return {
133
- data: undefined,
134
- dataUpdate: {
135
- departureDateParsed: parsedDate,
136
- } as Partial<FlightData>,
137
- };
125
+ departureDateParsed: parsedDate,
126
+ } as Partial<FlightData>;
138
127
  },
139
128
  };
140
129
 
141
- // Tool 3: Search for flights (triggered by flag)
142
- const searchFlightsTool: Tool<FlightBookingContext, FlightData, [], void> = {
130
+ // Tool 3: Search for flights (triggered by flag) using unified Tool interface
131
+ const searchFlightsTool: Tool<FlightBookingContext, FlightData> = {
143
132
  id: "search_flights",
144
133
  name: "Flight Availability Search",
145
134
  description: "Search for available flights based on collected data",
@@ -147,12 +136,12 @@ const searchFlightsTool: Tool<FlightBookingContext, FlightData, [], void> = {
147
136
  type: "object",
148
137
  properties: {},
149
138
  },
150
- handler: ({ data }: { data?: Partial<FlightData> }) => {
151
- const flightData = data as Partial<FlightData>;
139
+ handler: async (toolContext, args) => {
140
+ const flightData = toolContext.data;
152
141
 
153
142
  if (!flightData?.destinationCode || !flightData?.departureDateParsed) {
154
143
  console.log("[Tool] Cannot search flights: missing required data");
155
- return { data: undefined };
144
+ return { data: "Missing required data for flight search" };
156
145
  }
157
146
 
158
147
  // Simulate flight search API call
@@ -176,19 +165,19 @@ const searchFlightsTool: Tool<FlightBookingContext, FlightData, [], void> = {
176
165
  );
177
166
 
178
167
  return {
179
- data: undefined,
168
+ data: `Found ${flights.length} available flights`,
180
169
  contextUpdate: {
181
170
  availableFlights: flights,
182
171
  },
183
172
  dataUpdate: {
184
- shouldSearchFlights: false, // Clear the flag to prevent re-execution
185
- } as Partial<FlightData>,
173
+ shouldSearchFlights: false,
174
+ },
186
175
  };
187
176
  },
188
177
  };
189
178
 
190
- // Tool 4: Book the flight
191
- const bookFlightTool: Tool<FlightBookingContext, FlightData, [], void> = {
179
+ // Tool 4: Book the flight using unified Tool interface
180
+ const bookFlightTool: Tool<FlightBookingContext, FlightData> = {
192
181
  id: "book_flight",
193
182
  name: "Flight Booking Processor",
194
183
  description: "Finalize the flight booking",
@@ -196,11 +185,11 @@ const bookFlightTool: Tool<FlightBookingContext, FlightData, [], void> = {
196
185
  type: "object",
197
186
  properties: {},
198
187
  },
199
- handler: ({ data }: { data?: Partial<FlightData> }) => {
200
- const flightData = data as Partial<FlightData>;
188
+ handler: async (toolContext, args) => {
189
+ const flightData = toolContext.data;
201
190
  console.log("[Tool] Booking flight with data:", flightData);
202
191
  // Simulate booking API call
203
- return { data: undefined };
192
+ return { data: "Flight booking confirmed!" };
204
193
  },
205
194
  };
206
195
 
@@ -296,7 +285,7 @@ const agent = new Agent<FlightBookingContext, FlightData>({
296
285
  description: "I help you find and book flights",
297
286
  provider: new OpenAIProvider({
298
287
  apiKey: process.env.OPENAI_API_KEY || "your-api-key-here",
299
- model: "gpt-5o-mini",
288
+ model: "gpt-4o-mini",
300
289
  }),
301
290
  context: {},
302
291
  // NEW: Agent-level schema
@@ -306,6 +295,117 @@ const agent = new Agent<FlightBookingContext, FlightData>({
306
295
  },
307
296
  });
308
297
 
298
+ // Pattern 1: Using the enrichDestinationConfig with inline tool creation
299
+ const enrichDestinationTool = {
300
+ id: "enrich_destination",
301
+ name: "Destination Code Lookup",
302
+ description: "Convert city names to IATA airport codes",
303
+ parameters: {
304
+ type: "object",
305
+ properties: {},
306
+ },
307
+ handler: async (context: ToolContext<FlightBookingContext, FlightData>, args?: Record<string, unknown>) => {
308
+ const destination = context.data.destination;
309
+
310
+ if (!destination) {
311
+ return { data: "No destination to enrich" };
312
+ }
313
+
314
+ // Use the original enricher function from the config
315
+ const result = await enrichDestinationConfig.enricher(context.context, { destination });
316
+
317
+ console.log(`[Tool] Enriched: ${destination} → ${result.destinationCode}`);
318
+
319
+ return {
320
+ data: `Destination code: ${result.destinationCode}`,
321
+ dataUpdate: result,
322
+ };
323
+ },
324
+ };
325
+
326
+ // Pattern 2: Using the validateDateConfig with typed tool interface
327
+ const validateDateTool: Tool<FlightBookingContext, FlightData> = {
328
+ id: "validate_date",
329
+ name: "Date Parser & Validator",
330
+ description: "Parse relative dates (today, tomorrow) to ISO format and validate",
331
+ parameters: {
332
+ type: "object",
333
+ properties: {},
334
+ },
335
+ handler: async (context, args) => {
336
+ const departureDate = context.data.departureDate;
337
+
338
+ if (!departureDate) {
339
+ return { data: "No departure date to validate" };
340
+ }
341
+
342
+ // Use the original enricher function from the config
343
+ const result = await validateDateConfig.enricher(context.context, { departureDate });
344
+
345
+ console.log(`[Tool] Parsed date: ${departureDate} → ${result.departureDateParsed}`);
346
+
347
+ return {
348
+ data: `Parsed date: ${result.departureDateParsed}`,
349
+ dataUpdate: result,
350
+ };
351
+ },
352
+ };
353
+
354
+ // Demonstrate different registration methods for data enrichment tools
355
+
356
+ // Method 1: Register enrichment tools for ID-based reference
357
+ agent.tool.register(enrichDestinationTool);
358
+ agent.tool.register(validateDateTool);
359
+
360
+ // Method 2: Use registerMany for multiple tools
361
+ agent.tool.registerMany([searchFlightsTool, bookFlightTool]);
362
+
363
+ // Method 3: Create specialized data enrichment tools using the helper
364
+ const passengerEnrichmentTool = agent.tool.createDataEnrichment({
365
+ id: "enrich_passenger_info",
366
+ fields: ["passengers"] as const,
367
+ enricher: async (context, data) => {
368
+ // Add passenger type classification - return fields that exist in FlightData
369
+ const cabinClass = data.passengers === 1 ? "business" : data.passengers <= 4 ? "economy" : "economy";
370
+ return {
371
+ cabinClass, // This matches the cabinClass field in FlightData
372
+ };
373
+ },
374
+ });
375
+
376
+ // Method 4: Create validation tool using the helper
377
+ const flightDataValidator = agent.tool.createValidation({
378
+ id: "validate_flight_data",
379
+ fields: ["destination", "departureDate", "passengers"] as const,
380
+ validator: async (context, data) => {
381
+ const errors: ValidationError[] = [];
382
+ if (!data.destination) errors.push({
383
+ field: "destination",
384
+ value: data.destination,
385
+ message: "Destination is required",
386
+ schemaPath: "destination"
387
+ });
388
+ if (!data.departureDate) errors.push({
389
+ field: "departureDate",
390
+ value: data.departureDate,
391
+ message: "Departure date is required",
392
+ schemaPath: "departureDate"
393
+ });
394
+ if (!data.passengers || data.passengers < 1) errors.push({
395
+ field: "passengers",
396
+ value: data.passengers,
397
+ message: "At least 1 passenger required",
398
+ schemaPath: "passengers"
399
+ });
400
+
401
+ return {
402
+ valid: errors.length === 0,
403
+ errors,
404
+ warnings: [],
405
+ };
406
+ },
407
+ });
408
+
309
409
  // Define route with data extraction
310
410
  const bookingRoute = agent.createRoute({
311
411
  title: "Book Flight",
@@ -320,6 +420,13 @@ const bookingRoute = agent.createRoute({
320
420
  optionalFields: ["destinationCode", "departureDateParsed", "cabinClass", "shouldSearchFlights"],
321
421
  });
322
422
 
423
+ // Method 5: Add route-scoped tools (only available in this route)
424
+ bookingRoute.addTool({
425
+ id: "route_specific_tool",
426
+ description: "Tool only available in booking route",
427
+ handler: () => "This tool is scoped to the booking route only",
428
+ });
429
+
323
430
  // Step 1: Collect destination
324
431
  const collectDestination = bookingRoute.initialStep.nextStep({
325
432
  prompt: "Ask where they want to fly",
@@ -329,7 +436,7 @@ const collectDestination = bookingRoute.initialStep.nextStep({
329
436
 
330
437
  // Step 2: Enrich destination (tool execution)
331
438
  const enrichDestination = collectDestination.nextStep({
332
- tools: [enrichDestinationTool],
439
+ tools: ["enrich_destination"], // Reference by ID
333
440
  requires: ["destination"],
334
441
  });
335
442
 
@@ -342,7 +449,7 @@ const collectDate = enrichDestination.nextStep({
342
449
 
343
450
  // Step 4: Validate/parse date (tool execution)
344
451
  const validateDate = collectDate.nextStep({
345
- tools: [validateDateTool],
452
+ tools: ["validate_date"], // Reference by ID
346
453
  requires: ["departureDate"],
347
454
  });
348
455
 
@@ -355,7 +462,7 @@ const collectPassengers = validateDate.nextStep({
355
462
 
356
463
  // Step 6: Search flights (triggered by hook setting shouldSearchFlights)
357
464
  const searchFlights = collectPassengers.nextStep({
358
- tools: [searchFlightsTool],
465
+ tools: ["search_flights"], // Reference by ID
359
466
  // This step is entered when shouldSearchFlights is true
360
467
  // The hook automatically sets this flag when all data is collected
361
468
  });
@@ -371,9 +478,12 @@ const confirmBooking = presentResults.nextStep({
371
478
  requires: ["destinationCode", "departureDateParsed", "passengers"],
372
479
  });
373
480
 
481
+ // Method 6: Step-scoped tools can be added via the step's tools array
482
+ // Note: Step-scoped tools are typically defined inline in the step configuration
483
+
374
484
  // Step 9: Finalize booking
375
485
  const finalizeBooking = confirmBooking.nextStep({
376
- tools: [bookFlightTool],
486
+ tools: ["book_flight"], // Reference by ID
377
487
  when: "User confirms the booking",
378
488
  });
379
489
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falai/agent",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Standalone, strongly-typed AI Agent framework with route DSL and AI provider strategy",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
package/src/core/Agent.ts CHANGED
@@ -16,6 +16,7 @@ import type {
16
16
  StructuredSchema,
17
17
  ValidationError,
18
18
  ValidationResult,
19
+
19
20
  } from "../types";
20
21
  import type { StreamOptions, GenerateOptions, RespondParams } from "./ResponseModal";
21
22
  import {
@@ -30,8 +31,9 @@ import { Step } from "./Step";
30
31
  import { PersistenceManager } from "./PersistenceManager";
31
32
  import { SessionManager } from "./SessionManager";
32
33
  import { RoutingEngine } from "./RoutingEngine";
33
- import { ToolExecutor } from "./ToolExecutor";
34
+
34
35
  import { ResponseModal } from "./ResponseModal";
36
+ import { ToolManager } from "./ToolManager";
35
37
 
36
38
  /**
37
39
  * Error thrown when data validation fails
@@ -60,7 +62,7 @@ class RouteConfigurationError extends Error {
60
62
  export class Agent<TContext = any, TData = any> {
61
63
  private terms: Term<TContext, TData>[] = [];
62
64
  private guidelines: Guideline<TContext, TData>[] = [];
63
- private tools: Tool<TContext, TData, unknown[], unknown>[] = [];
65
+ private tools: Tool<TContext, TData>[] = [];
64
66
  private routes: Route<TContext, TData>[] = [];
65
67
  private context: TContext | undefined;
66
68
  private persistenceManager: PersistenceManager<TData> | undefined;
@@ -74,6 +76,9 @@ export class Agent<TContext = any, TData = any> {
74
76
  /** Public session manager for easy session management */
75
77
  public session: SessionManager<TData>;
76
78
 
79
+ /** Public tool manager for simplified tool creation and management */
80
+ public tool: ToolManager<TContext, TData>;
81
+
77
82
  constructor(private readonly options: AgentOptions<TContext, TData>) {
78
83
  // Set log level based on debug option
79
84
  if (options.debug) {
@@ -188,13 +193,23 @@ export class Agent<TContext = any, TData = any> {
188
193
  this.knowledgeBase = { ...options.knowledgeBase };
189
194
  }
190
195
 
191
- // Initialize session manager
192
- this.session = new SessionManager<TData>(this.persistenceManager);
196
+ // Initialize session manager with reference to this agent for bidirectional sync
197
+ this.session = new SessionManager<TData>(this.persistenceManager, this);
198
+
199
+ // Initialize tool manager with proper type inference
200
+ this.tool = new ToolManager<TContext, TData>(this);
193
201
 
194
202
  // Store sessionId for later use in getOrCreate calls
195
203
  if (options.sessionId) {
204
+ this.session.setDefaultSessionId(options.sessionId);
196
205
  // The session will be loaded on first getOrCreate call
197
- this.session.getOrCreate(options.sessionId).catch((err) => {
206
+ this.session.getOrCreate(options.sessionId).then((session) => {
207
+ // Sync session data to agent collected data
208
+ if (session.data && Object.keys(session.data).length > 0) {
209
+ this.collectedData = { ...session.data };
210
+ logger.debug("[Agent] Synced session data to collected data:", this.collectedData);
211
+ }
212
+ }).catch((err) => {
198
213
  logger.error("Failed to start session", err);
199
214
  });
200
215
  }
@@ -294,6 +309,8 @@ export class Agent<TContext = any, TData = any> {
294
309
  * Get the current collected data
295
310
  */
296
311
  getCollectedData(): Partial<TData> {
312
+ // Ensure agent collected data is synced with session
313
+ this.syncSessionDataToCollectedData();
297
314
  return { ...this.collectedData };
298
315
  }
299
316
 
@@ -334,6 +351,13 @@ export class Agent<TContext = any, TData = any> {
334
351
  this.currentSession = mergeCollected(this.currentSession, this.collectedData);
335
352
  }
336
353
 
354
+ // Also update the session manager's session data (avoid circular call)
355
+ const sessionManagerSession = this.session.current;
356
+ if (sessionManagerSession) {
357
+ sessionManagerSession.data = { ...this.collectedData };
358
+ sessionManagerSession.metadata!.lastUpdatedAt = new Date();
359
+ }
360
+
337
361
  logger.debug("[Agent] Collected data updated:", updates);
338
362
  }
339
363
 
@@ -401,7 +425,7 @@ export class Agent<TContext = any, TData = any> {
401
425
  }
402
426
  }
403
427
 
404
- const route = new Route<TContext, TData>(options);
428
+ const route = new Route<TContext, TData>(options, this);
405
429
  this.routes.push(route);
406
430
  return route;
407
431
  }
@@ -428,18 +452,53 @@ export class Agent<TContext = any, TData = any> {
428
452
  }
429
453
 
430
454
  /**
431
- * Register a tool at the agent level
455
+ * Add a tool to the agent using the unified Tool interface
456
+ * Creates and adds the tool to agent scope in one operation (BREAKING CHANGE: replaces createTool)
457
+ */
458
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
459
+ addTool<TResult = any>(
460
+ tool: Tool<TContext, TData, TResult>
461
+ ): this {
462
+ // Validate tool before adding
463
+ if (!tool || !tool.id || !tool.handler) {
464
+ throw new Error('Invalid tool: must have id and handler properties');
465
+ }
466
+
467
+ // Add directly to agent's tools array, preserving the TResult type
468
+ this.tools.push(tool);
469
+ logger.debug(`[Agent] Added tool to agent scope: ${tool.id}`);
470
+ return this;
471
+ }
472
+
473
+ /**
474
+ * Register a tool at the agent level (legacy method for backward compatibility)
475
+ * @deprecated Use addTool() with Tool interface instead
432
476
  */
433
- createTool(tool: Tool<TContext, TData, unknown[], unknown>): this {
477
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
478
+ createTool<TResult = any>(tool: Tool<TContext, TData, TResult>): this {
479
+ // Validate tool before adding
480
+ if (!tool || !tool.id || !tool.handler) {
481
+ throw new Error('Invalid tool: must have id and handler properties');
482
+ }
483
+
434
484
  this.tools.push(tool);
485
+ logger.debug(`[Agent] Created tool (legacy): ${tool.id}`);
435
486
  return this;
436
487
  }
437
488
 
438
489
  /**
439
490
  * Register multiple tools at the agent level
440
491
  */
441
- registerTools(tools: Tool<TContext, TData, unknown[], unknown>[]): this {
442
- tools.forEach((tool) => this.createTool(tool));
492
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
493
+ registerTools<TResult = any>(tools: Tool<TContext, TData, TResult>[]): this {
494
+ tools.forEach((tool) => {
495
+ // Validate each tool before adding
496
+ if (!tool || !tool.id || !tool.handler) {
497
+ throw new Error(`Invalid tool in batch: must have id and handler properties (tool: ${tool?.id || 'unknown'})`);
498
+ }
499
+ this.tools.push(tool);
500
+ });
501
+ logger.debug(`[Agent] Registered ${tools.length} tools`);
443
502
  return this;
444
503
  }
445
504
 
@@ -598,7 +657,7 @@ export class Agent<TContext = any, TData = any> {
598
657
  /**
599
658
  * Get all tools
600
659
  */
601
- getTools(): Tool<TContext, TData, unknown[], unknown>[] {
660
+ getTools(): Tool<TContext, TData>[] {
602
661
  return [...this.tools];
603
662
  }
604
663
 
@@ -660,7 +719,7 @@ export class Agent<TContext = any, TData = any> {
660
719
  async executePrepareFinalize(
661
720
  prepareOrFinalize:
662
721
  | string
663
- | Tool<TContext, TData, unknown[], unknown>
722
+ | Tool<TContext, TData>
664
723
  | ((context: TContext, data?: Partial<TData>) => void | Promise<void>)
665
724
  | undefined,
666
725
  context: TContext,
@@ -675,44 +734,24 @@ export class Agent<TContext = any, TData = any> {
675
734
  await prepareOrFinalize(context, data);
676
735
  } else {
677
736
  // It's a tool reference - find and execute the tool
678
- let tool: Tool<TContext, TData, unknown[], unknown> | undefined;
737
+ let tool: Tool<TContext, TData> | undefined;
679
738
 
680
739
  if (typeof prepareOrFinalize === "string") {
681
- // Tool ID - find it in available tools
682
- const availableTools = new Map<string, Tool<TContext, TData, unknown[], unknown>>();
683
-
684
- // Add agent-level tools
685
- this.tools.forEach((t) => {
686
- availableTools.set(t.id, t);
687
- });
688
-
689
- // Add route-level tools
690
- if (route) {
691
- route.getTools().forEach((t) => {
692
- availableTools.set(t.id, t);
693
- });
694
- }
695
-
696
- // Add step-level tools
697
- if (step?.tools) {
698
- for (const toolRef of step.tools) {
699
- if (typeof toolRef === "string") {
700
- // Keep as is
701
- } else if (toolRef.id) {
702
- availableTools.set(toolRef.id, toolRef);
703
- }
704
- }
705
- }
706
-
707
- tool = availableTools.get(prepareOrFinalize);
740
+ // Tool ID - use ToolManager to find it across all scopes
741
+ tool = this.tool.find(prepareOrFinalize, undefined, step, route);
708
742
  } else {
709
- // Tool object - use directly
710
- tool = prepareOrFinalize;
743
+ // Tool object - validate it has required properties
744
+ if (prepareOrFinalize.id && typeof prepareOrFinalize.handler === 'function') {
745
+ tool = prepareOrFinalize;
746
+ } else {
747
+ logger.error(`[Agent] Invalid tool object for prepare/finalize: missing id or invalid handler`);
748
+ return;
749
+ }
711
750
  }
712
751
 
713
752
  if (tool) {
714
- const toolExecutor = new ToolExecutor<TContext, TData>();
715
- const result = await toolExecutor.executeTool({
753
+ // Use ToolManager for execution
754
+ const result = await this.tool.executeTool({
716
755
  tool,
717
756
  context,
718
757
  updateContext: this.updateContext.bind(this),
@@ -745,12 +784,27 @@ export class Agent<TContext = any, TData = any> {
745
784
  this.currentSession = undefined;
746
785
  }
747
786
 
787
+ /**
788
+ * Sync session data to agent collected data
789
+ * @internal Used to keep agent and session data in sync
790
+ */
791
+ private syncSessionDataToCollectedData(): void {
792
+ const sessionData = this.session.getData();
793
+ if (sessionData && Object.keys(sessionData).length > 0) {
794
+ this.collectedData = { ...sessionData };
795
+ logger.debug("[Agent] Synced session data to collected data:", this.collectedData);
796
+ }
797
+ }
798
+
748
799
  /**
749
800
  * Get collected data from current session or agent-level collected data
750
801
  * @param routeId - Optional route ID to get data for (uses current route if not provided)
751
802
  * @returns The collected data from the current session or agent-level data
752
803
  */
753
804
  getData(): Partial<TData> {
805
+ // Ensure agent collected data is synced with session
806
+ this.syncSessionDataToCollectedData();
807
+
754
808
  // If we have a current session, use session data
755
809
  if (this.currentSession) {
756
810
  // With agent-level data, all routes share the same data structure