@falai/agent 0.6.3 → 0.6.5

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 (137) hide show
  1. package/README.md +89 -29
  2. package/dist/cjs/constants/index.d.ts +6 -1
  3. package/dist/cjs/constants/index.d.ts.map +1 -1
  4. package/dist/cjs/constants/index.js +8 -3
  5. package/dist/cjs/constants/index.js.map +1 -1
  6. package/dist/cjs/core/Agent.d.ts +22 -0
  7. package/dist/cjs/core/Agent.d.ts.map +1 -1
  8. package/dist/cjs/core/Agent.js +113 -26
  9. package/dist/cjs/core/Agent.js.map +1 -1
  10. package/dist/cjs/core/Events.d.ts +13 -0
  11. package/dist/cjs/core/Events.d.ts.map +1 -1
  12. package/dist/cjs/core/Events.js +28 -14
  13. package/dist/cjs/core/Events.js.map +1 -1
  14. package/dist/cjs/core/ResponseEngine.d.ts +1 -1
  15. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  16. package/dist/cjs/core/ResponseEngine.js +4 -1
  17. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  18. package/dist/cjs/core/Route.d.ts.map +1 -1
  19. package/dist/cjs/core/Route.js +4 -4
  20. package/dist/cjs/core/Route.js.map +1 -1
  21. package/dist/cjs/core/RoutingEngine.d.ts +6 -1
  22. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  23. package/dist/cjs/core/RoutingEngine.js +116 -41
  24. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  25. package/dist/cjs/core/State.d.ts +18 -6
  26. package/dist/cjs/core/State.d.ts.map +1 -1
  27. package/dist/cjs/core/State.js +30 -7
  28. package/dist/cjs/core/State.js.map +1 -1
  29. package/dist/cjs/core/Tool.d.ts +8 -1
  30. package/dist/cjs/core/Tool.d.ts.map +1 -1
  31. package/dist/cjs/core/Tool.js +25 -28
  32. package/dist/cjs/core/Tool.js.map +1 -1
  33. package/dist/cjs/core/Transition.js +1 -1
  34. package/dist/cjs/index.d.ts +1 -1
  35. package/dist/cjs/index.d.ts.map +1 -1
  36. package/dist/cjs/index.js +3 -2
  37. package/dist/cjs/index.js.map +1 -1
  38. package/dist/cjs/types/agent.d.ts +5 -0
  39. package/dist/cjs/types/agent.d.ts.map +1 -1
  40. package/dist/cjs/types/agent.js.map +1 -1
  41. package/dist/cjs/types/route.d.ts +7 -1
  42. package/dist/cjs/types/route.d.ts.map +1 -1
  43. package/dist/cjs/types/session.d.ts +13 -2
  44. package/dist/cjs/types/session.d.ts.map +1 -1
  45. package/dist/cjs/types/session.js +28 -5
  46. package/dist/cjs/types/session.js.map +1 -1
  47. package/dist/cjs/utils/logger.d.ts +10 -0
  48. package/dist/cjs/utils/logger.d.ts.map +1 -0
  49. package/dist/cjs/utils/logger.js +23 -0
  50. package/dist/cjs/utils/logger.js.map +1 -0
  51. package/dist/constants/index.d.ts +6 -1
  52. package/dist/constants/index.d.ts.map +1 -1
  53. package/dist/constants/index.js +6 -1
  54. package/dist/constants/index.js.map +1 -1
  55. package/dist/core/Agent.d.ts +22 -0
  56. package/dist/core/Agent.d.ts.map +1 -1
  57. package/dist/core/Agent.js +113 -26
  58. package/dist/core/Agent.js.map +1 -1
  59. package/dist/core/Events.d.ts +13 -0
  60. package/dist/core/Events.d.ts.map +1 -1
  61. package/dist/core/Events.js +28 -14
  62. package/dist/core/Events.js.map +1 -1
  63. package/dist/core/ResponseEngine.d.ts +1 -1
  64. package/dist/core/ResponseEngine.d.ts.map +1 -1
  65. package/dist/core/ResponseEngine.js +4 -1
  66. package/dist/core/ResponseEngine.js.map +1 -1
  67. package/dist/core/Route.d.ts.map +1 -1
  68. package/dist/core/Route.js +4 -4
  69. package/dist/core/Route.js.map +1 -1
  70. package/dist/core/RoutingEngine.d.ts +6 -1
  71. package/dist/core/RoutingEngine.d.ts.map +1 -1
  72. package/dist/core/RoutingEngine.js +116 -41
  73. package/dist/core/RoutingEngine.js.map +1 -1
  74. package/dist/core/State.d.ts +18 -6
  75. package/dist/core/State.d.ts.map +1 -1
  76. package/dist/core/State.js +31 -8
  77. package/dist/core/State.js.map +1 -1
  78. package/dist/core/Tool.d.ts +8 -1
  79. package/dist/core/Tool.d.ts.map +1 -1
  80. package/dist/core/Tool.js +25 -28
  81. package/dist/core/Tool.js.map +1 -1
  82. package/dist/core/Transition.js +1 -1
  83. package/dist/index.d.ts +1 -1
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +1 -1
  86. package/dist/index.js.map +1 -1
  87. package/dist/types/agent.d.ts +5 -0
  88. package/dist/types/agent.d.ts.map +1 -1
  89. package/dist/types/agent.js.map +1 -1
  90. package/dist/types/route.d.ts +7 -1
  91. package/dist/types/route.d.ts.map +1 -1
  92. package/dist/types/session.d.ts +13 -2
  93. package/dist/types/session.d.ts.map +1 -1
  94. package/dist/types/session.js +28 -5
  95. package/dist/types/session.js.map +1 -1
  96. package/dist/utils/logger.d.ts +10 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +17 -0
  99. package/dist/utils/logger.js.map +1 -0
  100. package/docs/{CONSTRUCTOR_OPTIONS.md → AGENT.md} +79 -7
  101. package/docs/API_REFERENCE.md +309 -18
  102. package/docs/ARCHITECTURE.md +1 -1
  103. package/docs/DOCS.md +46 -22
  104. package/docs/GETTING_STARTED.md +1 -1
  105. package/docs/README.md +13 -5
  106. package/docs/ROUTES.md +743 -0
  107. package/docs/STATES.md +798 -0
  108. package/examples/business-onboarding.ts +46 -5
  109. package/examples/company-qna-agent.ts +107 -1
  110. package/examples/custom-database-persistence.ts +44 -1
  111. package/examples/declarative-agent.ts +80 -37
  112. package/examples/domain-scoping.ts +91 -21
  113. package/examples/extracted-data-modification.ts +64 -2
  114. package/examples/healthcare-agent.ts +61 -4
  115. package/examples/openai-agent.ts +24 -2
  116. package/examples/opensearch-persistence.ts +26 -1
  117. package/examples/persistent-onboarding.ts +84 -18
  118. package/examples/prisma-persistence.ts +90 -16
  119. package/examples/redis-persistence.ts +89 -17
  120. package/examples/rules-prohibitions.ts +300 -139
  121. package/examples/streaming-agent.ts +60 -0
  122. package/examples/travel-agent.ts +66 -24
  123. package/package.json +3 -2
  124. package/src/constants/index.ts +6 -1
  125. package/src/core/Agent.ts +140 -24
  126. package/src/core/Events.ts +73 -10
  127. package/src/core/ResponseEngine.ts +6 -0
  128. package/src/core/Route.ts +8 -4
  129. package/src/core/RoutingEngine.ts +154 -43
  130. package/src/core/State.ts +45 -12
  131. package/src/core/Tool.ts +67 -10
  132. package/src/core/Transition.ts +1 -1
  133. package/src/index.ts +1 -1
  134. package/src/types/agent.ts +5 -0
  135. package/src/types/route.ts +10 -1
  136. package/src/types/session.ts +47 -7
  137. package/src/utils/logger.ts +19 -0
@@ -81,17 +81,80 @@ export function createMessageEvent(
81
81
  extracted?: Record<string, unknown>;
82
82
  };
83
83
  }
84
- ): Event<MessageEventData> {
85
- return {
86
- kind: EventKind.MESSAGE,
87
- source,
88
- data: {
89
- participant: { display_name: participantName },
90
- message,
91
- session: options?.session,
92
- },
93
- timestamp: options?.timestamp || new Date().toISOString(),
84
+ ): Event<MessageEventData>;
85
+ export function createMessageEvent(options: {
86
+ source: EventSource;
87
+ participantName: string;
88
+ message: string;
89
+ timestamp?: string;
90
+ session?: {
91
+ routeId?: string;
92
+ routeTitle?: string;
93
+ stateId?: string;
94
+ stateDescription?: string;
95
+ extracted?: Record<string, unknown>;
94
96
  };
97
+ }): Event<MessageEventData>;
98
+ export function createMessageEvent(
99
+ sourceOrOptions:
100
+ | EventSource
101
+ | {
102
+ source: EventSource;
103
+ participantName: string;
104
+ message: string;
105
+ timestamp?: string;
106
+ session?: {
107
+ routeId?: string;
108
+ routeTitle?: string;
109
+ stateId?: string;
110
+ stateDescription?: string;
111
+ extracted?: Record<string, unknown>;
112
+ };
113
+ },
114
+ participantName?: string,
115
+ message?: string,
116
+ options?: {
117
+ timestamp?: string;
118
+ session?: {
119
+ routeId?: string;
120
+ routeTitle?: string;
121
+ stateId?: string;
122
+ stateDescription?: string;
123
+ extracted?: Record<string, unknown>;
124
+ };
125
+ }
126
+ ): Event<MessageEventData> {
127
+ if (typeof sourceOrOptions === "object") {
128
+ // New signature: createMessageEvent(options)
129
+ const {
130
+ source,
131
+ participantName: pName,
132
+ message: msg,
133
+ ...restOptions
134
+ } = sourceOrOptions;
135
+ return {
136
+ kind: EventKind.MESSAGE,
137
+ source,
138
+ data: {
139
+ participant: { display_name: pName },
140
+ message: msg,
141
+ session: restOptions.session,
142
+ },
143
+ timestamp: restOptions.timestamp || new Date().toISOString(),
144
+ };
145
+ } else {
146
+ // Original signature: createMessageEvent(source, participantName, message, options)
147
+ return {
148
+ kind: EventKind.MESSAGE,
149
+ source: sourceOrOptions,
150
+ data: {
151
+ participant: { display_name: participantName! },
152
+ message: message!,
153
+ session: options?.session,
154
+ },
155
+ timestamp: options?.timestamp || new Date().toISOString(),
156
+ };
157
+ }
95
158
  }
96
159
 
97
160
  /**
@@ -44,6 +44,7 @@ export class ResponseEngine<TContext = unknown> {
44
44
 
45
45
  buildResponsePrompt(
46
46
  route: Route<TContext>,
47
+ currentState: State<TContext>,
47
48
  rules: string[],
48
49
  prohibitions: string[],
49
50
  directives: string[] | undefined,
@@ -71,6 +72,11 @@ export class ResponseEngine<TContext = unknown> {
71
72
  route.description ? ` — ${route.description}` : ""
72
73
  }`
73
74
  );
75
+ if (currentState.chatState) {
76
+ pc.addInstruction(
77
+ `Guideline for your response (adapt to the conversation):\n${currentState.chatState}`
78
+ );
79
+ }
74
80
  if (rules.length) pc.addInstruction(`Rules:\n- ${rules.join("\n- ")}`);
75
81
  if (prohibitions.length)
76
82
  pc.addInstruction(`Prohibitions:\n- ${prohibitions.join("\n- ")}`);
package/src/core/Route.ts CHANGED
@@ -43,7 +43,11 @@ export class Route<TContext = unknown, TExtracted = unknown> {
43
43
  this.prohibitions = options.prohibitions || ([] as string[]);
44
44
  this.initialState = new State<TContext, TExtracted>(
45
45
  this.id,
46
- "Initial state"
46
+ options.initialState?.chatState || "Initial state",
47
+ options.initialState?.id,
48
+ options.initialState?.gather,
49
+ options.initialState?.skipIf,
50
+ options.initialState?.requiredData
47
51
  );
48
52
  this.routingExtrasSchema = options.routingExtrasSchema;
49
53
  this.responseOutputSchema = options.responseOutputSchema;
@@ -70,8 +74,8 @@ export class Route<TContext = unknown, TExtracted = unknown> {
70
74
  private buildSequentialSteps(
71
75
  steps: Array<TransitionSpec<TContext, TExtracted>>
72
76
  ): void {
73
- // Import END_ROUTE dynamically to avoid circular dependency
74
- const END_ROUTE = Symbol.for("END_ROUTE");
77
+ // Import END_STATE dynamically to avoid circular dependency
78
+ const END_STATE = Symbol.for("END_STATE");
75
79
 
76
80
  let currentState: TransitionResult<TContext, TExtracted> =
77
81
  this.initialState;
@@ -81,7 +85,7 @@ export class Route<TContext = unknown, TExtracted = unknown> {
81
85
  }
82
86
 
83
87
  // End the route
84
- currentState.transitionTo({ state: END_ROUTE });
88
+ currentState.transitionTo({ state: END_STATE });
85
89
  }
86
90
 
87
91
  /**
@@ -8,6 +8,7 @@ import type { AiProvider } from "../types/ai";
8
8
  import { enterRoute, mergeExtracted } from "../types/session";
9
9
  import { PromptComposer } from "./PromptComposer";
10
10
  import { getLastMessageFromHistory } from "../utils/event";
11
+ import { logger } from "../utils/logger";
11
12
 
12
13
  export interface RoutingDecisionOutput {
13
14
  context: string;
@@ -69,12 +70,12 @@ export class RoutingEngine<TContext = unknown> {
69
70
  updatedSession = enterRoute(session, route.id, route.title);
70
71
  if (route.initialData) {
71
72
  updatedSession = mergeExtracted(updatedSession, route.initialData);
72
- console.log(
73
+ logger.debug(
73
74
  `[RoutingEngine] Single-route: Merged initial data:`,
74
75
  route.initialData
75
76
  );
76
77
  }
77
- console.log(
78
+ logger.debug(
78
79
  `[RoutingEngine] Single-route: Entered route: ${route.title}`
79
80
  );
80
81
  }
@@ -86,11 +87,11 @@ export class RoutingEngine<TContext = unknown> {
86
87
  const candidates = this.getCandidateStates(
87
88
  route,
88
89
  currentState,
89
- updatedSession.extracted
90
+ updatedSession.extracted || {}
90
91
  );
91
92
 
92
93
  if (candidates.length === 0) {
93
- console.warn(`[RoutingEngine] Single-route: No valid states found`);
94
+ logger.warn(`[RoutingEngine] Single-route: No valid states found`);
94
95
  return { selectedRoute, session: updatedSession };
95
96
  }
96
97
 
@@ -98,20 +99,27 @@ export class RoutingEngine<TContext = unknown> {
98
99
  if (candidates.length === 1) {
99
100
  const isRouteComplete = candidates[0].isRouteComplete;
100
101
  if (isRouteComplete) {
101
- console.log(
102
- `[RoutingEngine] Single-route: Route complete - all data collected`
102
+ logger.debug(
103
+ `[RoutingEngine] Single-route: Route complete - all data collected, END_STATE reached`
103
104
  );
105
+ // Don't return a selectedState when route is complete - there's no state to enter
106
+ return {
107
+ selectedRoute,
108
+ selectedState: undefined,
109
+ session: updatedSession,
110
+ isRouteComplete: true,
111
+ };
104
112
  } else {
105
- console.log(
113
+ logger.debug(
106
114
  `[RoutingEngine] Single-route: Only one valid state: ${candidates[0].state.id}`
107
115
  );
116
+ return {
117
+ selectedRoute,
118
+ selectedState: candidates[0].state,
119
+ session: updatedSession,
120
+ isRouteComplete: false,
121
+ };
108
122
  }
109
- return {
110
- selectedRoute,
111
- selectedState: candidates[0].state,
112
- session: updatedSession,
113
- isRouteComplete,
114
- };
115
123
  }
116
124
 
117
125
  // Multiple candidates - use AI to select best state
@@ -120,7 +128,7 @@ export class RoutingEngine<TContext = unknown> {
120
128
  route,
121
129
  currentState,
122
130
  candidates,
123
- updatedSession.extracted,
131
+ updatedSession.extracted || {},
124
132
  history,
125
133
  lastUserMessage,
126
134
  agentMeta
@@ -154,14 +162,14 @@ export class RoutingEngine<TContext = unknown> {
154
162
  )?.state;
155
163
 
156
164
  if (selectedState) {
157
- console.log(
165
+ logger.debug(
158
166
  `[RoutingEngine] Single-route: AI selected state: ${selectedState.id}`
159
167
  );
160
- console.log(
168
+ logger.debug(
161
169
  `[RoutingEngine] Single-route: Reasoning: ${stateResult.structured?.reasoning}`
162
170
  );
163
171
  } else {
164
- console.warn(
172
+ logger.warn(
165
173
  `[RoutingEngine] Single-route: Invalid state ID returned, using first candidate`
166
174
  );
167
175
  }
@@ -174,9 +182,76 @@ export class RoutingEngine<TContext = unknown> {
174
182
  };
175
183
  }
176
184
 
185
+ /**
186
+ * Recursively traverse state chain to find first non-skipped state or END_STATE
187
+ * @private
188
+ */
189
+ private findFirstValidStateRecursive<TExtracted = unknown>(
190
+ currentState: State<TContext, TExtracted>,
191
+ extracted: Partial<TExtracted>,
192
+ visited: Set<string>
193
+ ): {
194
+ state?: State<TContext, TExtracted>;
195
+ condition?: string;
196
+ isRouteComplete?: boolean;
197
+ } {
198
+ // Prevent infinite loops
199
+ if (visited.has(currentState.id)) {
200
+ return {};
201
+ }
202
+ visited.add(currentState.id);
203
+
204
+ const transitions = currentState.getTransitions();
205
+
206
+ for (const transition of transitions) {
207
+ const target = transition.getTarget();
208
+
209
+ // Check for END_STATE transition
210
+ if (
211
+ !target &&
212
+ transition.spec.state &&
213
+ typeof transition.spec.state === "symbol"
214
+ ) {
215
+ // Found END_STATE - route is complete
216
+ return { isRouteComplete: true };
217
+ }
218
+
219
+ if (!target) continue;
220
+
221
+ // If target should NOT be skipped, we found our state
222
+ if (!target.shouldSkip(extracted)) {
223
+ logger.debug(
224
+ `[RoutingEngine] Found valid state after skipping: ${target.id}`
225
+ );
226
+ return {
227
+ state: target,
228
+ condition: transition.condition,
229
+ };
230
+ }
231
+
232
+ // Target should be skipped too - recurse deeper
233
+ logger.debug(
234
+ `[RoutingEngine] Skipping state ${target.id} (skipIf condition met), continuing traversal...`
235
+ );
236
+ const result = this.findFirstValidStateRecursive(
237
+ target,
238
+ extracted,
239
+ visited
240
+ );
241
+
242
+ // If we found something (a valid state or END_STATE), return it
243
+ if (result.state || result.isRouteComplete) {
244
+ return result;
245
+ }
246
+ }
247
+
248
+ // No valid states or END_STATE found in this branch
249
+ return {};
250
+ }
251
+
177
252
  /**
178
253
  * Identify valid next candidate states based on current state and extracted data
179
- * Returns state with isRouteComplete flag if route is complete (all states skipped + has END_ROUTE transition)
254
+ * Returns state with isRouteComplete flag if route is complete (all states skipped + has END_STATE transition)
180
255
  */
181
256
  getCandidateStates<TExtracted = unknown>(
182
257
  route: Route<TContext, TExtracted>,
@@ -200,18 +275,35 @@ export class RoutingEngine<TContext = unknown> {
200
275
  if (!currentState) {
201
276
  const initialState = route.initialState;
202
277
  if (initialState.shouldSkip(extracted)) {
203
- const transitions = initialState.getTransitions();
204
- for (const transition of transitions) {
205
- const target = transition.getTarget();
206
- if (target && !target.shouldSkip(extracted)) {
207
- candidates.push({
208
- state: target,
209
- condition: transition.condition,
210
- requiredData: target.requiredData,
211
- gatherFields: target.gatherFields,
212
- });
213
- }
278
+ // Initial state should be skipped - recursively traverse to find first non-skipped state or END_STATE
279
+ const result = this.findFirstValidStateRecursive(
280
+ initialState,
281
+ extracted,
282
+ new Set<string>()
283
+ );
284
+
285
+ if (result.isRouteComplete) {
286
+ // All states are skipped and we reached END_STATE
287
+ logger.debug(
288
+ `[RoutingEngine] Route complete on entry: all states skipped, END_STATE reached`
289
+ );
290
+ return [
291
+ {
292
+ state: initialState,
293
+ condition: "Route complete - all data collected on entry",
294
+ isRouteComplete: true,
295
+ },
296
+ ];
297
+ } else if (result.state) {
298
+ // Found a non-skipped state
299
+ candidates.push({
300
+ state: result.state,
301
+ condition: result.condition,
302
+ requiredData: result.state.requiredData,
303
+ gatherFields: result.state.gatherFields,
304
+ });
214
305
  }
306
+ // If no state found and not complete, fall through to return empty candidates
215
307
  } else {
216
308
  candidates.push({
217
309
  state: initialState,
@@ -228,7 +320,7 @@ export class RoutingEngine<TContext = unknown> {
228
320
  for (const transition of transitions) {
229
321
  const target = transition.getTarget();
230
322
 
231
- // Check for END_ROUTE transition (no target state)
323
+ // Check for END_STATE transition (no target state)
232
324
  if (
233
325
  !target &&
234
326
  transition.spec.state &&
@@ -241,9 +333,28 @@ export class RoutingEngine<TContext = unknown> {
241
333
  if (!target) continue;
242
334
 
243
335
  if (target.shouldSkip(extracted)) {
244
- console.log(
336
+ logger.debug(
245
337
  `[RoutingEngine] Skipping state ${target.id} (skipIf condition met)`
246
338
  );
339
+
340
+ // Recursively traverse to find next valid state or END_STATE
341
+ const result = this.findFirstValidStateRecursive(
342
+ target,
343
+ extracted,
344
+ new Set<string>([currentState.id]) // Already visited current state
345
+ );
346
+
347
+ if (result.isRouteComplete) {
348
+ hasEndRoute = true;
349
+ } else if (result.state) {
350
+ // Found a non-skipped state deeper in the chain
351
+ candidates.push({
352
+ state: result.state,
353
+ condition: result.condition || transition.condition,
354
+ requiredData: result.state.requiredData,
355
+ gatherFields: result.state.gatherFields,
356
+ });
357
+ }
247
358
  continue;
248
359
  }
249
360
 
@@ -257,10 +368,10 @@ export class RoutingEngine<TContext = unknown> {
257
368
 
258
369
  // If no valid candidates found
259
370
  if (candidates.length === 0) {
260
- // If current state has END_ROUTE transition, the route is complete
371
+ // If current state has END_STATE transition, the route is complete
261
372
  if (hasEndRoute) {
262
- console.log(
263
- `[RoutingEngine] Route complete: all states processed, END_ROUTE reached`
373
+ logger.debug(
374
+ `[RoutingEngine] Route complete: all states processed, END_STATE reached`
264
375
  );
265
376
  // Return current state with completion flag
266
377
  return [
@@ -354,13 +465,13 @@ export class RoutingEngine<TContext = unknown> {
354
465
  const candidates = this.getCandidateStates(
355
466
  activeRoute,
356
467
  currentState,
357
- session.extracted
468
+ session.extracted || {}
358
469
  );
359
470
 
360
471
  // Check if route is complete
361
472
  if (candidates.length === 1 && candidates[0].isRouteComplete) {
362
473
  isRouteComplete = true;
363
- console.log(
474
+ logger.debug(
364
475
  `[RoutingEngine] Route ${activeRoute.title} is complete - all data collected`
365
476
  );
366
477
  // Don't include states in routing if route is complete
@@ -373,7 +484,7 @@ export class RoutingEngine<TContext = unknown> {
373
484
  requiredData: c.requiredData,
374
485
  gatherFields: c.gatherFields,
375
486
  }));
376
- console.log(
487
+ logger.debug(
377
488
  `[RoutingEngine] Found ${activeRouteStates.length} candidate states for active route`
378
489
  );
379
490
  }
@@ -432,17 +543,17 @@ export class RoutingEngine<TContext = unknown> {
432
543
  routingResult.structured.selectedStateId
433
544
  );
434
545
  if (selectedState) {
435
- console.log(
546
+ logger.debug(
436
547
  `[RoutingEngine] AI selected state: ${selectedState.id} in active route`
437
548
  );
438
- console.log(
549
+ logger.debug(
439
550
  `[RoutingEngine] State reasoning: ${routingResult.structured.stateReasoning}`
440
551
  );
441
552
  }
442
553
  }
443
554
 
444
555
  if (selectedRoute) {
445
- console.log(`[RoutingEngine] Selected route: ${selectedRoute.title}`);
556
+ logger.debug(`[RoutingEngine] Selected route: ${selectedRoute.title}`);
446
557
  if (
447
558
  !session.currentRoute ||
448
559
  session.currentRoute.id !== selectedRoute.id
@@ -457,12 +568,12 @@ export class RoutingEngine<TContext = unknown> {
457
568
  updatedSession,
458
569
  selectedRoute.initialData
459
570
  );
460
- console.log(
571
+ logger.debug(
461
572
  `[RoutingEngine] Merged initial data:`,
462
573
  selectedRoute.initialData
463
574
  );
464
575
  }
465
- console.log(`[RoutingEngine] Entered route: ${selectedRoute.title}`);
576
+ logger.debug(`[RoutingEngine] Entered route: ${selectedRoute.title}`);
466
577
  }
467
578
  }
468
579
  }
@@ -746,7 +857,7 @@ export class RoutingEngine<TContext = unknown> {
746
857
  sessionInfo.push(` "${session.currentState.description}"`);
747
858
  }
748
859
  }
749
- if (Object.keys(session.extracted).length > 0) {
860
+ if (session.extracted && Object.keys(session.extracted).length > 0) {
750
861
  sessionInfo.push(
751
862
  `- Extracted data: ${JSON.stringify(session.extracted)}`
752
863
  );
package/src/core/State.ts CHANGED
@@ -9,7 +9,7 @@ import type {
9
9
  } from "../types/route";
10
10
  import type { Guideline } from "../types/agent";
11
11
 
12
- import { END_ROUTE } from "../constants";
12
+ import { END_STATE } from "../constants";
13
13
  import { Transition } from "./Transition";
14
14
  import { generateStateId } from "../utils/id";
15
15
 
@@ -20,23 +20,55 @@ export class State<TContext = unknown, TExtracted = unknown> {
20
20
  public readonly id: string;
21
21
  private transitions: Transition<TContext, TExtracted>[] = [];
22
22
  private guidelines: Guideline[] = [];
23
- public readonly gatherFields?: string[];
24
- public readonly skipIf?: (extracted: Partial<TExtracted>) => boolean;
25
- public readonly requiredData?: string[];
23
+ public gatherFields?: string[];
24
+ public skipIf?: (extracted: Partial<TExtracted>) => boolean;
25
+ public requiredData?: string[];
26
+ public chatState?: string;
26
27
 
27
28
  constructor(
28
29
  public readonly routeId: string,
29
- public readonly description?: string,
30
+ public description?: string,
30
31
  customId?: string,
31
32
  gatherFields?: string[],
32
33
  skipIf?: (extracted: Partial<TExtracted>) => boolean,
33
- requiredData?: string[]
34
+ requiredData?: string[],
35
+ chatState?: string
34
36
  ) {
35
37
  // Use provided ID or generate a deterministic one
36
38
  this.id = customId || generateStateId(routeId, description);
37
39
  this.gatherFields = gatherFields;
38
40
  this.skipIf = skipIf;
39
41
  this.requiredData = requiredData;
42
+ this.chatState = chatState;
43
+ }
44
+
45
+ /**
46
+ * Configure the state properties after creation
47
+ * Useful for overriding initial state configuration
48
+ */
49
+ configure(config: {
50
+ description?: string;
51
+ gatherFields?: string[];
52
+ skipIf?: (extracted: Partial<TExtracted>) => boolean;
53
+ requiredData?: string[];
54
+ chatState?: string;
55
+ }): this {
56
+ if (config.description !== undefined) {
57
+ this.description = config.description;
58
+ }
59
+ if (config.gatherFields !== undefined) {
60
+ this.gatherFields = config.gatherFields;
61
+ }
62
+ if (config.skipIf !== undefined) {
63
+ this.skipIf = config.skipIf;
64
+ }
65
+ if (config.requiredData !== undefined) {
66
+ this.requiredData = config.requiredData;
67
+ }
68
+ if (config.chatState !== undefined) {
69
+ this.chatState = config.chatState;
70
+ }
71
+ return this;
40
72
  }
41
73
 
42
74
  /**
@@ -48,15 +80,15 @@ export class State<TContext = unknown, TExtracted = unknown> {
48
80
  transitionTo(
49
81
  spec: TransitionSpec<TContext, TExtracted>
50
82
  ): TransitionResult<TContext, TExtracted> {
51
- // Handle END_ROUTE
83
+ // Handle END_STATE
52
84
  if (
53
85
  spec.state &&
54
86
  typeof spec.state === "symbol" &&
55
- spec.state === END_ROUTE
87
+ spec.state === END_STATE
56
88
  ) {
57
89
  const endTransition = new Transition<TContext, TExtracted>(
58
90
  this.getRef(),
59
- { state: END_ROUTE, condition: spec.condition }
91
+ { state: END_STATE, condition: spec.condition }
60
92
  );
61
93
  this.transitions.push(endTransition);
62
94
 
@@ -82,7 +114,8 @@ export class State<TContext = unknown, TExtracted = unknown> {
82
114
  spec.id, // Use custom ID if provided
83
115
  spec.gather,
84
116
  spec.skipIf,
85
- spec.requiredData
117
+ spec.requiredData,
118
+ spec.chatState
86
119
  );
87
120
  const transition = new Transition<TContext, TExtracted>(
88
121
  this.getRef(),
@@ -161,7 +194,7 @@ export class State<TContext = unknown, TExtracted = unknown> {
161
194
  }
162
195
 
163
196
  /**
164
- * Create a terminal state reference (for END_ROUTE)
197
+ * Create a terminal state reference (for END_STATE)
165
198
  */
166
199
  private createTerminalRef(): TransitionResult<TContext, TExtracted> {
167
200
  const terminalRef: StateRef = {
@@ -172,7 +205,7 @@ export class State<TContext = unknown, TExtracted = unknown> {
172
205
  return {
173
206
  ...terminalRef,
174
207
  transitionTo: () => {
175
- throw new Error("Cannot transition from END_ROUTE state");
208
+ throw new Error("Cannot transition from END_STATE state");
176
209
  },
177
210
  };
178
211
  }
package/src/core/Tool.ts CHANGED
@@ -28,7 +28,11 @@ import { generateToolId } from "../utils/id";
28
28
  * );
29
29
  * ```
30
30
  */
31
- export function defineTool<TContext, TArgs extends unknown[], TResult>(
31
+ export function defineTool<
32
+ TContext = unknown,
33
+ TArgs extends unknown[] = unknown[],
34
+ TResult = unknown
35
+ >(
32
36
  name: string,
33
37
  handler: ToolHandler<TContext, TArgs, TResult>,
34
38
  options?: {
@@ -36,17 +40,70 @@ export function defineTool<TContext, TArgs extends unknown[], TResult>(
36
40
  description?: string;
37
41
  parameters?: unknown;
38
42
  }
43
+ ): ToolRef<TContext, TArgs, TResult>;
44
+ export function defineTool<
45
+ TContext = unknown,
46
+ TArgs extends unknown[] = unknown[],
47
+ TResult = unknown
48
+ >(options: {
49
+ name: string;
50
+ handler: ToolHandler<TContext, TArgs, TResult>;
51
+ id?: string;
52
+ description?: string;
53
+ parameters?: unknown;
54
+ }): ToolRef<TContext, TArgs, TResult>;
55
+ export function defineTool<
56
+ TContext = unknown,
57
+ TArgs extends unknown[] = unknown[],
58
+ TResult = unknown
59
+ >(
60
+ nameOrOptions:
61
+ | string
62
+ | {
63
+ name: string;
64
+ handler: ToolHandler<TContext, TArgs, TResult>;
65
+ id?: string;
66
+ description?: string;
67
+ parameters?: unknown;
68
+ },
69
+ handler?: ToolHandler<TContext, TArgs, TResult>,
70
+ options?: {
71
+ id?: string;
72
+ description?: string;
73
+ parameters?: unknown;
74
+ }
39
75
  ): ToolRef<TContext, TArgs, TResult> {
40
- // Use provided ID or generate a deterministic one from the name
41
- const id = options?.id || generateToolId(name);
76
+ if (typeof nameOrOptions === "string") {
77
+ // Original signature: defineTool(name, handler, options)
78
+ const name = nameOrOptions;
79
+ const id = options?.id || generateToolId(name);
80
+
81
+ return {
82
+ id,
83
+ name,
84
+ handler: handler!,
85
+ description: options?.description,
86
+ parameters: options?.parameters,
87
+ };
88
+ } else {
89
+ // New signature: defineTool(options)
90
+ const {
91
+ name,
92
+ handler: newHandler,
93
+ id,
94
+ description,
95
+ parameters,
96
+ } = nameOrOptions;
97
+ const toolId = id || generateToolId(name);
42
98
 
43
- return {
44
- id,
45
- name,
46
- handler,
47
- description: options?.description,
48
- parameters: options?.parameters,
49
- };
99
+ return {
100
+ id: toolId,
101
+ name,
102
+ handler: newHandler,
103
+ description,
104
+ parameters,
105
+ };
106
+ }
50
107
  }
51
108
 
52
109
  /**