@falai/agent 0.3.0 → 0.3.10

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 (49) hide show
  1. package/README.md +158 -9
  2. package/dist/cjs/core/Agent.d.ts +12 -0
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +37 -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/types/route.d.ts +6 -0
  19. package/dist/cjs/types/route.d.ts.map +1 -1
  20. package/dist/core/Agent.d.ts +12 -0
  21. package/dist/core/Agent.d.ts.map +1 -1
  22. package/dist/core/Agent.js +37 -1
  23. package/dist/core/Agent.js.map +1 -1
  24. package/dist/core/DomainRegistry.d.ts +10 -0
  25. package/dist/core/DomainRegistry.d.ts.map +1 -1
  26. package/dist/core/DomainRegistry.js +25 -0
  27. package/dist/core/DomainRegistry.js.map +1 -1
  28. package/dist/core/PromptBuilder.d.ts +9 -1
  29. package/dist/core/PromptBuilder.d.ts.map +1 -1
  30. package/dist/core/PromptBuilder.js +49 -2
  31. package/dist/core/PromptBuilder.js.map +1 -1
  32. package/dist/core/Route.d.ts +16 -0
  33. package/dist/core/Route.d.ts.map +1 -1
  34. package/dist/core/Route.js +22 -0
  35. package/dist/core/Route.js.map +1 -1
  36. package/dist/types/route.d.ts +6 -0
  37. package/dist/types/route.d.ts.map +1 -1
  38. package/docs/API_REFERENCE.md +99 -2
  39. package/docs/CONSTRUCTOR_OPTIONS.md +178 -37
  40. package/docs/GETTING_STARTED.md +10 -2
  41. package/examples/business-onboarding.ts +707 -0
  42. package/examples/domain-scoping.ts +266 -0
  43. package/examples/rules-prohibitions.ts +258 -0
  44. package/package.json +1 -1
  45. package/src/core/Agent.ts +46 -1
  46. package/src/core/DomainRegistry.ts +30 -0
  47. package/src/core/PromptBuilder.ts +70 -3
  48. package/src/core/Route.ts +28 -0
  49. package/src/types/route.ts +6 -0
@@ -40,9 +40,17 @@ Adds an agent capability. Returns `this` for chaining.
40
40
 
41
41
  Creates an observation for disambiguation.
42
42
 
43
- ##### `addDomain<TName, TDomain>(name: TName, domainObject: TDomain): this`
43
+ ##### `addDomain<TName, TDomain>(name: TName, domainObject: TDomain): void`
44
44
 
45
- Registers a domain with tools/methods. Returns `this` for chaining.
45
+ Registers a domain with tools/methods.
46
+
47
+ ##### `getDomainsForRoute(routeId: string): Record<string, Record<string, unknown>>`
48
+
49
+ Gets allowed domains for a specific route by ID. Returns filtered domains based on route's `domains` property, or all domains if route has no restrictions.
50
+
51
+ ##### `getDomainsForRouteByTitle(routeTitle: string): Record<string, Record<string, unknown>>`
52
+
53
+ Gets allowed domains for a specific route by title. Returns filtered domains based on route's `domains` property, or all domains if route has no restrictions.
46
54
 
47
55
  ##### `respond(input: RespondInput<TContext>): Promise<RespondOutput>`
48
56
 
@@ -136,6 +144,9 @@ interface RouteOptions {
136
144
  description?: string; // Route description
137
145
  conditions?: string[]; // Conditions that activate this route
138
146
  guidelines?: Guideline[]; // Initial guidelines for this route
147
+ domains?: string[]; // Domain names allowed in this route (undefined = all domains)
148
+ rules?: string[]; // Absolute rules the agent MUST follow in this route
149
+ prohibitions?: string[]; // Absolute prohibitions the agent MUST NEVER do in this route
139
150
  }
140
151
  ```
141
152
 
@@ -151,6 +162,18 @@ Adds a guideline specific to this route. Returns `this` for chaining.
151
162
 
152
163
  Returns all guidelines for this route.
153
164
 
165
+ ##### `getDomains(): string[] | undefined`
166
+
167
+ Returns allowed domain names for this route. Returns `undefined` if all domains are allowed, or an array of domain names if restricted.
168
+
169
+ ##### `getRules(): string[]`
170
+
171
+ Returns the rules that must be followed in this route.
172
+
173
+ ##### `getProhibitions(): string[]`
174
+
175
+ Returns the prohibitions that must never be done in this route.
176
+
154
177
  ##### `getRef(): RouteRef`
155
178
 
156
179
  Returns a reference to this route.
@@ -216,6 +239,7 @@ interface TransitionResult {
216
239
  **Example:**
217
240
 
218
241
  ```typescript
242
+ // Approach 1: Step-by-step (ideal for complex flows with branching)
219
243
  const t0 = route.initialState.transitionTo({
220
244
  chatState: "Ask for user name",
221
245
  });
@@ -224,9 +248,30 @@ const t1 = t0.transitionTo({
224
248
  chatState: "Ask for email",
225
249
  });
226
250
 
251
+ const t2 = t1.transitionTo({
252
+ chatState: "Confirm details",
253
+ });
254
+
227
255
  // Access state properties
228
256
  console.log(t1.id); // State ID
229
257
  console.log(t1.routeId); // Route ID
258
+
259
+ // Use saved references for branching
260
+ t1.transitionTo(
261
+ { chatState: "Handle invalid email" },
262
+ "Email validation failed"
263
+ );
264
+
265
+ // Approach 2: Fluent chaining (elegant for linear flows)
266
+ route.initialState
267
+ .transitionTo({ chatState: "Ask for user name" })
268
+ .transitionTo({ chatState: "Ask for email" })
269
+ .transitionTo({ chatState: "Confirm details" })
270
+ .transitionTo({ state: END_ROUTE });
271
+
272
+ // Both approaches are equivalent - choose based on your needs:
273
+ // - Use step-by-step for complex flows with conditional branches
274
+ // - Use chaining for simple linear flows for conciseness
230
275
  ```
231
276
 
232
277
  ##### `addGuideline(guideline: Guideline): void`
@@ -289,6 +334,58 @@ Observation description (readonly).
289
334
 
290
335
  ---
291
336
 
337
+ ### `DomainRegistry`
338
+
339
+ Registry for organizing agent tools and methods by domain.
340
+
341
+ #### Methods
342
+
343
+ ##### `register<TDomain>(name: string, domain: TDomain): void`
344
+
345
+ Registers a new domain with its tools/methods. Throws error if domain name already exists.
346
+
347
+ ##### `get<TDomain>(name: string): TDomain | undefined`
348
+
349
+ Gets a registered domain by name. Returns `undefined` if not found.
350
+
351
+ ##### `has(name: string): boolean`
352
+
353
+ Checks if a domain is registered.
354
+
355
+ ##### `all(): Record<string, Record<string, unknown>>`
356
+
357
+ Returns all registered domains as a single object.
358
+
359
+ ##### `getFiltered(allowedNames?: string[]): Record<string, Record<string, unknown>>`
360
+
361
+ Returns filtered domains by names. If `allowedNames` is `undefined`, returns all domains.
362
+
363
+ **Example:**
364
+
365
+ ```typescript
366
+ const registry = new DomainRegistry();
367
+
368
+ registry.register("payment", {
369
+ processPayment: async (amount: number) => { /* ... */ },
370
+ });
371
+
372
+ registry.register("shipping", {
373
+ calculateShipping: (zipCode: string) => { /* ... */ },
374
+ });
375
+
376
+ // Get specific domains
377
+ const filtered = registry.getFiltered(["payment"]); // Only payment domain
378
+
379
+ // Get all domains
380
+ const all = registry.getFiltered(); // payment + shipping
381
+ ```
382
+
383
+ ##### `getDomainNames(): string[]`
384
+
385
+ Returns list of all registered domain names.
386
+
387
+ ---
388
+
292
389
  ### `GeminiProvider`
293
390
 
294
391
  AI provider implementation for Google Gemini.
@@ -41,48 +41,48 @@ interface AgentOptions<TContext = unknown> {
41
41
 
42
42
  ```typescript
43
43
  const agent = new Agent({
44
- name: "SupportBot",
45
- description: "Helpful customer support",
46
- goal: "Resolve issues efficiently",
47
- ai: new GeminiProvider({ apiKey: "...", model: "..." }),
48
- context: { userId: "123" },
44
+ name: 'SupportBot',
45
+ description: 'Helpful customer support',
46
+ goal: 'Resolve issues efficiently',
47
+ ai: new GeminiProvider({ apiKey: '...', model: '...' }),
48
+ context: { userId: '123' },
49
49
 
50
50
  terms: [
51
51
  {
52
- name: "SLA",
53
- description: "Service Level Agreement",
54
- synonyms: ["response time"],
52
+ name: 'SLA',
53
+ description: 'Service Level Agreement',
54
+ synonyms: ['response time'],
55
55
  },
56
56
  ],
57
57
 
58
58
  guidelines: [
59
59
  {
60
- condition: "User is frustrated",
61
- action: "Show empathy and offer escalation",
62
- tags: ["support"],
60
+ condition: 'User is frustrated',
61
+ action: 'Show empathy and offer escalation',
62
+ tags: ['support'],
63
63
  enabled: true,
64
64
  },
65
65
  ],
66
66
 
67
67
  capabilities: [
68
- { title: "Ticket Management", description: "Create and track tickets" },
68
+ { title: 'Ticket Management', description: 'Create and track tickets' },
69
69
  ],
70
70
 
71
71
  routes: [
72
72
  {
73
- title: "Create Ticket",
74
- description: "Help user create a support ticket",
75
- conditions: ["User wants to report an issue"],
73
+ title: 'Create Ticket',
74
+ description: 'Help user create a support ticket',
75
+ conditions: ['User wants to report an issue'],
76
76
  guidelines: [
77
- { condition: "Issue is urgent", action: "Prioritize immediately" },
77
+ { condition: 'Issue is urgent', action: 'Prioritize immediately' },
78
78
  ],
79
79
  },
80
80
  ],
81
81
 
82
82
  observations: [
83
83
  {
84
- description: "User mentions problem but unclear what kind",
85
- routeRefs: ["Create Ticket", "Check Ticket Status"], // By title!
84
+ description: 'User mentions problem but unclear what kind',
85
+ routeRefs: ['Create Ticket', 'Check Ticket Status'], // By title!
86
86
  },
87
87
  ],
88
88
  });
@@ -98,33 +98,49 @@ interface RouteOptions {
98
98
  title: string;
99
99
 
100
100
  // Optional
101
+ id?: string; // Custom ID (auto-generated from title if not provided)
101
102
  description?: string;
102
103
  conditions?: string[];
103
- guidelines?: Guideline[]; // NEW!
104
+ guidelines?: Guideline[];
105
+ domains?: string[]; // Restrict which domains are available in this route
106
+ rules?: string[]; // Absolute rules the agent MUST follow
107
+ prohibitions?: string[]; // Absolute prohibitions the agent MUST NEVER do
104
108
  }
105
109
  ```
106
110
 
111
+ **Domain Scoping:**
112
+ - Use `domains` to limit which registered domains (tools/methods) can be accessed during this route
113
+ - If `undefined` or omitted, all registered domains are available
114
+ - Useful for security (preventing unauthorized tool calls) and performance (reducing AI decision space)
115
+
116
+ **Rules & Prohibitions:**
117
+ - **Rules**: Absolute requirements the agent must follow in this route (style, format, behavior)
118
+ - **Prohibitions**: Things the agent must never do in this route
119
+ - These override general guidelines if there's any conflict
120
+ - Applied automatically when the route is active
121
+ - Perfect for controlling message style, tone, length, emoji usage, etc.
122
+
107
123
  ### Example: Route with Nested Guidelines
108
124
 
109
125
  ```typescript
110
126
  const agent = new Agent({
111
- name: "Bot",
127
+ name: 'Bot',
112
128
  ai: provider,
113
129
  routes: [
114
130
  {
115
- title: "Onboarding",
116
- description: "Guide new users",
117
- conditions: ["User is new"],
131
+ title: 'Onboarding',
132
+ description: 'Guide new users',
133
+ conditions: ['User is new'],
118
134
  guidelines: [
119
135
  {
120
- condition: "User skips a step",
136
+ condition: 'User skips a step',
121
137
  action: "Gently remind them it's important",
122
- tags: ["onboarding"],
138
+ tags: ['onboarding'],
123
139
  },
124
140
  {
125
- condition: "User seems confused",
126
- action: "Offer a quick tutorial video",
127
- tags: ["help"],
141
+ condition: 'User seems confused',
142
+ action: 'Offer a quick tutorial video',
143
+ tags: ['help'],
128
144
  },
129
145
  ],
130
146
  },
@@ -132,6 +148,131 @@ const agent = new Agent({
132
148
  });
133
149
  ```
134
150
 
151
+ ### Example: Route with Domain Scoping
152
+
153
+ ```typescript
154
+ // Register domains
155
+ agent.addDomain('scraping', {
156
+ scrapeSite: async (url: string) => {
157
+ /* ... */
158
+ },
159
+ extractData: async (html: string) => {
160
+ /* ... */
161
+ },
162
+ });
163
+
164
+ agent.addDomain('calendar', {
165
+ scheduleEvent: async (date: Date, title: string) => {
166
+ /* ... */
167
+ },
168
+ listEvents: async () => {
169
+ /* ... */
170
+ },
171
+ });
172
+
173
+ agent.addDomain('payment', {
174
+ processPayment: async (amount: number) => {
175
+ /* ... */
176
+ },
177
+ });
178
+
179
+ // Create routes with domain restrictions
180
+ agent.createRoute({
181
+ title: 'Data Collection',
182
+ description: 'Collect and process web data',
183
+ domains: ['scraping'], // ✅ Only scraping tools available
184
+ });
185
+
186
+ agent.createRoute({
187
+ title: 'Schedule Meeting',
188
+ description: 'Book appointments',
189
+ domains: ['calendar'], // ✅ Only calendar tools available
190
+ });
191
+
192
+ agent.createRoute({
193
+ title: 'Checkout',
194
+ description: 'Process purchase',
195
+ domains: ['payment', 'calendar'], // ✅ Multiple domains allowed
196
+ });
197
+
198
+ agent.createRoute({
199
+ title: 'FAQ Support',
200
+ description: 'Answer general questions',
201
+ domains: [], // ✅ No tools available (conversation only)
202
+ });
203
+
204
+ agent.createRoute({
205
+ title: 'Admin Support',
206
+ description: 'Administrative tasks',
207
+ // domains not specified = all domains available (for demo purposes)
208
+ });
209
+ ```
210
+
211
+ ### Example: Route with Rules & Prohibitions
212
+
213
+ ```typescript
214
+ // WhatsApp support bot with different styles per route
215
+ agent.createRoute({
216
+ title: 'Customer Support',
217
+ description: 'Help customers with issues',
218
+ domains: [],
219
+ rules: [
220
+ 'Keep messages short (maximum 2 lines per message)',
221
+ 'Use maximum 1 emoji per message',
222
+ 'Always ask if the issue is resolved before ending',
223
+ 'Professional but friendly tone'
224
+ ],
225
+ prohibitions: [
226
+ 'Never send messages longer than 3 paragraphs',
227
+ 'Do not use slang or informal language',
228
+ 'Never promise what you cannot deliver',
229
+ 'Do not ask for sensitive information via chat'
230
+ ]
231
+ });
232
+
233
+ agent.createRoute({
234
+ title: 'Sales Consultation',
235
+ description: 'Help customer discover needs and present solutions',
236
+ domains: ['calendar', 'analytics'],
237
+ rules: [
238
+ 'Ask open-ended questions to discover needs',
239
+ 'Use storytelling when presenting solutions',
240
+ 'Emoji only to reinforce positive emotions 😊',
241
+ 'Always present value before mentioning price'
242
+ ],
243
+ prohibitions: [
244
+ 'Never talk about price before showing value',
245
+ 'Do not pressure the customer',
246
+ 'Avoid complex technical terms',
247
+ 'Never send more than 2 messages in a row without customer response'
248
+ ]
249
+ });
250
+
251
+ agent.createRoute({
252
+ title: 'Emergency Support',
253
+ description: 'Handle urgent customer issues',
254
+ domains: ['notifications', 'ticketing'],
255
+ rules: [
256
+ 'Respond immediately and acknowledge urgency',
257
+ 'Use clear, direct language',
258
+ 'Provide concrete next steps',
259
+ 'Set clear expectations on resolution time'
260
+ ],
261
+ prohibitions: [
262
+ 'Never downplay the customer\'s concern',
263
+ 'Do not use emojis',
264
+ 'Never say "calm down" or similar phrases',
265
+ 'Do not transfer without explaining why'
266
+ ]
267
+ });
268
+ ```
269
+
270
+ **How it works:**
271
+ - Rules and prohibitions are automatically applied when the route is active
272
+ - They override general guidelines if there's any conflict
273
+ - Perfect for controlling communication style per context
274
+ - Applied in the AI prompt to ensure compliance
275
+
135
276
  ---
136
277
 
137
278
  ## 🔍 Observation Options
@@ -175,14 +316,14 @@ All constructor options also have fluent methods that **return `this`** for chai
175
316
 
176
317
  ```typescript
177
318
  agent
178
- .createTerm({ name: "API", description: "..." })
179
- .createGuideline({ condition: "...", action: "..." })
180
- .createCapability({ title: "...", description: "..." });
319
+ .createTerm({ name: 'API', description: '...' })
320
+ .createGuideline({ condition: '...', action: '...' })
321
+ .createCapability({ title: '...', description: '...' });
181
322
 
182
- const route = agent.createRoute({ title: "..." });
183
- route.createGuideline({ condition: "...", action: "..." });
323
+ const route = agent.createRoute({ title: '...' });
324
+ route.createGuideline({ condition: '...', action: '...' });
184
325
 
185
- const obs = agent.createObservation("User intent unclear");
326
+ const obs = agent.createObservation('User intent unclear');
186
327
  obs.disambiguate([route1, route2]);
187
328
  ```
188
329
 
@@ -209,7 +350,7 @@ obs.disambiguate([route1, route2]);
209
350
  ```typescript
210
351
  // Start with static config
211
352
  const agent = new Agent({
212
- name: "Bot",
353
+ name: 'Bot',
213
354
  ai: provider,
214
355
  terms: loadTermsFromFile(),
215
356
  guidelines: loadGuidelinesFromDB(),
@@ -218,8 +359,8 @@ const agent = new Agent({
218
359
  // Add dynamic features
219
360
  if (user.isPremium) {
220
361
  agent.createGuideline({
221
- condition: "User asks for priority support",
222
- action: "Escalate immediately to premium team",
362
+ condition: 'User asks for priority support',
363
+ action: 'Escalate immediately to premium team',
223
364
  });
224
365
  }
225
366
  ```
@@ -154,7 +154,9 @@ const onboardingRoute = agent.createRoute({
154
154
  conditions: ["User is new and needs onboarding"],
155
155
  });
156
156
 
157
- // Build the flow
157
+ // Build the flow - two approaches:
158
+
159
+ // Approach 1: Step-by-step (great for complex flows with branching)
158
160
  const step1 = onboardingRoute.initialState.transitionTo({
159
161
  chatState: "Ask for user's name",
160
162
  });
@@ -167,8 +169,14 @@ const step3 = step2.transitionTo({
167
169
  chatState: "Confirm details and welcome user",
168
170
  });
169
171
 
170
- // End the route
171
172
  step3.transitionTo({ state: END_ROUTE });
173
+
174
+ // Approach 2: Fluent chaining (concise for linear flows)
175
+ onboardingRoute.initialState
176
+ .transitionTo({ chatState: "Ask for user's name" })
177
+ .transitionTo({ chatState: "Ask for user's email" })
178
+ .transitionTo({ chatState: "Confirm details and welcome user" })
179
+ .transitionTo({ state: END_ROUTE });
172
180
  ```
173
181
 
174
182
  ### Handle Context Dynamically