@falai/agent 0.4.1 → 0.5.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 (285) hide show
  1. package/README.md +21 -74
  2. package/dist/cjs/core/Agent.d.ts +22 -29
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +464 -291
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/Events.d.ts +10 -1
  7. package/dist/cjs/core/Events.d.ts.map +1 -1
  8. package/dist/cjs/core/Events.js +3 -2
  9. package/dist/cjs/core/Events.js.map +1 -1
  10. package/dist/cjs/core/PersistenceManager.d.ts +19 -0
  11. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
  12. package/dist/cjs/core/PersistenceManager.js +57 -0
  13. package/dist/cjs/core/PersistenceManager.js.map +1 -1
  14. package/dist/cjs/core/PromptComposer.d.ts +24 -0
  15. package/dist/cjs/core/PromptComposer.d.ts.map +1 -0
  16. package/dist/cjs/core/PromptComposer.js +127 -0
  17. package/dist/cjs/core/PromptComposer.js.map +1 -0
  18. package/dist/cjs/core/ResponseEngine.d.ts +19 -0
  19. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -0
  20. package/dist/cjs/core/ResponseEngine.js +51 -0
  21. package/dist/cjs/core/ResponseEngine.js.map +1 -0
  22. package/dist/cjs/core/Route.d.ts +18 -12
  23. package/dist/cjs/core/Route.d.ts.map +1 -1
  24. package/dist/cjs/core/Route.js +15 -9
  25. package/dist/cjs/core/Route.js.map +1 -1
  26. package/dist/cjs/core/RoutingEngine.d.ts +38 -0
  27. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -0
  28. package/dist/cjs/core/RoutingEngine.js +110 -0
  29. package/dist/cjs/core/RoutingEngine.js.map +1 -0
  30. package/dist/cjs/core/State.d.ts +15 -4
  31. package/dist/cjs/core/State.d.ts.map +1 -1
  32. package/dist/cjs/core/State.js +21 -2
  33. package/dist/cjs/core/State.js.map +1 -1
  34. package/dist/cjs/core/ToolExecutor.d.ts +29 -0
  35. package/dist/cjs/core/ToolExecutor.d.ts.map +1 -0
  36. package/dist/cjs/core/ToolExecutor.js +73 -0
  37. package/dist/cjs/core/ToolExecutor.js.map +1 -0
  38. package/dist/cjs/core/Transition.d.ts +5 -5
  39. package/dist/cjs/core/Transition.d.ts.map +1 -1
  40. package/dist/cjs/core/Transition.js.map +1 -1
  41. package/dist/cjs/index.d.ts +6 -8
  42. package/dist/cjs/index.d.ts.map +1 -1
  43. package/dist/cjs/index.js +8 -10
  44. package/dist/cjs/index.js.map +1 -1
  45. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  46. package/dist/cjs/providers/AnthropicProvider.js +10 -13
  47. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  48. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  49. package/dist/cjs/providers/GeminiProvider.js +12 -8
  50. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  51. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  52. package/dist/cjs/providers/OpenAIProvider.js +10 -53
  53. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  54. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  55. package/dist/cjs/providers/OpenRouterProvider.js +10 -53
  56. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  57. package/dist/cjs/types/agent.d.ts +13 -12
  58. package/dist/cjs/types/agent.d.ts.map +1 -1
  59. package/dist/cjs/types/ai.d.ts +8 -2
  60. package/dist/cjs/types/ai.d.ts.map +1 -1
  61. package/dist/cjs/types/history.d.ts +8 -0
  62. package/dist/cjs/types/history.d.ts.map +1 -1
  63. package/dist/cjs/types/index.d.ts +0 -3
  64. package/dist/cjs/types/index.d.ts.map +1 -1
  65. package/dist/cjs/types/index.js +1 -3
  66. package/dist/cjs/types/index.js.map +1 -1
  67. package/dist/cjs/types/route.d.ts +39 -4
  68. package/dist/cjs/types/route.d.ts.map +1 -1
  69. package/dist/cjs/types/routing.d.ts +16 -0
  70. package/dist/cjs/types/routing.d.ts.map +1 -0
  71. package/dist/cjs/types/routing.js +3 -0
  72. package/dist/cjs/types/routing.js.map +1 -0
  73. package/dist/cjs/types/schema.d.ts +22 -0
  74. package/dist/cjs/types/schema.d.ts.map +1 -0
  75. package/dist/cjs/types/schema.js +3 -0
  76. package/dist/cjs/types/schema.js.map +1 -0
  77. package/dist/cjs/types/session.d.ts +72 -0
  78. package/dist/cjs/types/session.d.ts.map +1 -0
  79. package/dist/cjs/types/session.js +140 -0
  80. package/dist/cjs/types/session.js.map +1 -0
  81. package/dist/cjs/types/tool.d.ts +11 -5
  82. package/dist/cjs/types/tool.d.ts.map +1 -1
  83. package/dist/cjs/utils/id.d.ts +0 -5
  84. package/dist/cjs/utils/id.d.ts.map +1 -1
  85. package/dist/cjs/utils/id.js +0 -10
  86. package/dist/cjs/utils/id.js.map +1 -1
  87. package/dist/cjs/utils/schema.d.ts +17 -0
  88. package/dist/cjs/utils/schema.d.ts.map +1 -0
  89. package/dist/cjs/utils/schema.js +32 -0
  90. package/dist/cjs/utils/schema.js.map +1 -0
  91. package/dist/core/Agent.d.ts +22 -29
  92. package/dist/core/Agent.d.ts.map +1 -1
  93. package/dist/core/Agent.js +464 -291
  94. package/dist/core/Agent.js.map +1 -1
  95. package/dist/core/Events.d.ts +10 -1
  96. package/dist/core/Events.d.ts.map +1 -1
  97. package/dist/core/Events.js +3 -2
  98. package/dist/core/Events.js.map +1 -1
  99. package/dist/core/PersistenceManager.d.ts +19 -0
  100. package/dist/core/PersistenceManager.d.ts.map +1 -1
  101. package/dist/core/PersistenceManager.js +57 -0
  102. package/dist/core/PersistenceManager.js.map +1 -1
  103. package/dist/core/PromptComposer.d.ts +24 -0
  104. package/dist/core/PromptComposer.d.ts.map +1 -0
  105. package/dist/core/PromptComposer.js +123 -0
  106. package/dist/core/PromptComposer.js.map +1 -0
  107. package/dist/core/ResponseEngine.d.ts +19 -0
  108. package/dist/core/ResponseEngine.d.ts.map +1 -0
  109. package/dist/core/ResponseEngine.js +47 -0
  110. package/dist/core/ResponseEngine.js.map +1 -0
  111. package/dist/core/Route.d.ts +18 -12
  112. package/dist/core/Route.d.ts.map +1 -1
  113. package/dist/core/Route.js +15 -9
  114. package/dist/core/Route.js.map +1 -1
  115. package/dist/core/RoutingEngine.d.ts +38 -0
  116. package/dist/core/RoutingEngine.d.ts.map +1 -0
  117. package/dist/core/RoutingEngine.js +106 -0
  118. package/dist/core/RoutingEngine.js.map +1 -0
  119. package/dist/core/State.d.ts +15 -4
  120. package/dist/core/State.d.ts.map +1 -1
  121. package/dist/core/State.js +21 -2
  122. package/dist/core/State.js.map +1 -1
  123. package/dist/core/ToolExecutor.d.ts +29 -0
  124. package/dist/core/ToolExecutor.d.ts.map +1 -0
  125. package/dist/core/ToolExecutor.js +69 -0
  126. package/dist/core/ToolExecutor.js.map +1 -0
  127. package/dist/core/Transition.d.ts +5 -5
  128. package/dist/core/Transition.d.ts.map +1 -1
  129. package/dist/core/Transition.js.map +1 -1
  130. package/dist/index.d.ts +6 -8
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +3 -5
  133. package/dist/index.js.map +1 -1
  134. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  135. package/dist/providers/AnthropicProvider.js +10 -13
  136. package/dist/providers/AnthropicProvider.js.map +1 -1
  137. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  138. package/dist/providers/GeminiProvider.js +12 -8
  139. package/dist/providers/GeminiProvider.js.map +1 -1
  140. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  141. package/dist/providers/OpenAIProvider.js +10 -53
  142. package/dist/providers/OpenAIProvider.js.map +1 -1
  143. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  144. package/dist/providers/OpenRouterProvider.js +10 -53
  145. package/dist/providers/OpenRouterProvider.js.map +1 -1
  146. package/dist/types/agent.d.ts +13 -12
  147. package/dist/types/agent.d.ts.map +1 -1
  148. package/dist/types/ai.d.ts +8 -2
  149. package/dist/types/ai.d.ts.map +1 -1
  150. package/dist/types/history.d.ts +8 -0
  151. package/dist/types/history.d.ts.map +1 -1
  152. package/dist/types/index.d.ts +0 -3
  153. package/dist/types/index.d.ts.map +1 -1
  154. package/dist/types/index.js +0 -1
  155. package/dist/types/index.js.map +1 -1
  156. package/dist/types/route.d.ts +39 -4
  157. package/dist/types/route.d.ts.map +1 -1
  158. package/dist/types/routing.d.ts +16 -0
  159. package/dist/types/routing.d.ts.map +1 -0
  160. package/dist/types/routing.js +2 -0
  161. package/dist/types/routing.js.map +1 -0
  162. package/dist/types/schema.d.ts +22 -0
  163. package/dist/types/schema.d.ts.map +1 -0
  164. package/dist/types/schema.js +2 -0
  165. package/dist/types/schema.js.map +1 -0
  166. package/dist/types/session.d.ts +72 -0
  167. package/dist/types/session.d.ts.map +1 -0
  168. package/dist/types/session.js +132 -0
  169. package/dist/types/session.js.map +1 -0
  170. package/dist/types/tool.d.ts +11 -5
  171. package/dist/types/tool.d.ts.map +1 -1
  172. package/dist/utils/id.d.ts +0 -5
  173. package/dist/utils/id.d.ts.map +1 -1
  174. package/dist/utils/id.js +0 -9
  175. package/dist/utils/id.js.map +1 -1
  176. package/dist/utils/schema.d.ts +17 -0
  177. package/dist/utils/schema.d.ts.map +1 -0
  178. package/dist/utils/schema.js +27 -0
  179. package/dist/utils/schema.js.map +1 -0
  180. package/docs/ADAPTERS.md +83 -3
  181. package/docs/API_REFERENCE.md +95 -104
  182. package/docs/ARCHITECTURE.md +284 -286
  183. package/docs/CONSTRUCTOR_OPTIONS.md +192 -135
  184. package/docs/CONTEXT_MANAGEMENT.md +311 -28
  185. package/docs/CONTRIBUTING.md +1 -1
  186. package/docs/DOMAINS.md +61 -0
  187. package/docs/GETTING_STARTED.md +177 -88
  188. package/docs/PERSISTENCE.md +170 -23
  189. package/docs/README.md +7 -10
  190. package/examples/business-onboarding.ts +21 -9
  191. package/examples/company-qna-agent.ts +508 -0
  192. package/examples/declarative-agent.ts +143 -26
  193. package/examples/domain-scoping.ts +31 -10
  194. package/examples/extracted-data-modification.ts +415 -0
  195. package/examples/healthcare-agent.ts +194 -90
  196. package/examples/openai-agent.ts +67 -25
  197. package/examples/opensearch-persistence.ts +455 -151
  198. package/examples/persistent-onboarding.ts +162 -96
  199. package/examples/prisma-persistence.ts +371 -125
  200. package/examples/redis-persistence.ts +393 -23
  201. package/examples/rules-prohibitions.ts +32 -11
  202. package/examples/streaming-agent.ts +61 -13
  203. package/examples/travel-agent.ts +266 -133
  204. package/package.json +1 -1
  205. package/src/core/Agent.ts +674 -356
  206. package/src/core/Events.ts +12 -2
  207. package/src/core/PersistenceManager.ts +83 -0
  208. package/src/core/PromptComposer.ts +143 -0
  209. package/src/core/ResponseEngine.ts +82 -0
  210. package/src/core/Route.ts +32 -17
  211. package/src/core/RoutingEngine.ts +165 -0
  212. package/src/core/State.ts +55 -15
  213. package/src/core/ToolExecutor.ts +117 -0
  214. package/src/core/Transition.ts +5 -5
  215. package/src/index.ts +12 -21
  216. package/src/providers/AnthropicProvider.ts +10 -13
  217. package/src/providers/GeminiProvider.ts +12 -8
  218. package/src/providers/OpenAIProvider.ts +10 -56
  219. package/src/providers/OpenRouterProvider.ts +10 -56
  220. package/src/types/agent.ts +16 -13
  221. package/src/types/ai.ts +6 -2
  222. package/src/types/history.ts +8 -0
  223. package/src/types/index.ts +0 -11
  224. package/src/types/route.ts +41 -5
  225. package/src/types/routing.ts +18 -0
  226. package/src/types/schema.ts +23 -0
  227. package/src/types/session.ts +207 -0
  228. package/src/types/tool.ts +29 -7
  229. package/src/utils/id.ts +0 -10
  230. package/src/utils/schema.ts +32 -0
  231. package/dist/cjs/core/ConditionEvaluator.d.ts +0 -72
  232. package/dist/cjs/core/ConditionEvaluator.d.ts.map +0 -1
  233. package/dist/cjs/core/ConditionEvaluator.js +0 -272
  234. package/dist/cjs/core/ConditionEvaluator.js.map +0 -1
  235. package/dist/cjs/core/Observation.d.ts +0 -24
  236. package/dist/cjs/core/Observation.d.ts.map +0 -1
  237. package/dist/cjs/core/Observation.js +0 -39
  238. package/dist/cjs/core/Observation.js.map +0 -1
  239. package/dist/cjs/core/PreparationEngine.d.ts +0 -116
  240. package/dist/cjs/core/PreparationEngine.d.ts.map +0 -1
  241. package/dist/cjs/core/PreparationEngine.js +0 -353
  242. package/dist/cjs/core/PreparationEngine.js.map +0 -1
  243. package/dist/cjs/core/PromptBuilder.d.ts +0 -136
  244. package/dist/cjs/core/PromptBuilder.d.ts.map +0 -1
  245. package/dist/cjs/core/PromptBuilder.js +0 -421
  246. package/dist/cjs/core/PromptBuilder.js.map +0 -1
  247. package/dist/cjs/types/observation.d.ts +0 -27
  248. package/dist/cjs/types/observation.d.ts.map +0 -1
  249. package/dist/cjs/types/observation.js +0 -6
  250. package/dist/cjs/types/observation.js.map +0 -1
  251. package/dist/cjs/types/prompt.d.ts +0 -46
  252. package/dist/cjs/types/prompt.d.ts.map +0 -1
  253. package/dist/cjs/types/prompt.js +0 -19
  254. package/dist/cjs/types/prompt.js.map +0 -1
  255. package/dist/core/ConditionEvaluator.d.ts +0 -72
  256. package/dist/core/ConditionEvaluator.d.ts.map +0 -1
  257. package/dist/core/ConditionEvaluator.js +0 -268
  258. package/dist/core/ConditionEvaluator.js.map +0 -1
  259. package/dist/core/Observation.d.ts +0 -24
  260. package/dist/core/Observation.d.ts.map +0 -1
  261. package/dist/core/Observation.js +0 -35
  262. package/dist/core/Observation.js.map +0 -1
  263. package/dist/core/PreparationEngine.d.ts +0 -116
  264. package/dist/core/PreparationEngine.d.ts.map +0 -1
  265. package/dist/core/PreparationEngine.js +0 -349
  266. package/dist/core/PreparationEngine.js.map +0 -1
  267. package/dist/core/PromptBuilder.d.ts +0 -136
  268. package/dist/core/PromptBuilder.d.ts.map +0 -1
  269. package/dist/core/PromptBuilder.js +0 -417
  270. package/dist/core/PromptBuilder.js.map +0 -1
  271. package/dist/types/observation.d.ts +0 -27
  272. package/dist/types/observation.d.ts.map +0 -1
  273. package/dist/types/observation.js +0 -5
  274. package/dist/types/observation.js.map +0 -1
  275. package/dist/types/prompt.d.ts +0 -46
  276. package/dist/types/prompt.d.ts.map +0 -1
  277. package/dist/types/prompt.js +0 -16
  278. package/dist/types/prompt.js.map +0 -1
  279. package/docs/STRUCTURE.md +0 -58
  280. package/src/core/ConditionEvaluator.ts +0 -381
  281. package/src/core/Observation.ts +0 -47
  282. package/src/core/PreparationEngine.ts +0 -561
  283. package/src/core/PromptBuilder.ts +0 -617
  284. package/src/types/observation.ts +0 -29
  285. package/src/types/prompt.ts +0 -49
@@ -71,7 +71,16 @@ export function createMessageEvent(
71
71
  source: EventSource,
72
72
  participantName: string,
73
73
  message: string,
74
- timestamp?: string
74
+ options?: {
75
+ timestamp?: string;
76
+ session?: {
77
+ routeId?: string;
78
+ routeTitle?: string;
79
+ stateId?: string;
80
+ stateDescription?: string;
81
+ extracted?: Record<string, unknown>;
82
+ };
83
+ }
75
84
  ): Event<MessageEventData> {
76
85
  return {
77
86
  kind: EventKind.MESSAGE,
@@ -79,8 +88,9 @@ export function createMessageEvent(
79
88
  data: {
80
89
  participant: { display_name: participantName },
81
90
  message,
91
+ session: options?.session,
82
92
  },
83
- timestamp: timestamp || new Date().toISOString(),
93
+ timestamp: options?.timestamp || new Date().toISOString(),
84
94
  };
85
95
  }
86
96
 
@@ -14,6 +14,12 @@ import type {
14
14
  MessageRepository,
15
15
  } from "../types/persistence";
16
16
  import type { Event } from "../types/history";
17
+ import type { SessionState } from "../types/session";
18
+ import {
19
+ createSession,
20
+ sessionStateToData,
21
+ sessionDataToState,
22
+ } from "../types/session";
17
23
 
18
24
  /**
19
25
  * Manager for handling persistence operations
@@ -219,4 +225,81 @@ export class PersistenceManager {
219
225
  .map((m) => this.messageToEvent(m))
220
226
  .filter((e): e is Event => e !== undefined);
221
227
  }
228
+
229
+ /**
230
+ * Save SessionState to database
231
+ * Converts SessionState to SessionData and persists it
232
+ */
233
+ async saveSessionState<TExtracted = Record<string, unknown>>(
234
+ sessionId: string,
235
+ sessionState: SessionState<TExtracted>
236
+ ): Promise<SessionData | null> {
237
+ const persistenceData = sessionStateToData(sessionState);
238
+
239
+ return await this.sessionRepository.update(sessionId, {
240
+ currentRoute: persistenceData.currentRoute,
241
+ currentState: persistenceData.currentState,
242
+ collectedData: persistenceData.collectedData,
243
+ lastMessageAt: new Date(),
244
+ });
245
+ }
246
+
247
+ /**
248
+ * Load SessionState from database
249
+ * Converts SessionData to SessionState
250
+ */
251
+ async loadSessionState<TExtracted = Record<string, unknown>>(
252
+ sessionId: string
253
+ ): Promise<SessionState<TExtracted> | null> {
254
+ const sessionData = await this.sessionRepository.findById(sessionId);
255
+
256
+ if (!sessionData) {
257
+ return null;
258
+ }
259
+
260
+ const stateData = sessionDataToState<TExtracted>({
261
+ currentRoute: sessionData.currentRoute,
262
+ currentState: sessionData.currentState,
263
+ collectedData: sessionData.collectedData,
264
+ });
265
+
266
+ // Create a full session state with the loaded data
267
+ const session = createSession<TExtracted>({
268
+ sessionId,
269
+ createdAt: sessionData.createdAt,
270
+ lastUpdatedAt: sessionData.updatedAt,
271
+ });
272
+
273
+ return {
274
+ ...session,
275
+ ...stateData,
276
+ };
277
+ }
278
+
279
+ /**
280
+ * Create session with SessionState support
281
+ * Returns both SessionData and initialized SessionState
282
+ */
283
+ async createSessionWithState<TExtracted = Record<string, unknown>>(
284
+ options: CreateSessionOptions
285
+ ): Promise<{
286
+ sessionData: SessionData;
287
+ sessionState: SessionState<TExtracted>;
288
+ }> {
289
+ const sessionData = await this.createSession(options);
290
+
291
+ // Create SessionState with database session ID
292
+ const sessionState = createSession<TExtracted>({
293
+ sessionId: sessionData.id,
294
+ createdAt: sessionData.createdAt,
295
+ lastUpdatedAt: sessionData.updatedAt,
296
+ });
297
+
298
+ // If initial data was provided, merge it as extracted data
299
+ if (options.initialData) {
300
+ sessionState.extracted = options.initialData as Partial<TExtracted>;
301
+ }
302
+
303
+ return { sessionData, sessionState };
304
+ }
222
305
  }
@@ -0,0 +1,143 @@
1
+ import type { Event } from "../types/history";
2
+ import type { Term, Guideline, Capability } from "../types/agent";
3
+ import type { Route } from "./Route";
4
+
5
+ export class PromptComposer<TContext = unknown> {
6
+ private parts: string[] = [];
7
+
8
+ // Specific, typed sections tailored to the framework
9
+
10
+ addAgentMeta(agent: {
11
+ name: string;
12
+ goal?: string;
13
+ description?: string;
14
+ }): this {
15
+ const lines: string[] = [];
16
+ lines.push(`Agent: ${agent.name}`);
17
+ if (agent.goal) lines.push(`Goal: ${agent.goal}`);
18
+ if (agent.description) lines.push(`Description: ${agent.description}`);
19
+ this.parts.push(lines.join("\n"));
20
+ return this;
21
+ }
22
+
23
+ addPersonality(personality?: string): this {
24
+ if (personality && personality.trim().length) {
25
+ this.parts.push(`Personality: ${personality.trim()}`);
26
+ }
27
+ return this;
28
+ }
29
+
30
+ addRoutingOverview(routes: Route<TContext>[]): this {
31
+ return this.addActiveRoutes(routes);
32
+ }
33
+
34
+ addScoringRules(): this {
35
+ this.parts.push(
36
+ [
37
+ "Scoring rules:",
38
+ "- 90-100: explicit keywords + clear intent",
39
+ "- 70-89: strong contextual evidence + relevant keywords",
40
+ "- 50-69: moderate relevance",
41
+ "- 30-49: weak connection or ambiguous",
42
+ "- 0-29: minimal/none",
43
+ "Return ONLY JSON matching the provided schema. Include scores for ALL routes.",
44
+ ].join("\n")
45
+ );
46
+ return this;
47
+ }
48
+
49
+ addInstruction(text: string): this {
50
+ if (text) this.parts.push(text);
51
+ return this;
52
+ }
53
+
54
+ addInteractionHistory(history: Event[], note?: string): this {
55
+ const recent = history
56
+ .slice(-10)
57
+ .map((e) => JSON.stringify(e))
58
+ .join("\n");
59
+ const header = note ? `${note}\n` : "";
60
+ this.parts.push(`${header}Recent conversation events:\n${recent}`);
61
+ return this;
62
+ }
63
+
64
+ addLastMessage(message: string): this {
65
+ this.parts.push(`Last user message:\n${message}`);
66
+ return this;
67
+ }
68
+
69
+ addGlossary(terms: Term[]): this {
70
+ if (!terms.length) return this;
71
+ const text = terms
72
+ .map(
73
+ (t, i) =>
74
+ `${i + 1}) ${t.name}${
75
+ t.synonyms?.length ? ` (synonyms: ${t.synonyms.join(", ")})` : ""
76
+ }: ${t.description}`
77
+ )
78
+ .join("\n");
79
+ this.parts.push(`Glossary:\n${text}`);
80
+ return this;
81
+ }
82
+
83
+ addGuidelines(guidelines: Guideline[]): this {
84
+ const enabled = guidelines.filter((g) => g.enabled !== false);
85
+ if (!enabled.length) return this;
86
+ const text = enabled
87
+ .map((g, i) => {
88
+ const cond = g.condition
89
+ ? `When ${g.condition}, then ${g.action}`
90
+ : g.action;
91
+ return `Guideline #${i + 1}) ${cond}`;
92
+ })
93
+ .join("\n");
94
+ this.parts.push(`Guidelines:\n${text}`);
95
+ return this;
96
+ }
97
+
98
+ addCapabilities(capabilities: Capability[]): this {
99
+ if (!capabilities.length) return this;
100
+ const text = capabilities
101
+ .map((c, i) => `Capability ${i + 1}: ${c.title}\n${c.description}`)
102
+ .join("\n\n");
103
+ this.parts.push(`Capabilities:\n${text}`);
104
+ return this;
105
+ }
106
+
107
+ addActiveRoutes(routes: Route<TContext>[]): this {
108
+ if (!routes.length) return this;
109
+ const text = routes
110
+ .map((r, i) => {
111
+ const conditions = r.conditions.length
112
+ ? `\n Triggered when: ${r.conditions.join(" OR ")}`
113
+ : "";
114
+ const desc = r.description ? `\n ${r.description}` : "";
115
+ const rules = r.getRules();
116
+ const prohibitions = r.getProhibitions();
117
+ const rulesInfo = rules.length
118
+ ? `\n RULES: ${rules.map((x, idx) => `${idx + 1}. ${x}`).join("; ")}`
119
+ : "";
120
+ const prohibitionsInfo = prohibitions.length
121
+ ? `\n PROHIBITIONS: ${prohibitions
122
+ .map((x, idx) => `${idx + 1}. ${x}`)
123
+ .join("; ")}`
124
+ : "";
125
+ return `${i + 1}) ${
126
+ r.title
127
+ }${desc}${conditions}${rulesInfo}${prohibitionsInfo}`;
128
+ })
129
+ .join("\n\n");
130
+ this.parts.push(`Available routes:\n${text}`);
131
+ return this;
132
+ }
133
+
134
+ addDirectives(directives?: string[]): this {
135
+ if (!directives?.length) return this;
136
+ this.parts.push(`Address concisely:\n- ${directives.join("\n- ")}`);
137
+ return this;
138
+ }
139
+
140
+ build(): string {
141
+ return this.parts.filter(Boolean).join("\n\n").trim();
142
+ }
143
+ }
@@ -0,0 +1,82 @@
1
+ import type { Event } from "../types/history";
2
+ import type { Route } from "./Route";
3
+ import type { State } from "./State";
4
+ import type { StructuredSchema } from "../types/schema";
5
+ import { PromptComposer } from "./PromptComposer";
6
+
7
+ export interface ResponseOutput<TData = unknown> {
8
+ message: string;
9
+ data?: TData;
10
+ contextUpdate?: Record<string, unknown>;
11
+ }
12
+
13
+ export class ResponseEngine<TContext = unknown> {
14
+ responseSchemaForRoute<TExtracted = unknown>(
15
+ route: Route<TContext, TExtracted>,
16
+ currentState?: State<TContext, TExtracted>
17
+ ): StructuredSchema {
18
+ const base: StructuredSchema = {
19
+ type: "object",
20
+ properties: {
21
+ message: { type: "string", description: "Final user-facing message" },
22
+ data: route.responseOutputSchema || { type: "object" },
23
+ contextUpdate: { type: "object" },
24
+ },
25
+ required: ["message"],
26
+ additionalProperties: false,
27
+ };
28
+
29
+ // Add gather fields from current state
30
+ if (currentState?.gatherFields && route.gatherSchema?.properties) {
31
+ for (const field of currentState.gatherFields) {
32
+ const fieldSchema = route.gatherSchema.properties[field];
33
+ if (fieldSchema) {
34
+ base.properties![field] = fieldSchema;
35
+ }
36
+ }
37
+ }
38
+
39
+ return base;
40
+ }
41
+
42
+ buildResponsePrompt(
43
+ route: Route<TContext>,
44
+ rules: string[],
45
+ prohibitions: string[],
46
+ directives: string[] | undefined,
47
+ history: Event[],
48
+ lastMessage: string,
49
+ agentMeta?: {
50
+ name?: string;
51
+ goal?: string;
52
+ description?: string;
53
+ personality?: string;
54
+ }
55
+ ): string {
56
+ const pc = new PromptComposer();
57
+ if (agentMeta?.name || agentMeta?.goal || agentMeta?.description)
58
+ pc.addAgentMeta({
59
+ name: agentMeta?.name || "Agent",
60
+ goal: agentMeta?.goal,
61
+ description: agentMeta?.description,
62
+ });
63
+ const personality =
64
+ agentMeta?.personality || "Tone: brief, natural, 1-2 short sentences.";
65
+ pc.addPersonality(personality);
66
+ pc.addInstruction(
67
+ `Route: ${route.title}${
68
+ route.description ? ` — ${route.description}` : ""
69
+ }`
70
+ );
71
+ if (rules.length) pc.addInstruction(`Rules:\n- ${rules.join("\n- ")}`);
72
+ if (prohibitions.length)
73
+ pc.addInstruction(`Prohibitions:\n- ${prohibitions.join("\n- ")}`);
74
+ pc.addDirectives(directives);
75
+ pc.addInteractionHistory(history);
76
+ pc.addLastMessage(lastMessage);
77
+ pc.addInstruction(
78
+ "Return ONLY JSON matching the schema (message + optional data/contextUpdate)."
79
+ );
80
+ return pc.build();
81
+ }
82
+ }
package/src/core/Route.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { RouteOptions, RouteRef } from "../types/route";
6
+ import type { StructuredSchema } from "../types/schema";
6
7
  import type { Guideline } from "../types/agent";
7
8
 
8
9
  import { State } from "./State";
@@ -11,7 +12,7 @@ import { generateRouteId } from "../utils/id";
11
12
  /**
12
13
  * Represents a conversational route/journey
13
14
  */
14
- export class Route<TContext = unknown> {
15
+ export class Route<TContext = unknown, TExtracted = unknown> {
15
16
  public readonly id: string;
16
17
  public readonly title: string;
17
18
  public readonly description?: string;
@@ -19,10 +20,14 @@ export class Route<TContext = unknown> {
19
20
  public readonly domains?: string[];
20
21
  public readonly rules: string[];
21
22
  public readonly prohibitions: string[];
22
- public readonly initialState: State<TContext>;
23
+ public readonly initialState: State<TContext, TExtracted>;
24
+ public readonly responseOutputSchema?: StructuredSchema;
25
+ public readonly gatherSchema?: StructuredSchema;
26
+ public readonly initialData?: Partial<TExtracted>;
27
+ private routingExtrasSchema?: StructuredSchema;
23
28
  private guidelines: Guideline[] = [];
24
29
 
25
- constructor(options: RouteOptions) {
30
+ constructor(options: RouteOptions<TExtracted>) {
26
31
  // Use provided ID or generate a deterministic one from the title
27
32
  this.id = options.id || generateRouteId(options.title);
28
33
  this.title = options.title;
@@ -31,7 +36,14 @@ export class Route<TContext = unknown> {
31
36
  this.domains = options.domains;
32
37
  this.rules = options.rules || ([] as string[]);
33
38
  this.prohibitions = options.prohibitions || ([] as string[]);
34
- this.initialState = new State<TContext>(this.id, "Initial state");
39
+ this.initialState = new State<TContext, TExtracted>(
40
+ this.id,
41
+ "Initial state"
42
+ );
43
+ this.routingExtrasSchema = options.routingExtrasSchema;
44
+ this.responseOutputSchema = options.responseOutputSchema;
45
+ this.gatherSchema = options.gatherSchema;
46
+ this.initialData = options.initialData;
35
47
 
36
48
  // Initialize guidelines from options
37
49
  if (options.guidelines) {
@@ -83,32 +95,35 @@ export class Route<TContext = unknown> {
83
95
  }
84
96
 
85
97
  /**
86
- * Get route reference
98
+ * Get optional extras schema requested during routing
87
99
  */
88
- getRef(): RouteRef {
89
- return {
90
- id: this.id,
91
- };
100
+ getRoutingExtrasSchema(): StructuredSchema | undefined {
101
+ return this.routingExtrasSchema;
92
102
  }
93
103
 
94
104
  /**
95
- * Create a route reference that includes this route instance
96
- * Useful for disambiguation
105
+ * Get optional structured response schema for this route's message
97
106
  */
98
- toRef(): RouteRef & { route: Route<TContext> } {
107
+ getResponseOutputSchema(): StructuredSchema | undefined {
108
+ return this.responseOutputSchema;
109
+ }
110
+
111
+ /**
112
+ * Get route reference
113
+ */
114
+ getRef(): RouteRef {
99
115
  return {
100
116
  id: this.id,
101
- route: this,
102
117
  };
103
118
  }
104
119
 
105
120
  /**
106
121
  * Get all states in this route (via traversal from initial state)
107
122
  */
108
- getAllStates(): State<TContext>[] {
123
+ getAllStates(): State<TContext, TExtracted>[] {
109
124
  const visited = new Set<string>();
110
- const states: State<TContext>[] = [];
111
- const queue: State<TContext>[] = [this.initialState];
125
+ const states: State<TContext, TExtracted>[] = [];
126
+ const queue: State<TContext, TExtracted>[] = [this.initialState];
112
127
 
113
128
  while (queue.length > 0) {
114
129
  const current = queue.shift()!;
@@ -137,7 +152,7 @@ export class Route<TContext = unknown> {
137
152
  * @param stateId - The state ID to find
138
153
  * @returns The state if found, undefined otherwise
139
154
  */
140
- getState(stateId: string): State<TContext> | undefined {
155
+ getState(stateId: string): State<TContext, TExtracted> | undefined {
141
156
  const states = this.getAllStates();
142
157
  return states.find((state) => state.id === stateId);
143
158
  }
@@ -0,0 +1,165 @@
1
+ import type { Event } from "../types/history";
2
+ import type { Route } from "./Route";
3
+ import type { StructuredSchema } from "../types/schema";
4
+ import type { RoutingDecision } from "../types/routing";
5
+ import type { SessionState } from "../types/session";
6
+ import { PromptComposer } from "./PromptComposer";
7
+
8
+ export interface RoutingDecisionOutput {
9
+ context: string;
10
+ routes: Record<string, number>;
11
+ responseDirectives?: string[];
12
+ extractions?: Array<{
13
+ name: string;
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ value: any;
16
+ confidence?: number;
17
+ source?: "message" | "history";
18
+ }>;
19
+ contextUpdate?: Record<string, unknown>;
20
+ }
21
+
22
+ export interface RoutingEngineOptions {
23
+ allowRouteSwitch?: boolean;
24
+ switchThreshold?: number; // 0-100
25
+ maxCandidates?: number;
26
+ }
27
+
28
+ export class RoutingEngine<TContext = unknown> {
29
+ constructor(private readonly options?: RoutingEngineOptions) {}
30
+
31
+ buildDynamicRoutingSchema(
32
+ routes: Route<TContext>[],
33
+ extrasSchema?: StructuredSchema
34
+ ): StructuredSchema {
35
+ const routeIds = routes.map((r) => r.id);
36
+ const routeProperties: Record<string, StructuredSchema> = {};
37
+ for (const id of routeIds) {
38
+ routeProperties[id] = {
39
+ type: "number",
40
+ nullable: false,
41
+ description: `Score for route ${id} based on direct evidence, context and semantic fit (0-100)`,
42
+ minimum: 0,
43
+ maximum: 100,
44
+ } as StructuredSchema;
45
+ }
46
+
47
+ const base: StructuredSchema = {
48
+ description:
49
+ "Full intent analysis: score ALL available routes (0-100) using evidence and context",
50
+ type: "object",
51
+ properties: {
52
+ context: {
53
+ type: "string",
54
+ nullable: false,
55
+ description: "Brief summary of the user's intent/context",
56
+ },
57
+ routes: {
58
+ type: "object",
59
+ properties: routeProperties,
60
+ required: routeIds,
61
+ nullable: false,
62
+ description: "Mapping of routeId to score (0-100)",
63
+ },
64
+ responseDirectives: {
65
+ type: "array",
66
+ items: { type: "string" },
67
+ description:
68
+ "Optional bullet points the response should address (concise)",
69
+ },
70
+ },
71
+ required: ["context", "routes"],
72
+ additionalProperties: false,
73
+ };
74
+
75
+ if (extrasSchema) {
76
+ base.properties = base.properties || {};
77
+ base.properties.extractions = extrasSchema;
78
+ }
79
+
80
+ return base;
81
+ }
82
+
83
+ buildRoutingPrompt(
84
+ history: Event[],
85
+ routes: Route<TContext>[],
86
+ lastMessage: string,
87
+ agentMeta?: {
88
+ name?: string;
89
+ goal?: string;
90
+ description?: string;
91
+ personality?: string;
92
+ },
93
+ session?: SessionState
94
+ ): string {
95
+ const pc = new PromptComposer();
96
+ if (agentMeta?.name || agentMeta?.goal || agentMeta?.description) {
97
+ pc.addAgentMeta({
98
+ name: agentMeta?.name || "Agent",
99
+ description: agentMeta?.description,
100
+ goal: agentMeta?.goal,
101
+ });
102
+ }
103
+ const personality =
104
+ agentMeta?.personality || "Tone: brief, natural, 1-2 short sentences.";
105
+ pc.addPersonality(personality);
106
+ pc.addInstruction(
107
+ "Task: Intent analysis and route scoring (0-100). Score ALL listed routes."
108
+ );
109
+
110
+ // Add session context if available
111
+ if (session?.currentRoute) {
112
+ const sessionInfo = [
113
+ "Current conversation context:",
114
+ `- Active route: ${session.currentRoute.title} (${session.currentRoute.id})`,
115
+ ];
116
+ if (session.currentState) {
117
+ sessionInfo.push(`- Current state: ${session.currentState.id}`);
118
+ if (session.currentState.description) {
119
+ sessionInfo.push(` "${session.currentState.description}"`);
120
+ }
121
+ }
122
+ if (Object.keys(session.extracted).length > 0) {
123
+ sessionInfo.push(
124
+ `- Extracted data: ${JSON.stringify(session.extracted)}`
125
+ );
126
+ }
127
+ sessionInfo.push(
128
+ "Note: User is mid-conversation. They may want to continue current route or switch to a new one based on their intent."
129
+ );
130
+ pc.addInstruction(sessionInfo.join("\n"));
131
+ }
132
+
133
+ pc.addInteractionHistory(history);
134
+ pc.addLastMessage(lastMessage);
135
+ // Cast to unknown to satisfy generic constraints in composer
136
+ // This is safe because PromptComposer only reads route metadata (id, title, description)
137
+ pc.addRoutingOverview(routes as unknown as Route<unknown>[]);
138
+ pc.addInstruction(
139
+ [
140
+ "Scoring rules:",
141
+ "- 90-100: explicit keywords + clear intent",
142
+ "- 70-89: strong contextual evidence + relevant keywords",
143
+ "- 50-69: moderate relevance",
144
+ "- 30-49: weak connection or ambiguous",
145
+ "- 0-29: minimal/none",
146
+ "Return ONLY JSON matching the provided schema. Include scores for ALL routes.",
147
+ ].join("\n")
148
+ );
149
+ return pc.build();
150
+ }
151
+
152
+ decideRouteFromScores(output: RoutingDecision): {
153
+ routeId: string;
154
+ maxScore: number;
155
+ } {
156
+ // Optionally limit candidates and apply switching threshold
157
+ const entries = Object.entries(output.routes).sort((a, b) => b[1] - a[1]);
158
+ const limited = this.options?.maxCandidates
159
+ ? entries.slice(0, this.options.maxCandidates)
160
+ : entries;
161
+ const [topId, topScore] = limited[0] || ["", 0];
162
+ // switchThreshold is enforced by caller when a current route exists
163
+ return { routeId: topId, maxScore: topScore };
164
+ }
165
+ }