@falai/agent 0.3.30 → 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/dist/cjs/core/Agent.d.ts +1 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +45 -2
- 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/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 +45 -2
- 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/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/package.json +1 -1
- package/src/core/Agent.ts +57 -2
- package/src/core/ConditionEvaluator.ts +381 -0
- package/src/core/PreparationEngine.ts +500 -0
- 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
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreparationEngine - Handles the preparation iteration loop
|
|
3
|
+
*
|
|
4
|
+
* This engine implements the core Parlant/Emcie architecture:
|
|
5
|
+
* 1. Before generating a message, run preparation iterations
|
|
6
|
+
* 2. Each iteration:
|
|
7
|
+
* - Match guidelines against current context
|
|
8
|
+
* - Walk the state machine and execute tool transitions
|
|
9
|
+
* - Execute tools when conditions are met
|
|
10
|
+
* - Update context from tool results
|
|
11
|
+
* - Check if prepared to respond
|
|
12
|
+
* 3. After preparation, the AI generates the final message
|
|
13
|
+
*
|
|
14
|
+
* The AI NEVER sees tools - tools execute automatically based on:
|
|
15
|
+
* - State machine transitions ({ toolState: tool })
|
|
16
|
+
* - Guideline matching with associated tools
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { Event, StateRef, AiProvider } from "../types/index";
|
|
20
|
+
import type { Guideline, GuidelineMatch } from "../types/agent";
|
|
21
|
+
import type { ToolRef } from "../types/tool";
|
|
22
|
+
import type { Route } from "./Route";
|
|
23
|
+
import { State } from "./State";
|
|
24
|
+
import { ConditionEvaluator } from "./ConditionEvaluator";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Tool execution result
|
|
28
|
+
*/
|
|
29
|
+
export interface ToolExecutionResult {
|
|
30
|
+
toolName: string;
|
|
31
|
+
arguments: Record<string, unknown>;
|
|
32
|
+
result: unknown;
|
|
33
|
+
success: boolean;
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Preparation iteration state
|
|
39
|
+
*/
|
|
40
|
+
export interface IterationState {
|
|
41
|
+
iterationNumber: number;
|
|
42
|
+
matchedGuidelines: GuidelineMatch[];
|
|
43
|
+
executedTools: ToolExecutionResult[];
|
|
44
|
+
contextUpdates: Record<string, unknown>;
|
|
45
|
+
preparedToRespond: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Preparation context
|
|
50
|
+
*/
|
|
51
|
+
export interface PreparationContext<TContext = unknown> {
|
|
52
|
+
history: Event[];
|
|
53
|
+
currentState?: StateRef;
|
|
54
|
+
context: TContext;
|
|
55
|
+
routes: Route<TContext>[];
|
|
56
|
+
guidelines: Guideline[];
|
|
57
|
+
maxIterations: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Preparation result
|
|
62
|
+
*/
|
|
63
|
+
export interface PreparationResult<TContext = unknown> {
|
|
64
|
+
iterations: IterationState[];
|
|
65
|
+
finalContext: TContext;
|
|
66
|
+
toolExecutions: ToolExecutionResult[];
|
|
67
|
+
preparedToRespond: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* PreparationEngine - Executes the preparation iteration loop
|
|
72
|
+
*/
|
|
73
|
+
export class PreparationEngine<TContext = unknown> {
|
|
74
|
+
private readonly conditionEvaluator?: ConditionEvaluator<TContext>;
|
|
75
|
+
|
|
76
|
+
constructor(ai?: AiProvider) {
|
|
77
|
+
if (ai) {
|
|
78
|
+
this.conditionEvaluator = new ConditionEvaluator<TContext>(ai);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Run preparation iterations before message generation
|
|
84
|
+
*
|
|
85
|
+
* This is the core engine that executes tools automatically
|
|
86
|
+
* based on state machine transitions and guideline matching.
|
|
87
|
+
*/
|
|
88
|
+
async prepare(
|
|
89
|
+
preparationContext: PreparationContext<TContext>
|
|
90
|
+
): Promise<PreparationResult<TContext>> {
|
|
91
|
+
const iterations: IterationState[] = [];
|
|
92
|
+
let currentContext = { ...preparationContext.context };
|
|
93
|
+
const allToolExecutions: ToolExecutionResult[] = [];
|
|
94
|
+
let preparedToRespond = false;
|
|
95
|
+
|
|
96
|
+
// Run preparation iterations
|
|
97
|
+
for (
|
|
98
|
+
let i = 0;
|
|
99
|
+
i < preparationContext.maxIterations && !preparedToRespond;
|
|
100
|
+
i++
|
|
101
|
+
) {
|
|
102
|
+
const iteration = await this.runIteration({
|
|
103
|
+
iterationNumber: i + 1,
|
|
104
|
+
history: preparationContext.history,
|
|
105
|
+
currentState: preparationContext.currentState,
|
|
106
|
+
context: currentContext,
|
|
107
|
+
routes: preparationContext.routes,
|
|
108
|
+
guidelines: preparationContext.guidelines,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
iterations.push(iteration);
|
|
112
|
+
allToolExecutions.push(...iteration.executedTools);
|
|
113
|
+
|
|
114
|
+
// Update context from tool results
|
|
115
|
+
if (Object.keys(iteration.contextUpdates).length > 0) {
|
|
116
|
+
currentContext = {
|
|
117
|
+
...currentContext,
|
|
118
|
+
...iteration.contextUpdates,
|
|
119
|
+
} as TContext;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check if we're prepared to respond
|
|
123
|
+
// We're prepared if:
|
|
124
|
+
// 1. No tools were executed in this iteration (nothing left to do)
|
|
125
|
+
// 2. OR we've reached max iterations
|
|
126
|
+
// 3. OR iteration explicitly set preparedToRespond to true
|
|
127
|
+
if (
|
|
128
|
+
iteration.executedTools.length === 0 ||
|
|
129
|
+
i === preparationContext.maxIterations - 1 ||
|
|
130
|
+
iteration.preparedToRespond
|
|
131
|
+
) {
|
|
132
|
+
preparedToRespond = true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
iterations,
|
|
138
|
+
finalContext: currentContext,
|
|
139
|
+
toolExecutions: allToolExecutions,
|
|
140
|
+
preparedToRespond,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Run a single preparation iteration
|
|
146
|
+
*/
|
|
147
|
+
private async runIteration(params: {
|
|
148
|
+
iterationNumber: number;
|
|
149
|
+
history: Event[];
|
|
150
|
+
currentState?: StateRef;
|
|
151
|
+
context: TContext;
|
|
152
|
+
routes: Route<TContext>[];
|
|
153
|
+
guidelines: Guideline[];
|
|
154
|
+
}): Promise<IterationState> {
|
|
155
|
+
const executedTools: ToolExecutionResult[] = [];
|
|
156
|
+
const contextUpdates: Record<string, unknown> = {};
|
|
157
|
+
const matchedGuidelines: GuidelineMatch[] = [];
|
|
158
|
+
|
|
159
|
+
// Step 1: Match guidelines against current context
|
|
160
|
+
const matchedResults = await this.matchGuidelines(
|
|
161
|
+
params.guidelines,
|
|
162
|
+
params.context,
|
|
163
|
+
params.history
|
|
164
|
+
);
|
|
165
|
+
matchedGuidelines.push(...matchedResults);
|
|
166
|
+
|
|
167
|
+
// Step 2: Execute tools from matched guidelines
|
|
168
|
+
// Guidelines with associated tools should execute those tools
|
|
169
|
+
for (const match of matchedGuidelines) {
|
|
170
|
+
if (match.guideline.tools && match.guideline.tools.length > 0) {
|
|
171
|
+
for (const tool of match.guideline.tools) {
|
|
172
|
+
const toolResult = await this.executeTool(
|
|
173
|
+
tool as ToolRef<TContext, unknown[], unknown>,
|
|
174
|
+
params.context,
|
|
175
|
+
params.history
|
|
176
|
+
);
|
|
177
|
+
if (toolResult) {
|
|
178
|
+
executedTools.push(toolResult);
|
|
179
|
+
|
|
180
|
+
// Update context from tool result if tool provided context updates
|
|
181
|
+
if (toolResult.result && typeof toolResult.result === "object") {
|
|
182
|
+
const result = toolResult.result as Record<string, unknown>;
|
|
183
|
+
if (result.contextUpdate) {
|
|
184
|
+
Object.assign(contextUpdates, result.contextUpdate);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Step 3: Walk state machine and execute tool transitions
|
|
193
|
+
// Find current route and state, then execute any toolState transitions
|
|
194
|
+
if (params.currentState?.id) {
|
|
195
|
+
const currentRoute = params.routes.find(
|
|
196
|
+
(r) => r.id === params.currentState!.routeId
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (currentRoute) {
|
|
200
|
+
const toolResults = await this.executeStateToolTransitions(
|
|
201
|
+
currentRoute,
|
|
202
|
+
params.currentState,
|
|
203
|
+
params.context,
|
|
204
|
+
params.history
|
|
205
|
+
);
|
|
206
|
+
executedTools.push(...toolResults);
|
|
207
|
+
|
|
208
|
+
// Update context from state tool results
|
|
209
|
+
for (const toolResult of toolResults) {
|
|
210
|
+
if (toolResult.result && typeof toolResult.result === "object") {
|
|
211
|
+
const result = toolResult.result as Record<string, unknown>;
|
|
212
|
+
if (result.contextUpdate) {
|
|
213
|
+
Object.assign(contextUpdates, result.contextUpdate);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
iterationNumber: params.iterationNumber,
|
|
222
|
+
matchedGuidelines,
|
|
223
|
+
executedTools,
|
|
224
|
+
contextUpdates,
|
|
225
|
+
preparedToRespond: false, // Will be determined by caller
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Match guidelines against current context
|
|
231
|
+
*
|
|
232
|
+
* Evaluates guideline conditions against the current context and history
|
|
233
|
+
* using AI to determine relevance and priority
|
|
234
|
+
*/
|
|
235
|
+
private async matchGuidelines(
|
|
236
|
+
guidelines: Guideline[],
|
|
237
|
+
context: TContext,
|
|
238
|
+
history: Event[]
|
|
239
|
+
): Promise<GuidelineMatch[]> {
|
|
240
|
+
const matches: GuidelineMatch[] = [];
|
|
241
|
+
|
|
242
|
+
for (const guideline of guidelines) {
|
|
243
|
+
// Skip disabled guidelines
|
|
244
|
+
if (guideline.enabled === false) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// If guideline has no condition, it's always matched
|
|
249
|
+
if (!guideline.condition) {
|
|
250
|
+
matches.push({
|
|
251
|
+
guideline,
|
|
252
|
+
rationale: "Guideline has no condition - always active",
|
|
253
|
+
});
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Evaluate condition using AI if evaluator is available
|
|
258
|
+
if (!this.conditionEvaluator) {
|
|
259
|
+
// No AI available, match all enabled guidelines with conditions
|
|
260
|
+
matches.push({
|
|
261
|
+
guideline,
|
|
262
|
+
rationale: "AI not available - defaulting to match",
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const evaluation =
|
|
268
|
+
await this.conditionEvaluator.evaluateGuidelineCondition(
|
|
269
|
+
guideline,
|
|
270
|
+
context,
|
|
271
|
+
history
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
if (evaluation.matches) {
|
|
275
|
+
matches.push({
|
|
276
|
+
guideline,
|
|
277
|
+
rationale: evaluation.rationale || "Condition evaluated as true",
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return matches;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Execute a single tool
|
|
287
|
+
*/
|
|
288
|
+
private async executeTool(
|
|
289
|
+
tool: ToolRef<TContext, unknown[], unknown>,
|
|
290
|
+
context: TContext,
|
|
291
|
+
history: Event[]
|
|
292
|
+
): Promise<ToolExecutionResult | null> {
|
|
293
|
+
try {
|
|
294
|
+
// Extract arguments from context and history
|
|
295
|
+
let args: unknown[];
|
|
296
|
+
|
|
297
|
+
if (this.conditionEvaluator && tool.parameters) {
|
|
298
|
+
// Use AI-powered extraction if available
|
|
299
|
+
const extraction = await this.conditionEvaluator.extractToolArguments(
|
|
300
|
+
tool,
|
|
301
|
+
context,
|
|
302
|
+
history
|
|
303
|
+
);
|
|
304
|
+
args = extraction.arguments;
|
|
305
|
+
} else {
|
|
306
|
+
// Fallback to simple extraction
|
|
307
|
+
args = this.conditionEvaluator
|
|
308
|
+
? this.conditionEvaluator.simpleArgumentExtraction(tool, context)
|
|
309
|
+
: [];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Execute the tool handler
|
|
313
|
+
const result = await tool.handler(
|
|
314
|
+
{
|
|
315
|
+
context,
|
|
316
|
+
history,
|
|
317
|
+
updateContext: async (_updates: Partial<TContext>) => {
|
|
318
|
+
// Context updates are handled separately in the preparation loop
|
|
319
|
+
// This is a no-op placeholder
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
...args
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
toolName: tool.name,
|
|
327
|
+
arguments: { args }, // Wrap array in object for logging
|
|
328
|
+
result,
|
|
329
|
+
success: true,
|
|
330
|
+
};
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error(
|
|
333
|
+
`[PreparationEngine] Tool execution failed: ${tool.name}`,
|
|
334
|
+
error
|
|
335
|
+
);
|
|
336
|
+
return {
|
|
337
|
+
toolName: tool.name,
|
|
338
|
+
arguments: {},
|
|
339
|
+
result: null,
|
|
340
|
+
success: false,
|
|
341
|
+
error: error instanceof Error ? error.message : String(error),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Execute tool transitions in the state machine
|
|
348
|
+
*
|
|
349
|
+
* Walks through state transitions and executes tools when
|
|
350
|
+
* reaching a { toolState: tool } transition
|
|
351
|
+
*/
|
|
352
|
+
private async executeStateToolTransitions(
|
|
353
|
+
route: Route<TContext>,
|
|
354
|
+
currentStateRef: StateRef,
|
|
355
|
+
context: TContext,
|
|
356
|
+
history: Event[]
|
|
357
|
+
): Promise<ToolExecutionResult[]> {
|
|
358
|
+
const executedTools: ToolExecutionResult[] = [];
|
|
359
|
+
|
|
360
|
+
// Get the current state from the route or use initial state
|
|
361
|
+
let stateToWalk: State<TContext>;
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const foundState = route.getState(currentStateRef.id);
|
|
365
|
+
if (foundState) {
|
|
366
|
+
stateToWalk = foundState;
|
|
367
|
+
} else {
|
|
368
|
+
stateToWalk = route.initialState;
|
|
369
|
+
}
|
|
370
|
+
} catch {
|
|
371
|
+
// If any error occurs, fallback to initial state
|
|
372
|
+
stateToWalk = route.initialState;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Walk the state machine starting from the determined state
|
|
376
|
+
const toolResults = await this.walkStateChain(
|
|
377
|
+
stateToWalk,
|
|
378
|
+
context,
|
|
379
|
+
history,
|
|
380
|
+
new Set() // Track visited states to prevent loops
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
executedTools.push(...toolResults);
|
|
384
|
+
|
|
385
|
+
return executedTools;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Walk through a chain of states, executing tools along the way
|
|
390
|
+
*/
|
|
391
|
+
private async walkStateChain(
|
|
392
|
+
state: State<TContext>,
|
|
393
|
+
context: TContext,
|
|
394
|
+
history: Event[],
|
|
395
|
+
visited: Set<string>
|
|
396
|
+
): Promise<ToolExecutionResult[]> {
|
|
397
|
+
const executedTools: ToolExecutionResult[] = [];
|
|
398
|
+
|
|
399
|
+
// Prevent infinite loops
|
|
400
|
+
if (visited.has(state.id)) {
|
|
401
|
+
return executedTools;
|
|
402
|
+
}
|
|
403
|
+
visited.add(state.id);
|
|
404
|
+
|
|
405
|
+
// Get all transitions from this state
|
|
406
|
+
const transitions = state.getTransitions();
|
|
407
|
+
|
|
408
|
+
// Process each transition
|
|
409
|
+
for (const transition of transitions) {
|
|
410
|
+
// Check if transition has a condition
|
|
411
|
+
if (transition.hasCondition()) {
|
|
412
|
+
// Evaluate condition
|
|
413
|
+
const shouldFollow = await this.evaluateTransitionCondition(
|
|
414
|
+
transition,
|
|
415
|
+
context,
|
|
416
|
+
history
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
if (!shouldFollow) {
|
|
420
|
+
continue; // Skip this transition
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check if this is a toolState transition
|
|
425
|
+
if (transition.spec.toolState) {
|
|
426
|
+
// Execute the tool
|
|
427
|
+
const toolResult = await this.executeTool(
|
|
428
|
+
transition.spec.toolState,
|
|
429
|
+
context,
|
|
430
|
+
history
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
if (toolResult) {
|
|
434
|
+
executedTools.push(toolResult);
|
|
435
|
+
|
|
436
|
+
// Update context with tool results for next transitions
|
|
437
|
+
if (toolResult.result && typeof toolResult.result === "object") {
|
|
438
|
+
const result = toolResult.result as Record<string, unknown>;
|
|
439
|
+
if (result.contextUpdate) {
|
|
440
|
+
context = {
|
|
441
|
+
...context,
|
|
442
|
+
...(result.contextUpdate as Partial<TContext>),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Get the target state and recursively walk it
|
|
450
|
+
const targetState = transition.getTarget();
|
|
451
|
+
if (targetState && !visited.has(targetState.id)) {
|
|
452
|
+
const childResults = await this.walkStateChain(
|
|
453
|
+
targetState,
|
|
454
|
+
context,
|
|
455
|
+
history,
|
|
456
|
+
visited
|
|
457
|
+
);
|
|
458
|
+
executedTools.push(...childResults);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return executedTools;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Evaluate a transition condition
|
|
467
|
+
*/
|
|
468
|
+
private async evaluateTransitionCondition(
|
|
469
|
+
transition: { condition?: string },
|
|
470
|
+
context: TContext,
|
|
471
|
+
history: Event[]
|
|
472
|
+
): Promise<boolean> {
|
|
473
|
+
if (!transition.condition) {
|
|
474
|
+
return true; // No condition = always follow
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// If no AI evaluator available, default to true
|
|
478
|
+
if (!this.conditionEvaluator) {
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
const evaluation =
|
|
484
|
+
await this.conditionEvaluator.evaluateTransitionCondition(
|
|
485
|
+
transition.condition,
|
|
486
|
+
context,
|
|
487
|
+
history
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
return evaluation.shouldFollow;
|
|
491
|
+
} catch (error) {
|
|
492
|
+
console.error(
|
|
493
|
+
`[PreparationEngine] Failed to evaluate transition condition`,
|
|
494
|
+
error
|
|
495
|
+
);
|
|
496
|
+
// On error, default to false (don't follow)
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
package/src/core/Route.ts
CHANGED
|
@@ -132,6 +132,16 @@ export class Route<TContext = unknown> {
|
|
|
132
132
|
return states;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Get a specific state by ID
|
|
137
|
+
* @param stateId - The state ID to find
|
|
138
|
+
* @returns The state if found, undefined otherwise
|
|
139
|
+
*/
|
|
140
|
+
getState(stateId: string): State<TContext> | undefined {
|
|
141
|
+
const states = this.getAllStates();
|
|
142
|
+
return states.find((state) => state.id === stateId);
|
|
143
|
+
}
|
|
144
|
+
|
|
135
145
|
/**
|
|
136
146
|
* Get a description of the route structure for debugging
|
|
137
147
|
*/
|
|
@@ -149,24 +149,36 @@ export class AnthropicProvider implements AiProvider {
|
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
async generateMessage<
|
|
152
|
+
async generateMessage<
|
|
153
|
+
TContext = unknown,
|
|
154
|
+
TStructured = AgentStructuredResponse
|
|
155
|
+
>(
|
|
153
156
|
input: GenerateMessageInput<TContext>
|
|
154
|
-
): Promise<GenerateMessageOutput
|
|
155
|
-
return this.generateWithBackup(input);
|
|
157
|
+
): Promise<GenerateMessageOutput<TStructured>> {
|
|
158
|
+
return this.generateWithBackup<TContext, TStructured>(input);
|
|
156
159
|
}
|
|
157
160
|
|
|
158
|
-
async *generateMessageStream<
|
|
161
|
+
async *generateMessageStream<
|
|
162
|
+
TContext = unknown,
|
|
163
|
+
TStructured = AgentStructuredResponse
|
|
164
|
+
>(
|
|
159
165
|
input: GenerateMessageInput<TContext>
|
|
160
|
-
): AsyncGenerator<GenerateMessageStreamChunk
|
|
161
|
-
yield* this.generateStreamWithBackup(input);
|
|
166
|
+
): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
|
|
167
|
+
yield* this.generateStreamWithBackup<TContext, TStructured>(input);
|
|
162
168
|
}
|
|
163
169
|
|
|
164
|
-
private async generateWithBackup<
|
|
170
|
+
private async generateWithBackup<
|
|
171
|
+
TContext = unknown,
|
|
172
|
+
TStructured = AgentStructuredResponse
|
|
173
|
+
>(
|
|
165
174
|
input: GenerateMessageInput<TContext>
|
|
166
|
-
): Promise<GenerateMessageOutput
|
|
175
|
+
): Promise<GenerateMessageOutput<TStructured>> {
|
|
167
176
|
// Try primary model first
|
|
168
177
|
try {
|
|
169
|
-
return await this.generateWithModel
|
|
178
|
+
return await this.generateWithModel<TContext, TStructured>(
|
|
179
|
+
this.primaryModel,
|
|
180
|
+
input
|
|
181
|
+
);
|
|
170
182
|
} catch (primaryError: unknown) {
|
|
171
183
|
const primaryErrMsg = getErrorMessage(primaryError);
|
|
172
184
|
console.warn(
|
|
@@ -190,7 +202,10 @@ export class AnthropicProvider implements AiProvider {
|
|
|
190
202
|
);
|
|
191
203
|
|
|
192
204
|
try {
|
|
193
|
-
const result = await this.generateWithModel
|
|
205
|
+
const result = await this.generateWithModel<TContext, TStructured>(
|
|
206
|
+
backupModel,
|
|
207
|
+
input
|
|
208
|
+
);
|
|
194
209
|
console.log(`[ANTHROPIC] Backup model ${backupModel} succeeded`);
|
|
195
210
|
return result;
|
|
196
211
|
} catch (backupError: unknown) {
|
|
@@ -220,10 +235,13 @@ export class AnthropicProvider implements AiProvider {
|
|
|
220
235
|
}
|
|
221
236
|
}
|
|
222
237
|
|
|
223
|
-
private async generateWithModel<
|
|
238
|
+
private async generateWithModel<
|
|
239
|
+
TContext = unknown,
|
|
240
|
+
TStructured = AgentStructuredResponse
|
|
241
|
+
>(
|
|
224
242
|
model: string,
|
|
225
243
|
input: GenerateMessageInput<TContext>
|
|
226
|
-
): Promise<GenerateMessageOutput
|
|
244
|
+
): Promise<GenerateMessageOutput<TStructured>> {
|
|
227
245
|
const operation = async (): Promise<GenerateMessageOutput> => {
|
|
228
246
|
// Anthropic requires max_tokens to be specified
|
|
229
247
|
const maxTokens = input.parameters?.maxOutputTokens || 4096;
|
|
@@ -306,15 +324,21 @@ export class AnthropicProvider implements AiProvider {
|
|
|
306
324
|
this.retryConfig.timeout,
|
|
307
325
|
this.retryConfig.retries,
|
|
308
326
|
`Anthropic ${model}`
|
|
309
|
-
)
|
|
327
|
+
) as Promise<GenerateMessageOutput<TStructured>>;
|
|
310
328
|
}
|
|
311
329
|
|
|
312
|
-
private async *generateStreamWithBackup<
|
|
330
|
+
private async *generateStreamWithBackup<
|
|
331
|
+
TContext = unknown,
|
|
332
|
+
TStructured = AgentStructuredResponse
|
|
333
|
+
>(
|
|
313
334
|
input: GenerateMessageInput<TContext>
|
|
314
|
-
): AsyncGenerator<GenerateMessageStreamChunk
|
|
335
|
+
): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
|
|
315
336
|
// Try primary model first
|
|
316
337
|
try {
|
|
317
|
-
yield* this.generateStreamWithModel
|
|
338
|
+
yield* this.generateStreamWithModel<TContext, TStructured>(
|
|
339
|
+
this.primaryModel,
|
|
340
|
+
input
|
|
341
|
+
);
|
|
318
342
|
} catch (primaryError: unknown) {
|
|
319
343
|
const primaryErrMsg = getErrorMessage(primaryError);
|
|
320
344
|
console.warn(
|
|
@@ -338,7 +362,10 @@ export class AnthropicProvider implements AiProvider {
|
|
|
338
362
|
);
|
|
339
363
|
|
|
340
364
|
try {
|
|
341
|
-
yield* this.generateStreamWithModel
|
|
365
|
+
yield* this.generateStreamWithModel<TContext, TStructured>(
|
|
366
|
+
backupModel,
|
|
367
|
+
input
|
|
368
|
+
);
|
|
342
369
|
console.log(`[ANTHROPIC] Backup model ${backupModel} succeeded`);
|
|
343
370
|
return;
|
|
344
371
|
} catch (backupError: unknown) {
|
|
@@ -368,10 +395,13 @@ export class AnthropicProvider implements AiProvider {
|
|
|
368
395
|
}
|
|
369
396
|
}
|
|
370
397
|
|
|
371
|
-
private async *generateStreamWithModel<
|
|
398
|
+
private async *generateStreamWithModel<
|
|
399
|
+
TContext = unknown,
|
|
400
|
+
TStructured = AgentStructuredResponse
|
|
401
|
+
>(
|
|
372
402
|
model: string,
|
|
373
403
|
input: GenerateMessageInput<TContext>
|
|
374
|
-
): AsyncGenerator<GenerateMessageStreamChunk
|
|
404
|
+
): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
|
|
375
405
|
// Anthropic requires max_tokens to be specified
|
|
376
406
|
const maxTokens = input.parameters?.maxOutputTokens || 4096;
|
|
377
407
|
|
|
@@ -428,7 +458,7 @@ export class AnthropicProvider implements AiProvider {
|
|
|
428
458
|
delta,
|
|
429
459
|
accumulated,
|
|
430
460
|
done: false,
|
|
431
|
-
}
|
|
461
|
+
} as GenerateMessageStreamChunk<TStructured>;
|
|
432
462
|
}
|
|
433
463
|
} else if (chunk.type === "message_delta") {
|
|
434
464
|
stopReason = chunk.delta.stop_reason || undefined;
|
|
@@ -462,6 +492,6 @@ export class AnthropicProvider implements AiProvider {
|
|
|
462
492
|
completionTokens: outputTokens,
|
|
463
493
|
},
|
|
464
494
|
structured,
|
|
465
|
-
}
|
|
495
|
+
} as GenerateMessageStreamChunk<TStructured>;
|
|
466
496
|
}
|
|
467
497
|
}
|