@falai/agent 0.3.25 → 0.4.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 (86) hide show
  1. package/README.md +97 -3
  2. package/dist/cjs/core/Agent.d.ts +1 -0
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +51 -12
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/ConditionEvaluator.d.ts +72 -0
  7. package/dist/cjs/core/ConditionEvaluator.d.ts.map +1 -0
  8. package/dist/cjs/core/ConditionEvaluator.js +272 -0
  9. package/dist/cjs/core/ConditionEvaluator.js.map +1 -0
  10. package/dist/cjs/core/PreparationEngine.d.ts +105 -0
  11. package/dist/cjs/core/PreparationEngine.d.ts.map +1 -0
  12. package/dist/cjs/core/PreparationEngine.js +320 -0
  13. package/dist/cjs/core/PreparationEngine.js.map +1 -0
  14. package/dist/cjs/core/PromptBuilder.d.ts +3 -0
  15. package/dist/cjs/core/PromptBuilder.d.ts.map +1 -1
  16. package/dist/cjs/core/PromptBuilder.js +10 -10
  17. package/dist/cjs/core/PromptBuilder.js.map +1 -1
  18. package/dist/cjs/core/Route.d.ts +6 -0
  19. package/dist/cjs/core/Route.d.ts.map +1 -1
  20. package/dist/cjs/core/Route.js +9 -0
  21. package/dist/cjs/core/Route.js.map +1 -1
  22. package/dist/cjs/providers/AnthropicProvider.d.ts +3 -3
  23. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  24. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  25. package/dist/cjs/providers/GeminiProvider.d.ts +3 -3
  26. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  27. package/dist/cjs/providers/GeminiProvider.js +43 -18
  28. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  29. package/dist/cjs/providers/OpenAIProvider.d.ts +3 -3
  30. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  31. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  32. package/dist/cjs/providers/OpenRouterProvider.d.ts +3 -3
  33. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  34. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  35. package/dist/cjs/types/ai.d.ts +6 -6
  36. package/dist/cjs/types/ai.d.ts.map +1 -1
  37. package/dist/core/Agent.d.ts +1 -0
  38. package/dist/core/Agent.d.ts.map +1 -1
  39. package/dist/core/Agent.js +51 -12
  40. package/dist/core/Agent.js.map +1 -1
  41. package/dist/core/ConditionEvaluator.d.ts +72 -0
  42. package/dist/core/ConditionEvaluator.d.ts.map +1 -0
  43. package/dist/core/ConditionEvaluator.js +268 -0
  44. package/dist/core/ConditionEvaluator.js.map +1 -0
  45. package/dist/core/PreparationEngine.d.ts +105 -0
  46. package/dist/core/PreparationEngine.d.ts.map +1 -0
  47. package/dist/core/PreparationEngine.js +316 -0
  48. package/dist/core/PreparationEngine.js.map +1 -0
  49. package/dist/core/PromptBuilder.d.ts +3 -0
  50. package/dist/core/PromptBuilder.d.ts.map +1 -1
  51. package/dist/core/PromptBuilder.js +10 -10
  52. package/dist/core/PromptBuilder.js.map +1 -1
  53. package/dist/core/Route.d.ts +6 -0
  54. package/dist/core/Route.d.ts.map +1 -1
  55. package/dist/core/Route.js +9 -0
  56. package/dist/core/Route.js.map +1 -1
  57. package/dist/providers/AnthropicProvider.d.ts +3 -3
  58. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  59. package/dist/providers/AnthropicProvider.js.map +1 -1
  60. package/dist/providers/GeminiProvider.d.ts +3 -3
  61. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  62. package/dist/providers/GeminiProvider.js +43 -18
  63. package/dist/providers/GeminiProvider.js.map +1 -1
  64. package/dist/providers/OpenAIProvider.d.ts +3 -3
  65. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  66. package/dist/providers/OpenAIProvider.js.map +1 -1
  67. package/dist/providers/OpenRouterProvider.d.ts +3 -3
  68. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  69. package/dist/providers/OpenRouterProvider.js.map +1 -1
  70. package/dist/types/ai.d.ts +6 -6
  71. package/dist/types/ai.d.ts.map +1 -1
  72. package/docs/ARCHITECTURE.md +386 -0
  73. package/docs/DOMAINS.md +674 -0
  74. package/docs/README.md +17 -3
  75. package/examples/domain-scoping.ts +18 -10
  76. package/package.json +1 -1
  77. package/src/core/Agent.ts +63 -12
  78. package/src/core/ConditionEvaluator.ts +381 -0
  79. package/src/core/PreparationEngine.ts +500 -0
  80. package/src/core/PromptBuilder.ts +16 -16
  81. package/src/core/Route.ts +10 -0
  82. package/src/providers/AnthropicProvider.ts +51 -21
  83. package/src/providers/GeminiProvider.ts +86 -40
  84. package/src/providers/OpenAIProvider.ts +48 -21
  85. package/src/providers/OpenRouterProvider.ts +36 -18
  86. package/src/types/ai.ts +13 -8
@@ -2,7 +2,13 @@
2
2
  * Domain Scoping Example
3
3
  *
4
4
  * This example demonstrates how to use domain scoping to restrict which tools
5
- * are available in different conversation routes for security and clarity.
5
+ * can EXECUTE in different conversation routes for security and isolation.
6
+ *
7
+ * Important: Domains are OPTIONAL. If you never use domains, all tools are
8
+ * available everywhere. Use domains when you need security and organization.
9
+ *
10
+ * Key Concept: The AI never sees tools. Domains control which tools can
11
+ * execute automatically when triggered by the state machine or guidelines.
6
12
  */
7
13
 
8
14
  import { Agent, createMessageEvent, EventSource } from "../src/index";
@@ -186,19 +192,21 @@ async function demonstrateScoping() {
186
192
  // Benefits demonstration
187
193
  console.log(`
188
194
  🔒 Security Benefits:
189
- - Customer Support route cannot accidentally call payment.processPayment()
190
- - Data Collection route cannot access calendar.scheduleEvent()
191
- - Each route has minimum necessary permissions
195
+ - Customer Support route cannot execute payment.processPayment()
196
+ - Data Collection route cannot execute calendar.scheduleEvent()
197
+ - Each route has minimum necessary tool permissions
198
+ - Prevents prompt injection attacks from calling sensitive tools
192
199
 
193
- Performance Benefits:
194
- - AI only sees relevant tools for each route
195
- - Faster decision making with reduced context
196
- - Clearer intent and fewer errors
200
+ 🎯 Isolation Benefits:
201
+ - Route execution is isolated - tools can't cross boundaries
202
+ - Checkout can't accidentally trigger admin operations
203
+ - Clear separation of concerns by capability
197
204
 
198
- 🎯 Clarity Benefits:
205
+ 📋 Clarity Benefits:
199
206
  - Routes clearly document their capabilities
200
- - Easy to audit what each route can do
207
+ - Easy to audit what each route can execute
201
208
  - Better debugging when tools are called
209
+ - Self-documenting security model
202
210
  `);
203
211
 
204
212
  // Inspect route configurations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falai/agent",
3
- "version": "0.3.25",
3
+ "version": "0.4.0",
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",
package/src/core/Agent.ts CHANGED
@@ -17,6 +17,7 @@ import { DomainRegistry } from "./DomainRegistry";
17
17
  import { PromptBuilder } from "./PromptBuilder";
18
18
  import { Observation } from "./Observation";
19
19
  import { PersistenceManager } from "./PersistenceManager";
20
+ import { PreparationEngine } from "./PreparationEngine";
20
21
 
21
22
  /**
22
23
  * Main Agent class with generic context support
@@ -30,6 +31,7 @@ export class Agent<TContext = unknown> {
30
31
  private domainRegistry = new DomainRegistry();
31
32
  private context: TContext | undefined;
32
33
  private persistenceManager: PersistenceManager | undefined;
34
+ private preparationEngine: PreparationEngine<TContext>;
33
35
 
34
36
  /**
35
37
  * Dynamic domain property - populated via addDomain
@@ -52,6 +54,9 @@ export class Agent<TContext = unknown> {
52
54
  // Initialize context if provided
53
55
  this.context = options.context;
54
56
 
57
+ // Initialize preparation engine with AI provider
58
+ this.preparationEngine = new PreparationEngine<TContext>(options.ai);
59
+
55
60
  // Initialize persistence if configured
56
61
  if (options.persistence) {
57
62
  this.persistenceManager = new PersistenceManager(options.persistence);
@@ -257,11 +262,36 @@ export class Agent<TContext = unknown> {
257
262
  }
258
263
 
259
264
  // Merge context with override
260
- const effectiveContext = {
265
+ let effectiveContext = {
261
266
  ...(currentContext as Record<string, unknown>),
262
267
  ...(contextOverride as Record<string, unknown>),
263
268
  } as TContext;
264
269
 
270
+ // RUN PREPARATION ITERATIONS
271
+ // This is where tools execute automatically based on:
272
+ // 1. Matched guidelines with associated tools
273
+ // 2. State machine transitions with toolState
274
+ //
275
+ // The AI will NEVER see these tools - they execute before message generation
276
+ const preparationResult = await this.preparationEngine.prepare({
277
+ history,
278
+ currentState: params.state,
279
+ context: effectiveContext,
280
+ routes: this.routes,
281
+ guidelines: this.guidelines,
282
+ maxIterations: this.options.maxEngineIterations || 1,
283
+ });
284
+
285
+ // Update context with results from tool executions
286
+ effectiveContext = preparationResult.finalContext;
287
+
288
+ // Log tool executions for debugging
289
+ if (preparationResult.toolExecutions.length > 0) {
290
+ console.log(
291
+ `[Agent] Preparation complete: ${preparationResult.toolExecutions.length} tools executed in ${preparationResult.iterations.length} iterations`
292
+ );
293
+ }
294
+
265
295
  // Build prompt (same as respond method)
266
296
  const promptBuilder = new PromptBuilder();
267
297
 
@@ -328,11 +358,9 @@ export class Agent<TContext = unknown> {
328
358
  );
329
359
  }
330
360
 
331
- // Add domains (tools) information if any domains are registered
332
- const allDomains = this.domainRegistry.all();
333
- if (Object.keys(allDomains).length > 0) {
334
- promptBuilder.addDomains(allDomains);
335
- }
361
+ // NOTE: Domains/tools are NOT added to the prompt.
362
+ // Tools execute automatically based on state transitions and guideline matching,
363
+ // NOT based on AI decisions. The AI should never see available tools.
336
364
 
337
365
  // Add JSON response schema instructions
338
366
  promptBuilder.addJsonResponseSchema();
@@ -429,11 +457,36 @@ export class Agent<TContext = unknown> {
429
457
  }
430
458
 
431
459
  // Merge context with override
432
- const effectiveContext = {
460
+ let effectiveContext = {
433
461
  ...(currentContext as Record<string, unknown>),
434
462
  ...(contextOverride as Record<string, unknown>),
435
463
  } as TContext;
436
464
 
465
+ // RUN PREPARATION ITERATIONS
466
+ // This is where tools execute automatically based on:
467
+ // 1. Matched guidelines with associated tools
468
+ // 2. State machine transitions with toolState
469
+ //
470
+ // The AI will NEVER see these tools - they execute before message generation
471
+ const preparationResult = await this.preparationEngine.prepare({
472
+ history,
473
+ currentState: params.state,
474
+ context: effectiveContext,
475
+ routes: this.routes,
476
+ guidelines: this.guidelines,
477
+ maxIterations: this.options.maxEngineIterations || 1,
478
+ });
479
+
480
+ // Update context with results from tool executions
481
+ effectiveContext = preparationResult.finalContext;
482
+
483
+ // Log tool executions for debugging
484
+ if (preparationResult.toolExecutions.length > 0) {
485
+ console.log(
486
+ `[Agent] Preparation complete: ${preparationResult.toolExecutions.length} tools executed in ${preparationResult.iterations.length} iterations`
487
+ );
488
+ }
489
+
437
490
  // Build prompt
438
491
  const promptBuilder = new PromptBuilder();
439
492
 
@@ -500,11 +553,9 @@ export class Agent<TContext = unknown> {
500
553
  );
501
554
  }
502
555
 
503
- // Add domains (tools) information if any domains are registered
504
- const allDomains = this.domainRegistry.all();
505
- if (Object.keys(allDomains).length > 0) {
506
- promptBuilder.addDomains(allDomains);
507
- }
556
+ // NOTE: Domains/tools are NOT added to the prompt.
557
+ // Tools execute automatically based on state transitions and guideline matching,
558
+ // NOT based on AI decisions. The AI should never see available tools.
508
559
 
509
560
  // Add JSON response schema instructions
510
561
  promptBuilder.addJsonResponseSchema();
@@ -0,0 +1,381 @@
1
+ /**
2
+ * ConditionEvaluator - Handles AI-powered condition evaluation
3
+ *
4
+ * This class is responsible for:
5
+ * - Evaluating guideline conditions
6
+ * - Evaluating transition conditions
7
+ * - Extracting tool arguments from context and history
8
+ */
9
+
10
+ import type { Event, AiProvider, MessageEventData } from "../types/index";
11
+ import { EventKind } from "../types/history";
12
+ import type { Guideline } from "../types/agent";
13
+ import type { ToolRef } from "../types/tool";
14
+
15
+ /**
16
+ * Result of guideline condition evaluation
17
+ */
18
+ export interface GuidelineEvaluationResult {
19
+ matches: boolean;
20
+ rationale?: string;
21
+ }
22
+
23
+ /**
24
+ * Result of transition condition evaluation
25
+ */
26
+ export interface TransitionEvaluationResult {
27
+ shouldFollow: boolean;
28
+ rationale?: string;
29
+ }
30
+
31
+ /**
32
+ * Result of tool argument extraction
33
+ */
34
+ export interface ArgumentExtractionResult {
35
+ arguments: unknown[];
36
+ rationale?: string;
37
+ }
38
+
39
+ /**
40
+ * Schema for guideline evaluation response
41
+ */
42
+ interface GuidelineEvaluationSchema {
43
+ matches: boolean;
44
+ rationale: string;
45
+ }
46
+
47
+ /**
48
+ * Schema for transition evaluation response
49
+ */
50
+ interface TransitionEvaluationSchema {
51
+ shouldFollow: boolean;
52
+ rationale: string;
53
+ }
54
+
55
+ /**
56
+ * Schema for argument extraction response
57
+ */
58
+ interface ArgumentExtractionSchema {
59
+ arguments: unknown[];
60
+ rationale?: string;
61
+ }
62
+
63
+ /**
64
+ * ConditionEvaluator - Evaluates conditions using AI
65
+ */
66
+ export class ConditionEvaluator<TContext = unknown> {
67
+ constructor(private readonly ai: AiProvider) {}
68
+
69
+ /**
70
+ * Evaluate a guideline condition against context and history
71
+ */
72
+ async evaluateGuidelineCondition(
73
+ guideline: Guideline,
74
+ context: TContext,
75
+ history: Event[]
76
+ ): Promise<GuidelineEvaluationResult> {
77
+ try {
78
+ const recentMessages = this.extractRecentMessages(history);
79
+
80
+ const prompt = `You are evaluating whether a guideline condition is met.
81
+
82
+ Guideline Condition: ${guideline.condition}
83
+ Action: ${guideline.action}
84
+
85
+ Current Context:
86
+ ${JSON.stringify(context, null, 2)}
87
+
88
+ Recent Conversation:
89
+ ${recentMessages || "(No recent messages)"}
90
+
91
+ Evaluate whether the guideline condition is currently met based on the context and conversation.
92
+
93
+ Your response must be a JSON object with this exact structure:
94
+ {
95
+ "matches": boolean (true or false),
96
+ "rationale": string (brief explanation)
97
+ }`;
98
+
99
+ const result = await this.ai.generateMessage<
100
+ TContext,
101
+ GuidelineEvaluationSchema
102
+ >({
103
+ prompt,
104
+ history,
105
+ context,
106
+ parameters: {
107
+ jsonMode: true,
108
+ maxOutputTokens: 500,
109
+ },
110
+ });
111
+
112
+ // Parse structured response
113
+ if (result.structured) {
114
+ return {
115
+ matches: result.structured.matches === true,
116
+ rationale: result.structured.rationale,
117
+ };
118
+ }
119
+
120
+ // Fallback parsing
121
+ return this.parseGuidelineResponse(result.message);
122
+ } catch (error) {
123
+ console.error(
124
+ `[ConditionEvaluator] Failed to evaluate guideline condition: ${guideline.id}`,
125
+ error
126
+ );
127
+ return {
128
+ matches: false,
129
+ rationale: "Failed to evaluate condition",
130
+ };
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Evaluate a transition condition
136
+ */
137
+ async evaluateTransitionCondition(
138
+ condition: string,
139
+ context: TContext,
140
+ history: Event[]
141
+ ): Promise<TransitionEvaluationResult> {
142
+ try {
143
+ const recentMessages = this.extractRecentMessages(history);
144
+
145
+ const prompt = `You are evaluating whether a state transition condition is met.
146
+
147
+ Transition Condition: ${condition}
148
+
149
+ Current Context:
150
+ ${JSON.stringify(context, null, 2)}
151
+
152
+ Recent Conversation:
153
+ ${recentMessages || "(No recent messages)"}
154
+
155
+ Evaluate whether this transition should be followed based on the condition, context, and conversation.
156
+
157
+ Your response must be a JSON object with this exact structure:
158
+ {
159
+ "shouldFollow": boolean (true or false),
160
+ "rationale": string (brief explanation)
161
+ }`;
162
+
163
+ const result = await this.ai.generateMessage<
164
+ TContext,
165
+ TransitionEvaluationSchema
166
+ >({
167
+ prompt,
168
+ history,
169
+ context,
170
+ parameters: {
171
+ jsonMode: true,
172
+ maxOutputTokens: 300,
173
+ },
174
+ });
175
+
176
+ // Parse structured response
177
+ if (result.structured) {
178
+ return {
179
+ shouldFollow: result.structured.shouldFollow === true,
180
+ rationale: result.structured.rationale,
181
+ };
182
+ }
183
+
184
+ // Fallback parsing
185
+ return this.parseTransitionResponse(result.message);
186
+ } catch (error) {
187
+ console.error(
188
+ `[ConditionEvaluator] Failed to evaluate transition condition`,
189
+ error
190
+ );
191
+ return {
192
+ shouldFollow: false,
193
+ rationale: "Failed to evaluate condition",
194
+ };
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Extract tool arguments from context and history
200
+ */
201
+ async extractToolArguments(
202
+ tool: ToolRef<TContext, unknown[], unknown>,
203
+ context: TContext,
204
+ history: Event[]
205
+ ): Promise<ArgumentExtractionResult> {
206
+ try {
207
+ const recentMessages = this.extractRecentMessages(history);
208
+
209
+ const prompt = `You are extracting arguments for a tool call.
210
+
211
+ Tool: ${tool.name}
212
+ Description: ${tool.description || "No description"}
213
+ Parameters: ${JSON.stringify(tool.parameters, null, 2)}
214
+
215
+ Current Context:
216
+ ${JSON.stringify(context, null, 2)}
217
+
218
+ Recent Conversation:
219
+ ${recentMessages || "(No recent messages)"}
220
+
221
+ Extract the arguments needed to call this tool based on the context and conversation.
222
+ If a parameter is not available, use null or a reasonable default value.
223
+
224
+ Your response must be a JSON object with this exact structure:
225
+ {
226
+ "arguments": [arg1, arg2, ...] (array of argument values),
227
+ "rationale": string (optional explanation)
228
+ }`;
229
+
230
+ const result = await this.ai.generateMessage<
231
+ TContext,
232
+ ArgumentExtractionSchema
233
+ >({
234
+ prompt,
235
+ history,
236
+ context,
237
+ parameters: {
238
+ jsonMode: true,
239
+ maxOutputTokens: 500,
240
+ },
241
+ });
242
+
243
+ // Parse structured response
244
+ if (result.structured) {
245
+ if (
246
+ result.structured.arguments &&
247
+ Array.isArray(result.structured.arguments)
248
+ ) {
249
+ return {
250
+ arguments: result.structured.arguments,
251
+ rationale: result.structured.rationale,
252
+ };
253
+ }
254
+ }
255
+
256
+ // Fallback: try to parse from message
257
+ return this.parseArgumentResponse(result.message);
258
+ } catch (error) {
259
+ console.error(
260
+ `[ConditionEvaluator] Failed to extract tool arguments: ${tool.name}`,
261
+ error
262
+ );
263
+ return {
264
+ arguments: [],
265
+ rationale: "Failed to extract arguments",
266
+ };
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Simple argument extraction from context (fallback)
272
+ */
273
+ simpleArgumentExtraction(
274
+ tool: ToolRef<TContext, unknown[], unknown>,
275
+ context: TContext
276
+ ): unknown[] {
277
+ const contextObj = context as Record<string, unknown>;
278
+ const args: unknown[] = [];
279
+
280
+ // If parameters is an object with properties, try to match context keys
281
+ if (
282
+ tool.parameters &&
283
+ typeof tool.parameters === "object" &&
284
+ !Array.isArray(tool.parameters)
285
+ ) {
286
+ const params = tool.parameters as Record<string, unknown>;
287
+
288
+ // Try to match parameter names with context keys
289
+ for (const [paramName, paramDef] of Object.entries(params)) {
290
+ if (contextObj[paramName] !== undefined) {
291
+ args.push(contextObj[paramName]);
292
+ } else if (
293
+ typeof paramDef === "object" &&
294
+ paramDef !== null &&
295
+ "default" in paramDef &&
296
+ typeof (paramDef as { default?: unknown }).default !== "undefined"
297
+ ) {
298
+ args.push((paramDef as { default: unknown }).default);
299
+ }
300
+ }
301
+ }
302
+
303
+ return args;
304
+ }
305
+
306
+ // Private helper methods
307
+
308
+ /**
309
+ * Extract recent messages from history
310
+ */
311
+ private extractRecentMessages(history: Event[], count = 5): string {
312
+ return history
313
+ .slice(-count)
314
+ .map((event) => {
315
+ if (event.kind === EventKind.MESSAGE) {
316
+ const data = event.data as MessageEventData;
317
+ return `${data.participant.display_name}: ${data.message}`;
318
+ }
319
+ return null;
320
+ })
321
+ .filter((msg): msg is string => msg !== null)
322
+ .join("\n");
323
+ }
324
+
325
+ /**
326
+ * Parse guideline evaluation from text response (fallback)
327
+ */
328
+ private parseGuidelineResponse(message: string): GuidelineEvaluationResult {
329
+ const lowerMessage = message.toLowerCase();
330
+ const matches =
331
+ lowerMessage.includes("true") || lowerMessage.includes('"matches": true');
332
+
333
+ return {
334
+ matches,
335
+ rationale: matches ? "Parsed from text response" : "Condition not met",
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Parse transition evaluation from text response (fallback)
341
+ */
342
+ private parseTransitionResponse(message: string): TransitionEvaluationResult {
343
+ const lowerMessage = message.toLowerCase();
344
+ const shouldFollow =
345
+ lowerMessage.includes("true") ||
346
+ lowerMessage.includes('"shouldfollow": true');
347
+
348
+ return {
349
+ shouldFollow,
350
+ rationale: shouldFollow
351
+ ? "Parsed from text response"
352
+ : "Condition not met",
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Parse argument extraction from text response (fallback)
358
+ */
359
+ private parseArgumentResponse(message: string): ArgumentExtractionResult {
360
+ try {
361
+ // Try to extract JSON from the message
362
+ const jsonMatch = message.match(/\{[\s\S]*\}/);
363
+ if (jsonMatch) {
364
+ const parsed = JSON.parse(jsonMatch[0]) as ArgumentExtractionSchema;
365
+ if (parsed.arguments && Array.isArray(parsed.arguments)) {
366
+ return {
367
+ arguments: parsed.arguments,
368
+ rationale: parsed.rationale,
369
+ };
370
+ }
371
+ }
372
+ } catch {
373
+ // Ignore parse errors
374
+ }
375
+
376
+ return {
377
+ arguments: [],
378
+ rationale: "Failed to parse arguments from response",
379
+ };
380
+ }
381
+ }