@falai/agent 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +9 -4
  2. package/dist/cjs/core/Agent.d.ts +0 -5
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +75 -157
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/ResponseEngine.js +2 -2
  7. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  8. package/dist/cjs/core/Route.d.ts +6 -1
  9. package/dist/cjs/core/Route.d.ts.map +1 -1
  10. package/dist/cjs/core/Route.js +19 -1
  11. package/dist/cjs/core/Route.js.map +1 -1
  12. package/dist/cjs/core/RoutingEngine.d.ts +68 -2
  13. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  14. package/dist/cjs/core/RoutingEngine.js +416 -2
  15. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  16. package/dist/cjs/core/State.d.ts +1 -2
  17. package/dist/cjs/core/State.d.ts.map +1 -1
  18. package/dist/cjs/core/State.js +5 -6
  19. package/dist/cjs/core/State.js.map +1 -1
  20. package/dist/cjs/core/Transition.d.ts +2 -2
  21. package/dist/cjs/core/Transition.d.ts.map +1 -1
  22. package/dist/cjs/core/Transition.js +3 -2
  23. package/dist/cjs/core/Transition.js.map +1 -1
  24. package/dist/cjs/types/route.d.ts +15 -4
  25. package/dist/cjs/types/route.d.ts.map +1 -1
  26. package/dist/cjs/utils/event.d.ts +6 -0
  27. package/dist/cjs/utils/event.d.ts.map +1 -0
  28. package/dist/cjs/utils/event.js +20 -0
  29. package/dist/cjs/utils/event.js.map +1 -0
  30. package/dist/core/Agent.d.ts +0 -5
  31. package/dist/core/Agent.d.ts.map +1 -1
  32. package/dist/core/Agent.js +74 -156
  33. package/dist/core/Agent.js.map +1 -1
  34. package/dist/core/ResponseEngine.js +2 -2
  35. package/dist/core/ResponseEngine.js.map +1 -1
  36. package/dist/core/Route.d.ts +6 -1
  37. package/dist/core/Route.d.ts.map +1 -1
  38. package/dist/core/Route.js +19 -1
  39. package/dist/core/Route.js.map +1 -1
  40. package/dist/core/RoutingEngine.d.ts +68 -2
  41. package/dist/core/RoutingEngine.d.ts.map +1 -1
  42. package/dist/core/RoutingEngine.js +416 -2
  43. package/dist/core/RoutingEngine.js.map +1 -1
  44. package/dist/core/State.d.ts +1 -2
  45. package/dist/core/State.d.ts.map +1 -1
  46. package/dist/core/State.js +5 -6
  47. package/dist/core/State.js.map +1 -1
  48. package/dist/core/Transition.d.ts +2 -2
  49. package/dist/core/Transition.d.ts.map +1 -1
  50. package/dist/core/Transition.js +3 -2
  51. package/dist/core/Transition.js.map +1 -1
  52. package/dist/types/route.d.ts +15 -4
  53. package/dist/types/route.d.ts.map +1 -1
  54. package/dist/utils/event.d.ts +6 -0
  55. package/dist/utils/event.d.ts.map +1 -0
  56. package/dist/utils/event.js +17 -0
  57. package/dist/utils/event.js.map +1 -0
  58. package/docs/ADAPTERS.md +1 -1
  59. package/docs/API_REFERENCE.md +15 -7
  60. package/docs/ARCHITECTURE.md +25 -5
  61. package/docs/CONSTRUCTOR_OPTIONS.md +2 -2
  62. package/docs/CONTEXT_MANAGEMENT.md +1 -1
  63. package/docs/GETTING_STARTED.md +1 -1
  64. package/docs/PERSISTENCE.md +3 -3
  65. package/examples/business-onboarding.ts +97 -70
  66. package/examples/company-qna-agent.ts +4 -4
  67. package/examples/custom-database-persistence.ts +2 -2
  68. package/examples/declarative-agent.ts +3 -3
  69. package/examples/extracted-data-modification.ts +1 -1
  70. package/examples/healthcare-agent.ts +9 -3
  71. package/examples/openai-agent.ts +1 -1
  72. package/examples/opensearch-persistence.ts +2 -2
  73. package/examples/persistent-onboarding.ts +18 -12
  74. package/examples/prisma-persistence.ts +3 -3
  75. package/examples/redis-persistence.ts +3 -3
  76. package/examples/travel-agent.ts +23 -4
  77. package/package.json +1 -1
  78. package/src/core/Agent.ts +78 -227
  79. package/src/core/ResponseEngine.ts +2 -2
  80. package/src/core/Route.ts +34 -3
  81. package/src/core/RoutingEngine.ts +663 -2
  82. package/src/core/State.ts +6 -13
  83. package/src/core/Transition.ts +6 -3
  84. package/src/types/route.ts +15 -5
  85. package/src/utils/event.ts +16 -0
@@ -223,7 +223,7 @@ async function createTravelAgent() {
223
223
  description:
224
224
  "Helps the customer find and book a flight to their desired destination.",
225
225
  conditions: ["The customer wants to book a flight"],
226
- gatherSchema: {
226
+ extractionSchema: {
227
227
  type: "object",
228
228
  properties: {
229
229
  destination: {
@@ -274,11 +274,13 @@ async function createTravelAgent() {
274
274
  chatState: "Ask about the destination",
275
275
  gather: ["destination"],
276
276
  skipIf: (extracted) => !!extracted.destination,
277
+ condition: "Customer needs to specify their travel destination",
277
278
  });
278
279
 
279
280
  const enrichDestination = askDestination.transitionTo({
280
281
  toolState: lookupDestinationCode,
281
282
  requiredData: ["destination"],
283
+ condition: "Destination provided, lookup airport code",
282
284
  });
283
285
 
284
286
  const askDates = enrichDestination.transitionTo({
@@ -286,6 +288,7 @@ async function createTravelAgent() {
286
288
  gather: ["departureDate"],
287
289
  skipIf: (extracted) => !!extracted.departureDate,
288
290
  requiredData: ["destination"],
291
+ condition: "Destination confirmed, need travel dates",
289
292
  });
290
293
 
291
294
  const askPassengers = askDates.transitionTo({
@@ -293,32 +296,41 @@ async function createTravelAgent() {
293
296
  gather: ["passengers"],
294
297
  skipIf: (extracted) => !!extracted.passengers,
295
298
  requiredData: ["destination", "departureDate"],
299
+ condition: "Dates confirmed, need passenger count",
296
300
  });
297
301
 
298
302
  const searchFlightsState = askPassengers.transitionTo({
299
303
  toolState: searchFlights,
300
304
  // Triggered when shouldSearchFlights flag is set by hook
305
+ condition: "All basic info gathered, search for available flights",
301
306
  });
302
307
 
303
308
  const presentFlights = searchFlightsState.transitionTo({
304
309
  chatState: "Present available flights and ask which one works for them",
310
+ condition: "Flight search complete, present options to customer",
305
311
  });
306
312
 
307
313
  // Happy path: customer selects a flight
308
314
  const confirmBooking = presentFlights.transitionTo({
309
315
  chatState: "Confirm booking details before proceeding",
310
316
  gather: ["cabinClass", "urgency"], // Additional optional data
317
+ condition: "Customer interested in a flight, confirm booking details",
311
318
  });
312
319
 
313
320
  const bookFlightState = confirmBooking.transitionTo({
314
321
  toolState: bookFlight,
322
+ condition: "Customer confirmed, proceed with booking",
315
323
  });
316
324
 
317
325
  const provideConfirmation = bookFlightState.transitionTo({
318
326
  chatState: "Provide confirmation number and booking summary",
327
+ condition: "Booking completed successfully",
319
328
  });
320
329
 
321
- provideConfirmation.transitionTo({ state: END_ROUTE });
330
+ provideConfirmation.transitionTo({
331
+ state: END_ROUTE,
332
+ condition: "Customer has confirmation, booking flow complete",
333
+ });
322
334
 
323
335
  // Add route-specific guidelines
324
336
  flightBookingRoute.createGuideline({
@@ -340,7 +352,7 @@ async function createTravelAgent() {
340
352
  description:
341
353
  "Retrieves the customer's booking status and provides relevant information.",
342
354
  conditions: ["The customer wants to check their booking status"],
343
- gatherSchema: {
355
+ extractionSchema: {
344
356
  type: "object",
345
357
  properties: {
346
358
  confirmationNumber: {
@@ -360,18 +372,25 @@ async function createTravelAgent() {
360
372
  chatState: "Ask for the confirmation number or booking reference",
361
373
  gather: ["confirmationNumber"],
362
374
  skipIf: (extracted) => !!extracted.confirmationNumber,
375
+ condition:
376
+ "Customer wants to check booking status but hasn't provided confirmation number",
363
377
  });
364
378
 
365
379
  const checkStatus = askConfirmation.transitionTo({
366
380
  toolState: getBookingStatus,
367
381
  requiredData: ["confirmationNumber"],
382
+ condition: "Confirmation number provided, look up booking details",
368
383
  });
369
384
 
370
385
  const provideStatus = checkStatus.transitionTo({
371
386
  chatState: "Provide booking status and relevant information",
387
+ condition: "Booking status retrieved successfully",
372
388
  });
373
389
 
374
- provideStatus.transitionTo({ state: END_ROUTE });
390
+ provideStatus.transitionTo({
391
+ state: END_ROUTE,
392
+ condition: "Booking information provided to customer",
393
+ });
375
394
 
376
395
  // Global guidelines
377
396
  agent.createGuideline({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falai/agent",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
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
@@ -3,18 +3,12 @@
3
3
  */
4
4
 
5
5
  import type { AgentOptions, Term, Guideline, Capability } from "../types/agent";
6
- import type { Event, StateRef, MessageEventData } from "../types/index";
6
+ import type { Event, StateRef } from "../types/index";
7
7
  import type { RouteOptions } from "../types/route";
8
- import type { RoutingDecisionOutput } from "./RoutingEngine";
8
+
9
9
  import type { SessionState } from "../types/session";
10
10
  import type { AgentStructuredResponse } from "../types/ai";
11
- import {
12
- createSession,
13
- enterRoute,
14
- enterState,
15
- mergeExtracted,
16
- } from "../types/session";
17
- import { EventKind } from "../types/history";
11
+ import { createSession, enterState, mergeExtracted } from "../types/session";
18
12
  import { PromptComposer } from "./PromptComposer";
19
13
 
20
14
  import { Route } from "./Route";
@@ -24,19 +18,7 @@ import { PersistenceManager } from "./PersistenceManager";
24
18
  import { RoutingEngine } from "./RoutingEngine";
25
19
  import { ResponseEngine } from "./ResponseEngine";
26
20
  import { ToolExecutor } from "./ToolExecutor";
27
-
28
- /**
29
- * Helper to extract last message from history
30
- */
31
- function getLastMessageFromHistory(history: Event[]): string {
32
- for (let i = history.length - 1; i >= 0; i--) {
33
- const event = history[i];
34
- if (event.kind === EventKind.MESSAGE) {
35
- return (event.data as MessageEventData).message;
36
- }
37
- }
38
- return "";
39
- }
21
+ import { getLastMessageFromHistory } from "../utils/event";
40
22
 
41
23
  /**
42
24
  * Main Agent class with generic context support
@@ -275,57 +257,6 @@ export class Agent<TContext = unknown> {
275
257
  return this.context;
276
258
  }
277
259
 
278
- /**
279
- * Determine the next state in a route based on extracted data
280
- * @internal
281
- */
282
- private getNextState<TExtracted = unknown>(
283
- route: Route<TContext, TExtracted>,
284
- currentState: State<TContext, TExtracted> | undefined,
285
- extracted: Partial<TExtracted>
286
- ): State<TContext, TExtracted> {
287
- // If no current state, start from initial state
288
- if (!currentState) {
289
- // Check if initial state should be skipped
290
- if (route.initialState.shouldSkip(extracted)) {
291
- return this.getNextState(route, route.initialState, extracted);
292
- }
293
- return route.initialState;
294
- }
295
-
296
- // Get transitions from current state
297
- const transitions = currentState.getTransitions();
298
-
299
- // If no transitions, stay in current state
300
- if (transitions.length === 0) {
301
- return currentState;
302
- }
303
-
304
- // Try to find the next state to transition to
305
- for (const transition of transitions) {
306
- const target = transition.getTarget();
307
- if (!target) continue;
308
-
309
- // Check if target state should be skipped
310
- if (target.shouldSkip(extracted)) {
311
- // Recursively find next non-skipped state
312
- return this.getNextState(route, target, extracted);
313
- }
314
-
315
- // Check if target state has required data
316
- if (!target.hasRequiredData(extracted)) {
317
- // Cannot enter this state yet, stay in current state
318
- continue;
319
- }
320
-
321
- // Found valid next state
322
- return target;
323
- }
324
-
325
- // No valid transition found, stay in current state
326
- return currentState;
327
- }
328
-
329
260
  /**
330
261
  * Generate a response based on history and context as a stream
331
262
  */
@@ -414,106 +345,66 @@ export class Agent<TContext = unknown> {
414
345
  }
415
346
  }
416
347
 
417
- // PHASE 2: ROUTING - Determine which route to use
348
+ // PHASE 2: ROUTING + STATE SELECTION - Determine which route and state to use (combined)
418
349
  let selectedRoute: Route<TContext> | undefined;
419
350
  let responseDirectives: string[] | undefined;
351
+ let selectedState: State<TContext> | undefined;
420
352
 
421
353
  if (this.routes.length > 0) {
422
- // Get last user message
423
- const lastUserMessage = getLastMessageFromHistory(history);
424
-
425
- // Build routing schema
426
- const routingSchema = this.routingEngine.buildDynamicRoutingSchema(
427
- this.routes
428
- );
429
-
430
- // Build routing prompt with session context
431
- const routingPrompt = this.routingEngine.buildRoutingPrompt(
354
+ const orchestration = await this.routingEngine.decideRouteAndState({
355
+ routes: this.routes,
356
+ session,
432
357
  history,
433
- this.routes,
434
- lastUserMessage,
435
- {
358
+ agentMeta: {
436
359
  name: this.options.name,
437
360
  goal: this.options.goal,
438
361
  description: this.options.description,
439
362
  personality: this.options.personality,
440
363
  },
441
- session // Pass session for context-aware routing
442
- );
443
-
444
- // Call AI to score routes (non-streaming for routing decision)
445
- const routingResult = await this.options.ai.generateMessage<
446
- TContext,
447
- RoutingDecisionOutput
448
- >({
449
- prompt: routingPrompt,
450
- history,
364
+ ai: this.options.ai,
451
365
  context: effectiveContext,
452
366
  signal,
453
- parameters: {
454
- jsonSchema: routingSchema,
455
- schemaName: "routing_output",
456
- },
457
367
  });
458
368
 
459
- // Select best route from scores
460
- if (routingResult.structured?.routes) {
461
- const decision = this.routingEngine.decideRouteFromScores({
462
- context: routingResult.structured.context,
463
- routes: routingResult.structured.routes,
464
- responseDirectives: routingResult.structured.responseDirectives,
465
- });
466
- selectedRoute = this.routes.find((r) => r.id === decision.routeId);
467
- responseDirectives = routingResult.structured.responseDirectives;
369
+ selectedRoute = orchestration.selectedRoute;
370
+ selectedState = orchestration.selectedState;
371
+ responseDirectives = orchestration.responseDirectives;
372
+ session = orchestration.session;
373
+ }
468
374
 
469
- if (selectedRoute) {
375
+ // PHASE 3: DETERMINE NEXT STATE - Use state from combined decision or get initial state
376
+ if (selectedRoute) {
377
+ let nextState: State<TContext>;
378
+
379
+ // If we have a selected state from the combined routing decision, use it
380
+ if (selectedState) {
381
+ nextState = selectedState;
382
+ } else {
383
+ // New route or no state selected - get initial state or first valid state
384
+ const candidates = this.routingEngine.getCandidateStates(
385
+ selectedRoute,
386
+ undefined,
387
+ session.extracted
388
+ );
389
+ if (candidates.length > 0) {
390
+ nextState = candidates[0].state;
470
391
  console.log(
471
- `[Agent] Selected route: ${selectedRoute.title} (score: ${decision.maxScore})`
392
+ `[Agent] Using first valid state: ${nextState.id} for new route`
393
+ );
394
+ } else {
395
+ // Fallback to initial state even if it should be skipped
396
+ nextState = selectedRoute.initialState;
397
+ console.warn(
398
+ `[Agent] No valid states found, using initial state: ${nextState.id}`
472
399
  );
473
-
474
- // Update session with selected route (if changed)
475
- if (
476
- !session.currentRoute ||
477
- session.currentRoute.id !== selectedRoute.id
478
- ) {
479
- session = enterRoute(
480
- session,
481
- selectedRoute.id,
482
- selectedRoute.title
483
- );
484
-
485
- // Merge initial data if provided by the route
486
- if (selectedRoute.initialData) {
487
- session = mergeExtracted(session, selectedRoute.initialData);
488
- console.log(
489
- `[Agent] Merged initial data:`,
490
- selectedRoute.initialData
491
- );
492
- }
493
-
494
- console.log(`[Agent] Entered route: ${selectedRoute.title}`);
495
- }
496
400
  }
497
401
  }
498
- }
499
-
500
- // PHASE 3: RESPONSE - Stream message using selected route
501
- if (selectedRoute) {
502
- // Determine next state based on current extracted data
503
- const currentStateRef = session.currentState;
504
- const currentState = currentStateRef
505
- ? selectedRoute.getState(currentStateRef.id)
506
- : undefined;
507
- const nextState = this.getNextState(
508
- selectedRoute,
509
- currentState,
510
- session.extracted
511
- );
512
402
 
513
403
  // Update session with next state
514
404
  session = enterState(session, nextState.id, nextState.description);
515
405
  console.log(`[Agent] Entered state: ${nextState.id}`);
516
406
 
407
+ // PHASE 4: RESPONSE GENERATION - Stream message using selected route and state
517
408
  // Get last user message
518
409
  const lastUserMessage = getLastMessageFromHistory(history);
519
410
 
@@ -742,111 +633,71 @@ export class Agent<TContext = unknown> {
742
633
  }
743
634
  }
744
635
 
745
- // PHASE 2: ROUTING - Determine which route to use
636
+ // PHASE 2: ROUTING + STATE SELECTION - Determine which route and state to use (combined)
746
637
  let selectedRoute: Route<TContext> | undefined;
747
638
  let responseDirectives: string[] | undefined;
639
+ let selectedState: State<TContext> | undefined;
748
640
 
749
641
  if (this.routes.length > 0) {
750
- // Get last user message
751
- const lastUserMessage = getLastMessageFromHistory(history);
752
-
753
- // Build routing schema
754
- const routingSchema = this.routingEngine.buildDynamicRoutingSchema(
755
- this.routes
756
- );
757
-
758
- // Build routing prompt with session context
759
- const routingPrompt = this.routingEngine.buildRoutingPrompt(
642
+ const orchestration = await this.routingEngine.decideRouteAndState({
643
+ routes: this.routes,
644
+ session,
760
645
  history,
761
- this.routes,
762
- lastUserMessage,
763
- {
646
+ agentMeta: {
764
647
  name: this.options.name,
765
648
  goal: this.options.goal,
766
649
  description: this.options.description,
767
650
  personality: this.options.personality,
768
651
  },
769
- session // Pass session for context-aware routing
770
- );
771
-
772
- // Call AI to score routes
773
- const routingResult = await this.options.ai.generateMessage<
774
- TContext,
775
- RoutingDecisionOutput
776
- >({
777
- prompt: routingPrompt,
778
- history,
652
+ ai: this.options.ai,
779
653
  context: effectiveContext,
780
654
  signal,
781
- parameters: {
782
- jsonSchema: routingSchema,
783
- schemaName: "routing_output",
784
- },
785
655
  });
786
656
 
787
- // Select best route from scores
788
- if (routingResult.structured?.routes) {
789
- const decision = this.routingEngine.decideRouteFromScores({
790
- context: routingResult.structured.context,
791
- routes: routingResult.structured.routes,
792
- responseDirectives: routingResult.structured.responseDirectives,
793
- });
794
- selectedRoute = this.routes.find((r) => r.id === decision.routeId);
795
- responseDirectives = routingResult.structured.responseDirectives;
796
-
797
- if (selectedRoute) {
798
- console.log(
799
- `[Agent] Selected route: ${selectedRoute.title} (score: ${decision.maxScore})`
800
- );
801
-
802
- // Update session with selected route (if changed)
803
- if (
804
- !session.currentRoute ||
805
- session.currentRoute.id !== selectedRoute.id
806
- ) {
807
- session = enterRoute(
808
- session,
809
- selectedRoute.id,
810
- selectedRoute.title
811
- );
812
-
813
- // Merge initial data if provided by the route
814
- if (selectedRoute.initialData) {
815
- session = mergeExtracted(session, selectedRoute.initialData);
816
- console.log(
817
- `[Agent] Merged initial data:`,
818
- selectedRoute.initialData
819
- );
820
- }
821
-
822
- console.log(`[Agent] Entered route: ${selectedRoute.title}`);
823
- }
824
- }
825
- }
657
+ selectedRoute = orchestration.selectedRoute;
658
+ selectedState = orchestration.selectedState;
659
+ responseDirectives = orchestration.responseDirectives;
660
+ session = orchestration.session;
826
661
  }
827
662
 
828
- // PHASE 3: RESPONSE - Generate message using selected route
663
+ // PHASE 3: DETERMINE NEXT STATE - Use state from combined decision or get initial state
829
664
  let message: string;
830
665
  const toolCalls:
831
666
  | Array<{ toolName: string; arguments: Record<string, unknown> }>
832
667
  | undefined = undefined;
833
668
 
834
669
  if (selectedRoute) {
835
- // Determine next state based on current extracted data
836
- const currentStateRef = session.currentState;
837
- const currentState = currentStateRef
838
- ? selectedRoute.getState(currentStateRef.id)
839
- : undefined;
840
- const nextState = this.getNextState(
841
- selectedRoute,
842
- currentState,
843
- session.extracted
844
- );
670
+ let nextState: State<TContext>;
671
+
672
+ // If we have a selected state from the combined routing decision, use it
673
+ if (selectedState) {
674
+ nextState = selectedState;
675
+ } else {
676
+ // New route or no state selected - get initial state or first valid state
677
+ const candidates = this.routingEngine.getCandidateStates(
678
+ selectedRoute,
679
+ undefined,
680
+ session.extracted
681
+ );
682
+ if (candidates.length > 0) {
683
+ nextState = candidates[0].state;
684
+ console.log(
685
+ `[Agent] Using first valid state: ${nextState.id} for new route`
686
+ );
687
+ } else {
688
+ // Fallback to initial state even if it should be skipped
689
+ nextState = selectedRoute.initialState;
690
+ console.warn(
691
+ `[Agent] No valid states found, using initial state: ${nextState.id}`
692
+ );
693
+ }
694
+ }
845
695
 
846
696
  // Update session with next state
847
697
  session = enterState(session, nextState.id, nextState.description);
848
698
  console.log(`[Agent] Entered state: ${nextState.id}`);
849
699
 
700
+ // PHASE 4: RESPONSE GENERATION - Generate message using selected route and state
850
701
  // Get last user message
851
702
  const lastUserMessage = getLastMessageFromHistory(history);
852
703
 
@@ -30,9 +30,9 @@ export class ResponseEngine<TContext = unknown> {
30
30
  }
31
31
 
32
32
  // Add gather fields from current state
33
- if (currentState?.gatherFields && route.gatherSchema?.properties) {
33
+ if (currentState?.gatherFields && route.extractionSchema?.properties) {
34
34
  for (const field of currentState.gatherFields) {
35
- const fieldSchema = route.gatherSchema.properties[field];
35
+ const fieldSchema = route.extractionSchema.properties[field];
36
36
  if (fieldSchema) {
37
37
  base.properties![field] = fieldSchema;
38
38
  }
package/src/core/Route.ts CHANGED
@@ -2,7 +2,12 @@
2
2
  * Route (Journey) DSL implementation
3
3
  */
4
4
 
5
- import type { RouteOptions, RouteRef } from "../types/route";
5
+ import type {
6
+ RouteOptions,
7
+ RouteRef,
8
+ TransitionSpec,
9
+ TransitionResult,
10
+ } from "../types/route";
6
11
  import type { StructuredSchema } from "../types/schema";
7
12
  import type { Guideline } from "../types/agent";
8
13
 
@@ -22,7 +27,7 @@ export class Route<TContext = unknown, TExtracted = unknown> {
22
27
  public readonly prohibitions: string[];
23
28
  public readonly initialState: State<TContext, TExtracted>;
24
29
  public readonly responseOutputSchema?: StructuredSchema;
25
- public readonly gatherSchema?: StructuredSchema;
30
+ public readonly extractionSchema?: StructuredSchema;
26
31
  public readonly initialData?: Partial<TExtracted>;
27
32
  private routingExtrasSchema?: StructuredSchema;
28
33
  private guidelines: Guideline[] = [];
@@ -42,7 +47,7 @@ export class Route<TContext = unknown, TExtracted = unknown> {
42
47
  );
43
48
  this.routingExtrasSchema = options.routingExtrasSchema;
44
49
  this.responseOutputSchema = options.responseOutputSchema;
45
- this.gatherSchema = options.gatherSchema;
50
+ this.extractionSchema = options.extractionSchema;
46
51
  this.initialData = options.initialData;
47
52
 
48
53
  // Initialize guidelines from options
@@ -51,6 +56,32 @@ export class Route<TContext = unknown, TExtracted = unknown> {
51
56
  this.createGuideline(guideline);
52
57
  });
53
58
  }
59
+
60
+ // Build sequential steps if provided
61
+ if (options.steps && options.steps.length > 0) {
62
+ this.buildSequentialSteps(options.steps);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Build a sequential state machine from an array of steps
68
+ * @private
69
+ */
70
+ private buildSequentialSteps(
71
+ steps: Array<TransitionSpec<TContext, TExtracted>>
72
+ ): void {
73
+ // Import END_ROUTE dynamically to avoid circular dependency
74
+ const END_ROUTE = Symbol.for("END_ROUTE");
75
+
76
+ let currentState: TransitionResult<TContext, TExtracted> =
77
+ this.initialState;
78
+
79
+ for (const step of steps) {
80
+ currentState = currentState.transitionTo(step);
81
+ }
82
+
83
+ // End the route
84
+ currentState.transitionTo({ state: END_ROUTE });
54
85
  }
55
86
 
56
87
  /**