@falai/agent 0.3.0 → 0.3.11

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 (114) hide show
  1. package/README.md +257 -24
  2. package/dist/cjs/core/Agent.d.ts +37 -0
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +167 -1
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/DomainRegistry.d.ts +10 -0
  7. package/dist/cjs/core/DomainRegistry.d.ts.map +1 -1
  8. package/dist/cjs/core/DomainRegistry.js +25 -0
  9. package/dist/cjs/core/DomainRegistry.js.map +1 -1
  10. package/dist/cjs/core/PromptBuilder.d.ts +9 -1
  11. package/dist/cjs/core/PromptBuilder.d.ts.map +1 -1
  12. package/dist/cjs/core/PromptBuilder.js +49 -2
  13. package/dist/cjs/core/PromptBuilder.js.map +1 -1
  14. package/dist/cjs/core/Route.d.ts +16 -0
  15. package/dist/cjs/core/Route.d.ts.map +1 -1
  16. package/dist/cjs/core/Route.js +22 -0
  17. package/dist/cjs/core/Route.js.map +1 -1
  18. package/dist/cjs/index.d.ts +2 -0
  19. package/dist/cjs/index.d.ts.map +1 -1
  20. package/dist/cjs/index.js +3 -1
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/providers/AnthropicProvider.d.ts +43 -0
  23. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -0
  24. package/dist/cjs/providers/AnthropicProvider.js +328 -0
  25. package/dist/cjs/providers/AnthropicProvider.js.map +1 -0
  26. package/dist/cjs/providers/GeminiProvider.d.ts +4 -1
  27. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  28. package/dist/cjs/providers/GeminiProvider.js +96 -0
  29. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  30. package/dist/cjs/providers/OpenAIProvider.d.ts +4 -1
  31. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  32. package/dist/cjs/providers/OpenAIProvider.js +115 -0
  33. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  34. package/dist/cjs/providers/OpenRouterProvider.d.ts +4 -1
  35. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  36. package/dist/cjs/providers/OpenRouterProvider.js +115 -0
  37. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  38. package/dist/cjs/providers/index.d.ts +13 -0
  39. package/dist/cjs/providers/index.d.ts.map +1 -0
  40. package/dist/cjs/providers/index.js +16 -0
  41. package/dist/cjs/providers/index.js.map +1 -0
  42. package/dist/cjs/types/ai.d.ts +28 -0
  43. package/dist/cjs/types/ai.d.ts.map +1 -1
  44. package/dist/cjs/types/route.d.ts +6 -0
  45. package/dist/cjs/types/route.d.ts.map +1 -1
  46. package/dist/core/Agent.d.ts +37 -0
  47. package/dist/core/Agent.d.ts.map +1 -1
  48. package/dist/core/Agent.js +167 -1
  49. package/dist/core/Agent.js.map +1 -1
  50. package/dist/core/DomainRegistry.d.ts +10 -0
  51. package/dist/core/DomainRegistry.d.ts.map +1 -1
  52. package/dist/core/DomainRegistry.js +25 -0
  53. package/dist/core/DomainRegistry.js.map +1 -1
  54. package/dist/core/PromptBuilder.d.ts +9 -1
  55. package/dist/core/PromptBuilder.d.ts.map +1 -1
  56. package/dist/core/PromptBuilder.js +49 -2
  57. package/dist/core/PromptBuilder.js.map +1 -1
  58. package/dist/core/Route.d.ts +16 -0
  59. package/dist/core/Route.d.ts.map +1 -1
  60. package/dist/core/Route.js +22 -0
  61. package/dist/core/Route.js.map +1 -1
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/providers/AnthropicProvider.d.ts +43 -0
  67. package/dist/providers/AnthropicProvider.d.ts.map +1 -0
  68. package/dist/providers/AnthropicProvider.js +321 -0
  69. package/dist/providers/AnthropicProvider.js.map +1 -0
  70. package/dist/providers/GeminiProvider.d.ts +4 -1
  71. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  72. package/dist/providers/GeminiProvider.js +96 -0
  73. package/dist/providers/GeminiProvider.js.map +1 -1
  74. package/dist/providers/OpenAIProvider.d.ts +4 -1
  75. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  76. package/dist/providers/OpenAIProvider.js +115 -0
  77. package/dist/providers/OpenAIProvider.js.map +1 -1
  78. package/dist/providers/OpenRouterProvider.d.ts +4 -1
  79. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  80. package/dist/providers/OpenRouterProvider.js +115 -0
  81. package/dist/providers/OpenRouterProvider.js.map +1 -1
  82. package/dist/providers/index.d.ts +13 -0
  83. package/dist/providers/index.d.ts.map +1 -0
  84. package/dist/providers/index.js +9 -0
  85. package/dist/providers/index.js.map +1 -0
  86. package/dist/types/ai.d.ts +28 -0
  87. package/dist/types/ai.d.ts.map +1 -1
  88. package/dist/types/route.d.ts +6 -0
  89. package/dist/types/route.d.ts.map +1 -1
  90. package/docs/API_REFERENCE.md +357 -2
  91. package/docs/CONSTRUCTOR_OPTIONS.md +178 -37
  92. package/docs/GETTING_STARTED.md +10 -2
  93. package/docs/PROVIDERS.md +139 -2
  94. package/examples/business-onboarding.ts +708 -0
  95. package/examples/declarative-agent.ts +1 -1
  96. package/examples/domain-scoping.ts +267 -0
  97. package/examples/healthcare-agent.ts +4 -4
  98. package/examples/openai-agent.ts +6 -4
  99. package/examples/rules-prohibitions.ts +258 -0
  100. package/examples/streaming-agent.ts +371 -0
  101. package/examples/travel-agent.ts +7 -4
  102. package/package.json +2 -1
  103. package/src/core/Agent.ts +220 -1
  104. package/src/core/DomainRegistry.ts +30 -0
  105. package/src/core/PromptBuilder.ts +70 -3
  106. package/src/core/Route.ts +28 -0
  107. package/src/index.ts +2 -0
  108. package/src/providers/AnthropicProvider.ts +467 -0
  109. package/src/providers/GeminiProvider.ts +135 -0
  110. package/src/providers/OpenAIProvider.ts +157 -0
  111. package/src/providers/OpenRouterProvider.ts +157 -0
  112. package/src/providers/index.ts +16 -0
  113. package/src/types/ai.ts +32 -0
  114. package/src/types/route.ts +6 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Example: Streaming Responses
3
+ *
4
+ * This example demonstrates how to use the respondStream method
5
+ * to stream AI responses in real-time for better user experience
6
+ */
7
+
8
+ import {
9
+ Agent,
10
+ createMessageEvent,
11
+ EventSource,
12
+ AnthropicProvider,
13
+ OpenAIProvider,
14
+ GeminiProvider,
15
+ } from "../src/index";
16
+
17
+ // Custom context type
18
+ interface ConversationContext {
19
+ userId: string;
20
+ sessionId: string;
21
+ preferences: {
22
+ streamingEnabled: boolean;
23
+ };
24
+ }
25
+
26
+ async function streamingWithAnthropic() {
27
+ console.log("\n🤖 Example 1: Streaming with Anthropic (Claude)\n");
28
+
29
+ // Initialize Anthropic provider
30
+ const provider = new AnthropicProvider({
31
+ apiKey: process.env.ANTHROPIC_API_KEY || "",
32
+ model: "claude-sonnet-4-5",
33
+ config: {
34
+ temperature: 0.7,
35
+ max_tokens: 1000,
36
+ },
37
+ });
38
+
39
+ // Create agent
40
+ const agent = new Agent<ConversationContext>({
41
+ name: "StreamingAssistant",
42
+ description: "An AI assistant that streams responses in real-time",
43
+ goal: "Provide helpful information with streaming responses",
44
+ context: {
45
+ userId: "user123",
46
+ sessionId: "session456",
47
+ preferences: {
48
+ streamingEnabled: true,
49
+ },
50
+ },
51
+ ai: provider,
52
+ });
53
+
54
+ // Add guidelines
55
+ agent.createGuideline({
56
+ action: "Be concise but informative in your responses",
57
+ enabled: true,
58
+ });
59
+
60
+ // Create conversation history
61
+ const history = [
62
+ createMessageEvent(
63
+ EventSource.CUSTOMER,
64
+ "User",
65
+ "Explain quantum computing in simple terms."
66
+ ),
67
+ ];
68
+
69
+ try {
70
+ console.log("📤 Streaming response from Claude...\n");
71
+ console.log("Response: ");
72
+
73
+ // Use respondStream for real-time streaming
74
+ let fullMessage = "";
75
+ for await (const chunk of agent.respondStream({ history })) {
76
+ // chunk.delta contains the new text
77
+ // chunk.accumulated contains the full text so far
78
+ // chunk.done indicates if this is the final chunk
79
+
80
+ if (chunk.delta) {
81
+ process.stdout.write(chunk.delta);
82
+ fullMessage += chunk.delta;
83
+ }
84
+
85
+ if (chunk.done) {
86
+ console.log("\n\n✅ Stream complete!");
87
+ console.log(`\n📊 Metadata:`);
88
+ console.log(` - Route: ${chunk.route?.title || "None"}`);
89
+ console.log(` - State: ${chunk.state?.description || "None"}`);
90
+ console.log(` - Tool Calls: ${chunk.toolCalls?.length || 0}`);
91
+ }
92
+ }
93
+ } catch (error) {
94
+ console.error("❌ Error:", error);
95
+ }
96
+ }
97
+
98
+ async function streamingWithOpenAI() {
99
+ console.log("\n🤖 Example 2: Streaming with OpenAI\n");
100
+
101
+ const provider = new OpenAIProvider({
102
+ apiKey: process.env.OPENAI_API_KEY || "",
103
+ model: "gpt-5",
104
+ config: {
105
+ temperature: 0.8,
106
+ },
107
+ });
108
+
109
+ const agent = new Agent<ConversationContext>({
110
+ name: "CreativeAssistant",
111
+ description: "A creative AI assistant",
112
+ context: {
113
+ userId: "user123",
114
+ sessionId: "session789",
115
+ preferences: {
116
+ streamingEnabled: true,
117
+ },
118
+ },
119
+ ai: provider,
120
+ });
121
+
122
+ const history = [
123
+ createMessageEvent(
124
+ EventSource.CUSTOMER,
125
+ "User",
126
+ "Write a short poem about TypeScript"
127
+ ),
128
+ ];
129
+
130
+ try {
131
+ console.log("📤 Streaming response from OpenAI...\n");
132
+ console.log("Response: ");
133
+
134
+ for await (const chunk of agent.respondStream({ history })) {
135
+ if (chunk.delta) {
136
+ process.stdout.write(chunk.delta);
137
+ }
138
+
139
+ if (chunk.done) {
140
+ console.log("\n\n✅ Stream complete!");
141
+ }
142
+ }
143
+ } catch (error) {
144
+ console.error("❌ Error:", error);
145
+ }
146
+ }
147
+
148
+ async function streamingWithGemini() {
149
+ console.log("\n🤖 Example 3: Streaming with Google Gemini\n");
150
+
151
+ const provider = new GeminiProvider({
152
+ apiKey: process.env.GEMINI_API_KEY || "",
153
+ model: "models/gemini-2.0-flash-exp",
154
+ config: {
155
+ temperature: 0.7,
156
+ },
157
+ });
158
+
159
+ const agent = new Agent<ConversationContext>({
160
+ name: "AnalyticalAssistant",
161
+ description: "An analytical AI assistant",
162
+ context: {
163
+ userId: "user123",
164
+ sessionId: "session101",
165
+ preferences: {
166
+ streamingEnabled: true,
167
+ },
168
+ },
169
+ ai: provider,
170
+ });
171
+
172
+ const history = [
173
+ createMessageEvent(
174
+ EventSource.CUSTOMER,
175
+ "User",
176
+ "What are the key differences between REST and GraphQL?"
177
+ ),
178
+ ];
179
+
180
+ try {
181
+ console.log("📤 Streaming response from Gemini...\n");
182
+ console.log("Response: ");
183
+
184
+ for await (const chunk of agent.respondStream({ history })) {
185
+ if (chunk.delta) {
186
+ process.stdout.write(chunk.delta);
187
+ }
188
+
189
+ if (chunk.done) {
190
+ console.log("\n\n✅ Stream complete!");
191
+ }
192
+ }
193
+ } catch (error) {
194
+ console.error("❌ Error:", error);
195
+ }
196
+ }
197
+
198
+ async function streamingWithRoutes() {
199
+ console.log("\n🤖 Example 4: Streaming with Routes and States\n");
200
+
201
+ const provider = new AnthropicProvider({
202
+ apiKey: process.env.ANTHROPIC_API_KEY || "",
203
+ model: "claude-sonnet-4-5",
204
+ });
205
+
206
+ const agent = new Agent<ConversationContext>({
207
+ name: "SupportAgent",
208
+ description: "A customer support agent with conversation routes",
209
+ context: {
210
+ userId: "user123",
211
+ sessionId: "session202",
212
+ preferences: {
213
+ streamingEnabled: true,
214
+ },
215
+ },
216
+ ai: provider,
217
+ });
218
+
219
+ // Create a route
220
+ const supportRoute = agent.createRoute({
221
+ title: "Product Support",
222
+ description: "Help users with product questions",
223
+ conditions: ["User asks about product features or issues"],
224
+ });
225
+
226
+ supportRoute.initialState.transitionTo({
227
+ chatState: "Understand the user's product question",
228
+ });
229
+
230
+ const history = [
231
+ createMessageEvent(
232
+ EventSource.CUSTOMER,
233
+ "User",
234
+ "How do I reset my password?"
235
+ ),
236
+ ];
237
+
238
+ try {
239
+ console.log("📤 Streaming response with route detection...\n");
240
+ console.log("Response: ");
241
+
242
+ for await (const chunk of agent.respondStream({ history })) {
243
+ if (chunk.delta) {
244
+ process.stdout.write(chunk.delta);
245
+ }
246
+
247
+ if (chunk.done) {
248
+ console.log("\n\n✅ Stream complete!");
249
+ if (chunk.route) {
250
+ console.log(`\n🗺️ Route detected: ${chunk.route.title}`);
251
+ }
252
+ if (chunk.state) {
253
+ console.log(`📍 State: ${chunk.state.description}`);
254
+ }
255
+ }
256
+ }
257
+ } catch (error) {
258
+ console.error("❌ Error:", error);
259
+ }
260
+ }
261
+
262
+ async function streamingWithAbortSignal() {
263
+ console.log("\n🤖 Example 5: Streaming with Abort Control\n");
264
+
265
+ const provider = new AnthropicProvider({
266
+ apiKey: process.env.ANTHROPIC_API_KEY || "",
267
+ model: "claude-sonnet-4-5",
268
+ });
269
+
270
+ const agent = new Agent<ConversationContext>({
271
+ name: "Assistant",
272
+ description: "An assistant that can be interrupted",
273
+ context: {
274
+ userId: "user123",
275
+ sessionId: "session303",
276
+ preferences: {
277
+ streamingEnabled: true,
278
+ },
279
+ },
280
+ ai: provider,
281
+ });
282
+
283
+ const history = [
284
+ createMessageEvent(
285
+ EventSource.CUSTOMER,
286
+ "User",
287
+ "Tell me a very long story about space exploration."
288
+ ),
289
+ ];
290
+
291
+ // Create an AbortController to cancel the stream
292
+ const abortController = new AbortController();
293
+
294
+ // Automatically abort after 3 seconds
295
+ const timeout = setTimeout(() => {
296
+ console.log("\n\n⚠️ Aborting stream after 3 seconds...");
297
+ abortController.abort();
298
+ }, 3000);
299
+
300
+ try {
301
+ console.log("📤 Streaming response (will abort after 3s)...\n");
302
+ console.log("Response: ");
303
+
304
+ for await (const chunk of agent.respondStream({
305
+ history,
306
+ signal: abortController.signal,
307
+ })) {
308
+ if (chunk.delta) {
309
+ process.stdout.write(chunk.delta);
310
+ }
311
+
312
+ if (chunk.done) {
313
+ console.log("\n\n✅ Stream complete!");
314
+ clearTimeout(timeout);
315
+ }
316
+ }
317
+ } catch (error) {
318
+ if (error instanceof Error && error.name === "AbortError") {
319
+ console.log("\n\n🛑 Stream was aborted successfully!");
320
+ } else {
321
+ console.error("❌ Error:", error);
322
+ }
323
+ clearTimeout(timeout);
324
+ }
325
+ }
326
+
327
+ async function main() {
328
+ console.log("🚀 Starting Streaming Examples\n");
329
+ console.log("=".repeat(60));
330
+
331
+ const examples = [
332
+ { name: "Anthropic Streaming", fn: streamingWithAnthropic },
333
+ { name: "OpenAI Streaming", fn: streamingWithOpenAI },
334
+ { name: "Gemini Streaming", fn: streamingWithGemini },
335
+ { name: "Streaming with Routes", fn: streamingWithRoutes },
336
+ { name: "Streaming with Abort", fn: streamingWithAbortSignal },
337
+ ];
338
+
339
+ console.log("\nAvailable Examples:");
340
+ examples.forEach((ex, i) => {
341
+ console.log(` ${i + 1}. ${ex.name}`);
342
+ });
343
+
344
+ console.log("\n💡 Tips:");
345
+ console.log(" - Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY");
346
+ console.log(" - Streaming provides real-time responses for better UX");
347
+ console.log(" - Use AbortSignal to cancel long-running streams");
348
+ console.log(" - Access chunk.route and chunk.state for flow information");
349
+
350
+ console.log("\n" + "=".repeat(60));
351
+
352
+ // Run first example if API key is available
353
+ if (process.env.ANTHROPIC_API_KEY) {
354
+ await streamingWithAnthropic();
355
+ } else if (process.env.OPENAI_API_KEY) {
356
+ await streamingWithOpenAI();
357
+ } else if (process.env.GEMINI_API_KEY) {
358
+ await streamingWithGemini();
359
+ } else {
360
+ console.log(
361
+ "\n⚠️ No API keys found. Set one of the environment variables to run examples."
362
+ );
363
+ }
364
+ }
365
+
366
+ // Run if executed directly
367
+ if (import.meta.url === `file://${process.argv[1]}`) {
368
+ main().catch(console.error);
369
+ }
370
+
371
+ export { main };
@@ -6,7 +6,7 @@
6
6
  import {
7
7
  Agent,
8
8
  defineTool,
9
- GeminiProvider,
9
+ OpenRouterProvider,
10
10
  END_ROUTE,
11
11
  EventSource,
12
12
  createMessageEvent,
@@ -113,9 +113,12 @@ const getBookingStatus = defineTool<
113
113
 
114
114
  // Initialize agent
115
115
  async function createTravelAgent() {
116
- const provider = new GeminiProvider({
117
- apiKey: process.env.GEMINI_API_KEY || "test-key",
118
- model: "models/gemini-2.0-flash-exp",
116
+ const provider = new OpenRouterProvider({
117
+ apiKey: process.env.OPENROUTER_API_KEY || "test-key",
118
+ model: "google/gemini-2.0-flash-exp",
119
+ backupModels: ["anthropic/claude-sonnet-4-5", "openai/gpt-5"],
120
+ siteUrl: "https://github.com/gusnips/falai",
121
+ siteName: "Falai Travel Agent Example",
119
122
  retryConfig: {
120
123
  timeout: 60000,
121
124
  retries: 3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falai/agent",
3
- "version": "0.3.0",
3
+ "version": "0.3.11",
4
4
  "description": "Standalone, strongly-typed AI Agent framework with route DSL and AI provider strategy",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -77,6 +77,7 @@
77
77
  "typescript-eslint": "^8.18.2"
78
78
  },
79
79
  "dependencies": {
80
+ "@anthropic-ai/sdk": "^0.65.0",
80
81
  "@google/genai": "^0.3.0",
81
82
  "openai": "^6.3.0"
82
83
  }
package/src/core/Agent.ts CHANGED
@@ -211,6 +211,180 @@ export class Agent<TContext = unknown> {
211
211
  return this.context;
212
212
  }
213
213
 
214
+ /**
215
+ * Generate a response based on history and context as a stream
216
+ */
217
+ async *respondStream(params: {
218
+ history: Event[];
219
+ state?: StateRef;
220
+ contextOverride?: Partial<TContext>;
221
+ signal?: AbortSignal;
222
+ }): AsyncGenerator<{
223
+ delta: string;
224
+ accumulated: string;
225
+ done: boolean;
226
+ route?: { id: string; title: string } | null;
227
+ state?: { id: string; description?: string } | null;
228
+ toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
229
+ }> {
230
+ const { history, contextOverride, signal } = params;
231
+
232
+ // Get current context (may fetch from provider)
233
+ let currentContext = await this.getContext();
234
+
235
+ // Call beforeRespond hook if configured
236
+ if (this.options.hooks?.beforeRespond && currentContext !== undefined) {
237
+ currentContext = await this.options.hooks.beforeRespond(currentContext);
238
+ // Update stored context with the result from beforeRespond
239
+ this.context = currentContext;
240
+ }
241
+
242
+ // Merge context with override
243
+ const effectiveContext = {
244
+ ...(currentContext as Record<string, unknown>),
245
+ ...(contextOverride as Record<string, unknown>),
246
+ } as TContext;
247
+
248
+ // Build prompt (same as respond method)
249
+ const promptBuilder = new PromptBuilder();
250
+
251
+ // Add agent identity
252
+ if (this.options.description) {
253
+ promptBuilder.addAgentIdentity({
254
+ name: this.options.name,
255
+ description: this.options.description,
256
+ });
257
+ }
258
+
259
+ // Add interaction history
260
+ promptBuilder.addInteractionHistoryForMessageGeneration(history);
261
+
262
+ // Add glossary
263
+ if (this.terms.length > 0) {
264
+ promptBuilder.addGlossary(this.terms);
265
+ }
266
+
267
+ // Add guidelines (convert to GuidelineMatch format, filter enabled only)
268
+ const enabledGuidelines = this.guidelines.filter(
269
+ (g) => g.enabled !== false
270
+ );
271
+ if (enabledGuidelines.length > 0) {
272
+ const guidelineMatches: GuidelineMatch[] = enabledGuidelines.map((g) => ({
273
+ guideline: g,
274
+ }));
275
+ promptBuilder.addGuidelinesForMessageGeneration(guidelineMatches);
276
+ }
277
+
278
+ // Add capabilities
279
+ if (this.capabilities.length > 0) {
280
+ promptBuilder.addCapabilitiesForMessageGeneration(this.capabilities);
281
+ }
282
+
283
+ // Add observations
284
+ if (this.observations.length > 0) {
285
+ const observationsWithRoutes = this.observations
286
+ .map((obs) => ({
287
+ description: obs.description,
288
+ routes: obs.getRoutes().map((routeRef) => {
289
+ const route = this.routes.find((r) => r.id === routeRef.id);
290
+ return { title: route?.title || routeRef.id };
291
+ }),
292
+ }))
293
+ .filter((obs) => obs.routes.length > 0);
294
+
295
+ if (observationsWithRoutes.length > 0) {
296
+ promptBuilder.addObservations(observationsWithRoutes);
297
+ }
298
+ }
299
+
300
+ // Add active routes with their rules and prohibitions
301
+ if (this.routes.length > 0) {
302
+ promptBuilder.addActiveRoutes(
303
+ this.routes.map((r) => ({
304
+ title: r.title,
305
+ description: r.description,
306
+ conditions: r.conditions,
307
+ domains: r.getDomains(),
308
+ rules: r.getRules(),
309
+ prohibitions: r.getProhibitions(),
310
+ }))
311
+ );
312
+ }
313
+
314
+ // Add domains (tools) information if any domains are registered
315
+ const allDomains = this.domainRegistry.all();
316
+ if (Object.keys(allDomains).length > 0) {
317
+ promptBuilder.addDomains(allDomains);
318
+ }
319
+
320
+ // Add JSON response schema instructions
321
+ promptBuilder.addJsonResponseSchema();
322
+
323
+ // Build final prompt
324
+ const prompt = promptBuilder.build();
325
+
326
+ // Generate message stream using AI provider with JSON mode enabled
327
+ const stream = this.options.ai.generateMessageStream({
328
+ prompt,
329
+ history,
330
+ context: effectiveContext,
331
+ signal,
332
+ parameters: {
333
+ jsonMode: true,
334
+ },
335
+ });
336
+
337
+ // Stream chunks to caller
338
+ for await (const chunk of stream) {
339
+ // Extract route and state from structured response on final chunk
340
+ let route: { id: string; title: string } | null = null;
341
+ let state: { id: string; description?: string } | null = null;
342
+ let toolCalls:
343
+ | Array<{ toolName: string; arguments: Record<string, unknown> }>
344
+ | undefined;
345
+
346
+ if (chunk.done && chunk.structured) {
347
+ // Find route by title
348
+ if (chunk.structured.route) {
349
+ const foundRoute = this.routes.find(
350
+ (r) => r.title === chunk.structured?.route
351
+ );
352
+ if (foundRoute) {
353
+ route = {
354
+ id: foundRoute.id,
355
+ title: foundRoute.title,
356
+ };
357
+ }
358
+ }
359
+
360
+ // Create state reference if provided
361
+ if (chunk.structured.state) {
362
+ state = {
363
+ id: "dynamic_state",
364
+ description: chunk.structured.state,
365
+ };
366
+ }
367
+
368
+ // Extract tool calls
369
+ if (
370
+ chunk.structured.toolCalls &&
371
+ chunk.structured.toolCalls.length > 0
372
+ ) {
373
+ toolCalls = chunk.structured.toolCalls;
374
+ }
375
+ }
376
+
377
+ yield {
378
+ delta: chunk.delta,
379
+ accumulated: chunk.accumulated,
380
+ done: chunk.done,
381
+ route: route || undefined,
382
+ state: state || undefined,
383
+ toolCalls,
384
+ };
385
+ }
386
+ }
387
+
214
388
  /**
215
389
  * Generate a response based on history and context
216
390
  */
@@ -295,17 +469,26 @@ export class Agent<TContext = unknown> {
295
469
  }
296
470
  }
297
471
 
298
- // Add active routes
472
+ // Add active routes with their rules and prohibitions
299
473
  if (this.routes.length > 0) {
300
474
  promptBuilder.addActiveRoutes(
301
475
  this.routes.map((r) => ({
302
476
  title: r.title,
303
477
  description: r.description,
304
478
  conditions: r.conditions,
479
+ domains: r.getDomains(),
480
+ rules: r.getRules(),
481
+ prohibitions: r.getProhibitions(),
305
482
  }))
306
483
  );
307
484
  }
308
485
 
486
+ // Add domains (tools) information if any domains are registered
487
+ const allDomains = this.domainRegistry.all();
488
+ if (Object.keys(allDomains).length > 0) {
489
+ promptBuilder.addDomains(allDomains);
490
+ }
491
+
309
492
  // Add JSON response schema instructions
310
493
  promptBuilder.addJsonResponseSchema();
311
494
 
@@ -414,4 +597,40 @@ export class Agent<TContext = unknown> {
414
597
  getDomainRegistry(): DomainRegistry {
415
598
  return this.domainRegistry;
416
599
  }
600
+
601
+ /**
602
+ * Get allowed domains for a specific route
603
+ * @param routeId - Route ID to check
604
+ * @returns Filtered domains object, or all domains if route has no restrictions
605
+ */
606
+ getDomainsForRoute(routeId: string): Record<string, Record<string, unknown>> {
607
+ const route = this.routes.find((r) => r.id === routeId);
608
+
609
+ if (!route) {
610
+ // Route not found, return all domains
611
+ return this.domainRegistry.all();
612
+ }
613
+
614
+ const allowedDomains = route.getDomains();
615
+ return this.domainRegistry.getFiltered(allowedDomains);
616
+ }
617
+
618
+ /**
619
+ * Get allowed domains for a specific route by title
620
+ * @param routeTitle - Route title to check
621
+ * @returns Filtered domains object, or all domains if route has no restrictions
622
+ */
623
+ getDomainsForRouteByTitle(
624
+ routeTitle: string
625
+ ): Record<string, Record<string, unknown>> {
626
+ const route = this.routes.find((r) => r.title === routeTitle);
627
+
628
+ if (!route) {
629
+ // Route not found, return all domains
630
+ return this.domainRegistry.all();
631
+ }
632
+
633
+ const allowedDomains = route.getDomains();
634
+ return this.domainRegistry.getFiltered(allowedDomains);
635
+ }
417
636
  }
@@ -47,4 +47,34 @@ export class DomainRegistry {
47
47
  }
48
48
  return result;
49
49
  }
50
+
51
+ /**
52
+ * Get filtered domains by names
53
+ * @param allowedNames - Array of domain names to include (undefined = all domains)
54
+ * @returns Object with only the specified domains
55
+ */
56
+ getFiltered(
57
+ allowedNames?: string[]
58
+ ): Record<string, Record<string, unknown>> {
59
+ // If no filter specified, return all domains
60
+ if (!allowedNames) {
61
+ return this.all();
62
+ }
63
+
64
+ const result: Record<string, Record<string, unknown>> = {};
65
+ for (const name of allowedNames) {
66
+ const domain = this.domains.get(name);
67
+ if (domain) {
68
+ result[name] = domain;
69
+ }
70
+ }
71
+ return result;
72
+ }
73
+
74
+ /**
75
+ * Get list of all registered domain names
76
+ */
77
+ getDomainNames(): string[] {
78
+ return Array.from(this.domains.keys());
79
+ }
50
80
  }