@falai/agent 1.2.0 → 1.2.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 (78) hide show
  1. package/dist/cjs/core/ResponseEngine.d.ts +0 -2
  2. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  3. package/dist/cjs/core/ResponseEngine.js +2 -6
  4. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  5. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  6. package/dist/cjs/core/ResponseModal.js +45 -54
  7. package/dist/cjs/core/ResponseModal.js.map +1 -1
  8. package/dist/cjs/core/ResponsePipeline.d.ts +2 -1
  9. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  10. package/dist/cjs/core/ResponsePipeline.js +17 -19
  11. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  12. package/dist/cjs/core/RoutingEngine.d.ts +18 -0
  13. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  14. package/dist/cjs/core/RoutingEngine.js +108 -2
  15. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  16. package/dist/cjs/providers/AnthropicProvider.d.ts +7 -0
  17. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  18. package/dist/cjs/providers/AnthropicProvider.js +101 -12
  19. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  20. package/dist/cjs/providers/GeminiProvider.d.ts +7 -0
  21. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  22. package/dist/cjs/providers/GeminiProvider.js +81 -2
  23. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  24. package/dist/cjs/providers/OpenAIProvider.d.ts +5 -0
  25. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  26. package/dist/cjs/providers/OpenAIProvider.js +51 -12
  27. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  28. package/dist/cjs/providers/OpenRouterProvider.d.ts +5 -0
  29. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  30. package/dist/cjs/providers/OpenRouterProvider.js +50 -12
  31. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  32. package/dist/cjs/types/ai.d.ts +2 -2
  33. package/dist/cjs/types/ai.d.ts.map +1 -1
  34. package/dist/core/ResponseEngine.d.ts +0 -2
  35. package/dist/core/ResponseEngine.d.ts.map +1 -1
  36. package/dist/core/ResponseEngine.js +2 -6
  37. package/dist/core/ResponseEngine.js.map +1 -1
  38. package/dist/core/ResponseModal.d.ts.map +1 -1
  39. package/dist/core/ResponseModal.js +46 -55
  40. package/dist/core/ResponseModal.js.map +1 -1
  41. package/dist/core/ResponsePipeline.d.ts +2 -1
  42. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  43. package/dist/core/ResponsePipeline.js +18 -20
  44. package/dist/core/ResponsePipeline.js.map +1 -1
  45. package/dist/core/RoutingEngine.d.ts +18 -0
  46. package/dist/core/RoutingEngine.d.ts.map +1 -1
  47. package/dist/core/RoutingEngine.js +109 -3
  48. package/dist/core/RoutingEngine.js.map +1 -1
  49. package/dist/providers/AnthropicProvider.d.ts +7 -0
  50. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  51. package/dist/providers/AnthropicProvider.js +101 -12
  52. package/dist/providers/AnthropicProvider.js.map +1 -1
  53. package/dist/providers/GeminiProvider.d.ts +7 -0
  54. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  55. package/dist/providers/GeminiProvider.js +81 -2
  56. package/dist/providers/GeminiProvider.js.map +1 -1
  57. package/dist/providers/OpenAIProvider.d.ts +5 -0
  58. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  59. package/dist/providers/OpenAIProvider.js +51 -12
  60. package/dist/providers/OpenAIProvider.js.map +1 -1
  61. package/dist/providers/OpenRouterProvider.d.ts +5 -0
  62. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  63. package/dist/providers/OpenRouterProvider.js +50 -12
  64. package/dist/providers/OpenRouterProvider.js.map +1 -1
  65. package/dist/types/ai.d.ts +2 -2
  66. package/dist/types/ai.d.ts.map +1 -1
  67. package/docs/core/agent/session-management.md +53 -0
  68. package/docs/core/conversation-flows/routes.md +2 -0
  69. package/package.json +1 -1
  70. package/src/core/ResponseEngine.ts +2 -9
  71. package/src/core/ResponseModal.ts +56 -67
  72. package/src/core/ResponsePipeline.ts +22 -22
  73. package/src/core/RoutingEngine.ts +159 -3
  74. package/src/providers/AnthropicProvider.ts +110 -12
  75. package/src/providers/GeminiProvider.ts +91 -2
  76. package/src/providers/OpenAIProvider.ts +56 -12
  77. package/src/providers/OpenRouterProvider.ts +55 -12
  78. package/src/types/ai.ts +2 -2
@@ -12,13 +12,11 @@ import type {
12
12
  HistoryItem,
13
13
  Tool,
14
14
  Event,
15
- ToolEventData,
16
15
  AgentStructuredResponse,
17
16
  Term,
18
17
  StoppedReason,
19
18
  ToolCallRequest,
20
19
  } from "../types";
21
- import { EventKind, MessageRole } from "../types";
22
20
  import type { Agent } from "./Agent";
23
21
  import type { Route } from "./Route";
24
22
  import { Step } from "./Step";
@@ -26,7 +24,7 @@ import { ResponseEngine } from "./ResponseEngine";
26
24
  import { ResponsePipeline } from "./ResponsePipeline";
27
25
  import { BatchExecutor, type HookFunction } from "./BatchExecutor";
28
26
  import { BatchPromptBuilder } from "./BatchPromptBuilder";
29
- import { cloneDeep, mergeCollected, enterStep, getLastMessageFromHistory, render, logger, historyToEvents } from "../utils";
27
+ import { cloneDeep, mergeCollected, enterStep, getLastMessageFromHistory, render, logger, historyToEvents, eventsToHistory } from "../utils";
30
28
  import { createTemplateContext } from "../utils/template";
31
29
  import type { ToolManager } from "./ToolManager";
32
30
  import { END_ROUTE_ID } from "../constants";
@@ -669,12 +667,15 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
669
667
  `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`
670
668
  );
671
669
 
670
+ // Convert Event[] to HistoryItem[] for provider call
671
+ const historyItems = eventsToHistory(history);
672
+
672
673
  // Call AI to extract data
673
674
  const agentOptions = this.agent.getAgentOptions();
674
675
  try {
675
676
  const result = await agentOptions.provider.generateMessage<TContext, Partial<TData>>({
676
677
  prompt: extractionPrompt.join('\n'),
677
- history,
678
+ history: historyItems,
678
679
  context: {} as TContext, // Passed as empty object so AI doesn't "extract" from context
679
680
  // NOTE: context is intentionally NOT passed here.
680
681
  // Passing context caused the AI to "extract" data from the lead's context
@@ -716,7 +717,6 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
716
717
  // Get last user message (needed for both route and completion handling)
717
718
  // Convert HistoryItem[] to Event[] for internal processing
718
719
  const historyEvents = historyToEvents(history);
719
- const lastMessageText = getLastMessageFromHistory(historyEvents);
720
720
 
721
721
  let message: string;
722
722
  let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
@@ -757,7 +757,6 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
757
757
  session,
758
758
  history,
759
759
  context: effectiveContext,
760
- lastMessageText,
761
760
  historyEvents,
762
761
  signal: undefined,
763
762
  });
@@ -785,7 +784,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
785
784
  selectedRoute,
786
785
  session,
787
786
  context: effectiveContext,
788
- lastMessageText,
787
+ history,
789
788
  historyEvents,
790
789
  signal: undefined,
791
790
  });
@@ -806,7 +805,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
806
805
  // Fallback: No routes defined, generate a simple response
807
806
 
808
807
  message = await this.generateFallbackResponse({
809
- history: historyEvents, // Use Event[] for fallback response
808
+ history,
810
809
  context: effectiveContext,
811
810
  session,
812
811
  });
@@ -926,7 +925,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
926
925
  const agentOptions = this.agent.getAgentOptions();
927
926
  const result = await agentOptions.provider.generateMessage({
928
927
  prompt: batchPromptResult.prompt,
929
- history: historyEvents,
928
+ history, // Use HistoryItem[] for AI provider
930
929
  context,
931
930
  tools: availableTools,
932
931
  signal,
@@ -1125,7 +1124,6 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1125
1124
  // Get last user message (needed for both route and completion handling)
1126
1125
  // Convert HistoryItem[] to Event[] for internal processing
1127
1126
  const historyEvents = historyToEvents(history);
1128
- const lastMessageText = getLastMessageFromHistory(historyEvents);
1129
1127
 
1130
1128
  if (selectedRoute && !isRouteComplete) {
1131
1129
  // Check if we have batch steps to execute
@@ -1153,7 +1151,6 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1153
1151
  session,
1154
1152
  history,
1155
1153
  context: effectiveContext,
1156
- lastMessageText,
1157
1154
  historyEvents,
1158
1155
  });
1159
1156
  }
@@ -1164,14 +1161,14 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1164
1161
  selectedRoute,
1165
1162
  session,
1166
1163
  context: effectiveContext,
1167
- lastMessageText,
1164
+ history,
1168
1165
  historyEvents,
1169
1166
  });
1170
1167
 
1171
1168
  } else {
1172
1169
  // Fallback: No routes defined, stream a simple response
1173
1170
  yield* this.streamFallbackResponse({
1174
- history: historyEvents, // Use Event[] for fallback response
1171
+ history,
1175
1172
  context: effectiveContext,
1176
1173
  session,
1177
1174
  });
@@ -1196,7 +1193,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1196
1193
  batchStoppedReason?: StoppedReason;
1197
1194
  signal?: AbortSignal;
1198
1195
  }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1199
- const { selectedRoute, batchSteps, context, historyEvents, batchStoppedReason, signal } = params;
1196
+ const { selectedRoute, batchSteps, history, context, historyEvents, batchStoppedReason, signal } = params;
1200
1197
  let session = params.session;
1201
1198
 
1202
1199
  // Create hook executor function
@@ -1251,7 +1248,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1251
1248
  const agentOptions = this.agent.getAgentOptions();
1252
1249
  const stream = agentOptions.provider.generateMessageStream({
1253
1250
  prompt: batchPromptResult.prompt,
1254
- history: historyEvents,
1251
+ history, // Use HistoryItem[] for AI provider
1255
1252
  context,
1256
1253
  tools: availableTools,
1257
1254
  signal,
@@ -1380,17 +1377,16 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1380
1377
  selectedStep?: Step<TContext, TData>;
1381
1378
  responseDirectives?: string[];
1382
1379
  session: SessionState<TData>;
1383
- history: HistoryItem[]; // Keep as HistoryItem[] for AI provider compatibility
1380
+ history: HistoryItem[];
1384
1381
  context: TContext;
1385
- lastMessageText: string; // String version for buildResponsePrompt
1386
- historyEvents: Event[]; // Event[] version for buildResponsePrompt
1382
+ historyEvents: Event[];
1387
1383
  signal?: AbortSignal;
1388
1384
  }): Promise<{
1389
1385
  message: string;
1390
1386
  toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
1391
1387
  session: SessionState<TData>;
1392
1388
  }> {
1393
- const { selectedRoute, selectedStep, responseDirectives, history, context, lastMessageText, historyEvents, signal } = params;
1389
+ const { selectedRoute, selectedStep, responseDirectives, history, context, historyEvents, signal } = params;
1394
1390
  let session = params.session;
1395
1391
 
1396
1392
  // Determine next step
@@ -1466,8 +1462,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1466
1462
  rules: selectedRoute.getRules(),
1467
1463
  prohibitions: selectedRoute.getProhibitions(),
1468
1464
  directives: responseDirectives,
1469
- history: historyEvents, // Use Event[] for buildResponsePrompt
1470
- lastMessage: lastMessageText, // Use string for buildResponsePrompt
1465
+ history: historyEvents,
1471
1466
  agentOptions: this.agent.getAgentOptions(),
1472
1467
  combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
1473
1468
  combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
@@ -1483,7 +1478,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1483
1478
  const agentOptions = this.agent.getAgentOptions();
1484
1479
  const result = await agentOptions.provider.generateMessage({
1485
1480
  prompt: responsePrompt,
1486
- history: historyEvents, // Use Event[] for AI provider
1481
+ history, // Use HistoryItem[] for AI provider
1487
1482
  context,
1488
1483
  tools: availableTools,
1489
1484
  signal,
@@ -1538,11 +1533,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1538
1533
  session: SessionState<TData>;
1539
1534
  history: HistoryItem[];
1540
1535
  context: TContext;
1541
- lastMessageText: string; // String version for buildResponsePrompt
1542
- historyEvents: Event[]; // Event[] version for buildResponsePrompt
1536
+ historyEvents: Event[];
1543
1537
  signal?: AbortSignal;
1544
1538
  }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
1545
- const { selectedRoute, selectedStep, responseDirectives, history, context, lastMessageText, historyEvents, signal } = params;
1539
+ const { selectedRoute, selectedStep, responseDirectives, history, context, historyEvents, signal } = params;
1546
1540
  let session = params.session;
1547
1541
 
1548
1542
  // Determine next step (same logic as non-streaming)
@@ -1609,8 +1603,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1609
1603
  rules: selectedRoute.getRules(),
1610
1604
  prohibitions: selectedRoute.getProhibitions(),
1611
1605
  directives: responseDirectives,
1612
- history: historyEvents, // Use Event[] for buildResponsePrompt
1613
- lastMessage: lastMessageText, // Use string for buildResponsePrompt
1606
+ history: historyEvents,
1614
1607
  agentOptions: this.agent.getAgentOptions(),
1615
1608
  combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
1616
1609
  combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
@@ -1626,7 +1619,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1626
1619
  const agentOptions = this.agent.getAgentOptions();
1627
1620
  const stream = agentOptions.provider.generateMessageStream({
1628
1621
  prompt: responsePrompt,
1629
- history: historyEvents, // Use Event[] for AI provider
1622
+ history, // Use HistoryItem[] for AI provider
1630
1623
  context,
1631
1624
  tools: availableTools,
1632
1625
  signal,
@@ -1863,34 +1856,34 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1863
1856
  toolLoopCount++;
1864
1857
  logger.debug(`[ResponseModal] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS} with ${toolCalls?.length || 0} tool calls`);
1865
1858
 
1866
- // Create tool result events with proper Event format structure
1867
- const toolResultEvents: Event<ToolEventData>[] = [];
1859
+ // Create tool result history items
1860
+ const toolResultHistoryItems: HistoryItem[] = [];
1868
1861
  for (const toolCall of toolCalls || []) {
1869
1862
  const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1870
1863
  if (tool) {
1871
- // Create proper Event format for tool results
1872
- const toolResultEvent: Event<ToolEventData> = {
1873
- kind: EventKind.TOOL,
1874
- source: MessageRole.AGENT,
1875
- timestamp: new Date().toISOString(),
1876
- data: {
1877
- tool_calls: [
1878
- {
1879
- tool_id: toolCall.toolName,
1880
- arguments: toolCall.arguments,
1881
- result: {
1882
- data: "Tool executed successfully",
1883
- },
1884
- },
1885
- ],
1886
- },
1887
- };
1888
- toolResultEvents.push(toolResultEvent);
1864
+ // Create HistoryItem format for tool results
1865
+ // Add assistant message with tool_calls
1866
+ toolResultHistoryItems.push({
1867
+ role: "assistant" as const,
1868
+ content: null,
1869
+ tool_calls: [{
1870
+ id: toolCall.toolName,
1871
+ name: toolCall.toolName,
1872
+ arguments: toolCall.arguments,
1873
+ }],
1874
+ });
1875
+ // Add tool result
1876
+ toolResultHistoryItems.push({
1877
+ role: "tool" as const,
1878
+ tool_call_id: toolCall.toolName,
1879
+ name: toolCall.toolName,
1880
+ content: "Tool executed successfully",
1881
+ });
1889
1882
  }
1890
1883
  }
1891
1884
 
1892
- // Create updated history with tool results (combine Event arrays)
1893
- const updatedHistoryEvents = [...historyEvents, ...toolResultEvents];
1885
+ // Create updated history with tool results
1886
+ const updatedHistory = [...history, ...toolResultHistoryItems];
1894
1887
 
1895
1888
  // Make follow-up AI call to see if more tools are needed
1896
1889
  // After first iteration, don't provide tools to force a text response
@@ -1905,7 +1898,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1905
1898
 
1906
1899
  const followUpResult = await agentOptions.provider.generateMessage({
1907
1900
  prompt: responsePrompt + (toolLoopCount > 1 ? "\n\nProvide a text response to the user based on the tool results." : ""),
1908
- history: updatedHistoryEvents, // Use Event[] for AI provider
1901
+ history: updatedHistory, // Use HistoryItem[] for AI provider
1909
1902
  context,
1910
1903
  tools: shouldProvideTools ? availableTools : [], // Only provide tools on first iteration
1911
1904
  parameters: responseSchema ? {
@@ -1949,7 +1942,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
1949
1942
  context,
1950
1943
  updateContext: this.agent.updateContext.bind(this.agent),
1951
1944
  updateData: this.agent.updateCollectedData.bind(this.agent),
1952
- history: updatedHistoryEvents, // Use Event[] for tool execution
1945
+ history: historyToEvents(updatedHistory), // Convert to Event[] for tool execution
1953
1946
  data: session.data,
1954
1947
  toolArguments: toolCall.arguments,
1955
1948
  });
@@ -2121,11 +2114,11 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2121
2114
  selectedRoute: Route<TContext, TData>;
2122
2115
  session: SessionState<TData>;
2123
2116
  context: TContext;
2124
- lastMessageText: string; // String version for buildResponsePrompt
2125
- historyEvents: Event[]; // Event[] version for buildResponsePrompt
2117
+ history: HistoryItem[];
2118
+ historyEvents: Event[];
2126
2119
  signal?: AbortSignal;
2127
2120
  }): Promise<string> {
2128
- const { selectedRoute, session, context, lastMessageText, historyEvents, signal } = params;
2121
+ const { selectedRoute, session, context, history, historyEvents, signal } = params;
2129
2122
 
2130
2123
  // Get endStep spec from route
2131
2124
  const endStepSpec = selectedRoute.endStepSpec;
@@ -2181,7 +2174,6 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2181
2174
  completitionPrompt,
2182
2175
  ],
2183
2176
  history: historyEvents,
2184
- lastMessage: lastMessageText,
2185
2177
  agentOptions: this.agent.getAgentOptions(),
2186
2178
  combinedGuidelines: alwaysActiveGuidelines, // Only non-conditional guidelines
2187
2179
  combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
@@ -2196,7 +2188,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2196
2188
 
2197
2189
  const completionResult = await agentOptions.provider.generateMessage({
2198
2190
  prompt: completionPrompt,
2199
- history: historyEvents,
2191
+ history, // Use HistoryItem[] for AI provider
2200
2192
  context,
2201
2193
  signal,
2202
2194
  parameters: { jsonSchema: completionSchema, schemaName: "completion_message" },
@@ -2240,11 +2232,11 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2240
2232
  selectedRoute: Route<TContext, TData>;
2241
2233
  session: SessionState<TData>;
2242
2234
  context: TContext;
2243
- lastMessageText: string; // String version for buildResponsePrompt
2244
- historyEvents: Event[]; // Event[] version for buildResponsePrompt
2235
+ history: HistoryItem[];
2236
+ historyEvents: Event[];
2245
2237
  signal?: AbortSignal;
2246
2238
  }): AsyncGenerator<AgentResponseStreamChunk<TData>> {
2247
- const { selectedRoute, context, lastMessageText, historyEvents, signal } = params;
2239
+ const { selectedRoute, context, history, historyEvents, signal } = params;
2248
2240
  let session = params.session;
2249
2241
 
2250
2242
  // Get endStep spec from route
@@ -2270,8 +2262,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2270
2262
  rules: selectedRoute.getRules(),
2271
2263
  prohibitions: selectedRoute.getProhibitions(),
2272
2264
  directives: undefined, // No directives for completion
2273
- history: historyEvents, // Use Event[] for buildResponsePrompt
2274
- lastMessage: lastMessageText, // Use string for buildResponsePrompt
2265
+ history: historyEvents,
2275
2266
  agentOptions: this.agent.getAgentOptions(),
2276
2267
  combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
2277
2268
  combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
@@ -2284,7 +2275,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2284
2275
  const agentOptions = this.agent.getAgentOptions();
2285
2276
  const stream = agentOptions.provider.generateMessageStream({
2286
2277
  prompt: completionPrompt,
2287
- history: historyEvents, // Use Event[] for AI provider
2278
+ history, // Use HistoryItem[] for AI provider
2288
2279
  context,
2289
2280
  signal,
2290
2281
  parameters: { jsonSchema: responseSchema, schemaName: "completion_message_stream" },
@@ -2353,7 +2344,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2353
2344
  * @private
2354
2345
  */
2355
2346
  private async generateFallbackResponse(params: {
2356
- history: Event[]; // Use Event[] for buildFallbackPrompt
2347
+ history: HistoryItem[];
2357
2348
  context: TContext;
2358
2349
  session: SessionState<TData>;
2359
2350
  signal?: AbortSignal;
@@ -2364,7 +2355,6 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2364
2355
 
2365
2356
  // Build basic response prompt without route context
2366
2357
  const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
2367
- history,
2368
2358
  agentOptions: this.agent.getAgentOptions(),
2369
2359
  terms: this.agent.getTerms(),
2370
2360
  guidelines: this.agent.getGuidelines(),
@@ -2397,7 +2387,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2397
2387
  * @private
2398
2388
  */
2399
2389
  private async *streamFallbackResponse(params: {
2400
- history: Event[]; // Use Event[] for buildFallbackPrompt
2390
+ history: HistoryItem[];
2401
2391
  context: TContext;
2402
2392
  session: SessionState<TData>;
2403
2393
  signal?: AbortSignal;
@@ -2405,7 +2395,6 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
2405
2395
  const { history, context, session, signal } = params;
2406
2396
 
2407
2397
  const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
2408
- history,
2409
2398
  agentOptions: this.agent.getAgentOptions(),
2410
2399
  terms: this.agent.getTerms(),
2411
2400
  guidelines: this.agent.getGuidelines(),
@@ -10,7 +10,7 @@ import type {
10
10
  Tool,
11
11
  RouteTransitionConfig,
12
12
  } from "../types";
13
- import { EventKind, MessageRole } from "../types/history";
13
+ import type { HistoryItem } from "../types/history";
14
14
  import {
15
15
  createSession,
16
16
  enterRoute,
@@ -18,6 +18,7 @@ import {
18
18
  mergeCollected,
19
19
  logger,
20
20
  render,
21
+ historyToEvents,
21
22
  } from "../utils";
22
23
  import { createTemplateContext } from "../utils/template";
23
24
  import { Route } from "../core/Route";
@@ -171,7 +172,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
171
172
  };
172
173
  }
173
174
  }
174
-
175
+
175
176
  // If no pending transition or transition handled, do normal routing
176
177
  if (routes.length > 0 && !selectedRoute) {
177
178
  const orchestration = await this.routingEngine.decideRouteAndStep({
@@ -242,7 +243,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
242
243
  currentStep, // Pass current step instead of undefined to maintain progression
243
244
  createTemplateContext({ data: session.data, session, context: contextToUse })
244
245
  );
245
-
246
+
246
247
  if (candidates.length > 0) {
247
248
  nextStep = candidates[0].step;
248
249
  logger.debug(
@@ -368,7 +369,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
368
369
  selectedRoute?: Route<TContext, TData>;
369
370
  nextStep: Step<TContext, TData>;
370
371
  responsePrompt: string;
371
- history: Event[];
372
+ history: HistoryItem[];
372
373
  context: TContext;
373
374
  session: SessionState<TData>;
374
375
  responseSchema: Record<string, unknown>;
@@ -400,31 +401,30 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
400
401
  );
401
402
 
402
403
  // Add tool execution results to history so AI knows what happened
403
- const toolResultsEvents: Event[] = [];
404
+ const toolResultItems: HistoryItem[] = [];
404
405
  for (const toolCall of currentToolCalls || []) {
405
406
  const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
406
407
  if (tool) {
407
- toolResultsEvents.push({
408
- kind: EventKind.TOOL,
409
- source: MessageRole.AGENT,
410
- timestamp: new Date().toISOString(),
411
- data: {
412
- tool_calls: [
413
- {
414
- tool_id: toolCall.toolName,
415
- arguments: toolCall.arguments,
416
- result: {
417
- data: "Tool executed successfully",
418
- },
419
- },
420
- ],
421
- },
408
+ toolResultItems.push({
409
+ role: "assistant" as const,
410
+ content: null,
411
+ tool_calls: [{
412
+ id: toolCall.toolName,
413
+ name: toolCall.toolName,
414
+ arguments: toolCall.arguments,
415
+ }],
416
+ });
417
+ toolResultItems.push({
418
+ role: "tool" as const,
419
+ tool_call_id: toolCall.toolName,
420
+ name: toolCall.toolName,
421
+ content: "Tool executed successfully",
422
422
  });
423
423
  }
424
424
  }
425
425
 
426
426
  // Create updated history with tool results
427
- const updatedHistory = [...history, ...toolResultsEvents];
427
+ const updatedHistory = [...history, ...toolResultItems];
428
428
 
429
429
  // Make follow-up AI call to see if more tools are needed
430
430
  const followUpResult = await this.options.provider.generateMessage({
@@ -454,7 +454,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
454
454
  selectedRoute,
455
455
  context,
456
456
  session: currentSession,
457
- history: updatedHistory,
457
+ history: historyToEvents(updatedHistory),
458
458
  isStreaming,
459
459
  });
460
460
 
@@ -6,13 +6,14 @@ import type {
6
6
  AiProvider,
7
7
  TemplateContext,
8
8
  } from "../types";
9
+ import { MessageRole } from "../types";
9
10
  import { enterRoute, mergeCollected } from "../utils";
10
11
  import type { Route } from "./Route";
11
12
  import type { Step } from "./Step";
12
13
  import { PromptComposer } from "./PromptComposer";
13
14
  import { PromptSectionCache } from "./PromptSectionCache";
14
15
  import { END_ROUTE_ID } from "../constants";
15
- import { createTemplateContext, getLastMessageFromHistory, logger } from "../utils";
16
+ import { createTemplateContext, getLastMessageFromHistory, logger, eventsToHistory } from "../utils";
16
17
 
17
18
  export interface CandidateStep<TContext = unknown, TData = unknown> {
18
19
  step: Step<TContext, TData>;
@@ -83,6 +84,148 @@ export interface BuildRoutingPromptParams<TContext = unknown, TData = unknown> {
83
84
  export class RoutingEngine<TContext = unknown, TData = unknown> {
84
85
  constructor(private readonly options?: RoutingEngineOptions) { }
85
86
 
87
+ /**
88
+ * Check whether the history contains any user messages.
89
+ * Used to detect "session resume" scenarios where a route/step was
90
+ * pre-set programmatically and the conversation starts with only
91
+ * system messages (or no messages at all).
92
+ * @private
93
+ */
94
+ private hasUserMessages(history: Event[]): boolean {
95
+ return history.some(
96
+ (event) => event.source === MessageRole.USER
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Handle the "session resume" fast-path: when a session already has a
102
+ * pre-set currentRoute (and optionally currentStep) and the conversation
103
+ * history contains no user messages, honor the pre-set position instead
104
+ * of running AI route/step selection.
105
+ *
106
+ * Returns `undefined` when the fast-path does not apply.
107
+ * @private
108
+ */
109
+ private async handleSessionResume(params: {
110
+ routes: Route<TContext, TData>[];
111
+ session: SessionState<TData>;
112
+ history: Event[];
113
+ context: TContext;
114
+ }): Promise<{
115
+ selectedRoute?: Route<TContext, TData>;
116
+ selectedStep?: Step<TContext, TData>;
117
+ session: SessionState<TData>;
118
+ isRouteComplete?: boolean;
119
+ completedRoutes?: Route<TContext, TData>[];
120
+ } | undefined> {
121
+ const { routes, session, history, context } = params;
122
+
123
+ // Fast-path only applies when:
124
+ // 1. Session already has a currentRoute set
125
+ // 2. There are no user messages in the history (system-only or empty)
126
+ if (!session.currentRoute || this.hasUserMessages(history)) {
127
+ return undefined;
128
+ }
129
+
130
+ // Find the pre-set route among available routes
131
+ const presetRoute = routes.find(
132
+ (r) => r.id === session.currentRoute!.id
133
+ );
134
+
135
+ if (!presetRoute) {
136
+ logger.warn(
137
+ `[RoutingEngine] Session resume: pre-set route '${session.currentRoute.id}' not found among available routes, falling back to normal routing`
138
+ );
139
+ return undefined;
140
+ }
141
+
142
+ logger.debug(
143
+ `[RoutingEngine] Session resume: honoring pre-set route '${presetRoute.title}' (no user messages in history)`
144
+ );
145
+
146
+ // Enter route if needed (merges initialData, no-op if already entered)
147
+ const updatedSession = this.enterRouteIfNeeded(session, presetRoute);
148
+
149
+ // Evaluate cross-route completions
150
+ const completedRoutes = this.evaluateRouteCompletions(routes, updatedSession.data || {});
151
+
152
+ // If a currentStep is also pre-set, honor it — stay on that step
153
+ if (session.currentStep) {
154
+ const presetStep = presetRoute.getStep(session.currentStep.id);
155
+
156
+ if (presetStep) {
157
+ logger.debug(
158
+ `[RoutingEngine] Session resume: honoring pre-set step '${presetStep.id}'`
159
+ );
160
+ return {
161
+ selectedRoute: presetRoute,
162
+ selectedStep: presetStep,
163
+ session: updatedSession,
164
+ isRouteComplete: false,
165
+ completedRoutes,
166
+ };
167
+ }
168
+
169
+ logger.warn(
170
+ `[RoutingEngine] Session resume: pre-set step '${session.currentStep.id}' not found in route '${presetRoute.title}', resolving from initial step`
171
+ );
172
+ }
173
+
174
+ // No currentStep pre-set (or it wasn't found) — resolve from initialStep
175
+ // using the standard candidate logic (handles skipIf, etc.)
176
+ const templateContext = createTemplateContext({
177
+ context,
178
+ session: updatedSession,
179
+ history,
180
+ data: updatedSession.data,
181
+ });
182
+
183
+ const candidates = await this.getCandidateStepsWithConditions(
184
+ presetRoute,
185
+ undefined, // No current step — start from beginning
186
+ templateContext
187
+ );
188
+
189
+ if (candidates.length === 0) {
190
+ logger.warn(
191
+ `[RoutingEngine] Session resume: no valid steps found for route '${presetRoute.title}'`
192
+ );
193
+ return {
194
+ selectedRoute: presetRoute,
195
+ selectedStep: undefined,
196
+ session: updatedSession,
197
+ isRouteComplete: false,
198
+ completedRoutes,
199
+ };
200
+ }
201
+
202
+ const candidate = candidates[0];
203
+
204
+ if (candidate.isRouteComplete) {
205
+ logger.debug(
206
+ `[RoutingEngine] Session resume: route '${presetRoute.title}' is already complete`
207
+ );
208
+ return {
209
+ selectedRoute: presetRoute,
210
+ selectedStep: undefined,
211
+ session: updatedSession,
212
+ isRouteComplete: true,
213
+ completedRoutes,
214
+ };
215
+ }
216
+
217
+ logger.debug(
218
+ `[RoutingEngine] Session resume: resolved initial step '${candidate.step.id}'`
219
+ );
220
+ return {
221
+ selectedRoute: presetRoute,
222
+ selectedStep: candidate.step,
223
+ session: updatedSession,
224
+ isRouteComplete: false,
225
+ completedRoutes,
226
+ };
227
+ }
228
+
86
229
  /**
87
230
  * Enter a route if not already in it, merging initial data
88
231
  * @private
@@ -246,7 +389,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
246
389
  }
247
390
  >({
248
391
  prompt: stepPrompt,
249
- history,
392
+ history: eventsToHistory(history),
250
393
  context,
251
394
  signal,
252
395
  parameters: {
@@ -543,6 +686,19 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
543
686
  return { session };
544
687
  }
545
688
 
689
+ // SESSION RESUME: If the session has a pre-set route and there are no user
690
+ // messages in the history, honor the pre-set position without AI routing.
691
+ // This supports programmatic session setup and persistence-based resume.
692
+ const resumeResult = await this.handleSessionResume({
693
+ routes,
694
+ session,
695
+ history,
696
+ context,
697
+ });
698
+ if (resumeResult) {
699
+ return resumeResult;
700
+ }
701
+
546
702
  // CROSS-ROUTE COMPLETION EVALUATION: Check all routes for completion
547
703
  const completedRoutes = this.evaluateRouteCompletions(routes, session.data || {});
548
704
 
@@ -659,7 +815,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
659
815
  RoutingDecisionOutput
660
816
  >({
661
817
  prompt: routingPrompt,
662
- history,
818
+ history: eventsToHistory(history),
663
819
  context,
664
820
  signal,
665
821
  parameters: {