@falai/agent 0.9.0-alpha-2 → 0.9.2
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 +42 -34
- package/dist/cjs/src/core/Agent.d.ts +48 -44
- package/dist/cjs/src/core/Agent.d.ts.map +1 -1
- package/dist/cjs/src/core/Agent.js +151 -1110
- package/dist/cjs/src/core/Agent.js.map +1 -1
- package/dist/cjs/src/core/ResponseModal.d.ts +211 -0
- package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -0
- package/dist/cjs/src/core/ResponseModal.js +1394 -0
- package/dist/cjs/src/core/ResponseModal.js.map +1 -0
- package/dist/cjs/src/core/ResponsePipeline.d.ts +8 -4
- package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/src/core/ResponsePipeline.js +48 -20
- package/dist/cjs/src/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/src/core/Route.d.ts +12 -5
- package/dist/cjs/src/core/Route.d.ts.map +1 -1
- package/dist/cjs/src/core/Route.js +26 -5
- package/dist/cjs/src/core/Route.js.map +1 -1
- package/dist/cjs/src/core/RoutingEngine.d.ts +5 -0
- package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/src/core/RoutingEngine.js +37 -25
- package/dist/cjs/src/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/src/core/SessionManager.d.ts +9 -1
- package/dist/cjs/src/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/src/core/SessionManager.js +27 -5
- package/dist/cjs/src/core/SessionManager.js.map +1 -1
- package/dist/cjs/src/core/Step.d.ts +60 -7
- package/dist/cjs/src/core/Step.d.ts.map +1 -1
- package/dist/cjs/src/core/Step.js +151 -4
- package/dist/cjs/src/core/Step.js.map +1 -1
- package/dist/cjs/src/core/ToolManager.d.ts +234 -0
- package/dist/cjs/src/core/ToolManager.d.ts.map +1 -0
- package/dist/cjs/src/core/ToolManager.js +1117 -0
- package/dist/cjs/src/core/ToolManager.js.map +1 -0
- package/dist/cjs/src/index.d.ts +5 -4
- package/dist/cjs/src/index.d.ts.map +1 -1
- package/dist/cjs/src/index.js +11 -3
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/types/agent.d.ts +2 -1
- package/dist/cjs/src/types/agent.d.ts.map +1 -1
- package/dist/cjs/src/types/ai.d.ts +1 -1
- package/dist/cjs/src/types/ai.d.ts.map +1 -1
- package/dist/cjs/src/types/index.d.ts +3 -2
- package/dist/cjs/src/types/index.d.ts.map +1 -1
- package/dist/cjs/src/types/index.js +3 -1
- package/dist/cjs/src/types/index.js.map +1 -1
- package/dist/cjs/src/types/route.d.ts +6 -4
- package/dist/cjs/src/types/route.d.ts.map +1 -1
- package/dist/cjs/src/types/tool.d.ts +84 -14
- package/dist/cjs/src/types/tool.d.ts.map +1 -1
- package/dist/cjs/src/types/tool.js +13 -0
- package/dist/cjs/src/types/tool.js.map +1 -1
- package/dist/cjs/src/utils/clone.d.ts.map +1 -1
- package/dist/cjs/src/utils/clone.js +0 -4
- package/dist/cjs/src/utils/clone.js.map +1 -1
- package/dist/cjs/src/utils/history.d.ts +30 -1
- package/dist/cjs/src/utils/history.d.ts.map +1 -1
- package/dist/cjs/src/utils/history.js +169 -23
- package/dist/cjs/src/utils/history.js.map +1 -1
- package/dist/cjs/src/utils/index.d.ts +1 -1
- package/dist/cjs/src/utils/index.d.ts.map +1 -1
- package/dist/cjs/src/utils/index.js +5 -1
- package/dist/cjs/src/utils/index.js.map +1 -1
- package/dist/src/core/Agent.d.ts +48 -44
- package/dist/src/core/Agent.d.ts.map +1 -1
- package/dist/src/core/Agent.js +152 -1111
- package/dist/src/core/Agent.js.map +1 -1
- package/dist/src/core/ResponseModal.d.ts +211 -0
- package/dist/src/core/ResponseModal.d.ts.map +1 -0
- package/dist/src/core/ResponseModal.js +1389 -0
- package/dist/src/core/ResponseModal.js.map +1 -0
- package/dist/src/core/ResponsePipeline.d.ts +8 -4
- package/dist/src/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/src/core/ResponsePipeline.js +48 -20
- package/dist/src/core/ResponsePipeline.js.map +1 -1
- package/dist/src/core/Route.d.ts +12 -5
- package/dist/src/core/Route.d.ts.map +1 -1
- package/dist/src/core/Route.js +26 -5
- package/dist/src/core/Route.js.map +1 -1
- package/dist/src/core/RoutingEngine.d.ts +5 -0
- package/dist/src/core/RoutingEngine.d.ts.map +1 -1
- package/dist/src/core/RoutingEngine.js +37 -25
- package/dist/src/core/RoutingEngine.js.map +1 -1
- package/dist/src/core/SessionManager.d.ts +9 -1
- package/dist/src/core/SessionManager.d.ts.map +1 -1
- package/dist/src/core/SessionManager.js +27 -5
- package/dist/src/core/SessionManager.js.map +1 -1
- package/dist/src/core/Step.d.ts +60 -7
- package/dist/src/core/Step.d.ts.map +1 -1
- package/dist/src/core/Step.js +151 -4
- package/dist/src/core/Step.js.map +1 -1
- package/dist/src/core/ToolManager.d.ts +234 -0
- package/dist/src/core/ToolManager.d.ts.map +1 -0
- package/dist/src/core/ToolManager.js +1111 -0
- package/dist/src/core/ToolManager.js.map +1 -0
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/agent.d.ts +2 -1
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/ai.d.ts +1 -1
- package/dist/src/types/ai.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +3 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -0
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/route.d.ts +6 -4
- package/dist/src/types/route.d.ts.map +1 -1
- package/dist/src/types/tool.d.ts +84 -14
- package/dist/src/types/tool.d.ts.map +1 -1
- package/dist/src/types/tool.js +12 -1
- package/dist/src/types/tool.js.map +1 -1
- package/dist/src/utils/clone.d.ts.map +1 -1
- package/dist/src/utils/clone.js +0 -4
- package/dist/src/utils/clone.js.map +1 -1
- package/dist/src/utils/history.d.ts +30 -1
- package/dist/src/utils/history.d.ts.map +1 -1
- package/dist/src/utils/history.js +165 -23
- package/dist/src/utils/history.js.map +1 -1
- package/dist/src/utils/index.d.ts +1 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -1
- package/dist/src/utils/index.js.map +1 -1
- package/docs/CONTRIBUTING.md +40 -0
- package/docs/README.md +14 -6
- package/docs/api/README.md +235 -45
- package/docs/api/overview.md +140 -33
- package/docs/core/agent/session-management.md +152 -5
- package/docs/core/ai-integration/response-processing.md +115 -4
- package/docs/core/conversation-flows/routes.md +130 -0
- package/docs/core/error-handling.md +638 -0
- package/docs/core/tools/tool-definition.md +684 -60
- package/docs/core/tools/tool-scoping.md +244 -53
- package/docs/guides/error-handling-patterns.md +578 -0
- package/docs/guides/getting-started/README.md +139 -28
- package/docs/guides/migration/README.md +72 -0
- package/docs/guides/migration/response-modal-refactor.md +518 -0
- package/examples/advanced-patterns/knowledge-based-agent.ts +6 -6
- package/examples/advanced-patterns/persistent-onboarding.ts +30 -43
- package/examples/advanced-patterns/streaming-responses.ts +169 -96
- package/examples/ai-providers/anthropic-integration.ts +9 -5
- package/examples/ai-providers/openai-integration.ts +11 -7
- package/examples/core-concepts/basic-agent.ts +106 -67
- package/examples/core-concepts/modern-streaming-api.ts +309 -0
- package/examples/core-concepts/schema-driven-extraction.ts +10 -7
- package/examples/core-concepts/session-management.ts +71 -18
- package/examples/integrations/healthcare-integration.ts +15 -29
- package/examples/integrations/server-session-management.ts +3 -3
- package/examples/persistence/memory-sessions.ts +3 -3
- package/examples/tools/basic-tools.ts +293 -89
- package/examples/tools/data-enrichment-tools.ts +185 -75
- package/package.json +1 -1
- package/src/core/Agent.ts +190 -1529
- package/src/core/ResponseModal.ts +1798 -0
- package/src/core/ResponsePipeline.ts +83 -57
- package/src/core/Route.ts +39 -12
- package/src/core/RoutingEngine.ts +46 -42
- package/src/core/SessionManager.ts +39 -7
- package/src/core/Step.ts +198 -20
- package/src/core/ToolManager.ts +1394 -0
- package/src/index.ts +19 -3
- package/src/types/agent.ts +2 -1
- package/src/types/ai.ts +1 -1
- package/src/types/index.ts +13 -2
- package/src/types/route.ts +6 -4
- package/src/types/tool.ts +116 -25
- package/src/utils/clone.ts +6 -8
- package/src/utils/history.ts +190 -27
- package/src/utils/index.ts +4 -0
- package/dist/cjs/src/core/ToolExecutor.d.ts +0 -45
- package/dist/cjs/src/core/ToolExecutor.d.ts.map +0 -1
- package/dist/cjs/src/core/ToolExecutor.js +0 -84
- package/dist/cjs/src/core/ToolExecutor.js.map +0 -1
- package/dist/src/core/ToolExecutor.d.ts +0 -45
- package/dist/src/core/ToolExecutor.d.ts.map +0 -1
- package/dist/src/core/ToolExecutor.js +0 -80
- package/dist/src/core/ToolExecutor.js.map +0 -1
- package/docs/core/tools/tool-execution.md +0 -815
- package/src/core/ToolExecutor.ts +0 -126
package/src/core/Agent.ts
CHANGED
|
@@ -10,27 +10,20 @@ import type {
|
|
|
10
10
|
Event,
|
|
11
11
|
RouteOptions,
|
|
12
12
|
SessionState,
|
|
13
|
-
AgentStructuredResponse,
|
|
14
13
|
Template,
|
|
15
|
-
StepRef,
|
|
16
|
-
History,
|
|
17
14
|
AgentResponseStreamChunk,
|
|
18
15
|
AgentResponse,
|
|
19
16
|
StructuredSchema,
|
|
20
17
|
ValidationError,
|
|
21
18
|
ValidationResult,
|
|
19
|
+
|
|
22
20
|
} from "../types";
|
|
23
|
-
import {
|
|
21
|
+
import type { StreamOptions, GenerateOptions, RespondParams } from "./ResponseModal";
|
|
24
22
|
import {
|
|
25
|
-
enterRoute,
|
|
26
|
-
enterStep,
|
|
27
23
|
mergeCollected,
|
|
28
24
|
logger,
|
|
29
25
|
LoggerLevel,
|
|
30
26
|
render,
|
|
31
|
-
getLastMessageFromHistory,
|
|
32
|
-
normalizeHistory,
|
|
33
|
-
cloneDeep,
|
|
34
27
|
} from "../utils";
|
|
35
28
|
|
|
36
29
|
import { Route } from "./Route";
|
|
@@ -38,10 +31,9 @@ import { Step } from "./Step";
|
|
|
38
31
|
import { PersistenceManager } from "./PersistenceManager";
|
|
39
32
|
import { SessionManager } from "./SessionManager";
|
|
40
33
|
import { RoutingEngine } from "./RoutingEngine";
|
|
41
|
-
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import { END_ROUTE_ID } from "../constants";
|
|
34
|
+
|
|
35
|
+
import { ResponseModal } from "./ResponseModal";
|
|
36
|
+
import { ToolManager } from "./ToolManager";
|
|
45
37
|
|
|
46
38
|
/**
|
|
47
39
|
* Error thrown when data validation fails
|
|
@@ -70,13 +62,12 @@ class RouteConfigurationError extends Error {
|
|
|
70
62
|
export class Agent<TContext = any, TData = any> {
|
|
71
63
|
private terms: Term<TContext, TData>[] = [];
|
|
72
64
|
private guidelines: Guideline<TContext, TData>[] = [];
|
|
73
|
-
private tools: Tool<TContext, TData
|
|
65
|
+
private tools: Tool<TContext, TData>[] = [];
|
|
74
66
|
private routes: Route<TContext, TData>[] = [];
|
|
75
67
|
private context: TContext | undefined;
|
|
76
68
|
private persistenceManager: PersistenceManager<TData> | undefined;
|
|
77
69
|
private routingEngine: RoutingEngine<TContext, TData>;
|
|
78
|
-
private
|
|
79
|
-
private responsePipeline: ResponsePipeline<TContext, TData>;
|
|
70
|
+
private responseModal: ResponseModal<TContext, TData>;
|
|
80
71
|
private currentSession?: SessionState<TData>;
|
|
81
72
|
private knowledgeBase: Record<string, unknown> = {};
|
|
82
73
|
private schema?: StructuredSchema;
|
|
@@ -85,6 +76,9 @@ export class Agent<TContext = any, TData = any> {
|
|
|
85
76
|
/** Public session manager for easy session management */
|
|
86
77
|
public session: SessionManager<TData>;
|
|
87
78
|
|
|
79
|
+
/** Public tool manager for simplified tool creation and management */
|
|
80
|
+
public tool: ToolManager<TContext, TData>;
|
|
81
|
+
|
|
88
82
|
constructor(private readonly options: AgentOptions<TContext, TData>) {
|
|
89
83
|
// Set log level based on debug option
|
|
90
84
|
if (options.debug) {
|
|
@@ -125,22 +119,15 @@ export class Agent<TContext = any, TData = any> {
|
|
|
125
119
|
// Initialize current session if provided
|
|
126
120
|
this.currentSession = options.session;
|
|
127
121
|
|
|
128
|
-
// Initialize routing
|
|
122
|
+
// Initialize routing engine
|
|
129
123
|
this.routingEngine = new RoutingEngine<TContext, TData>({
|
|
130
124
|
maxCandidates: 5,
|
|
131
125
|
allowRouteSwitch: true,
|
|
132
126
|
switchThreshold: 70,
|
|
133
127
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
this.routes,
|
|
138
|
-
this.tools,
|
|
139
|
-
this.routingEngine,
|
|
140
|
-
this.updateContext.bind(this),
|
|
141
|
-
this.updateData.bind(this),
|
|
142
|
-
this.updateCollectedData.bind(this)
|
|
143
|
-
);
|
|
128
|
+
|
|
129
|
+
// Initialize ResponseModal for handling all response generation
|
|
130
|
+
this.responseModal = new ResponseModal<TContext, TData>(this);
|
|
144
131
|
|
|
145
132
|
// Initialize persistence if configured
|
|
146
133
|
if (options.persistence) {
|
|
@@ -206,13 +193,23 @@ export class Agent<TContext = any, TData = any> {
|
|
|
206
193
|
this.knowledgeBase = { ...options.knowledgeBase };
|
|
207
194
|
}
|
|
208
195
|
|
|
209
|
-
// Initialize session manager
|
|
210
|
-
this.session = new SessionManager<TData>(this.persistenceManager);
|
|
196
|
+
// Initialize session manager with reference to this agent for bidirectional sync
|
|
197
|
+
this.session = new SessionManager<TData>(this.persistenceManager, this);
|
|
198
|
+
|
|
199
|
+
// Initialize tool manager with proper type inference
|
|
200
|
+
this.tool = new ToolManager<TContext, TData>(this);
|
|
211
201
|
|
|
212
202
|
// Store sessionId for later use in getOrCreate calls
|
|
213
203
|
if (options.sessionId) {
|
|
204
|
+
this.session.setDefaultSessionId(options.sessionId);
|
|
214
205
|
// The session will be loaded on first getOrCreate call
|
|
215
|
-
this.session.getOrCreate(options.sessionId).
|
|
206
|
+
this.session.getOrCreate(options.sessionId).then((session) => {
|
|
207
|
+
// Sync session data to agent collected data
|
|
208
|
+
if (session.data && Object.keys(session.data).length > 0) {
|
|
209
|
+
this.collectedData = { ...session.data };
|
|
210
|
+
logger.debug("[Agent] Synced session data to collected data:", this.collectedData);
|
|
211
|
+
}
|
|
212
|
+
}).catch((err) => {
|
|
216
213
|
logger.error("Failed to start session", err);
|
|
217
214
|
});
|
|
218
215
|
}
|
|
@@ -312,6 +309,8 @@ export class Agent<TContext = any, TData = any> {
|
|
|
312
309
|
* Get the current collected data
|
|
313
310
|
*/
|
|
314
311
|
getCollectedData(): Partial<TData> {
|
|
312
|
+
// Ensure agent collected data is synced with session
|
|
313
|
+
this.syncSessionDataToCollectedData();
|
|
315
314
|
return { ...this.collectedData };
|
|
316
315
|
}
|
|
317
316
|
|
|
@@ -352,6 +351,13 @@ export class Agent<TContext = any, TData = any> {
|
|
|
352
351
|
this.currentSession = mergeCollected(this.currentSession, this.collectedData);
|
|
353
352
|
}
|
|
354
353
|
|
|
354
|
+
// Also update the session manager's session data (avoid circular call)
|
|
355
|
+
const sessionManagerSession = this.session.current;
|
|
356
|
+
if (sessionManagerSession) {
|
|
357
|
+
sessionManagerSession.data = { ...this.collectedData };
|
|
358
|
+
sessionManagerSession.metadata!.lastUpdatedAt = new Date();
|
|
359
|
+
}
|
|
360
|
+
|
|
355
361
|
logger.debug("[Agent] Collected data updated:", updates);
|
|
356
362
|
}
|
|
357
363
|
|
|
@@ -419,7 +425,7 @@ export class Agent<TContext = any, TData = any> {
|
|
|
419
425
|
}
|
|
420
426
|
}
|
|
421
427
|
|
|
422
|
-
const route = new Route<TContext, TData>(options);
|
|
428
|
+
const route = new Route<TContext, TData>(options, this);
|
|
423
429
|
this.routes.push(route);
|
|
424
430
|
return route;
|
|
425
431
|
}
|
|
@@ -446,18 +452,53 @@ export class Agent<TContext = any, TData = any> {
|
|
|
446
452
|
}
|
|
447
453
|
|
|
448
454
|
/**
|
|
449
|
-
*
|
|
455
|
+
* Add a tool to the agent using the unified Tool interface
|
|
456
|
+
* Creates and adds the tool to agent scope in one operation (BREAKING CHANGE: replaces createTool)
|
|
450
457
|
*/
|
|
451
|
-
|
|
458
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
459
|
+
addTool<TResult = any>(
|
|
460
|
+
tool: Tool<TContext, TData, TResult>
|
|
461
|
+
): this {
|
|
462
|
+
// Validate tool before adding
|
|
463
|
+
if (!tool || !tool.id || !tool.handler) {
|
|
464
|
+
throw new Error('Invalid tool: must have id and handler properties');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Add directly to agent's tools array, preserving the TResult type
|
|
452
468
|
this.tools.push(tool);
|
|
469
|
+
logger.debug(`[Agent] Added tool to agent scope: ${tool.id}`);
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Register a tool at the agent level (legacy method for backward compatibility)
|
|
475
|
+
* @deprecated Use addTool() with Tool interface instead
|
|
476
|
+
*/
|
|
477
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
478
|
+
createTool<TResult = any>(tool: Tool<TContext, TData, TResult>): this {
|
|
479
|
+
// Validate tool before adding
|
|
480
|
+
if (!tool || !tool.id || !tool.handler) {
|
|
481
|
+
throw new Error('Invalid tool: must have id and handler properties');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
this.tools.push(tool);
|
|
485
|
+
logger.debug(`[Agent] Created tool (legacy): ${tool.id}`);
|
|
453
486
|
return this;
|
|
454
487
|
}
|
|
455
488
|
|
|
456
489
|
/**
|
|
457
490
|
* Register multiple tools at the agent level
|
|
458
491
|
*/
|
|
459
|
-
|
|
460
|
-
|
|
492
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
493
|
+
registerTools<TResult = any>(tools: Tool<TContext, TData, TResult>[]): this {
|
|
494
|
+
tools.forEach((tool) => {
|
|
495
|
+
// Validate each tool before adding
|
|
496
|
+
if (!tool || !tool.id || !tool.handler) {
|
|
497
|
+
throw new Error(`Invalid tool in batch: must have id and handler properties (tool: ${tool?.id || 'unknown'})`);
|
|
498
|
+
}
|
|
499
|
+
this.tools.push(tool);
|
|
500
|
+
});
|
|
501
|
+
logger.debug(`[Agent] Registered ${tools.length} tools`);
|
|
461
502
|
return this;
|
|
462
503
|
}
|
|
463
504
|
|
|
@@ -560,1399 +601,125 @@ export class Agent<TContext = any, TData = any> {
|
|
|
560
601
|
/**
|
|
561
602
|
* Generate a response based on history and context as a stream
|
|
562
603
|
*/
|
|
563
|
-
async *respondStream(params: {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
session?: SessionState<TData>;
|
|
567
|
-
contextOverride?: Partial<TContext>;
|
|
568
|
-
signal?: AbortSignal;
|
|
569
|
-
}): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
570
|
-
const { history: simpleHistory, signal } = params;
|
|
571
|
-
const history = normalizeHistory(simpleHistory);
|
|
572
|
-
|
|
573
|
-
// Prepare context and session using the response pipeline
|
|
574
|
-
this.responsePipeline.setContext(this.context);
|
|
575
|
-
this.responsePipeline.setCurrentSession(this.currentSession);
|
|
576
|
-
let session: SessionState;
|
|
577
|
-
const responseContext = await this.responsePipeline.prepareResponseContext({
|
|
578
|
-
contextOverride: params.contextOverride,
|
|
579
|
-
session: params.session ? cloneDeep(params.session) : undefined,
|
|
580
|
-
});
|
|
581
|
-
const { effectiveContext } = responseContext;
|
|
582
|
-
session = responseContext.session;
|
|
583
|
-
|
|
584
|
-
// Merge agent's collected data into session (agent data takes precedence)
|
|
585
|
-
if (Object.keys(this.collectedData).length > 0) {
|
|
586
|
-
session = mergeCollected(session, this.collectedData);
|
|
587
|
-
logger.debug("[Agent] Merged agent collected data into session:", this.collectedData);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Update our stored context if it was modified by beforeRespond hook
|
|
591
|
-
this.context = this.responsePipeline.getStoredContext();
|
|
592
|
-
|
|
593
|
-
// PHASE 1: PREPARE - Execute prepare function if current step has one
|
|
594
|
-
if (session.currentRoute && session.currentStep) {
|
|
595
|
-
const currentRoute = this.routes.find(
|
|
596
|
-
(r) => r.id === session.currentRoute?.id
|
|
597
|
-
);
|
|
598
|
-
if (currentRoute) {
|
|
599
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
600
|
-
if (currentStep?.prepare) {
|
|
601
|
-
logger.debug(`[Agent] Executing prepare for step: ${currentStep.id}`);
|
|
602
|
-
await this.executePrepareFinalize(
|
|
603
|
-
currentStep.prepare,
|
|
604
|
-
effectiveContext,
|
|
605
|
-
session.data,
|
|
606
|
-
currentRoute,
|
|
607
|
-
currentStep
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// PHASE 2: ROUTING + STEP SELECTION - Use response pipeline
|
|
614
|
-
const routingResult =
|
|
615
|
-
await this.responsePipeline.handleRoutingAndStepSelection({
|
|
616
|
-
session,
|
|
617
|
-
history,
|
|
618
|
-
context: effectiveContext,
|
|
619
|
-
signal,
|
|
620
|
-
});
|
|
621
|
-
const selectedRoute = routingResult.selectedRoute;
|
|
622
|
-
const selectedStep = routingResult.selectedStep;
|
|
623
|
-
const responseDirectives = routingResult.responseDirectives;
|
|
624
|
-
const isRouteComplete = routingResult.isRouteComplete;
|
|
625
|
-
session = routingResult.session;
|
|
626
|
-
|
|
627
|
-
// PHASE 3: DETERMINE NEXT STEP - Use pipeline method
|
|
628
|
-
const stepResult = this.responsePipeline.determineNextStep({
|
|
629
|
-
selectedRoute,
|
|
630
|
-
selectedStep,
|
|
631
|
-
session,
|
|
632
|
-
isRouteComplete,
|
|
633
|
-
});
|
|
634
|
-
const nextStep = stepResult.nextStep;
|
|
635
|
-
session = stepResult.session;
|
|
636
|
-
|
|
637
|
-
if (selectedRoute && !isRouteComplete) {
|
|
638
|
-
// PHASE 4: RESPONSE GENERATION - Stream message using selected route and step
|
|
639
|
-
// Get last user message
|
|
640
|
-
const lastUserMessage = getLastMessageFromHistory(history);
|
|
641
|
-
|
|
642
|
-
// Build response schema for this route (with collect fields from step)
|
|
643
|
-
const responseSchema = this.responseEngine.responseSchemaForRoute(
|
|
644
|
-
selectedRoute,
|
|
645
|
-
nextStep,
|
|
646
|
-
this.schema
|
|
647
|
-
);
|
|
648
|
-
|
|
649
|
-
// Check if selected route and next step are defined
|
|
650
|
-
if (!selectedRoute || !nextStep) {
|
|
651
|
-
logger.error("[Agent] Selected route or next step is not defined", {
|
|
652
|
-
selectedRoute,
|
|
653
|
-
nextStep,
|
|
654
|
-
});
|
|
655
|
-
throw new Error("Selected route or next step is not defined");
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Build response prompt
|
|
659
|
-
const responsePrompt = await this.responseEngine.buildResponsePrompt({
|
|
660
|
-
route: selectedRoute,
|
|
661
|
-
currentStep: nextStep,
|
|
662
|
-
rules: selectedRoute.getRules(),
|
|
663
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
664
|
-
directives: responseDirectives,
|
|
665
|
-
history,
|
|
666
|
-
lastMessage: lastUserMessage,
|
|
667
|
-
agentOptions: this.options,
|
|
668
|
-
// Combine agent and route properties according to the specified logic
|
|
669
|
-
combinedGuidelines: [
|
|
670
|
-
...this.getGuidelines(),
|
|
671
|
-
...selectedRoute.getGuidelines(),
|
|
672
|
-
],
|
|
673
|
-
combinedTerms: this.mergeTerms(
|
|
674
|
-
this.getTerms(),
|
|
675
|
-
selectedRoute.getTerms()
|
|
676
|
-
),
|
|
677
|
-
context: effectiveContext,
|
|
678
|
-
session,
|
|
679
|
-
agentSchema: this.schema,
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// Collect available tools for AI
|
|
683
|
-
const availableTools = this.collectAvailableTools(
|
|
684
|
-
selectedRoute,
|
|
685
|
-
nextStep
|
|
686
|
-
);
|
|
687
|
-
|
|
688
|
-
// Generate message stream using AI provider
|
|
689
|
-
const stream = this.options.provider.generateMessageStream({
|
|
690
|
-
prompt: responsePrompt,
|
|
691
|
-
history,
|
|
692
|
-
context: effectiveContext,
|
|
693
|
-
tools: availableTools,
|
|
694
|
-
signal,
|
|
695
|
-
parameters: {
|
|
696
|
-
jsonSchema: responseSchema,
|
|
697
|
-
schemaName: "response_stream_output",
|
|
698
|
-
},
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
// Stream chunks to caller
|
|
702
|
-
for await (const chunk of stream) {
|
|
703
|
-
let toolCalls:
|
|
704
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
705
|
-
| undefined = undefined;
|
|
706
|
-
|
|
707
|
-
// Extract tool calls from AI response on final chunk
|
|
708
|
-
if (chunk.done && chunk.structured?.toolCalls) {
|
|
709
|
-
toolCalls = chunk.structured.toolCalls;
|
|
710
|
-
|
|
711
|
-
// Execute dynamic tool calls
|
|
712
|
-
if (toolCalls.length > 0) {
|
|
713
|
-
logger.debug(
|
|
714
|
-
`[Agent] Executing ${toolCalls.length} dynamic tool calls`
|
|
715
|
-
);
|
|
716
|
-
|
|
717
|
-
for (const toolCall of toolCalls) {
|
|
718
|
-
const tool = this.findAvailableTool(
|
|
719
|
-
toolCall.toolName,
|
|
720
|
-
selectedRoute
|
|
721
|
-
);
|
|
722
|
-
if (!tool) {
|
|
723
|
-
logger.warn(`[Agent] Tool not found: ${toolCall.toolName}`);
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
const toolExecutor = new ToolExecutor<TContext, TData>();
|
|
728
|
-
const result = await toolExecutor.executeTool({
|
|
729
|
-
tool: tool,
|
|
730
|
-
context: effectiveContext,
|
|
731
|
-
updateContext: this.updateContext.bind(this),
|
|
732
|
-
updateData: this.updateCollectedData.bind(this),
|
|
733
|
-
history,
|
|
734
|
-
data: session.data,
|
|
735
|
-
toolArguments: toolCall.arguments,
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
// Update context with tool results
|
|
739
|
-
if (result.contextUpdate) {
|
|
740
|
-
await this.updateContext(
|
|
741
|
-
result.contextUpdate as Partial<TContext>
|
|
742
|
-
);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Update collected data with tool results
|
|
746
|
-
if (result.dataUpdate) {
|
|
747
|
-
session = await this.updateData(session, result.dataUpdate as Partial<TData>);
|
|
748
|
-
logger.debug(
|
|
749
|
-
`[Agent] Tool updated collected data:`,
|
|
750
|
-
result.dataUpdate
|
|
751
|
-
);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
logger.debug(
|
|
755
|
-
`[Agent] Executed dynamic tool: ${result.toolName} (success: ${result.success})`
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution (streaming)
|
|
762
|
-
const MAX_TOOL_LOOPS = 5;
|
|
763
|
-
let toolLoopCount = 0;
|
|
764
|
-
let hasToolCalls = toolCalls && toolCalls.length > 0;
|
|
765
|
-
|
|
766
|
-
while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
|
|
767
|
-
toolLoopCount++;
|
|
768
|
-
logger.debug(
|
|
769
|
-
`[Agent] Starting streaming tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`
|
|
770
|
-
);
|
|
771
|
-
|
|
772
|
-
// Add tool execution results to history so AI knows what happened
|
|
773
|
-
const toolResultsEvents: Event[] = [];
|
|
774
|
-
for (const toolCall of toolCalls || []) {
|
|
775
|
-
const tool = this.findAvailableTool(
|
|
776
|
-
toolCall.toolName,
|
|
777
|
-
selectedRoute
|
|
778
|
-
);
|
|
779
|
-
if (tool) {
|
|
780
|
-
toolResultsEvents.push({
|
|
781
|
-
kind: EventKind.TOOL,
|
|
782
|
-
source: MessageRole.AGENT,
|
|
783
|
-
timestamp: new Date().toISOString(),
|
|
784
|
-
data: {
|
|
785
|
-
tool_calls: [
|
|
786
|
-
{
|
|
787
|
-
tool_id: toolCall.toolName,
|
|
788
|
-
arguments: toolCall.arguments,
|
|
789
|
-
result: {
|
|
790
|
-
data: "Tool executed successfully",
|
|
791
|
-
},
|
|
792
|
-
},
|
|
793
|
-
],
|
|
794
|
-
},
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Create updated history with tool results
|
|
800
|
-
const updatedHistory = [...history, ...toolResultsEvents];
|
|
801
|
-
|
|
802
|
-
// Make follow-up streaming AI call to see if more tools are needed
|
|
803
|
-
const followUpStream = this.options.provider.generateMessageStream({
|
|
804
|
-
prompt: responsePrompt,
|
|
805
|
-
history: updatedHistory,
|
|
806
|
-
context: effectiveContext,
|
|
807
|
-
tools: availableTools,
|
|
808
|
-
parameters: {
|
|
809
|
-
jsonSchema: responseSchema,
|
|
810
|
-
schemaName: "tool_followup",
|
|
811
|
-
},
|
|
812
|
-
signal,
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
let followUpToolCalls:
|
|
816
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
817
|
-
| undefined;
|
|
818
|
-
|
|
819
|
-
for await (const followUpChunk of followUpStream) {
|
|
820
|
-
// Extract tool calls from follow-up stream
|
|
821
|
-
if (followUpChunk.done && followUpChunk.structured?.toolCalls) {
|
|
822
|
-
followUpToolCalls = followUpChunk.structured.toolCalls;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
|
|
827
|
-
|
|
828
|
-
if (hasToolCalls) {
|
|
829
|
-
logger.debug(
|
|
830
|
-
`[Agent] Follow-up streaming call produced ${followUpToolCalls!.length
|
|
831
|
-
} additional tool calls`
|
|
832
|
-
);
|
|
833
|
-
|
|
834
|
-
// Execute the follow-up tool calls
|
|
835
|
-
for (const toolCall of followUpToolCalls!) {
|
|
836
|
-
const tool = this.findAvailableTool(
|
|
837
|
-
toolCall.toolName,
|
|
838
|
-
selectedRoute
|
|
839
|
-
);
|
|
840
|
-
if (!tool) {
|
|
841
|
-
logger.warn(
|
|
842
|
-
`[Agent] Tool not found in streaming follow-up: ${toolCall.toolName}`
|
|
843
|
-
);
|
|
844
|
-
continue;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
const toolExecutor = new ToolExecutor<TContext, TData>();
|
|
848
|
-
const result = await toolExecutor.executeTool({
|
|
849
|
-
tool: tool,
|
|
850
|
-
context: effectiveContext,
|
|
851
|
-
updateContext: this.updateContext.bind(this),
|
|
852
|
-
updateData: this.updateCollectedData.bind(this),
|
|
853
|
-
history: updatedHistory,
|
|
854
|
-
data: session.data,
|
|
855
|
-
toolArguments: toolCall.arguments,
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
// Update context with follow-up tool results
|
|
859
|
-
if (result.contextUpdate) {
|
|
860
|
-
await this.updateContext(
|
|
861
|
-
result.contextUpdate as Partial<TContext>
|
|
862
|
-
);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
if (result.dataUpdate) {
|
|
866
|
-
session = await this.updateData(session, result.dataUpdate as Partial<TData>);
|
|
867
|
-
logger.debug(
|
|
868
|
-
`[Agent] Streaming follow-up tool updated collected data:`,
|
|
869
|
-
result.dataUpdate
|
|
870
|
-
);
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
logger.debug(
|
|
874
|
-
`[Agent] Executed streaming follow-up tool: ${result.toolName} (success: ${result.success})`
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
// Update toolCalls for next iteration
|
|
879
|
-
toolCalls = followUpToolCalls;
|
|
880
|
-
} else {
|
|
881
|
-
logger.debug(
|
|
882
|
-
`[Agent] Streaming tool loop completed after ${toolLoopCount} iterations`
|
|
883
|
-
);
|
|
884
|
-
// Update toolCalls for final response
|
|
885
|
-
toolCalls = followUpToolCalls || [];
|
|
886
|
-
break;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
891
|
-
logger.warn(
|
|
892
|
-
`[Agent] Streaming tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`
|
|
893
|
-
);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
// Extract collected data on final chunk
|
|
897
|
-
if (chunk.done && chunk.structured && nextStep.collect) {
|
|
898
|
-
const collectedData: Record<string, unknown> = {};
|
|
899
|
-
// The structured response includes both base fields and collected extraction fields
|
|
900
|
-
const structuredData = chunk.structured as AgentStructuredResponse &
|
|
901
|
-
Record<string, unknown>;
|
|
902
|
-
|
|
903
|
-
for (const field of nextStep.collect) {
|
|
904
|
-
const fieldKey = String(field);
|
|
905
|
-
if (fieldKey in structuredData) {
|
|
906
|
-
collectedData[fieldKey] = structuredData[fieldKey];
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// Merge collected data into session using agent-level data validation
|
|
911
|
-
if (Object.keys(collectedData).length > 0) {
|
|
912
|
-
// Update agent-level collected data with validation
|
|
913
|
-
await this.updateCollectedData(collectedData as Partial<TData>);
|
|
914
|
-
|
|
915
|
-
// Update session with validated data
|
|
916
|
-
session = await this.updateData(session, collectedData as Partial<TData>);
|
|
917
|
-
logger.debug(`[Agent] Collected data:`, collectedData);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// Extract any additional data from structured response on final chunk
|
|
922
|
-
if (
|
|
923
|
-
chunk.done &&
|
|
924
|
-
chunk.structured &&
|
|
925
|
-
typeof chunk.structured === "object" &&
|
|
926
|
-
"contextUpdate" in chunk.structured
|
|
927
|
-
) {
|
|
928
|
-
await this.updateContext(
|
|
929
|
-
(chunk.structured as { contextUpdate?: Partial<TContext> })
|
|
930
|
-
.contextUpdate as Partial<TContext>
|
|
931
|
-
);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// Auto-save session step on final chunk
|
|
935
|
-
if (
|
|
936
|
-
chunk.done &&
|
|
937
|
-
this.persistenceManager &&
|
|
938
|
-
session.id &&
|
|
939
|
-
this.options.persistence?.autoSave !== false
|
|
940
|
-
) {
|
|
941
|
-
await this.persistenceManager.saveSessionState(session.id, session);
|
|
942
|
-
logger.debug(
|
|
943
|
-
`[Agent] Auto-saved session step to persistence: ${session.id}`
|
|
944
|
-
);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// Execute finalize function on final chunk
|
|
948
|
-
if (chunk.done && session.currentRoute && session.currentStep) {
|
|
949
|
-
const currentRoute = this.routes.find(
|
|
950
|
-
(r) => r.id === session.currentRoute?.id
|
|
951
|
-
);
|
|
952
|
-
if (currentRoute) {
|
|
953
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
954
|
-
if (currentStep?.finalize) {
|
|
955
|
-
logger.debug(
|
|
956
|
-
`[Agent] Executing finalize for step: ${currentStep.id}`
|
|
957
|
-
);
|
|
958
|
-
await this.executePrepareFinalize(
|
|
959
|
-
currentStep.finalize,
|
|
960
|
-
effectiveContext,
|
|
961
|
-
session.data,
|
|
962
|
-
currentRoute,
|
|
963
|
-
currentStep
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
// Update current session if we have one
|
|
970
|
-
if (chunk.done && this.currentSession) {
|
|
971
|
-
this.currentSession = session;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
yield {
|
|
975
|
-
delta: chunk.delta,
|
|
976
|
-
accumulated: chunk.accumulated,
|
|
977
|
-
done: chunk.done,
|
|
978
|
-
session, // Return updated session
|
|
979
|
-
toolCalls,
|
|
980
|
-
isRouteComplete,
|
|
981
|
-
metadata: chunk.metadata,
|
|
982
|
-
structured: chunk.structured,
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
} else if (isRouteComplete && selectedRoute) {
|
|
986
|
-
// Route is complete - generate completion message then check for onComplete transition
|
|
987
|
-
const lastUserMessage = getLastMessageFromHistory(history);
|
|
988
|
-
|
|
989
|
-
// Get endStep spec from route
|
|
990
|
-
const endStepSpec = selectedRoute.endStepSpec;
|
|
991
|
-
|
|
992
|
-
// Create a temporary step for completion message generation using endStep configuration
|
|
993
|
-
const completionStep = new Step<TContext, TData>(selectedRoute.id, {
|
|
994
|
-
description: endStepSpec.description,
|
|
995
|
-
id: endStepSpec.id || END_ROUTE_ID,
|
|
996
|
-
collect: endStepSpec.collect,
|
|
997
|
-
requires: endStepSpec.requires,
|
|
998
|
-
prompt:
|
|
999
|
-
endStepSpec.prompt ||
|
|
1000
|
-
"Summarize what was accomplished and confirm completion based on the conversation history and collected data",
|
|
1001
|
-
});
|
|
1002
|
-
|
|
1003
|
-
// Build response schema for completion
|
|
1004
|
-
const responseSchema = this.responseEngine.responseSchemaForRoute(
|
|
1005
|
-
selectedRoute,
|
|
1006
|
-
completionStep,
|
|
1007
|
-
this.schema
|
|
1008
|
-
);
|
|
1009
|
-
const templateContext = {
|
|
1010
|
-
context: effectiveContext,
|
|
1011
|
-
session,
|
|
1012
|
-
history,
|
|
1013
|
-
};
|
|
1014
|
-
|
|
1015
|
-
// Build completion response prompt
|
|
1016
|
-
const completionPrompt = await this.responseEngine.buildResponsePrompt({
|
|
1017
|
-
route: selectedRoute,
|
|
1018
|
-
currentStep: completionStep,
|
|
1019
|
-
rules: selectedRoute.getRules(),
|
|
1020
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1021
|
-
directives: undefined, // No directives for completion
|
|
1022
|
-
history,
|
|
1023
|
-
lastMessage: lastUserMessage,
|
|
1024
|
-
agentOptions: this.options,
|
|
1025
|
-
// Combine agent and route properties according to the specified logic
|
|
1026
|
-
combinedGuidelines: [
|
|
1027
|
-
...this.getGuidelines(),
|
|
1028
|
-
...selectedRoute.getGuidelines(),
|
|
1029
|
-
],
|
|
1030
|
-
combinedTerms: this.mergeTerms(
|
|
1031
|
-
this.getTerms(),
|
|
1032
|
-
selectedRoute.getTerms()
|
|
1033
|
-
),
|
|
1034
|
-
context: effectiveContext,
|
|
1035
|
-
session,
|
|
1036
|
-
agentSchema: this.schema,
|
|
1037
|
-
});
|
|
1038
|
-
|
|
1039
|
-
// Stream completion message using AI provider
|
|
1040
|
-
const stream = this.options.provider.generateMessageStream({
|
|
1041
|
-
prompt: completionPrompt,
|
|
1042
|
-
history,
|
|
1043
|
-
context: effectiveContext,
|
|
1044
|
-
signal,
|
|
1045
|
-
parameters: {
|
|
1046
|
-
jsonSchema: responseSchema,
|
|
1047
|
-
schemaName: "completion_message_stream",
|
|
1048
|
-
},
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
logger.debug(
|
|
1052
|
-
`[Agent] Streaming completion message for route: ${selectedRoute.title}`
|
|
1053
|
-
);
|
|
1054
|
-
|
|
1055
|
-
// Check for onComplete transition
|
|
1056
|
-
const transitionConfig = await selectedRoute.evaluateOnComplete(
|
|
1057
|
-
{ data: session.data },
|
|
1058
|
-
effectiveContext
|
|
1059
|
-
);
|
|
1060
|
-
|
|
1061
|
-
if (transitionConfig) {
|
|
1062
|
-
// Find target route by ID or title
|
|
1063
|
-
const targetRoute = this.routes.find(
|
|
1064
|
-
(r) =>
|
|
1065
|
-
r.id === transitionConfig.nextStep ||
|
|
1066
|
-
r.title === transitionConfig.nextStep
|
|
1067
|
-
);
|
|
1068
|
-
|
|
1069
|
-
if (targetRoute) {
|
|
1070
|
-
const renderedCondition = await render(
|
|
1071
|
-
transitionConfig.condition,
|
|
1072
|
-
templateContext
|
|
1073
|
-
);
|
|
1074
|
-
// Set pending transition in session
|
|
1075
|
-
session = {
|
|
1076
|
-
...session,
|
|
1077
|
-
pendingTransition: {
|
|
1078
|
-
targetRouteId: targetRoute.id,
|
|
1079
|
-
condition: renderedCondition,
|
|
1080
|
-
reason: "route_complete",
|
|
1081
|
-
},
|
|
1082
|
-
};
|
|
1083
|
-
logger.debug(
|
|
1084
|
-
`[Agent] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`
|
|
1085
|
-
);
|
|
1086
|
-
} else {
|
|
1087
|
-
logger.warn(
|
|
1088
|
-
`[Agent] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`
|
|
1089
|
-
);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// Set step to END_ROUTE marker
|
|
1094
|
-
session = enterStep(session, END_ROUTE_ID, "Route completed");
|
|
1095
|
-
logger.debug(
|
|
1096
|
-
`[Agent] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`
|
|
1097
|
-
);
|
|
1098
|
-
|
|
1099
|
-
// Stream completion chunks
|
|
1100
|
-
for await (const chunk of stream) {
|
|
1101
|
-
// Update current session if we have one
|
|
1102
|
-
if (chunk.done && this.currentSession) {
|
|
1103
|
-
this.currentSession = session;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
yield {
|
|
1107
|
-
delta: chunk.delta,
|
|
1108
|
-
accumulated: chunk.accumulated,
|
|
1109
|
-
done: chunk.done,
|
|
1110
|
-
session,
|
|
1111
|
-
toolCalls: undefined,
|
|
1112
|
-
isRouteComplete: true,
|
|
1113
|
-
metadata: chunk.metadata,
|
|
1114
|
-
structured: chunk.structured,
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
} else {
|
|
1118
|
-
// Fallback: No routes defined, stream a simple response
|
|
1119
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
1120
|
-
history,
|
|
1121
|
-
agentOptions: this.options,
|
|
1122
|
-
terms: this.terms,
|
|
1123
|
-
guidelines: this.guidelines,
|
|
1124
|
-
context: effectiveContext,
|
|
1125
|
-
session,
|
|
1126
|
-
});
|
|
1127
|
-
|
|
1128
|
-
const stream = this.options.provider.generateMessageStream({
|
|
1129
|
-
prompt: fallbackPrompt,
|
|
1130
|
-
history,
|
|
1131
|
-
context: effectiveContext,
|
|
1132
|
-
signal,
|
|
1133
|
-
parameters: {
|
|
1134
|
-
jsonSchema: {
|
|
1135
|
-
type: "object",
|
|
1136
|
-
properties: {
|
|
1137
|
-
message: { type: "string" },
|
|
1138
|
-
},
|
|
1139
|
-
required: ["message"],
|
|
1140
|
-
additionalProperties: false,
|
|
1141
|
-
},
|
|
1142
|
-
schemaName: "fallback_stream_response",
|
|
1143
|
-
},
|
|
1144
|
-
});
|
|
1145
|
-
|
|
1146
|
-
for await (const chunk of stream) {
|
|
1147
|
-
// Update current session if we have one
|
|
1148
|
-
if (chunk.done && this.currentSession) {
|
|
1149
|
-
this.currentSession = session;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
yield {
|
|
1153
|
-
delta: chunk.delta,
|
|
1154
|
-
accumulated: chunk.accumulated,
|
|
1155
|
-
done: chunk.done,
|
|
1156
|
-
session, // Return updated session
|
|
1157
|
-
toolCalls: undefined,
|
|
1158
|
-
isRouteComplete: false,
|
|
1159
|
-
metadata: chunk.metadata,
|
|
1160
|
-
structured: chunk.structured,
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
604
|
+
async *respondStream(params: RespondParams<TContext, TData>): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
605
|
+
// Delegate to ResponseModal
|
|
606
|
+
yield* this.responseModal.respondStream(params);
|
|
1164
607
|
}
|
|
1165
608
|
|
|
1166
609
|
/**
|
|
1167
610
|
* Generate a response based on history and context
|
|
1168
611
|
*/
|
|
1169
|
-
async respond(params: {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
contextOverride?: Partial<TContext>;
|
|
1174
|
-
signal?: AbortSignal;
|
|
1175
|
-
}): Promise<AgentResponse<TData>> {
|
|
1176
|
-
const { history: simpleHistory, contextOverride, signal } = params;
|
|
1177
|
-
const history = normalizeHistory(simpleHistory);
|
|
1178
|
-
|
|
1179
|
-
// Get current context (may fetch from provider)
|
|
1180
|
-
let currentContext = await this.getContext();
|
|
1181
|
-
|
|
1182
|
-
// Call beforeRespond hook if configured
|
|
1183
|
-
if (this.options.hooks?.beforeRespond && currentContext !== undefined) {
|
|
1184
|
-
currentContext = await this.options.hooks.beforeRespond(currentContext);
|
|
1185
|
-
// Update stored context with the result from beforeRespond
|
|
1186
|
-
this.context = currentContext;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
// Merge context with override
|
|
1190
|
-
const effectiveContext = {
|
|
1191
|
-
...(currentContext as Record<string, unknown>),
|
|
1192
|
-
...(contextOverride as Record<string, unknown>),
|
|
1193
|
-
} as TContext;
|
|
1194
|
-
|
|
1195
|
-
// Initialize or get session (use current session if available)
|
|
1196
|
-
let session =
|
|
1197
|
-
cloneDeep(params.session) ||
|
|
1198
|
-
cloneDeep(this.currentSession) ||
|
|
1199
|
-
(await this.session.getOrCreate());
|
|
1200
|
-
|
|
1201
|
-
// Merge agent's collected data into session (agent data takes precedence)
|
|
1202
|
-
if (Object.keys(this.collectedData).length > 0) {
|
|
1203
|
-
session = mergeCollected(session, this.collectedData);
|
|
1204
|
-
logger.debug("[Agent] Merged agent collected data into session:", this.collectedData);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
// PHASE 1: PREPARE - Execute prepare function if current step has one
|
|
1208
|
-
if (session.currentRoute && session.currentStep) {
|
|
1209
|
-
const currentRoute = this.routes.find(
|
|
1210
|
-
(r) => r.id === session.currentRoute?.id
|
|
1211
|
-
);
|
|
1212
|
-
if (currentRoute) {
|
|
1213
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
1214
|
-
if (currentStep?.prepare) {
|
|
1215
|
-
logger.debug(`[Agent] Executing prepare for step: ${currentStep.id}`);
|
|
1216
|
-
await this.executePrepareFinalize(
|
|
1217
|
-
currentStep.prepare,
|
|
1218
|
-
effectiveContext,
|
|
1219
|
-
session.data,
|
|
1220
|
-
currentRoute,
|
|
1221
|
-
currentStep
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
// PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use (combined)
|
|
1228
|
-
let selectedRoute: Route<TContext, TData> | undefined;
|
|
1229
|
-
let responseDirectives: string[] | undefined;
|
|
1230
|
-
let selectedStep: Step<TContext, TData> | undefined;
|
|
1231
|
-
let isRouteComplete = false;
|
|
1232
|
-
|
|
1233
|
-
// Check for pending transition from previous route completion
|
|
1234
|
-
if (session.pendingTransition) {
|
|
1235
|
-
const targetRoute = this.routes.find(
|
|
1236
|
-
(r) => r.id === session.pendingTransition?.targetRouteId
|
|
1237
|
-
);
|
|
1238
|
-
|
|
1239
|
-
if (targetRoute) {
|
|
1240
|
-
logger.debug(
|
|
1241
|
-
`[Agent] Auto-transitioning from pending transition to route: ${targetRoute.title}`
|
|
1242
|
-
);
|
|
1243
|
-
// Clear pending transition and enter new route
|
|
1244
|
-
session = {
|
|
1245
|
-
...session,
|
|
1246
|
-
pendingTransition: undefined,
|
|
1247
|
-
};
|
|
1248
|
-
session = enterRoute(session, targetRoute.id, targetRoute.title);
|
|
1249
|
-
|
|
1250
|
-
// Merge initial data if available
|
|
1251
|
-
if (targetRoute.initialData) {
|
|
1252
|
-
session = mergeCollected(session, targetRoute.initialData);
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
selectedRoute = targetRoute;
|
|
1256
|
-
} else {
|
|
1257
|
-
logger.warn(
|
|
1258
|
-
`[Agent] Pending transition target route not found: ${session.pendingTransition.targetRouteId}`
|
|
1259
|
-
);
|
|
1260
|
-
// Clear invalid transition
|
|
1261
|
-
session = {
|
|
1262
|
-
...session,
|
|
1263
|
-
pendingTransition: undefined,
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
// If no pending transition or transition handled, do normal routing
|
|
1269
|
-
if (this.routes.length > 0 && !selectedRoute) {
|
|
1270
|
-
const orchestration = await this.routingEngine.decideRouteAndStep({
|
|
1271
|
-
routes: this.routes,
|
|
1272
|
-
session,
|
|
1273
|
-
history,
|
|
1274
|
-
agentOptions: this.options,
|
|
1275
|
-
provider: this.options.provider,
|
|
1276
|
-
context: effectiveContext,
|
|
1277
|
-
signal,
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
selectedRoute = orchestration.selectedRoute;
|
|
1281
|
-
selectedStep = orchestration.selectedStep;
|
|
1282
|
-
responseDirectives = orchestration.responseDirectives;
|
|
1283
|
-
session = orchestration.session;
|
|
1284
|
-
isRouteComplete = orchestration.isRouteComplete || false;
|
|
1285
|
-
|
|
1286
|
-
// Log if route is complete
|
|
1287
|
-
if (isRouteComplete) {
|
|
1288
|
-
logger.debug(
|
|
1289
|
-
`[Agent] Route complete: all required data collected, END_ROUTE reached`
|
|
1290
|
-
);
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
// PHASE 3: DETERMINE NEXT STEP - Use step from combined decision or get initial step
|
|
1295
|
-
let message: string;
|
|
1296
|
-
let toolCalls:
|
|
1297
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
1298
|
-
| undefined = undefined;
|
|
1299
|
-
let responsePrompt: string;
|
|
1300
|
-
let availableTools: Array<{
|
|
1301
|
-
id: string;
|
|
1302
|
-
name: string;
|
|
1303
|
-
description?: string;
|
|
1304
|
-
parameters?: unknown;
|
|
1305
|
-
}> = [];
|
|
1306
|
-
let responseSchema: StructuredSchema | undefined;
|
|
1307
|
-
let nextStep: Step<TContext, TData> | undefined;
|
|
1308
|
-
|
|
1309
|
-
// Get last user message (needed for both route and completion handling)
|
|
1310
|
-
const lastUserMessage = getLastMessageFromHistory(history);
|
|
1311
|
-
|
|
1312
|
-
if (selectedRoute && !isRouteComplete) {
|
|
1313
|
-
// If we have a selected step from the combined routing decision, use it
|
|
1314
|
-
if (selectedStep) {
|
|
1315
|
-
nextStep = selectedStep;
|
|
1316
|
-
} else {
|
|
1317
|
-
// New route or no step selected - get initial step or first valid step
|
|
1318
|
-
const candidates = this.routingEngine.getCandidateSteps(
|
|
1319
|
-
selectedRoute,
|
|
1320
|
-
undefined,
|
|
1321
|
-
session.data || {}
|
|
1322
|
-
);
|
|
1323
|
-
if (candidates.length > 0) {
|
|
1324
|
-
nextStep = candidates[0].step;
|
|
1325
|
-
logger.debug(
|
|
1326
|
-
`[Agent] Using first valid step: ${nextStep.id} for new route`
|
|
1327
|
-
);
|
|
1328
|
-
} else {
|
|
1329
|
-
// Fallback to initial step even if it should be skipped
|
|
1330
|
-
nextStep = selectedRoute.initialStep;
|
|
1331
|
-
logger.warn(
|
|
1332
|
-
`[Agent] No valid steps found, using initial step: ${nextStep.id}`
|
|
1333
|
-
);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// Update session with next step
|
|
1338
|
-
session = enterStep(session, nextStep.id, nextStep.description);
|
|
1339
|
-
logger.debug(`[Agent] Entered step: ${nextStep.id}`);
|
|
1340
|
-
|
|
1341
|
-
// PHASE 4: RESPONSE GENERATION - Generate message using selected route and step
|
|
1342
|
-
// Get last user message
|
|
1343
|
-
const lastUserMessage = getLastMessageFromHistory(history);
|
|
1344
|
-
|
|
1345
|
-
// Build response schema for this route (with collect fields from step)
|
|
1346
|
-
responseSchema = this.responseEngine.responseSchemaForRoute(
|
|
1347
|
-
selectedRoute,
|
|
1348
|
-
nextStep,
|
|
1349
|
-
this.schema
|
|
1350
|
-
);
|
|
1351
|
-
|
|
1352
|
-
// Build response prompt
|
|
1353
|
-
responsePrompt = await this.responseEngine.buildResponsePrompt({
|
|
1354
|
-
route: selectedRoute,
|
|
1355
|
-
currentStep: nextStep,
|
|
1356
|
-
rules: selectedRoute.getRules(),
|
|
1357
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1358
|
-
directives: responseDirectives,
|
|
1359
|
-
history,
|
|
1360
|
-
lastMessage: lastUserMessage,
|
|
1361
|
-
agentOptions: this.options,
|
|
1362
|
-
// Combine agent and route properties according to the specified logic
|
|
1363
|
-
combinedGuidelines: [
|
|
1364
|
-
...this.getGuidelines(),
|
|
1365
|
-
...selectedRoute.getGuidelines(),
|
|
1366
|
-
],
|
|
1367
|
-
combinedTerms: this.mergeTerms(
|
|
1368
|
-
this.getTerms(),
|
|
1369
|
-
selectedRoute.getTerms()
|
|
1370
|
-
),
|
|
1371
|
-
context: effectiveContext,
|
|
1372
|
-
session,
|
|
1373
|
-
agentSchema: this.schema,
|
|
1374
|
-
});
|
|
1375
|
-
|
|
1376
|
-
// Collect available tools for AI
|
|
1377
|
-
availableTools = this.collectAvailableTools(
|
|
1378
|
-
selectedRoute,
|
|
1379
|
-
nextStep
|
|
1380
|
-
);
|
|
1381
|
-
} else {
|
|
1382
|
-
// No route selected - generate basic response without route context
|
|
1383
|
-
logger.debug(`[Agent] No route selected, generating basic response`);
|
|
1384
|
-
|
|
1385
|
-
// Build basic response prompt without route context
|
|
1386
|
-
responsePrompt = await this.responseEngine.buildFallbackPrompt({
|
|
1387
|
-
history,
|
|
1388
|
-
agentOptions: this.options,
|
|
1389
|
-
terms: this.getTerms(),
|
|
1390
|
-
guidelines: this.getGuidelines(),
|
|
1391
|
-
context: effectiveContext,
|
|
1392
|
-
session,
|
|
1393
|
-
});
|
|
1394
|
-
|
|
1395
|
-
// Use agent-level tools only
|
|
1396
|
-
availableTools = this.collectAvailableTools();
|
|
1397
|
-
responseSchema = undefined;
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
// Generate message using AI provider (common for both route and no-route cases)
|
|
1401
|
-
const result = await this.options.provider.generateMessage({
|
|
1402
|
-
prompt: responsePrompt,
|
|
1403
|
-
history,
|
|
1404
|
-
context: effectiveContext,
|
|
1405
|
-
tools: availableTools,
|
|
1406
|
-
signal,
|
|
1407
|
-
parameters: responseSchema
|
|
1408
|
-
? {
|
|
1409
|
-
jsonSchema: responseSchema,
|
|
1410
|
-
schemaName: "response_output",
|
|
1411
|
-
}
|
|
1412
|
-
: undefined,
|
|
1413
|
-
});
|
|
1414
|
-
|
|
1415
|
-
message = result.structured?.message || result.message;
|
|
1416
|
-
|
|
1417
|
-
// Process dynamic tool calls from AI response (common for both route and no-route cases)
|
|
1418
|
-
if (result.structured?.toolCalls) {
|
|
1419
|
-
toolCalls = result.structured.toolCalls;
|
|
1420
|
-
|
|
1421
|
-
// Execute dynamic tool calls
|
|
1422
|
-
if (toolCalls.length > 0) {
|
|
1423
|
-
logger.debug(
|
|
1424
|
-
`[Agent] Executing ${toolCalls.length} dynamic tool calls`
|
|
1425
|
-
);
|
|
1426
|
-
|
|
1427
|
-
for (const toolCall of toolCalls) {
|
|
1428
|
-
const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
|
|
1429
|
-
if (!tool) {
|
|
1430
|
-
logger.warn(`[Agent] Tool not found: ${toolCall.toolName}`);
|
|
1431
|
-
continue;
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
const toolExecutor = new ToolExecutor<TContext, TData>();
|
|
1435
|
-
const toolResult = await toolExecutor.executeTool({
|
|
1436
|
-
tool: tool,
|
|
1437
|
-
context: effectiveContext,
|
|
1438
|
-
updateContext: this.updateContext.bind(this),
|
|
1439
|
-
updateData: this.updateCollectedData.bind(this),
|
|
1440
|
-
history,
|
|
1441
|
-
data: session.data,
|
|
1442
|
-
toolArguments: toolCall.arguments,
|
|
1443
|
-
});
|
|
1444
|
-
|
|
1445
|
-
// Update context with tool results
|
|
1446
|
-
if (toolResult.contextUpdate) {
|
|
1447
|
-
await this.updateContext(
|
|
1448
|
-
toolResult.contextUpdate as Partial<TContext>
|
|
1449
|
-
);
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// Update collected data with tool results
|
|
1453
|
-
if (toolResult.dataUpdate) {
|
|
1454
|
-
session = await this.updateData(session, toolResult.dataUpdate as Partial<TData>);
|
|
1455
|
-
logger.debug(
|
|
1456
|
-
`[Agent] Tool updated collected data:`,
|
|
1457
|
-
toolResult.dataUpdate
|
|
1458
|
-
);
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
logger.debug(
|
|
1462
|
-
`[Agent] Executed dynamic tool: ${toolResult.toolName} (success: ${toolResult.success})`
|
|
1463
|
-
);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
// TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution
|
|
1469
|
-
const MAX_TOOL_LOOPS = 5;
|
|
1470
|
-
let toolLoopCount = 0;
|
|
1471
|
-
let hasToolCalls = toolCalls && toolCalls.length > 0;
|
|
1472
|
-
|
|
1473
|
-
while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
|
|
1474
|
-
toolLoopCount++;
|
|
1475
|
-
logger.debug(
|
|
1476
|
-
`[Agent] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`
|
|
1477
|
-
);
|
|
1478
|
-
|
|
1479
|
-
// Add tool execution results to history so AI knows what happened
|
|
1480
|
-
const toolResultsEvents: Event[] = [];
|
|
1481
|
-
for (const toolCall of toolCalls || []) {
|
|
1482
|
-
const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
|
|
1483
|
-
if (tool) {
|
|
1484
|
-
toolResultsEvents.push({
|
|
1485
|
-
kind: EventKind.TOOL,
|
|
1486
|
-
source: MessageRole.AGENT,
|
|
1487
|
-
timestamp: new Date().toISOString(),
|
|
1488
|
-
data: {
|
|
1489
|
-
tool_calls: [
|
|
1490
|
-
{
|
|
1491
|
-
tool_id: toolCall.toolName,
|
|
1492
|
-
arguments: toolCall.arguments,
|
|
1493
|
-
result: {
|
|
1494
|
-
data: "Tool executed successfully",
|
|
1495
|
-
},
|
|
1496
|
-
},
|
|
1497
|
-
],
|
|
1498
|
-
},
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
// Create updated history with tool results
|
|
1504
|
-
const updatedHistory = [...history, ...toolResultsEvents];
|
|
1505
|
-
|
|
1506
|
-
// Make follow-up AI call to see if more tools are needed
|
|
1507
|
-
const followUpResult = await this.options.provider.generateMessage({
|
|
1508
|
-
prompt: responsePrompt,
|
|
1509
|
-
history: updatedHistory,
|
|
1510
|
-
context: effectiveContext,
|
|
1511
|
-
tools: availableTools,
|
|
1512
|
-
parameters: {
|
|
1513
|
-
jsonSchema: responseSchema as StructuredSchema,
|
|
1514
|
-
schemaName: "tool_followup",
|
|
1515
|
-
},
|
|
1516
|
-
signal,
|
|
1517
|
-
});
|
|
1518
|
-
|
|
1519
|
-
// Check if follow-up call has more tool calls
|
|
1520
|
-
const followUpToolCalls = followUpResult.structured?.toolCalls;
|
|
1521
|
-
hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
|
|
1522
|
-
|
|
1523
|
-
if (hasToolCalls) {
|
|
1524
|
-
logger.debug(
|
|
1525
|
-
`[Agent] Follow-up call produced ${followUpToolCalls!.length
|
|
1526
|
-
} additional tool calls`
|
|
1527
|
-
);
|
|
1528
|
-
|
|
1529
|
-
// Execute the follow-up tool calls
|
|
1530
|
-
for (const toolCall of followUpToolCalls!) {
|
|
1531
|
-
const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
|
|
1532
|
-
if (!tool) {
|
|
1533
|
-
logger.warn(
|
|
1534
|
-
`[Agent] Tool not found in follow-up: ${toolCall.toolName}`
|
|
1535
|
-
);
|
|
1536
|
-
continue;
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
const toolExecutor = new ToolExecutor<TContext, TData>();
|
|
1540
|
-
const toolResult = await toolExecutor.executeTool({
|
|
1541
|
-
tool: tool,
|
|
1542
|
-
context: effectiveContext,
|
|
1543
|
-
updateContext: this.updateContext.bind(this),
|
|
1544
|
-
updateData: this.updateCollectedData.bind(this),
|
|
1545
|
-
history: updatedHistory,
|
|
1546
|
-
data: session.data,
|
|
1547
|
-
toolArguments: toolCall.arguments,
|
|
1548
|
-
});
|
|
1549
|
-
|
|
1550
|
-
// Update context with follow-up tool results
|
|
1551
|
-
if (toolResult.contextUpdate) {
|
|
1552
|
-
await this.updateContext(
|
|
1553
|
-
toolResult.contextUpdate as Partial<TContext>
|
|
1554
|
-
);
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
if (toolResult.dataUpdate) {
|
|
1558
|
-
session = await this.updateData(session, toolResult.dataUpdate as Partial<TData>);
|
|
1559
|
-
logger.debug(
|
|
1560
|
-
`[Agent] Follow-up tool updated collected data:`,
|
|
1561
|
-
toolResult.dataUpdate
|
|
1562
|
-
);
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
logger.debug(
|
|
1566
|
-
`[Agent] Executed follow-up tool: ${toolResult.toolName} (success: ${toolResult.success})`
|
|
1567
|
-
);
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
// Update toolCalls for next iteration or final response
|
|
1571
|
-
toolCalls = followUpToolCalls;
|
|
1572
|
-
} else {
|
|
1573
|
-
logger.debug(
|
|
1574
|
-
`[Agent] Tool loop completed after ${toolLoopCount} iterations`
|
|
1575
|
-
);
|
|
1576
|
-
// Update final message and toolCalls from follow-up result if no more tools
|
|
1577
|
-
message = followUpResult.structured?.message || followUpResult.message;
|
|
1578
|
-
toolCalls = followUpToolCalls || [];
|
|
1579
|
-
break;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
1584
|
-
logger.warn(
|
|
1585
|
-
`[Agent] Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`
|
|
1586
|
-
);
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
// Extract collected data from final response (only for route-based interactions)
|
|
1590
|
-
if (selectedRoute && result.structured && nextStep?.collect) {
|
|
1591
|
-
const collectedData: Record<string, unknown> = {};
|
|
1592
|
-
// The structured response includes both base fields and collected extraction fields
|
|
1593
|
-
const structuredData = result.structured as AgentStructuredResponse &
|
|
1594
|
-
Record<string, unknown>;
|
|
1595
|
-
|
|
1596
|
-
for (const field of nextStep.collect) {
|
|
1597
|
-
const fieldKey = String(field);
|
|
1598
|
-
if (fieldKey in structuredData) {
|
|
1599
|
-
collectedData[fieldKey] = structuredData[fieldKey];
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// Merge collected data into session using agent-level data validation
|
|
1604
|
-
if (Object.keys(collectedData).length > 0) {
|
|
1605
|
-
// Update agent-level collected data with validation
|
|
1606
|
-
await this.updateCollectedData(collectedData as Partial<TData>);
|
|
1607
|
-
|
|
1608
|
-
// Update session with validated data
|
|
1609
|
-
session = await this.updateData(session, collectedData as Partial<TData>);
|
|
1610
|
-
logger.debug(`[Agent] Collected data:`, collectedData);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
// Extract any additional data from structured response
|
|
1615
|
-
if (
|
|
1616
|
-
result.structured &&
|
|
1617
|
-
typeof result.structured === "object" &&
|
|
1618
|
-
"contextUpdate" in result.structured
|
|
1619
|
-
) {
|
|
1620
|
-
await this.updateContext(
|
|
1621
|
-
(result.structured as { contextUpdate?: Partial<TContext> })
|
|
1622
|
-
.contextUpdate as Partial<TContext>
|
|
1623
|
-
);
|
|
1624
|
-
}
|
|
612
|
+
async respond(params: RespondParams<TContext, TData>): Promise<AgentResponse<TData>> {
|
|
613
|
+
// Delegate to ResponseModal
|
|
614
|
+
return this.responseModal.respond(params);
|
|
615
|
+
}
|
|
1625
616
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
// Create a temporary step for completion message generation using endStep configuration
|
|
1634
|
-
const completionStep = new Step<TContext, TData>(selectedRoute!.id, {
|
|
1635
|
-
description: endStepSpec.description,
|
|
1636
|
-
id: endStepSpec.id || END_ROUTE_ID,
|
|
1637
|
-
collect: endStepSpec.collect,
|
|
1638
|
-
requires: endStepSpec.requires,
|
|
1639
|
-
prompt:
|
|
1640
|
-
endStepSpec.prompt ||
|
|
1641
|
-
"Summarize what was accomplished and confirm completion based on the conversation history and collected data",
|
|
1642
|
-
});
|
|
617
|
+
/**
|
|
618
|
+
* Get all routes
|
|
619
|
+
*/
|
|
620
|
+
getRoutes(): Route<TContext, TData>[] {
|
|
621
|
+
return [...this.routes];
|
|
622
|
+
}
|
|
1643
623
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
624
|
+
/**
|
|
625
|
+
* Get agent options
|
|
626
|
+
* @internal Used by ResponseModal
|
|
627
|
+
*/
|
|
628
|
+
getAgentOptions(): AgentOptions<TContext, TData> {
|
|
629
|
+
return this.options;
|
|
630
|
+
}
|
|
1647
631
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
context: effectiveContext,
|
|
1656
|
-
session,
|
|
1657
|
-
history,
|
|
1658
|
-
};
|
|
1659
|
-
|
|
1660
|
-
// Build completion response prompt
|
|
1661
|
-
const completionPrompt = await this.responseEngine.buildResponsePrompt({
|
|
1662
|
-
route: selectedRoute,
|
|
1663
|
-
currentStep: completionStep,
|
|
1664
|
-
rules: selectedRoute.getRules(),
|
|
1665
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1666
|
-
directives: undefined, // No directives for completion
|
|
1667
|
-
history,
|
|
1668
|
-
lastMessage: lastUserMessage,
|
|
1669
|
-
agentOptions: this.options,
|
|
1670
|
-
// Combine agent and route properties according to the specified logic
|
|
1671
|
-
combinedGuidelines: [
|
|
1672
|
-
...this.getGuidelines(),
|
|
1673
|
-
...selectedRoute.getGuidelines(),
|
|
1674
|
-
],
|
|
1675
|
-
combinedTerms: this.mergeTerms(
|
|
1676
|
-
this.getTerms(),
|
|
1677
|
-
selectedRoute.getTerms()
|
|
1678
|
-
),
|
|
1679
|
-
context: effectiveContext,
|
|
1680
|
-
session,
|
|
1681
|
-
agentSchema: this.schema,
|
|
1682
|
-
});
|
|
632
|
+
/**
|
|
633
|
+
* Get routing engine
|
|
634
|
+
* @internal Used by ResponseModal
|
|
635
|
+
*/
|
|
636
|
+
getRoutingEngine(): RoutingEngine<TContext, TData> {
|
|
637
|
+
return this.routingEngine;
|
|
638
|
+
}
|
|
1683
639
|
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
jsonSchema: responseSchema,
|
|
1692
|
-
schemaName: "completion_message",
|
|
1693
|
-
},
|
|
1694
|
-
});
|
|
640
|
+
/**
|
|
641
|
+
* Get the updateData method bound to this agent
|
|
642
|
+
* @internal Used by ResponseModal
|
|
643
|
+
*/
|
|
644
|
+
getUpdateDataMethod(): (session: SessionState<TData>, dataUpdate: Partial<TData>) => Promise<SessionState<TData>> {
|
|
645
|
+
return this.updateData.bind(this);
|
|
646
|
+
}
|
|
1695
647
|
|
|
1696
|
-
message =
|
|
1697
|
-
completionResult.structured?.message || completionResult.message;
|
|
1698
|
-
logger.debug(
|
|
1699
|
-
`[Agent] Generated completion message for route: ${selectedRoute.title}`
|
|
1700
|
-
);
|
|
1701
648
|
|
|
1702
|
-
// Check for onComplete transition
|
|
1703
|
-
const transitionConfig = await selectedRoute.evaluateOnComplete(
|
|
1704
|
-
{ data: session.data },
|
|
1705
|
-
effectiveContext
|
|
1706
|
-
);
|
|
1707
649
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
);
|
|
650
|
+
/**
|
|
651
|
+
* Get all terms
|
|
652
|
+
*/
|
|
653
|
+
getTerms(): Term<TContext, TData>[] {
|
|
654
|
+
return [...this.terms];
|
|
655
|
+
}
|
|
1715
656
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
session = {
|
|
1723
|
-
...session,
|
|
1724
|
-
pendingTransition: {
|
|
1725
|
-
targetRouteId: targetRoute.id,
|
|
1726
|
-
condition: renderedCondition,
|
|
1727
|
-
reason: "route_complete",
|
|
1728
|
-
},
|
|
1729
|
-
};
|
|
1730
|
-
logger.debug(
|
|
1731
|
-
`[Agent] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`
|
|
1732
|
-
);
|
|
1733
|
-
} else {
|
|
1734
|
-
logger.warn(
|
|
1735
|
-
`[Agent] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`
|
|
1736
|
-
);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
657
|
+
/**
|
|
658
|
+
* Get all tools
|
|
659
|
+
*/
|
|
660
|
+
getTools(): Tool<TContext, TData>[] {
|
|
661
|
+
return [...this.tools];
|
|
662
|
+
}
|
|
1739
663
|
|
|
1740
|
-
// Set step to END_ROUTE marker
|
|
1741
|
-
session = enterStep(session, END_ROUTE_ID, "Route completed");
|
|
1742
|
-
logger.debug(
|
|
1743
|
-
`[Agent] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`
|
|
1744
|
-
);
|
|
1745
|
-
} else {
|
|
1746
|
-
// Fallback: No routes defined, generate a simple response
|
|
1747
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
1748
|
-
history,
|
|
1749
|
-
agentOptions: this.options,
|
|
1750
|
-
terms: this.terms,
|
|
1751
|
-
guidelines: this.guidelines,
|
|
1752
|
-
context: effectiveContext,
|
|
1753
|
-
session,
|
|
1754
|
-
});
|
|
1755
664
|
|
|
1756
|
-
const result = await this.options.provider.generateMessage({
|
|
1757
|
-
prompt: fallbackPrompt,
|
|
1758
|
-
history,
|
|
1759
|
-
context: effectiveContext,
|
|
1760
|
-
signal,
|
|
1761
|
-
parameters: {
|
|
1762
|
-
jsonSchema: {
|
|
1763
|
-
type: "object",
|
|
1764
|
-
properties: {
|
|
1765
|
-
message: { type: "string" },
|
|
1766
|
-
},
|
|
1767
|
-
required: ["message"],
|
|
1768
|
-
additionalProperties: false,
|
|
1769
|
-
},
|
|
1770
|
-
schemaName: "fallback_response",
|
|
1771
|
-
},
|
|
1772
|
-
});
|
|
1773
665
|
|
|
1774
|
-
message = result.structured?.message || result.message;
|
|
1775
|
-
}
|
|
1776
666
|
|
|
1777
|
-
// Auto-save session step to persistence if configured
|
|
1778
|
-
if (
|
|
1779
|
-
this.persistenceManager &&
|
|
1780
|
-
session.id &&
|
|
1781
|
-
this.options.persistence?.autoSave !== false
|
|
1782
|
-
) {
|
|
1783
|
-
await this.persistenceManager.saveSessionState(session.id, session);
|
|
1784
|
-
logger.debug(
|
|
1785
|
-
`[Agent] Auto-saved session step to persistence: ${session.id}`
|
|
1786
|
-
);
|
|
1787
|
-
}
|
|
1788
667
|
|
|
1789
|
-
// Execute finalize function
|
|
1790
|
-
if (session.currentRoute && session.currentStep) {
|
|
1791
|
-
const currentRoute = this.routes.find(
|
|
1792
|
-
(r) => r.id === session.currentRoute?.id
|
|
1793
|
-
);
|
|
1794
|
-
if (currentRoute) {
|
|
1795
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
1796
|
-
if (currentStep?.finalize) {
|
|
1797
|
-
logger.debug(
|
|
1798
|
-
`[Agent] Executing finalize for step: ${currentStep.id}`
|
|
1799
|
-
);
|
|
1800
|
-
await this.executePrepareFinalize(
|
|
1801
|
-
currentStep.finalize,
|
|
1802
|
-
effectiveContext,
|
|
1803
|
-
session.data,
|
|
1804
|
-
currentRoute,
|
|
1805
|
-
currentStep
|
|
1806
|
-
);
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
668
|
|
|
1811
|
-
// Update current session if we have one
|
|
1812
|
-
if (this.currentSession) {
|
|
1813
|
-
this.currentSession = session;
|
|
1814
|
-
}
|
|
1815
669
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
};
|
|
670
|
+
/**
|
|
671
|
+
* Get all guidelines
|
|
672
|
+
*/
|
|
673
|
+
getGuidelines(): Guideline<TContext, TData>[] {
|
|
674
|
+
return [...this.guidelines];
|
|
1822
675
|
}
|
|
1823
676
|
|
|
1824
677
|
/**
|
|
1825
|
-
* Get
|
|
678
|
+
* Get the agent's knowledge base
|
|
1826
679
|
*/
|
|
1827
|
-
|
|
1828
|
-
return
|
|
680
|
+
getKnowledgeBase(): Record<string, unknown> {
|
|
681
|
+
return { ...this.knowledgeBase };
|
|
1829
682
|
}
|
|
1830
683
|
|
|
684
|
+
|
|
685
|
+
|
|
1831
686
|
/**
|
|
1832
|
-
* Get
|
|
687
|
+
* Get the persistence manager (if configured)
|
|
1833
688
|
*/
|
|
1834
|
-
|
|
1835
|
-
return
|
|
689
|
+
getPersistenceManager(): PersistenceManager<TData> | undefined {
|
|
690
|
+
return this.persistenceManager;
|
|
1836
691
|
}
|
|
1837
692
|
|
|
1838
693
|
/**
|
|
1839
|
-
*
|
|
694
|
+
* Check if persistence is enabled
|
|
1840
695
|
*/
|
|
1841
|
-
|
|
1842
|
-
return
|
|
696
|
+
hasPersistence(): boolean {
|
|
697
|
+
return this.persistenceManager !== undefined;
|
|
1843
698
|
}
|
|
1844
699
|
|
|
1845
700
|
/**
|
|
1846
|
-
*
|
|
1847
|
-
*
|
|
1848
|
-
* @private
|
|
701
|
+
* Set the current session for convenience methods
|
|
702
|
+
* @param session - Session step to use for subsequent calls
|
|
1849
703
|
*/
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
route?: Route<TContext, TData>
|
|
1853
|
-
): Tool<TContext, TData, unknown[], unknown> | undefined {
|
|
1854
|
-
// Check route-level tools first (if route provided)
|
|
1855
|
-
if (route) {
|
|
1856
|
-
const routeTool = route
|
|
1857
|
-
.getTools()
|
|
1858
|
-
.find((tool) => tool.id === toolName || tool.name === toolName);
|
|
1859
|
-
if (routeTool) return routeTool;
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
// Fall back to agent-level tools
|
|
1863
|
-
return this.tools.find(
|
|
1864
|
-
(tool) => tool.id === toolName || tool.name === toolName
|
|
1865
|
-
);
|
|
704
|
+
setCurrentSession(session: SessionState): void {
|
|
705
|
+
this.currentSession = session;
|
|
1866
706
|
}
|
|
1867
707
|
|
|
1868
708
|
/**
|
|
1869
|
-
*
|
|
1870
|
-
* @private
|
|
709
|
+
* Get the current session (if set)
|
|
1871
710
|
*/
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
step?: Step<TContext, TData>
|
|
1875
|
-
): Array<{
|
|
1876
|
-
id: string;
|
|
1877
|
-
name: string;
|
|
1878
|
-
description?: string;
|
|
1879
|
-
parameters?: unknown;
|
|
1880
|
-
}> {
|
|
1881
|
-
const availableTools = new Map<
|
|
1882
|
-
string,
|
|
1883
|
-
Tool<TContext, TData, unknown[], unknown>
|
|
1884
|
-
>();
|
|
1885
|
-
|
|
1886
|
-
// Add agent-level tools
|
|
1887
|
-
this.tools.forEach((tool) => {
|
|
1888
|
-
availableTools.set(tool.id, tool);
|
|
1889
|
-
});
|
|
1890
|
-
|
|
1891
|
-
// Add route-level tools (these take precedence)
|
|
1892
|
-
if (route) {
|
|
1893
|
-
route.getTools().forEach((tool) => {
|
|
1894
|
-
availableTools.set(tool.id, tool);
|
|
1895
|
-
});
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
// Filter by step-level allowed tools if specified
|
|
1899
|
-
if (step?.tools) {
|
|
1900
|
-
const allowedToolIds = new Set<string>();
|
|
1901
|
-
const stepTools: Tool<TContext, TData, unknown[], unknown>[] = [];
|
|
1902
|
-
|
|
1903
|
-
for (const toolRef of step.tools) {
|
|
1904
|
-
if (typeof toolRef === "string") {
|
|
1905
|
-
// Reference to registered tool
|
|
1906
|
-
allowedToolIds.add(toolRef);
|
|
1907
|
-
} else {
|
|
1908
|
-
// Inline tool definition
|
|
1909
|
-
if (toolRef.id) {
|
|
1910
|
-
allowedToolIds.add(toolRef.id);
|
|
1911
|
-
stepTools.push(toolRef);
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
// If step specifies tools, only include those
|
|
1917
|
-
if (allowedToolIds.size > 0) {
|
|
1918
|
-
const filteredTools = new Map<
|
|
1919
|
-
string,
|
|
1920
|
-
Tool<TContext, TData, unknown[], unknown>
|
|
1921
|
-
>();
|
|
1922
|
-
for (const toolId of allowedToolIds) {
|
|
1923
|
-
const tool = availableTools.get(toolId);
|
|
1924
|
-
if (tool) {
|
|
1925
|
-
filteredTools.set(toolId, tool);
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
// Add inline tools
|
|
1929
|
-
stepTools.forEach((tool) => {
|
|
1930
|
-
if (tool.id) {
|
|
1931
|
-
filteredTools.set(tool.id, tool);
|
|
1932
|
-
}
|
|
1933
|
-
});
|
|
1934
|
-
availableTools.clear();
|
|
1935
|
-
filteredTools.forEach((tool, id) => availableTools.set(id, tool));
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
// Convert to the format expected by AI providers
|
|
1940
|
-
return Array.from(availableTools.values()).map((tool) => ({
|
|
1941
|
-
id: tool.id,
|
|
1942
|
-
name: tool.name || tool.id,
|
|
1943
|
-
description: tool.description,
|
|
1944
|
-
parameters: tool.parameters,
|
|
1945
|
-
}));
|
|
711
|
+
getCurrentSession(): SessionState | undefined {
|
|
712
|
+
return this.currentSession;
|
|
1946
713
|
}
|
|
1947
714
|
|
|
1948
715
|
/**
|
|
1949
716
|
* Execute a prepare or finalize function/tool
|
|
1950
|
-
* @
|
|
717
|
+
* @internal Used by ResponseModal
|
|
1951
718
|
*/
|
|
1952
|
-
|
|
719
|
+
async executePrepareFinalize(
|
|
1953
720
|
prepareOrFinalize:
|
|
1954
721
|
| string
|
|
1955
|
-
| Tool<TContext, TData
|
|
722
|
+
| Tool<TContext, TData>
|
|
1956
723
|
| ((context: TContext, data?: Partial<TData>) => void | Promise<void>)
|
|
1957
724
|
| undefined,
|
|
1958
725
|
context: TContext,
|
|
@@ -1967,47 +734,24 @@ export class Agent<TContext = any, TData = any> {
|
|
|
1967
734
|
await prepareOrFinalize(context, data);
|
|
1968
735
|
} else {
|
|
1969
736
|
// It's a tool reference - find and execute the tool
|
|
1970
|
-
let tool: Tool<TContext, TData
|
|
737
|
+
let tool: Tool<TContext, TData> | undefined;
|
|
1971
738
|
|
|
1972
739
|
if (typeof prepareOrFinalize === "string") {
|
|
1973
|
-
// Tool ID - find it
|
|
1974
|
-
|
|
1975
|
-
string,
|
|
1976
|
-
Tool<TContext, TData, unknown[], unknown>
|
|
1977
|
-
>();
|
|
1978
|
-
|
|
1979
|
-
// Add agent-level tools
|
|
1980
|
-
this.tools.forEach((t) => {
|
|
1981
|
-
availableTools.set(t.id, t);
|
|
1982
|
-
});
|
|
1983
|
-
|
|
1984
|
-
// Add route-level tools
|
|
1985
|
-
if (route) {
|
|
1986
|
-
route.getTools().forEach((t) => {
|
|
1987
|
-
availableTools.set(t.id, t);
|
|
1988
|
-
});
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
// Add step-level tools
|
|
1992
|
-
if (step?.tools) {
|
|
1993
|
-
for (const toolRef of step.tools) {
|
|
1994
|
-
if (typeof toolRef === "string") {
|
|
1995
|
-
// Keep as is
|
|
1996
|
-
} else if (toolRef.id) {
|
|
1997
|
-
availableTools.set(toolRef.id, toolRef);
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
tool = availableTools.get(prepareOrFinalize);
|
|
740
|
+
// Tool ID - use ToolManager to find it across all scopes
|
|
741
|
+
tool = this.tool.find(prepareOrFinalize, undefined, step, route);
|
|
2003
742
|
} else {
|
|
2004
|
-
// Tool object -
|
|
2005
|
-
|
|
743
|
+
// Tool object - validate it has required properties
|
|
744
|
+
if (prepareOrFinalize.id && typeof prepareOrFinalize.handler === 'function') {
|
|
745
|
+
tool = prepareOrFinalize;
|
|
746
|
+
} else {
|
|
747
|
+
logger.error(`[Agent] Invalid tool object for prepare/finalize: missing id or invalid handler`);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
2006
750
|
}
|
|
2007
751
|
|
|
2008
752
|
if (tool) {
|
|
2009
|
-
|
|
2010
|
-
const result = await
|
|
753
|
+
// Use ToolManager for execution
|
|
754
|
+
const result = await this.tool.executeTool({
|
|
2011
755
|
tool,
|
|
2012
756
|
context,
|
|
2013
757
|
updateContext: this.updateContext.bind(this),
|
|
@@ -2034,80 +778,22 @@ export class Agent<TContext = any, TData = any> {
|
|
|
2034
778
|
}
|
|
2035
779
|
|
|
2036
780
|
/**
|
|
2037
|
-
*
|
|
2038
|
-
*/
|
|
2039
|
-
getGuidelines(): Guideline<TContext, TData>[] {
|
|
2040
|
-
return [...this.guidelines];
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
/**
|
|
2044
|
-
* Get the agent's knowledge base
|
|
2045
|
-
*/
|
|
2046
|
-
getKnowledgeBase(): Record<string, unknown> {
|
|
2047
|
-
return { ...this.knowledgeBase };
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
/**
|
|
2051
|
-
* Merge terms with route-specific taking precedence on conflicts
|
|
2052
|
-
* @private
|
|
2053
|
-
*/
|
|
2054
|
-
private mergeTerms(
|
|
2055
|
-
agentTerms: Term<TContext, TData>[],
|
|
2056
|
-
routeTerms: Term<TContext, TData>[]
|
|
2057
|
-
): Term<TContext, TData>[] {
|
|
2058
|
-
const merged = new Map<string, Term<TContext, TData>>();
|
|
2059
|
-
|
|
2060
|
-
// Add agent terms first
|
|
2061
|
-
agentTerms.forEach((term) => {
|
|
2062
|
-
const name =
|
|
2063
|
-
typeof term.name === "string" ? term.name : term.name.toString();
|
|
2064
|
-
merged.set(name, term);
|
|
2065
|
-
});
|
|
2066
|
-
|
|
2067
|
-
// Add route terms (these take precedence)
|
|
2068
|
-
routeTerms.forEach((term) => {
|
|
2069
|
-
const name =
|
|
2070
|
-
typeof term.name === "string" ? term.name : term.name.toString();
|
|
2071
|
-
merged.set(name, term);
|
|
2072
|
-
});
|
|
2073
|
-
|
|
2074
|
-
return Array.from(merged.values());
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
/**
|
|
2078
|
-
* Get the persistence manager (if configured)
|
|
2079
|
-
*/
|
|
2080
|
-
getPersistenceManager(): PersistenceManager<TData> | undefined {
|
|
2081
|
-
return this.persistenceManager;
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
/**
|
|
2085
|
-
* Check if persistence is enabled
|
|
2086
|
-
*/
|
|
2087
|
-
hasPersistence(): boolean {
|
|
2088
|
-
return this.persistenceManager !== undefined;
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
/**
|
|
2092
|
-
* Set the current session for convenience methods
|
|
2093
|
-
* @param session - Session step to use for subsequent calls
|
|
2094
|
-
*/
|
|
2095
|
-
setCurrentSession(session: SessionState): void {
|
|
2096
|
-
this.currentSession = session;
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
/**
|
|
2100
|
-
* Get the current session (if set)
|
|
781
|
+
* Clear the current session
|
|
2101
782
|
*/
|
|
2102
|
-
|
|
2103
|
-
|
|
783
|
+
clearCurrentSession(): void {
|
|
784
|
+
this.currentSession = undefined;
|
|
2104
785
|
}
|
|
2105
786
|
|
|
2106
787
|
/**
|
|
2107
|
-
*
|
|
788
|
+
* Sync session data to agent collected data
|
|
789
|
+
* @internal Used to keep agent and session data in sync
|
|
2108
790
|
*/
|
|
2109
|
-
|
|
2110
|
-
|
|
791
|
+
private syncSessionDataToCollectedData(): void {
|
|
792
|
+
const sessionData = this.session.getData();
|
|
793
|
+
if (sessionData && Object.keys(sessionData).length > 0) {
|
|
794
|
+
this.collectedData = { ...sessionData };
|
|
795
|
+
logger.debug("[Agent] Synced session data to collected data:", this.collectedData);
|
|
796
|
+
}
|
|
2111
797
|
}
|
|
2112
798
|
|
|
2113
799
|
/**
|
|
@@ -2116,6 +802,9 @@ export class Agent<TContext = any, TData = any> {
|
|
|
2116
802
|
* @returns The collected data from the current session or agent-level data
|
|
2117
803
|
*/
|
|
2118
804
|
getData(): Partial<TData> {
|
|
805
|
+
// Ensure agent collected data is synced with session
|
|
806
|
+
this.syncSessionDataToCollectedData();
|
|
807
|
+
|
|
2119
808
|
// If we have a current session, use session data
|
|
2120
809
|
if (this.currentSession) {
|
|
2121
810
|
// With agent-level data, all routes share the same data structure
|
|
@@ -2205,53 +894,25 @@ export class Agent<TContext = any, TData = any> {
|
|
|
2205
894
|
*/
|
|
2206
895
|
async chat(
|
|
2207
896
|
message?: string,
|
|
2208
|
-
options?:
|
|
2209
|
-
history?: History; // Optional: override session history for this response
|
|
2210
|
-
contextOverride?: Partial<TContext>;
|
|
2211
|
-
signal?: AbortSignal;
|
|
2212
|
-
}
|
|
897
|
+
options?: GenerateOptions<TContext>
|
|
2213
898
|
): Promise<AgentResponse<TData>> {
|
|
2214
|
-
//
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
// Use provided history for this response only
|
|
2218
|
-
history = options.history;
|
|
2219
|
-
} else {
|
|
2220
|
-
// Add user message to session history if provided
|
|
2221
|
-
if (message) {
|
|
2222
|
-
await this.session.addMessage("user", message);
|
|
2223
|
-
}
|
|
2224
|
-
history = this.session.getHistory();
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
// Get or create session
|
|
2228
|
-
let session = await this.session.getOrCreate();
|
|
2229
|
-
|
|
2230
|
-
// Merge agent's collected data into session (agent data takes precedence)
|
|
2231
|
-
if (Object.keys(this.collectedData).length > 0) {
|
|
2232
|
-
session = mergeCollected(session, this.collectedData);
|
|
2233
|
-
// Update the session manager with the merged data
|
|
2234
|
-
await this.session.setData(this.collectedData);
|
|
2235
|
-
logger.debug("[Agent] Merged agent collected data into chat session:", this.collectedData);
|
|
2236
|
-
}
|
|
899
|
+
// Delegate to ResponseModal.generate()
|
|
900
|
+
return this.responseModal.generate(message, options);
|
|
901
|
+
}
|
|
2237
902
|
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
903
|
+
/**
|
|
904
|
+
* Modern streaming API - simple interface like chat() but returns a stream
|
|
905
|
+
* Automatically manages conversation history through the session
|
|
906
|
+
*/
|
|
907
|
+
async *stream(
|
|
908
|
+
message?: string,
|
|
909
|
+
options?: StreamOptions<TContext>
|
|
910
|
+
): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
911
|
+
// Delegate to ResponseModal with the same options structure as chat()
|
|
912
|
+
yield* this.responseModal.stream(message, {
|
|
913
|
+
history: options?.history,
|
|
2242
914
|
contextOverride: options?.contextOverride,
|
|
2243
915
|
signal: options?.signal,
|
|
2244
916
|
});
|
|
2245
|
-
|
|
2246
|
-
// Add agent response to session history (only if not using override history)
|
|
2247
|
-
if (!options?.history) {
|
|
2248
|
-
await this.session.addMessage("assistant", result.message);
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
|
-
// Ensure the result includes the current session
|
|
2252
|
-
return {
|
|
2253
|
-
...result,
|
|
2254
|
-
session: result.session || this.session.current,
|
|
2255
|
-
};
|
|
2256
917
|
}
|
|
2257
|
-
}
|
|
918
|
+
}
|