@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.
- package/README.md +97 -3
- package/dist/cjs/core/Agent.d.ts +1 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +51 -12
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/ConditionEvaluator.d.ts +72 -0
- package/dist/cjs/core/ConditionEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/ConditionEvaluator.js +272 -0
- package/dist/cjs/core/ConditionEvaluator.js.map +1 -0
- package/dist/cjs/core/PreparationEngine.d.ts +105 -0
- package/dist/cjs/core/PreparationEngine.d.ts.map +1 -0
- package/dist/cjs/core/PreparationEngine.js +320 -0
- package/dist/cjs/core/PreparationEngine.js.map +1 -0
- package/dist/cjs/core/PromptBuilder.d.ts +3 -0
- package/dist/cjs/core/PromptBuilder.d.ts.map +1 -1
- package/dist/cjs/core/PromptBuilder.js +10 -10
- package/dist/cjs/core/PromptBuilder.js.map +1 -1
- package/dist/cjs/core/Route.d.ts +6 -0
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +9 -0
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +3 -3
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +3 -3
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +43 -18
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +3 -3
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts +3 -3
- package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
- package/dist/cjs/types/ai.d.ts +6 -6
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/core/Agent.d.ts +1 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +51 -12
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/ConditionEvaluator.d.ts +72 -0
- package/dist/core/ConditionEvaluator.d.ts.map +1 -0
- package/dist/core/ConditionEvaluator.js +268 -0
- package/dist/core/ConditionEvaluator.js.map +1 -0
- package/dist/core/PreparationEngine.d.ts +105 -0
- package/dist/core/PreparationEngine.d.ts.map +1 -0
- package/dist/core/PreparationEngine.js +316 -0
- package/dist/core/PreparationEngine.js.map +1 -0
- package/dist/core/PromptBuilder.d.ts +3 -0
- package/dist/core/PromptBuilder.d.ts.map +1 -1
- package/dist/core/PromptBuilder.js +10 -10
- package/dist/core/PromptBuilder.js.map +1 -1
- package/dist/core/Route.d.ts +6 -0
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +9 -0
- package/dist/core/Route.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +3 -3
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +3 -3
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +43 -18
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +3 -3
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts +3 -3
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/OpenRouterProvider.js.map +1 -1
- package/dist/types/ai.d.ts +6 -6
- package/dist/types/ai.d.ts.map +1 -1
- package/docs/ARCHITECTURE.md +386 -0
- package/docs/DOMAINS.md +674 -0
- package/docs/README.md +17 -3
- package/examples/domain-scoping.ts +18 -10
- package/package.json +1 -1
- package/src/core/Agent.ts +63 -12
- package/src/core/ConditionEvaluator.ts +381 -0
- package/src/core/PreparationEngine.ts +500 -0
- package/src/core/PromptBuilder.ts +16 -16
- package/src/core/Route.ts +10 -0
- package/src/providers/AnthropicProvider.ts +51 -21
- package/src/providers/GeminiProvider.ts +86 -40
- package/src/providers/OpenAIProvider.ts +48 -21
- package/src/providers/OpenRouterProvider.ts +36 -18
- 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
|
-
*
|
|
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
|
|
190
|
-
- Data Collection route cannot
|
|
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
|
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
-
|
|
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
|
-
|
|
205
|
+
📋 Clarity Benefits:
|
|
199
206
|
- Routes clearly document their capabilities
|
|
200
|
-
- Easy to audit what each route can
|
|
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
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
|
-
|
|
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
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
504
|
-
|
|
505
|
-
|
|
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
|
+
}
|