@falai/agent 0.1.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 (129) hide show
  1. package/README.md +516 -0
  2. package/dist/constants/index.d.ts +5 -0
  3. package/dist/constants/index.d.ts.map +1 -0
  4. package/dist/constants/index.js +5 -0
  5. package/dist/constants/index.js.map +1 -0
  6. package/dist/core/Agent.d.ts +98 -0
  7. package/dist/core/Agent.d.ts.map +1 -0
  8. package/dist/core/Agent.js +248 -0
  9. package/dist/core/Agent.js.map +1 -0
  10. package/dist/core/DomainRegistry.d.ts +26 -0
  11. package/dist/core/DomainRegistry.d.ts.map +1 -0
  12. package/dist/core/DomainRegistry.js +41 -0
  13. package/dist/core/DomainRegistry.js.map +1 -0
  14. package/dist/core/Events.d.ts +19 -0
  15. package/dist/core/Events.d.ts.map +1 -0
  16. package/dist/core/Events.js +79 -0
  17. package/dist/core/Events.js.map +1 -0
  18. package/dist/core/Observation.d.ts +24 -0
  19. package/dist/core/Observation.d.ts.map +1 -0
  20. package/dist/core/Observation.js +35 -0
  21. package/dist/core/Observation.js.map +1 -0
  22. package/dist/core/PromptBuilder.d.ts +121 -0
  23. package/dist/core/PromptBuilder.d.ts.map +1 -0
  24. package/dist/core/PromptBuilder.js +339 -0
  25. package/dist/core/PromptBuilder.js.map +1 -0
  26. package/dist/core/Route.d.ts +46 -0
  27. package/dist/core/Route.d.ts.map +1 -0
  28. package/dist/core/Route.js +113 -0
  29. package/dist/core/Route.js.map +1 -0
  30. package/dist/core/State.d.ts +50 -0
  31. package/dist/core/State.d.ts.map +1 -0
  32. package/dist/core/State.js +110 -0
  33. package/dist/core/State.js.map +1 -0
  34. package/dist/core/Tool.d.ts +31 -0
  35. package/dist/core/Tool.d.ts.map +1 -0
  36. package/dist/core/Tool.js +33 -0
  37. package/dist/core/Tool.js.map +1 -0
  38. package/dist/core/Transition.d.ts +32 -0
  39. package/dist/core/Transition.d.ts.map +1 -0
  40. package/dist/core/Transition.js +59 -0
  41. package/dist/core/Transition.js.map +1 -0
  42. package/dist/index.d.ts +34 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +26 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/providers/GeminiProvider.d.ts +40 -0
  47. package/dist/providers/GeminiProvider.d.ts.map +1 -0
  48. package/dist/providers/GeminiProvider.js +126 -0
  49. package/dist/providers/GeminiProvider.js.map +1 -0
  50. package/dist/providers/OpenAIProvider.d.ts +42 -0
  51. package/dist/providers/OpenAIProvider.d.ts.map +1 -0
  52. package/dist/providers/OpenAIProvider.js +164 -0
  53. package/dist/providers/OpenAIProvider.js.map +1 -0
  54. package/dist/providers/OpenRouterProvider.d.ts +46 -0
  55. package/dist/providers/OpenRouterProvider.d.ts.map +1 -0
  56. package/dist/providers/OpenRouterProvider.js +171 -0
  57. package/dist/providers/OpenRouterProvider.js.map +1 -0
  58. package/dist/types/agent.d.ts +105 -0
  59. package/dist/types/agent.d.ts.map +1 -0
  60. package/dist/types/agent.js +18 -0
  61. package/dist/types/agent.js.map +1 -0
  62. package/dist/types/ai.d.ts +78 -0
  63. package/dist/types/ai.d.ts.map +1 -0
  64. package/dist/types/ai.js +5 -0
  65. package/dist/types/ai.js.map +1 -0
  66. package/dist/types/history.d.ts +112 -0
  67. package/dist/types/history.d.ts.map +1 -0
  68. package/dist/types/history.js +34 -0
  69. package/dist/types/history.js.map +1 -0
  70. package/dist/types/index.d.ts +14 -0
  71. package/dist/types/index.d.ts.map +1 -0
  72. package/dist/types/index.js +7 -0
  73. package/dist/types/index.js.map +1 -0
  74. package/dist/types/observation.d.ts +25 -0
  75. package/dist/types/observation.d.ts.map +1 -0
  76. package/dist/types/observation.js +5 -0
  77. package/dist/types/observation.js.map +1 -0
  78. package/dist/types/prompt.d.ts +46 -0
  79. package/dist/types/prompt.d.ts.map +1 -0
  80. package/dist/types/prompt.js +16 -0
  81. package/dist/types/prompt.js.map +1 -0
  82. package/dist/types/route.d.ts +59 -0
  83. package/dist/types/route.d.ts.map +1 -0
  84. package/dist/types/route.js +5 -0
  85. package/dist/types/route.js.map +1 -0
  86. package/dist/types/tool.d.ts +46 -0
  87. package/dist/types/tool.d.ts.map +1 -0
  88. package/dist/types/tool.js +5 -0
  89. package/dist/types/tool.js.map +1 -0
  90. package/dist/utils/retry.d.ts +13 -0
  91. package/dist/utils/retry.d.ts.map +1 -0
  92. package/dist/utils/retry.js +70 -0
  93. package/dist/utils/retry.js.map +1 -0
  94. package/docs/API_REFERENCE.md +517 -0
  95. package/docs/CONSTRUCTOR_OPTIONS.md +256 -0
  96. package/docs/CONTRIBUTING.md +481 -0
  97. package/docs/GETTING_STARTED.md +328 -0
  98. package/docs/PROVIDERS.md +472 -0
  99. package/docs/PUBLISHING.md +174 -0
  100. package/docs/README.md +68 -0
  101. package/docs/STRUCTURE.md +32 -0
  102. package/examples/declarative-agent.ts +217 -0
  103. package/examples/healthcare-agent.ts +283 -0
  104. package/examples/openai-agent.ts +167 -0
  105. package/examples/travel-agent.ts +342 -0
  106. package/package.json +73 -0
  107. package/src/constants/index.ts +5 -0
  108. package/src/core/Agent.ts +307 -0
  109. package/src/core/DomainRegistry.ts +50 -0
  110. package/src/core/Events.ts +101 -0
  111. package/src/core/Observation.ts +46 -0
  112. package/src/core/PromptBuilder.ts +511 -0
  113. package/src/core/Route.ts +136 -0
  114. package/src/core/State.ts +153 -0
  115. package/src/core/Tool.ts +54 -0
  116. package/src/core/Transition.ts +66 -0
  117. package/src/index.ts +83 -0
  118. package/src/providers/GeminiProvider.ts +220 -0
  119. package/src/providers/OpenAIProvider.ts +272 -0
  120. package/src/providers/OpenRouterProvider.ts +282 -0
  121. package/src/types/agent.ts +112 -0
  122. package/src/types/ai.ts +85 -0
  123. package/src/types/history.ts +125 -0
  124. package/src/types/index.ts +56 -0
  125. package/src/types/observation.ts +27 -0
  126. package/src/types/prompt.ts +49 -0
  127. package/src/types/route.ts +68 -0
  128. package/src/types/tool.ts +53 -0
  129. package/src/utils/retry.ts +96 -0
@@ -0,0 +1,511 @@
1
+ /**
2
+ * Prompt construction and management
3
+ */
4
+
5
+ import type { Event, EmittedEvent, MessageEventData } from "@/types/history";
6
+ import { EventKind, EventSource } from "@/types/history";
7
+ import type { Term, Capability, GuidelineMatch } from "@/types/agent";
8
+ import type { PromptSection, ContextVariableValue } from "@/types/prompt";
9
+ import { SectionStatus } from "@/types/prompt";
10
+
11
+ import { adaptEvent } from "@/core/Events";
12
+
13
+ /**
14
+ * Built-in section identifiers
15
+ */
16
+ export enum BuiltInSection {
17
+ AGENT_IDENTITY = "agent_identity",
18
+ CUSTOMER_IDENTITY = "customer_identity",
19
+ INTERACTION_HISTORY = "interaction_history",
20
+ CONTEXT_VARIABLES = "context_variables",
21
+ GLOSSARY = "glossary",
22
+ GUIDELINE_DESCRIPTIONS = "guideline_descriptions",
23
+ GUIDELINES = "guidelines",
24
+ STAGED_EVENTS = "staged_events",
25
+ ROUTES = "routes",
26
+ OBSERVATIONS = "observations",
27
+ CAPABILITIES = "capabilities",
28
+ ACTIVE_ROUTES = "active_routes",
29
+ }
30
+
31
+ /**
32
+ * Customer/user information
33
+ */
34
+ export interface Customer {
35
+ name: string;
36
+ id?: string;
37
+ }
38
+
39
+ /**
40
+ * Agent information for prompt building
41
+ */
42
+ export interface AgentInfo {
43
+ name: string;
44
+ description?: string;
45
+ }
46
+
47
+ /**
48
+ * Builds prompts from composable sections
49
+ */
50
+ export class PromptBuilder {
51
+ private sections: Map<string | BuiltInSection, PromptSection> = new Map();
52
+ private onBuild?: (prompt: string) => void;
53
+ private cachedResults = new Set<string>();
54
+
55
+ constructor(onBuild?: (prompt: string) => void) {
56
+ this.onBuild = onBuild;
57
+ }
58
+
59
+ /**
60
+ * Build the final prompt from all sections
61
+ */
62
+ build(): string {
63
+ const parts: string[] = [];
64
+
65
+ for (const section of this.sections.values()) {
66
+ try {
67
+ const formatted = this.formatTemplate(section.template, section.props);
68
+ parts.push(formatted);
69
+ } catch (error) {
70
+ throw new Error(
71
+ `Error formatting section: ${
72
+ error instanceof Error ? error.message : "Unknown error"
73
+ }`
74
+ );
75
+ }
76
+ }
77
+
78
+ const prompt = parts.join("\n\n").trim();
79
+
80
+ if (this.onBuild && !this.cachedResults.has(prompt)) {
81
+ this.onBuild(prompt);
82
+ this.cachedResults.add(prompt);
83
+ }
84
+
85
+ return prompt;
86
+ }
87
+
88
+ /**
89
+ * Add a section to the prompt
90
+ */
91
+ addSection(
92
+ name: string | BuiltInSection,
93
+ template: string,
94
+ props: Record<string, unknown> = {},
95
+ status?: SectionStatus
96
+ ): this {
97
+ if (this.sections.has(name)) {
98
+ throw new Error(`Section '${String(name)}' already exists`);
99
+ }
100
+
101
+ this.sections.set(name, {
102
+ template,
103
+ props,
104
+ status,
105
+ });
106
+
107
+ return this;
108
+ }
109
+
110
+ /**
111
+ * Edit an existing section
112
+ */
113
+ editSection(
114
+ name: string | BuiltInSection,
115
+ editor: (section: PromptSection) => PromptSection
116
+ ): this {
117
+ const section = this.sections.get(name);
118
+ if (section) {
119
+ this.sections.set(name, editor(section));
120
+ }
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ * Get section status
126
+ */
127
+ sectionStatus(name: string | BuiltInSection): SectionStatus {
128
+ const section = this.sections.get(name);
129
+ return section?.status || SectionStatus.NONE;
130
+ }
131
+
132
+ /**
133
+ * Add agent identity section
134
+ */
135
+ addAgentIdentity(agent: AgentInfo): this {
136
+ if (agent.description) {
137
+ this.addSection(
138
+ BuiltInSection.AGENT_IDENTITY,
139
+ `You are an AI agent named {agent_name}.
140
+
141
+ The following is a description of your background and personality: ###
142
+ {agent_description}
143
+ ###`,
144
+ {
145
+ agent_name: agent.name,
146
+ agent_description: agent.description,
147
+ },
148
+ SectionStatus.ACTIVE
149
+ );
150
+ }
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Add customer identity section
156
+ */
157
+ addCustomerIdentity(customer: Customer): this {
158
+ this.addSection(
159
+ BuiltInSection.CUSTOMER_IDENTITY,
160
+ `The user you're interacting with is called {customer_name}.`,
161
+ { customer_name: customer.name },
162
+ SectionStatus.ACTIVE
163
+ );
164
+ return this;
165
+ }
166
+
167
+ /**
168
+ * Add interaction history section
169
+ */
170
+ addInteractionHistory(
171
+ events: Event[],
172
+ stagedEvents: EmittedEvent[] = []
173
+ ): this {
174
+ if (events.length === 0 && stagedEvents.length === 0) {
175
+ this.addSection(
176
+ BuiltInSection.INTERACTION_HISTORY,
177
+ `Your interaction with the user has just began, and no events have been recorded yet.
178
+ Proceed with your task accordingly.`,
179
+ {},
180
+ SectionStatus.PASSIVE
181
+ );
182
+ } else {
183
+ const interactionEvents = this.gatherInteractionEvents(
184
+ events,
185
+ stagedEvents
186
+ );
187
+ this.addSection(
188
+ BuiltInSection.INTERACTION_HISTORY,
189
+ `The following is a list of events describing a back-and-forth
190
+ interaction between you and a user: ###
191
+ {interaction_events}
192
+ ###`,
193
+ { interaction_events: interactionEvents.join("\n") },
194
+ SectionStatus.ACTIVE
195
+ );
196
+ }
197
+ return this;
198
+ }
199
+
200
+ /**
201
+ * Add interaction history with special handling for message generation
202
+ */
203
+ addInteractionHistoryForMessageGeneration(
204
+ events: Event[],
205
+ stagedEvents: EmittedEvent[] = []
206
+ ): this {
207
+ if (events.length === 0 && stagedEvents.length === 0) {
208
+ return this.addInteractionHistory(events, stagedEvents);
209
+ }
210
+
211
+ const interactionEvents = this.gatherInteractionEvents(
212
+ events,
213
+ stagedEvents
214
+ );
215
+ const lastEventNote = this.lastAgentMessageNote(events);
216
+
217
+ let template = `The following is a list of events describing a back-and-forth
218
+ interaction between you and a user: ###
219
+ {interaction_events}
220
+ ###`;
221
+
222
+ const props: Record<string, unknown> = {
223
+ interaction_events: interactionEvents.join("\n"),
224
+ };
225
+
226
+ if (lastEventNote) {
227
+ template += "\n\n{last_event_note}";
228
+ props.last_event_note = lastEventNote;
229
+ }
230
+
231
+ this.addSection(
232
+ BuiltInSection.INTERACTION_HISTORY,
233
+ template,
234
+ props,
235
+ SectionStatus.ACTIVE
236
+ );
237
+
238
+ return this;
239
+ }
240
+
241
+ /**
242
+ * Add context variables section
243
+ */
244
+ addContextVariables(variables: ContextVariableValue[]): this {
245
+ if (variables.length > 0) {
246
+ const contextValues = this.contextVariablesToJson(variables);
247
+ this.addSection(
248
+ BuiltInSection.CONTEXT_VARIABLES,
249
+ `The following is information that you're given about the user and context of the interaction: ###
250
+ {context_values}
251
+ ###`,
252
+ { context_values: contextValues },
253
+ SectionStatus.ACTIVE
254
+ );
255
+ }
256
+ return this;
257
+ }
258
+
259
+ /**
260
+ * Add glossary section
261
+ */
262
+ addGlossary(terms: Term[]): this {
263
+ if (terms.length > 0) {
264
+ const termsString = terms
265
+ .map((t, i) => {
266
+ const synonyms = t.synonyms?.length
267
+ ? ` (synonyms: ${t.synonyms.join(", ")})`
268
+ : "";
269
+ return `${i + 1}) ${t.name}${synonyms}: ${t.description}`;
270
+ })
271
+ .join("\n");
272
+
273
+ this.addSection(
274
+ BuiltInSection.GLOSSARY,
275
+ `The following is a glossary of the business.
276
+ Understanding these terms, as they apply to the business, is critical for your task.
277
+ When encountering any of these terms, prioritize the interpretation provided here over any definitions you may already know.
278
+ Please be tolerant of possible typos by the user with regards to these terms,
279
+ and let the user know if/when you assume they meant a term by their typo: ###
280
+ {terms_string}
281
+ ###`,
282
+ { terms_string: termsString },
283
+ SectionStatus.ACTIVE
284
+ );
285
+ }
286
+ return this;
287
+ }
288
+
289
+ /**
290
+ * Add staged tool events section
291
+ */
292
+ addStagedToolEvents(events: EmittedEvent[]): this {
293
+ const toolEvents = events.filter((e) => e.kind === EventKind.TOOL);
294
+
295
+ if (toolEvents.length > 0) {
296
+ const stagedEventsAsDict = toolEvents.map((e) => adaptEvent(e));
297
+
298
+ this.addSection(
299
+ BuiltInSection.STAGED_EVENTS,
300
+ `Here are the most recent staged events for your reference.
301
+ They represent interactions with external tools that perform actions or provide information.
302
+ Prioritize their data over any other sources and use their details to complete your task: ###
303
+ {staged_events_as_dict}
304
+ ###`,
305
+ { staged_events_as_dict: stagedEventsAsDict.join("\n") },
306
+ SectionStatus.ACTIVE
307
+ );
308
+ }
309
+ return this;
310
+ }
311
+
312
+ /**
313
+ * Add guidelines for message generation
314
+ */
315
+ addGuidelinesForMessageGeneration(guidelines: GuidelineMatch[]): this {
316
+ if (guidelines.length === 0) {
317
+ this.addSection(
318
+ BuiltInSection.GUIDELINE_DESCRIPTIONS,
319
+ `In formulating your reply, you are normally required to follow a number of behavioral guidelines.
320
+ However, in this case, no special behavioral guidelines were provided. Therefore, when generating revisions,
321
+ you don't need to specifically double-check if you followed or broke any guidelines.`,
322
+ {},
323
+ SectionStatus.PASSIVE
324
+ );
325
+ return this;
326
+ }
327
+
328
+ const guidelineList = guidelines
329
+ .map((g, i) => {
330
+ const num = i + 1;
331
+ let text = g.guideline.condition
332
+ ? `Guideline #${num}) When ${g.guideline.condition}, then ${g.guideline.action}`
333
+ : `Guideline #${num}) ${g.guideline.action}`;
334
+
335
+ if (g.rationale) {
336
+ text += `\n - Rationale: ${g.rationale}`;
337
+ }
338
+
339
+ return text;
340
+ })
341
+ .join("\n");
342
+
343
+ this.addSection(
344
+ BuiltInSection.GUIDELINE_DESCRIPTIONS,
345
+ `When crafting your reply, you must follow the behavioral guidelines provided below, which have been identified as relevant to the current state of the interaction.
346
+
347
+ - **Guidelines**:
348
+ {guideline_list}
349
+
350
+ You may choose not to follow a guideline only in the following cases:
351
+ - It conflicts with a previous customer request.
352
+ - It is clearly inappropriate given the current context of the conversation.
353
+ - It lacks sufficient context or data to apply reliably.
354
+ - It conflicts with an insight.
355
+
356
+ In all other situations, you are expected to adhere to the guidelines.
357
+ These guidelines have already been pre-filtered based on the interaction's context and other considerations outside your scope.`,
358
+ { guideline_list: guidelineList },
359
+ SectionStatus.ACTIVE
360
+ );
361
+
362
+ return this;
363
+ }
364
+
365
+ /**
366
+ * Add capabilities section for message generation
367
+ */
368
+ addCapabilitiesForMessageGeneration(capabilities: Capability[]): this {
369
+ if (capabilities.length > 0) {
370
+ const capabilitiesString = capabilities
371
+ .map(
372
+ (c, i) => `Supported Capability ${i + 1}: ${c.title}
373
+ ${c.description}`
374
+ )
375
+ .join("\n\n");
376
+
377
+ this.addSection(
378
+ BuiltInSection.CAPABILITIES,
379
+ `Below are the capabilities available to you as an agent.
380
+ You may inform the customer that you can assist them using these capabilities.
381
+ If you choose to use any of them, additional details will be provided in your next response.
382
+ Always prefer adhering to guidelines, before offering capabilities - only offer capabilities if you have no other instruction that's relevant for the current stage of the interaction.
383
+ Be proactive and offer the most relevant capabilities—but only if they are likely to move the conversation forward.
384
+ If multiple capabilities are appropriate, aim to present them all to the customer.
385
+ If none of the capabilities address the current request of the customer - DO NOT MENTION THEM.
386
+ ###
387
+ {capabilities_string}
388
+ ###`,
389
+ { capabilities_string: capabilitiesString },
390
+ SectionStatus.ACTIVE
391
+ );
392
+ }
393
+ return this;
394
+ }
395
+
396
+ /**
397
+ * Add observations for disambiguation
398
+ */
399
+ addObservations(
400
+ observations: Array<{
401
+ description: string;
402
+ routes: Array<{ title: string }>;
403
+ }>
404
+ ): this {
405
+ if (observations.length > 0) {
406
+ const observationsString = observations
407
+ .map((obs, i) => {
408
+ const routeTitles = obs.routes.map((r) => `"${r.title}"`).join(", ");
409
+ return `${i + 1}) "${
410
+ obs.description
411
+ }" → Can lead to routes: ${routeTitles}`;
412
+ })
413
+ .join("\n");
414
+
415
+ this.addSection(
416
+ BuiltInSection.OBSERVATIONS,
417
+ `The following observations may help you understand the user's intent and choose the appropriate response path:
418
+ ###
419
+ {observations_string}
420
+ ###
421
+
422
+ When you detect any of these situations, consider which route would be most appropriate based on the user's actual need.`,
423
+ { observations_string: observationsString },
424
+ SectionStatus.ACTIVE
425
+ );
426
+ }
427
+ return this;
428
+ }
429
+
430
+ /**
431
+ * Add active routes information
432
+ */
433
+ addActiveRoutes(
434
+ routes: Array<{ title: string; description?: string; conditions: string[] }>
435
+ ): this {
436
+ if (routes.length > 0) {
437
+ const routesString = routes
438
+ .map((route, i) => {
439
+ const conditions =
440
+ route.conditions.length > 0
441
+ ? `\n Triggered when: ${route.conditions.join(" OR ")}`
442
+ : "";
443
+ const desc = route.description ? `\n ${route.description}` : "";
444
+ return `${i + 1}) ${route.title}${desc}${conditions}`;
445
+ })
446
+ .join("\n\n");
447
+
448
+ this.addSection(
449
+ BuiltInSection.ACTIVE_ROUTES,
450
+ `Available conversation routes:
451
+ ###
452
+ {routes_string}
453
+ ###
454
+
455
+ These routes represent different paths the conversation can take. Choose the most appropriate route based on the user's needs.`,
456
+ { routes_string: routesString },
457
+ SectionStatus.ACTIVE
458
+ );
459
+ }
460
+ return this;
461
+ }
462
+
463
+ // Helper methods
464
+
465
+ private formatTemplate(
466
+ template: string,
467
+ props: Record<string, unknown>
468
+ ): string {
469
+ return template.replace(/\{(\w+)\}/g, (match, key: string) => {
470
+ if (key in props) {
471
+ return String(props[key]);
472
+ }
473
+ return match;
474
+ });
475
+ }
476
+
477
+ private gatherInteractionEvents(
478
+ events: Event[],
479
+ stagedEvents: EmittedEvent[]
480
+ ): string[] {
481
+ const combined = [...events, ...stagedEvents];
482
+ return combined
483
+ .filter((e) => e.kind !== EventKind.STATUS)
484
+ .map((e) => adaptEvent(e));
485
+ }
486
+
487
+ private lastAgentMessageNote(events: Event[]): string | null {
488
+ const lastMessageEvent = [...events]
489
+ .reverse()
490
+ .find((e) => e.kind === EventKind.MESSAGE);
491
+
492
+ if (!lastMessageEvent || lastMessageEvent.source !== EventSource.AI_AGENT) {
493
+ return null;
494
+ }
495
+
496
+ const lastMessage = (lastMessageEvent.data as MessageEventData).message;
497
+ return `IMPORTANT: Please note that the last message was sent by you, the AI agent (likely as a preamble). Your last message was: ###
498
+ ${lastMessage}
499
+ ###
500
+
501
+ You must keep that in mind when responding to the user, to continue the last message naturally (without repeating anything similar in your last message - make sure you don't repeat something like this in your next message - it was already said!).`;
502
+ }
503
+
504
+ private contextVariablesToJson(variables: ContextVariableValue[]): string {
505
+ const obj: Record<string, unknown> = {};
506
+ for (const v of variables) {
507
+ obj[v.variable.name] = v.value;
508
+ }
509
+ return JSON.stringify(obj, null, 2);
510
+ }
511
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Route (Journey) DSL implementation
3
+ */
4
+
5
+ import type { RouteOptions, RouteRef } from "@/types/route";
6
+ import type { Guideline } from "@/types/agent";
7
+
8
+ import { State } from "@/core/State";
9
+
10
+ let routeIdCounter = 0;
11
+
12
+ /**
13
+ * Represents a conversational route/journey
14
+ */
15
+ export class Route {
16
+ public readonly id: string;
17
+ public readonly title: string;
18
+ public readonly description?: string;
19
+ public readonly conditions: string[];
20
+ public readonly initialState: State;
21
+ private guidelines: Guideline[] = [];
22
+
23
+ constructor(options: RouteOptions) {
24
+ this.id = `route_${++routeIdCounter}_${options.title
25
+ .toLowerCase()
26
+ .replace(/\s+/g, "_")}`;
27
+ this.title = options.title;
28
+ this.description = options.description;
29
+ this.conditions = options.conditions || [];
30
+ this.initialState = new State(this.id, "Initial state");
31
+
32
+ // Initialize guidelines from options
33
+ if (options.guidelines) {
34
+ options.guidelines.forEach((guideline) => {
35
+ this.createGuideline(guideline);
36
+ });
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Create a guideline specific to this route
42
+ */
43
+ createGuideline(guideline: Guideline): this {
44
+ this.guidelines.push({
45
+ ...guideline,
46
+ id: guideline.id || `guideline_${this.id}_${this.guidelines.length}`,
47
+ enabled: guideline.enabled !== false, // Default to true
48
+ });
49
+ return this;
50
+ }
51
+
52
+ /**
53
+ * Get all guidelines for this route
54
+ */
55
+ getGuidelines(): Guideline[] {
56
+ return [...this.guidelines];
57
+ }
58
+
59
+ /**
60
+ * Get route reference
61
+ */
62
+ getRef(): RouteRef {
63
+ return {
64
+ id: this.id,
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Create a route reference that includes this route instance
70
+ * Useful for disambiguation
71
+ */
72
+ toRef(): RouteRef & { route: Route } {
73
+ return {
74
+ id: this.id,
75
+ route: this,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Get all states in this route (via traversal from initial state)
81
+ */
82
+ getAllStates(): State[] {
83
+ const visited = new Set<string>();
84
+ const states: State[] = [];
85
+ const queue: State[] = [this.initialState];
86
+
87
+ while (queue.length > 0) {
88
+ const current = queue.shift()!;
89
+
90
+ if (visited.has(current.id)) {
91
+ continue;
92
+ }
93
+
94
+ visited.add(current.id);
95
+ states.push(current);
96
+
97
+ // Add target states from transitions
98
+ for (const transition of current.getTransitions()) {
99
+ const target = transition.getTarget();
100
+ if (target && !visited.has(target.id)) {
101
+ queue.push(target);
102
+ }
103
+ }
104
+ }
105
+
106
+ return states;
107
+ }
108
+
109
+ /**
110
+ * Get a description of the route structure for debugging
111
+ */
112
+ describe(): string {
113
+ const lines: string[] = [
114
+ `Route: ${this.title}`,
115
+ `ID: ${this.id}`,
116
+ `Description: ${this.description || "N/A"}`,
117
+ `Conditions: ${this.conditions.join(", ") || "None"}`,
118
+ "",
119
+ "States:",
120
+ ];
121
+
122
+ const states = this.getAllStates();
123
+ for (const state of states) {
124
+ lines.push(
125
+ ` - ${state.id}${state.description ? `: ${state.description}` : ""}`
126
+ );
127
+
128
+ const transitions = state.getTransitions();
129
+ for (const transition of transitions) {
130
+ lines.push(` -> ${transition.describe()}`);
131
+ }
132
+ }
133
+
134
+ return lines.join("\n");
135
+ }
136
+ }