@falai/agent 0.9.0-alpha-1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -22
- package/dist/cjs/src/core/Agent.d.ts +77 -59
- package/dist/cjs/src/core/Agent.d.ts.map +1 -1
- package/dist/cjs/src/core/Agent.js +284 -1060
- package/dist/cjs/src/core/Agent.js.map +1 -1
- package/dist/cjs/src/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/src/core/PersistenceManager.js +48 -25
- package/dist/cjs/src/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/src/core/PromptComposer.d.ts +1 -1
- package/dist/cjs/src/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/src/core/PromptComposer.js.map +1 -1
- package/dist/cjs/src/core/ResponseEngine.d.ts +13 -12
- package/dist/cjs/src/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/src/core/ResponseEngine.js +4 -4
- package/dist/cjs/src/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/src/core/ResponseModal.d.ts +205 -0
- package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -0
- package/dist/cjs/src/core/ResponseModal.js +1328 -0
- package/dist/cjs/src/core/ResponseModal.js.map +1 -0
- package/dist/cjs/src/core/ResponsePipeline.d.ts +66 -38
- package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/src/core/ResponsePipeline.js +72 -4
- package/dist/cjs/src/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/src/core/Route.d.ts +24 -5
- package/dist/cjs/src/core/Route.d.ts.map +1 -1
- package/dist/cjs/src/core/Route.js +45 -1
- package/dist/cjs/src/core/Route.js.map +1 -1
- package/dist/cjs/src/core/RoutingEngine.d.ts +31 -6
- package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/src/core/RoutingEngine.js +113 -9
- package/dist/cjs/src/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/src/core/SessionManager.d.ts +14 -4
- package/dist/cjs/src/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/src/core/SessionManager.js +25 -5
- package/dist/cjs/src/core/SessionManager.js.map +1 -1
- package/dist/cjs/src/core/Step.d.ts +10 -10
- package/dist/cjs/src/core/Step.d.ts.map +1 -1
- package/dist/cjs/src/core/Step.js.map +1 -1
- package/dist/cjs/src/core/ToolExecutor.d.ts +4 -2
- package/dist/cjs/src/core/ToolExecutor.d.ts.map +1 -1
- package/dist/cjs/src/core/ToolExecutor.js +13 -3
- package/dist/cjs/src/core/ToolExecutor.js.map +1 -1
- package/dist/cjs/src/index.d.ts +3 -1
- package/dist/cjs/src/index.d.ts.map +1 -1
- package/dist/cjs/src/index.js +7 -1
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/types/agent.d.ts +42 -21
- package/dist/cjs/src/types/agent.d.ts.map +1 -1
- package/dist/cjs/src/types/agent.js.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 +1 -1
- package/dist/cjs/src/types/index.d.ts.map +1 -1
- package/dist/cjs/src/types/index.js.map +1 -1
- package/dist/cjs/src/types/persistence.d.ts +0 -1
- package/dist/cjs/src/types/persistence.d.ts.map +1 -1
- package/dist/cjs/src/types/route.d.ts +22 -16
- package/dist/cjs/src/types/route.d.ts.map +1 -1
- package/dist/cjs/src/types/session.d.ts +6 -11
- package/dist/cjs/src/types/session.d.ts.map +1 -1
- package/dist/cjs/src/types/tool.d.ts +12 -6
- package/dist/cjs/src/types/tool.d.ts.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/cjs/src/utils/session.d.ts +2 -2
- package/dist/cjs/src/utils/session.d.ts.map +1 -1
- package/dist/cjs/src/utils/session.js +6 -26
- package/dist/cjs/src/utils/session.js.map +1 -1
- package/dist/src/core/Agent.d.ts +77 -59
- package/dist/src/core/Agent.d.ts.map +1 -1
- package/dist/src/core/Agent.js +285 -1061
- package/dist/src/core/Agent.js.map +1 -1
- package/dist/src/core/PersistenceManager.d.ts.map +1 -1
- package/dist/src/core/PersistenceManager.js +48 -25
- package/dist/src/core/PersistenceManager.js.map +1 -1
- package/dist/src/core/PromptComposer.d.ts +1 -1
- package/dist/src/core/PromptComposer.d.ts.map +1 -1
- package/dist/src/core/PromptComposer.js.map +1 -1
- package/dist/src/core/ResponseEngine.d.ts +13 -12
- package/dist/src/core/ResponseEngine.d.ts.map +1 -1
- package/dist/src/core/ResponseEngine.js +4 -4
- package/dist/src/core/ResponseEngine.js.map +1 -1
- package/dist/src/core/ResponseModal.d.ts +205 -0
- package/dist/src/core/ResponseModal.d.ts.map +1 -0
- package/dist/src/core/ResponseModal.js +1323 -0
- package/dist/src/core/ResponseModal.js.map +1 -0
- package/dist/src/core/ResponsePipeline.d.ts +66 -38
- package/dist/src/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/src/core/ResponsePipeline.js +72 -4
- package/dist/src/core/ResponsePipeline.js.map +1 -1
- package/dist/src/core/Route.d.ts +24 -5
- package/dist/src/core/Route.d.ts.map +1 -1
- package/dist/src/core/Route.js +45 -1
- package/dist/src/core/Route.js.map +1 -1
- package/dist/src/core/RoutingEngine.d.ts +31 -6
- package/dist/src/core/RoutingEngine.d.ts.map +1 -1
- package/dist/src/core/RoutingEngine.js +113 -9
- package/dist/src/core/RoutingEngine.js.map +1 -1
- package/dist/src/core/SessionManager.d.ts +14 -4
- package/dist/src/core/SessionManager.d.ts.map +1 -1
- package/dist/src/core/SessionManager.js +25 -5
- package/dist/src/core/SessionManager.js.map +1 -1
- package/dist/src/core/Step.d.ts +10 -10
- package/dist/src/core/Step.d.ts.map +1 -1
- package/dist/src/core/Step.js.map +1 -1
- package/dist/src/core/ToolExecutor.d.ts +4 -2
- package/dist/src/core/ToolExecutor.d.ts.map +1 -1
- package/dist/src/core/ToolExecutor.js +13 -3
- package/dist/src/core/ToolExecutor.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/agent.d.ts +42 -21
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js.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 +1 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/persistence.d.ts +0 -1
- package/dist/src/types/persistence.d.ts.map +1 -1
- package/dist/src/types/route.d.ts +22 -16
- package/dist/src/types/route.d.ts.map +1 -1
- package/dist/src/types/session.d.ts +6 -11
- package/dist/src/types/session.d.ts.map +1 -1
- package/dist/src/types/tool.d.ts +12 -6
- package/dist/src/types/tool.d.ts.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/dist/src/utils/session.d.ts +2 -2
- package/dist/src/utils/session.d.ts.map +1 -1
- package/dist/src/utils/session.js +6 -26
- package/dist/src/utils/session.js.map +1 -1
- package/docs/README.md +5 -4
- package/docs/api/README.md +195 -4
- package/docs/api/overview.md +232 -13
- package/docs/core/agent/README.md +162 -17
- package/docs/core/agent/context-management.md +39 -15
- package/docs/core/agent/session-management.md +49 -16
- package/docs/core/ai-integration/prompt-composition.md +38 -14
- package/docs/core/ai-integration/response-processing.md +28 -17
- package/docs/core/conversation-flows/data-collection.md +103 -25
- package/docs/core/conversation-flows/route-dsl.md +45 -22
- package/docs/core/conversation-flows/routes.md +74 -18
- package/docs/core/conversation-flows/step-transitions.md +3 -3
- package/docs/core/conversation-flows/steps.md +39 -15
- package/docs/core/routing/intelligent-routing.md +18 -9
- package/docs/core/tools/tool-definition.md +8 -8
- package/docs/core/tools/tool-execution.md +26 -26
- package/docs/core/tools/tool-scoping.md +5 -5
- package/docs/guides/getting-started/README.md +54 -32
- 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 +37 -28
- package/examples/advanced-patterns/persistent-onboarding.ts +70 -41
- package/examples/advanced-patterns/route-lifecycle-hooks.ts +28 -2
- package/examples/advanced-patterns/streaming-responses.ts +197 -119
- package/examples/ai-providers/anthropic-integration.ts +40 -33
- package/examples/ai-providers/openai-integration.ts +25 -25
- package/examples/conversation-flows/completion-transitions.ts +36 -32
- package/examples/core-concepts/basic-agent.ts +76 -78
- package/examples/core-concepts/modern-streaming-api.ts +309 -0
- package/examples/core-concepts/schema-driven-extraction.ts +20 -16
- package/examples/core-concepts/session-management.ts +65 -53
- package/examples/integrations/database-integration.ts +49 -34
- package/examples/integrations/healthcare-integration.ts +96 -91
- package/examples/integrations/search-integration.ts +79 -82
- package/examples/integrations/server-session-management.ts +25 -17
- package/examples/persistence/database-persistence.ts +61 -45
- package/examples/persistence/memory-sessions.ts +52 -63
- package/examples/persistence/redis-persistence.ts +81 -95
- package/examples/tools/basic-tools.ts +73 -62
- package/examples/tools/data-enrichment-tools.ts +52 -44
- package/package.json +1 -1
- package/src/core/Agent.ts +396 -1499
- package/src/core/PersistenceManager.ts +51 -27
- package/src/core/PromptComposer.ts +1 -1
- package/src/core/ResponseEngine.ts +21 -19
- package/src/core/ResponseModal.ts +1722 -0
- package/src/core/ResponsePipeline.ts +175 -60
- package/src/core/Route.ts +58 -6
- package/src/core/RoutingEngine.ts +174 -27
- package/src/core/SessionManager.ts +32 -8
- package/src/core/Step.ts +20 -12
- package/src/core/ToolExecutor.ts +19 -5
- package/src/index.ts +11 -0
- package/src/types/agent.ts +47 -23
- package/src/types/ai.ts +1 -1
- package/src/types/index.ts +2 -0
- package/src/types/persistence.ts +0 -1
- package/src/types/route.ts +22 -16
- package/src/types/session.ts +6 -12
- package/src/types/tool.ts +15 -9
- package/src/utils/clone.ts +6 -8
- package/src/utils/history.ts +190 -27
- package/src/utils/index.ts +4 -0
- package/src/utils/session.ts +6 -31
package/src/core/Agent.ts
CHANGED
|
@@ -10,25 +10,19 @@ 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,
|
|
17
|
+
ValidationError,
|
|
18
|
+
ValidationResult,
|
|
20
19
|
} from "../types";
|
|
21
|
-
import {
|
|
20
|
+
import type { StreamOptions, GenerateOptions, RespondParams } from "./ResponseModal";
|
|
22
21
|
import {
|
|
23
|
-
enterRoute,
|
|
24
|
-
enterStep,
|
|
25
22
|
mergeCollected,
|
|
26
23
|
logger,
|
|
27
24
|
LoggerLevel,
|
|
28
25
|
render,
|
|
29
|
-
getLastMessageFromHistory,
|
|
30
|
-
normalizeHistory,
|
|
31
|
-
cloneDeep,
|
|
32
26
|
} from "../utils";
|
|
33
27
|
|
|
34
28
|
import { Route } from "./Route";
|
|
@@ -36,32 +30,51 @@ import { Step } from "./Step";
|
|
|
36
30
|
import { PersistenceManager } from "./PersistenceManager";
|
|
37
31
|
import { SessionManager } from "./SessionManager";
|
|
38
32
|
import { RoutingEngine } from "./RoutingEngine";
|
|
39
|
-
import { ResponseEngine } from "./ResponseEngine";
|
|
40
33
|
import { ToolExecutor } from "./ToolExecutor";
|
|
41
|
-
import {
|
|
42
|
-
import { END_ROUTE_ID } from "../constants";
|
|
34
|
+
import { ResponseModal } from "./ResponseModal";
|
|
43
35
|
|
|
44
36
|
/**
|
|
45
|
-
*
|
|
37
|
+
* Error thrown when data validation fails
|
|
46
38
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
class DataValidationError extends Error {
|
|
40
|
+
constructor(public errors: ValidationError[], message?: string) {
|
|
41
|
+
super(message || "Data validation failed");
|
|
42
|
+
this.name = "DataValidationError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Error thrown when route configuration is invalid
|
|
48
|
+
*/
|
|
49
|
+
class RouteConfigurationError extends Error {
|
|
50
|
+
constructor(public routeTitle: string, public invalidFields: string[], message?: string) {
|
|
51
|
+
super(message || `Route configuration error in '${routeTitle}'`);
|
|
52
|
+
this.name = "RouteConfigurationError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Main Agent class with generic context and data support
|
|
58
|
+
*/
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
export class Agent<TContext = any, TData = any> {
|
|
61
|
+
private terms: Term<TContext, TData>[] = [];
|
|
62
|
+
private guidelines: Guideline<TContext, TData>[] = [];
|
|
63
|
+
private tools: Tool<TContext, TData, unknown[], unknown>[] = [];
|
|
64
|
+
private routes: Route<TContext, TData>[] = [];
|
|
53
65
|
private context: TContext | undefined;
|
|
54
|
-
private persistenceManager: PersistenceManager | undefined;
|
|
55
|
-
private routingEngine: RoutingEngine<TContext>;
|
|
56
|
-
private
|
|
57
|
-
private
|
|
58
|
-
private currentSession?: SessionState;
|
|
66
|
+
private persistenceManager: PersistenceManager<TData> | undefined;
|
|
67
|
+
private routingEngine: RoutingEngine<TContext, TData>;
|
|
68
|
+
private responseModal: ResponseModal<TContext, TData>;
|
|
69
|
+
private currentSession?: SessionState<TData>;
|
|
59
70
|
private knowledgeBase: Record<string, unknown> = {};
|
|
71
|
+
private schema?: StructuredSchema;
|
|
72
|
+
private collectedData: Partial<TData> = {};
|
|
60
73
|
|
|
61
74
|
/** Public session manager for easy session management */
|
|
62
|
-
public session: SessionManager<
|
|
75
|
+
public session: SessionManager<TData>;
|
|
63
76
|
|
|
64
|
-
constructor(private readonly options: AgentOptions<TContext>) {
|
|
77
|
+
constructor(private readonly options: AgentOptions<TContext, TData>) {
|
|
65
78
|
// Set log level based on debug option
|
|
66
79
|
if (options.debug) {
|
|
67
80
|
logger.setLevel(LoggerLevel.DEBUG);
|
|
@@ -74,40 +87,74 @@ export class Agent<TContext = unknown> {
|
|
|
74
87
|
);
|
|
75
88
|
}
|
|
76
89
|
|
|
90
|
+
// Initialize and validate agent-level schema if provided
|
|
91
|
+
if (options.schema) {
|
|
92
|
+
this.schema = options.schema;
|
|
93
|
+
this.validateSchema(this.schema);
|
|
94
|
+
logger.debug("[Agent] Agent-level schema initialized and validated");
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
// Initialize context if provided
|
|
78
98
|
this.context = options.context;
|
|
79
99
|
|
|
100
|
+
// Initialize collected data with initial data if provided
|
|
101
|
+
if (options.initialData) {
|
|
102
|
+
if (this.schema) {
|
|
103
|
+
const validation = this.validateData(options.initialData);
|
|
104
|
+
if (!validation.valid) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Initial data validation failed: ${validation.errors.map(e => e.message).join(', ')}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.collectedData = { ...options.initialData };
|
|
111
|
+
logger.debug("[Agent] Initial data set:", this.collectedData);
|
|
112
|
+
}
|
|
113
|
+
|
|
80
114
|
// Initialize current session if provided
|
|
81
115
|
this.currentSession = options.session;
|
|
82
116
|
|
|
83
|
-
// Initialize routing
|
|
84
|
-
this.routingEngine = new RoutingEngine<TContext>({
|
|
117
|
+
// Initialize routing engine
|
|
118
|
+
this.routingEngine = new RoutingEngine<TContext, TData>({
|
|
85
119
|
maxCandidates: 5,
|
|
86
120
|
allowRouteSwitch: true,
|
|
87
121
|
switchThreshold: 70,
|
|
88
122
|
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.routes,
|
|
93
|
-
this.tools,
|
|
94
|
-
this.routingEngine,
|
|
95
|
-
this.updateContext.bind(this),
|
|
96
|
-
this.updateData.bind(this)
|
|
97
|
-
);
|
|
123
|
+
|
|
124
|
+
// Initialize ResponseModal for handling all response generation
|
|
125
|
+
this.responseModal = new ResponseModal<TContext, TData>(this);
|
|
98
126
|
|
|
99
127
|
// Initialize persistence if configured
|
|
100
128
|
if (options.persistence) {
|
|
101
|
-
|
|
129
|
+
try {
|
|
130
|
+
// Validate persistence configuration
|
|
131
|
+
if (!options.persistence.adapter) {
|
|
132
|
+
throw new Error("Persistence adapter is required when persistence is configured");
|
|
133
|
+
}
|
|
102
134
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
if (!options.persistence.adapter.sessionRepository) {
|
|
136
|
+
throw new Error("Persistence adapter must provide a sessionRepository");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!options.persistence.adapter.messageRepository) {
|
|
140
|
+
throw new Error("Persistence adapter must provide a messageRepository");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.persistenceManager = new PersistenceManager<TData>(options.persistence);
|
|
144
|
+
|
|
145
|
+
// Initialize the adapter if it has an initialize method
|
|
146
|
+
if (options.persistence.adapter.initialize) {
|
|
147
|
+
options.persistence.adapter.initialize().catch((error) => {
|
|
148
|
+
logger.error(
|
|
149
|
+
"[Agent] Persistence adapter initialization failed:",
|
|
150
|
+
error instanceof Error ? error.message : String(error)
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
156
|
+
logger.error("[Agent] Failed to initialize persistence:", errorMessage);
|
|
157
|
+
throw new Error(`Failed to initialize persistence: ${errorMessage}`);
|
|
111
158
|
}
|
|
112
159
|
}
|
|
113
160
|
|
|
@@ -132,7 +179,7 @@ export class Agent<TContext = unknown> {
|
|
|
132
179
|
|
|
133
180
|
if (options.routes) {
|
|
134
181
|
options.routes.forEach((routeOptions) => {
|
|
135
|
-
this.createRoute
|
|
182
|
+
this.createRoute(routeOptions);
|
|
136
183
|
});
|
|
137
184
|
}
|
|
138
185
|
|
|
@@ -142,7 +189,7 @@ export class Agent<TContext = unknown> {
|
|
|
142
189
|
}
|
|
143
190
|
|
|
144
191
|
// Initialize session manager
|
|
145
|
-
this.session = new SessionManager(this.persistenceManager);
|
|
192
|
+
this.session = new SessionManager<TData>(this.persistenceManager);
|
|
146
193
|
|
|
147
194
|
// Store sessionId for later use in getOrCreate calls
|
|
148
195
|
if (options.sessionId) {
|
|
@@ -153,6 +200,143 @@ export class Agent<TContext = unknown> {
|
|
|
153
200
|
}
|
|
154
201
|
}
|
|
155
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Validate the agent-level schema structure
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
private validateSchema(schema: StructuredSchema): void {
|
|
208
|
+
if (!schema || typeof schema !== 'object') {
|
|
209
|
+
throw new Error(
|
|
210
|
+
"Agent schema must be a valid JSON Schema object. " +
|
|
211
|
+
"Provide a schema with 'type': 'object' and 'properties' to define the data structure."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (schema.type !== 'object') {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Agent schema must be of type 'object', but received '${String(schema.type)}'. ` +
|
|
218
|
+
"Agent-level schemas must define object structures for data collection."
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!schema.properties || typeof schema.properties !== 'object') {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"Agent schema must have a 'properties' field defining the data fields. " +
|
|
225
|
+
"Example: { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string' } } }"
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
logger.debug("[Agent] Schema validation passed");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Validate data against the agent-level schema
|
|
234
|
+
*/
|
|
235
|
+
validateData(data: Partial<TData>): ValidationResult {
|
|
236
|
+
if (!this.schema) {
|
|
237
|
+
// No schema defined, consider all data valid
|
|
238
|
+
return { valid: true, errors: [], warnings: [] };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const errors: ValidationError[] = [];
|
|
242
|
+
const warnings: ValidationError[] = [];
|
|
243
|
+
|
|
244
|
+
// Basic validation - check if provided fields exist in schema
|
|
245
|
+
if (this.schema.properties) {
|
|
246
|
+
for (const [key, value] of Object.entries(data)) {
|
|
247
|
+
if (!(key in this.schema.properties)) {
|
|
248
|
+
errors.push({
|
|
249
|
+
field: key,
|
|
250
|
+
value,
|
|
251
|
+
message: `Field '${key}' is not defined in agent schema`,
|
|
252
|
+
schemaPath: `properties.${key}`
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check required fields if specified
|
|
259
|
+
if (this.schema.required && Array.isArray(this.schema.required)) {
|
|
260
|
+
for (const requiredField of this.schema.required) {
|
|
261
|
+
if (!(requiredField in data) || data[requiredField as keyof TData] === undefined) {
|
|
262
|
+
warnings.push({
|
|
263
|
+
field: requiredField,
|
|
264
|
+
value: undefined,
|
|
265
|
+
message: `Required field '${requiredField}' is missing`,
|
|
266
|
+
schemaPath: `required`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
valid: errors.length === 0,
|
|
274
|
+
errors,
|
|
275
|
+
warnings
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Check if a field is valid according to the agent schema
|
|
281
|
+
* @param field - The field key to validate
|
|
282
|
+
* @returns true if field exists in schema or no schema is defined, false otherwise
|
|
283
|
+
*/
|
|
284
|
+
isValidSchemaField(field: keyof TData): boolean {
|
|
285
|
+
if (!this.schema || !this.schema.properties) {
|
|
286
|
+
// No schema defined, consider all fields valid
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return field as string in this.schema.properties;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Get the current collected data
|
|
295
|
+
*/
|
|
296
|
+
getCollectedData(): Partial<TData> {
|
|
297
|
+
return { ...this.collectedData };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Update collected data with validation
|
|
302
|
+
*/
|
|
303
|
+
async updateCollectedData(updates: Partial<TData>): Promise<void> {
|
|
304
|
+
// Validate the updates
|
|
305
|
+
const validation = this.validateData(updates);
|
|
306
|
+
if (!validation.valid) {
|
|
307
|
+
const errorMessages = validation.errors.map(e => e.message).join(', ');
|
|
308
|
+
throw new DataValidationError(validation.errors, `Data validation failed: ${errorMessages}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Log warnings if any
|
|
312
|
+
if (validation.warnings.length > 0) {
|
|
313
|
+
const warningMessages = validation.warnings.map(w => w.message).join(', ');
|
|
314
|
+
logger.warn(`[Agent] Data validation warnings: ${warningMessages}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Merge updates with current data
|
|
318
|
+
const previousData = { ...this.collectedData };
|
|
319
|
+
this.collectedData = {
|
|
320
|
+
...this.collectedData,
|
|
321
|
+
...updates
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// Trigger agent-level lifecycle hook if configured
|
|
325
|
+
if (this.options.hooks?.onDataUpdate) {
|
|
326
|
+
this.collectedData = await this.options.hooks.onDataUpdate(
|
|
327
|
+
this.collectedData,
|
|
328
|
+
previousData
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Update current session if it exists to keep it in sync
|
|
333
|
+
if (this.currentSession) {
|
|
334
|
+
this.currentSession = mergeCollected(this.currentSession, this.collectedData);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
logger.debug("[Agent] Collected data updated:", updates);
|
|
338
|
+
}
|
|
339
|
+
|
|
156
340
|
/**
|
|
157
341
|
* Get agent name
|
|
158
342
|
*/
|
|
@@ -182,12 +366,41 @@ export class Agent<TContext = unknown> {
|
|
|
182
366
|
}
|
|
183
367
|
|
|
184
368
|
/**
|
|
185
|
-
* Create a new route (journey)
|
|
186
|
-
* @template TData - Type of data collected throughout the route
|
|
369
|
+
* Create a new route (journey) using agent-level data type
|
|
187
370
|
*/
|
|
188
|
-
createRoute
|
|
371
|
+
createRoute(
|
|
189
372
|
options: RouteOptions<TContext, TData>
|
|
190
373
|
): Route<TContext, TData> {
|
|
374
|
+
// Validate that requiredFields exist in agent schema
|
|
375
|
+
if (options.requiredFields && this.schema?.properties) {
|
|
376
|
+
const invalidRequiredFields = options.requiredFields.filter(
|
|
377
|
+
field => !(String(field) in this.schema!.properties!)
|
|
378
|
+
);
|
|
379
|
+
if (invalidRequiredFields.length > 0) {
|
|
380
|
+
throw new RouteConfigurationError(
|
|
381
|
+
options.title,
|
|
382
|
+
invalidRequiredFields.map(f => String(f)),
|
|
383
|
+
`Invalid required fields in route '${options.title}': ${invalidRequiredFields.join(', ')}. ` +
|
|
384
|
+
`Must be valid keys from agent schema. Available fields: ${Object.keys(this.schema.properties).join(', ')}.`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Validate that optionalFields exist in agent schema
|
|
390
|
+
if (options.optionalFields && this.schema?.properties) {
|
|
391
|
+
const invalidOptionalFields = options.optionalFields.filter(
|
|
392
|
+
field => !(String(field) in this.schema!.properties!)
|
|
393
|
+
);
|
|
394
|
+
if (invalidOptionalFields.length > 0) {
|
|
395
|
+
throw new RouteConfigurationError(
|
|
396
|
+
options.title,
|
|
397
|
+
invalidOptionalFields.map(f => String(f)),
|
|
398
|
+
`Invalid optional fields in route '${options.title}': ${invalidOptionalFields.join(', ')}. ` +
|
|
399
|
+
`Must be valid keys from agent schema. Available fields: ${Object.keys(this.schema.properties).join(', ')}.`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
191
404
|
const route = new Route<TContext, TData>(options);
|
|
192
405
|
this.routes.push(route);
|
|
193
406
|
return route;
|
|
@@ -196,7 +409,7 @@ export class Agent<TContext = unknown> {
|
|
|
196
409
|
/**
|
|
197
410
|
* Create a domain term for the glossary
|
|
198
411
|
*/
|
|
199
|
-
createTerm(term: Term<TContext>): this {
|
|
412
|
+
createTerm(term: Term<TContext, TData>): this {
|
|
200
413
|
this.terms.push(term);
|
|
201
414
|
return this;
|
|
202
415
|
}
|
|
@@ -204,7 +417,7 @@ export class Agent<TContext = unknown> {
|
|
|
204
417
|
/**
|
|
205
418
|
* Create a behavioral guideline
|
|
206
419
|
*/
|
|
207
|
-
createGuideline(guideline: Guideline<TContext>): this {
|
|
420
|
+
createGuideline(guideline: Guideline<TContext, TData>): this {
|
|
208
421
|
const guidelineWithId = {
|
|
209
422
|
...guideline,
|
|
210
423
|
id: guideline.id || `guideline_${this.guidelines.length}`,
|
|
@@ -217,7 +430,7 @@ export class Agent<TContext = unknown> {
|
|
|
217
430
|
/**
|
|
218
431
|
* Register a tool at the agent level
|
|
219
432
|
*/
|
|
220
|
-
createTool(tool: Tool<TContext, unknown[], unknown
|
|
433
|
+
createTool(tool: Tool<TContext, TData, unknown[], unknown>): this {
|
|
221
434
|
this.tools.push(tool);
|
|
222
435
|
return this;
|
|
223
436
|
}
|
|
@@ -225,7 +438,7 @@ export class Agent<TContext = unknown> {
|
|
|
225
438
|
/**
|
|
226
439
|
* Register multiple tools at the agent level
|
|
227
440
|
*/
|
|
228
|
-
registerTools(tools: Tool<TContext, unknown[], unknown
|
|
441
|
+
registerTools(tools: Tool<TContext, TData, unknown[], unknown>[]): this {
|
|
229
442
|
tools.forEach((tool) => this.createTool(tool));
|
|
230
443
|
return this;
|
|
231
444
|
}
|
|
@@ -267,7 +480,7 @@ export class Agent<TContext = unknown> {
|
|
|
267
480
|
* Triggers both agent-level and route-specific onDataUpdate lifecycle hooks if configured
|
|
268
481
|
* @internal
|
|
269
482
|
*/
|
|
270
|
-
private async updateData
|
|
483
|
+
private async updateData(
|
|
271
484
|
session: SessionState<TData>,
|
|
272
485
|
dataUpdate: Partial<TData>
|
|
273
486
|
): Promise<SessionState<TData>> {
|
|
@@ -297,9 +510,12 @@ export class Agent<TContext = unknown> {
|
|
|
297
510
|
newCollected = (await this.options.hooks.onDataUpdate(
|
|
298
511
|
newCollected,
|
|
299
512
|
previousCollected
|
|
300
|
-
))
|
|
513
|
+
));
|
|
301
514
|
}
|
|
302
515
|
|
|
516
|
+
// Update agent's collected data to stay in sync
|
|
517
|
+
this.collectedData = { ...newCollected };
|
|
518
|
+
|
|
303
519
|
// Return updated session
|
|
304
520
|
return mergeCollected(session, newCollected);
|
|
305
521
|
}
|
|
@@ -316,1370 +532,141 @@ export class Agent<TContext = unknown> {
|
|
|
316
532
|
// Otherwise return the stored context
|
|
317
533
|
return this.context;
|
|
318
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Get current schema
|
|
537
|
+
*/
|
|
538
|
+
getSchema(): StructuredSchema | undefined {
|
|
539
|
+
return this.schema;
|
|
540
|
+
}
|
|
319
541
|
|
|
320
542
|
/**
|
|
321
543
|
* Generate a response based on history and context as a stream
|
|
322
544
|
*/
|
|
323
|
-
async *respondStream
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
session?: SessionState<TData>;
|
|
327
|
-
contextOverride?: Partial<TContext>;
|
|
328
|
-
signal?: AbortSignal;
|
|
329
|
-
}): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
330
|
-
const { history: simpleHistory, signal } = params;
|
|
331
|
-
const history = normalizeHistory(simpleHistory);
|
|
332
|
-
|
|
333
|
-
// Prepare context and session using the response pipeline
|
|
334
|
-
this.responsePipeline.setContext(this.context);
|
|
335
|
-
this.responsePipeline.setCurrentSession(this.currentSession);
|
|
336
|
-
let session: SessionState;
|
|
337
|
-
const responseContext = await this.responsePipeline.prepareResponseContext({
|
|
338
|
-
contextOverride: params.contextOverride,
|
|
339
|
-
session: params.session ? cloneDeep(params.session) : undefined,
|
|
340
|
-
});
|
|
341
|
-
const { effectiveContext } = responseContext;
|
|
342
|
-
session = responseContext.session;
|
|
343
|
-
// Update our stored context if it was modified by beforeRespond hook
|
|
344
|
-
this.context = this.responsePipeline.getStoredContext();
|
|
345
|
-
|
|
346
|
-
// PHASE 1: PREPARE - Execute prepare function if current step has one
|
|
347
|
-
if (session.currentRoute && session.currentStep) {
|
|
348
|
-
const currentRoute = this.routes.find(
|
|
349
|
-
(r) => r.id === session.currentRoute?.id
|
|
350
|
-
);
|
|
351
|
-
if (currentRoute) {
|
|
352
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
353
|
-
if (currentStep?.prepare) {
|
|
354
|
-
logger.debug(`[Agent] Executing prepare for step: ${currentStep.id}`);
|
|
355
|
-
await this.executePrepareFinalize(
|
|
356
|
-
currentStep.prepare,
|
|
357
|
-
effectiveContext,
|
|
358
|
-
session.data,
|
|
359
|
-
currentRoute,
|
|
360
|
-
currentStep
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// PHASE 2: ROUTING + STEP SELECTION - Use response pipeline
|
|
367
|
-
const routingResult =
|
|
368
|
-
await this.responsePipeline.handleRoutingAndStepSelection({
|
|
369
|
-
session,
|
|
370
|
-
history,
|
|
371
|
-
context: effectiveContext,
|
|
372
|
-
signal,
|
|
373
|
-
});
|
|
374
|
-
const selectedRoute = routingResult.selectedRoute;
|
|
375
|
-
const selectedStep = routingResult.selectedStep;
|
|
376
|
-
const responseDirectives = routingResult.responseDirectives;
|
|
377
|
-
const isRouteComplete = routingResult.isRouteComplete;
|
|
378
|
-
session = routingResult.session;
|
|
379
|
-
|
|
380
|
-
// PHASE 3: DETERMINE NEXT STEP - Use pipeline method
|
|
381
|
-
const stepResult = this.responsePipeline.determineNextStep({
|
|
382
|
-
selectedRoute,
|
|
383
|
-
selectedStep,
|
|
384
|
-
session,
|
|
385
|
-
isRouteComplete,
|
|
386
|
-
});
|
|
387
|
-
const nextStep = stepResult.nextStep;
|
|
388
|
-
session = stepResult.session;
|
|
389
|
-
|
|
390
|
-
if (selectedRoute && !isRouteComplete) {
|
|
391
|
-
// PHASE 4: RESPONSE GENERATION - Stream message using selected route and step
|
|
392
|
-
// Get last user message
|
|
393
|
-
const lastUserMessage = getLastMessageFromHistory(history);
|
|
394
|
-
|
|
395
|
-
// Build response schema for this route (with collect fields from step)
|
|
396
|
-
const responseSchema = this.responseEngine.responseSchemaForRoute(
|
|
397
|
-
selectedRoute,
|
|
398
|
-
nextStep
|
|
399
|
-
);
|
|
400
|
-
|
|
401
|
-
// Check if selected route and next step are defined
|
|
402
|
-
if (!selectedRoute || !nextStep) {
|
|
403
|
-
logger.error("[Agent] Selected route or next step is not defined", {
|
|
404
|
-
selectedRoute,
|
|
405
|
-
nextStep,
|
|
406
|
-
});
|
|
407
|
-
throw new Error("Selected route or next step is not defined");
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Build response prompt
|
|
411
|
-
const responsePrompt = await this.responseEngine.buildResponsePrompt({
|
|
412
|
-
route: selectedRoute,
|
|
413
|
-
currentStep: nextStep,
|
|
414
|
-
rules: selectedRoute.getRules(),
|
|
415
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
416
|
-
directives: responseDirectives,
|
|
417
|
-
history,
|
|
418
|
-
lastMessage: lastUserMessage,
|
|
419
|
-
agentOptions: this.options,
|
|
420
|
-
// Combine agent and route properties according to the specified logic
|
|
421
|
-
combinedGuidelines: [
|
|
422
|
-
...this.getGuidelines(),
|
|
423
|
-
...selectedRoute.getGuidelines(),
|
|
424
|
-
],
|
|
425
|
-
combinedTerms: this.mergeTerms(
|
|
426
|
-
this.getTerms(),
|
|
427
|
-
selectedRoute.getTerms()
|
|
428
|
-
),
|
|
429
|
-
context: effectiveContext,
|
|
430
|
-
session,
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
// Collect available tools for AI
|
|
434
|
-
const availableTools = this.collectAvailableTools(
|
|
435
|
-
selectedRoute,
|
|
436
|
-
nextStep
|
|
437
|
-
);
|
|
438
|
-
|
|
439
|
-
// Generate message stream using AI provider
|
|
440
|
-
const stream = this.options.provider.generateMessageStream({
|
|
441
|
-
prompt: responsePrompt,
|
|
442
|
-
history,
|
|
443
|
-
context: effectiveContext,
|
|
444
|
-
tools: availableTools,
|
|
445
|
-
signal,
|
|
446
|
-
parameters: {
|
|
447
|
-
jsonSchema: responseSchema,
|
|
448
|
-
schemaName: "response_stream_output",
|
|
449
|
-
},
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
// Stream chunks to caller
|
|
453
|
-
for await (const chunk of stream) {
|
|
454
|
-
let toolCalls:
|
|
455
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
456
|
-
| undefined = undefined;
|
|
457
|
-
|
|
458
|
-
// Extract tool calls from AI response on final chunk
|
|
459
|
-
if (chunk.done && chunk.structured?.toolCalls) {
|
|
460
|
-
toolCalls = chunk.structured.toolCalls;
|
|
461
|
-
|
|
462
|
-
// Execute dynamic tool calls
|
|
463
|
-
if (toolCalls.length > 0) {
|
|
464
|
-
logger.debug(
|
|
465
|
-
`[Agent] Executing ${toolCalls.length} dynamic tool calls`
|
|
466
|
-
);
|
|
467
|
-
|
|
468
|
-
for (const toolCall of toolCalls) {
|
|
469
|
-
const tool = this.findAvailableTool(
|
|
470
|
-
toolCall.toolName,
|
|
471
|
-
selectedRoute
|
|
472
|
-
);
|
|
473
|
-
if (!tool) {
|
|
474
|
-
logger.warn(`[Agent] Tool not found: ${toolCall.toolName}`);
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const toolExecutor = new ToolExecutor<TContext, unknown>();
|
|
479
|
-
const result = await toolExecutor.executeTool({
|
|
480
|
-
tool: tool,
|
|
481
|
-
context: effectiveContext,
|
|
482
|
-
updateContext: this.updateContext.bind(this),
|
|
483
|
-
history,
|
|
484
|
-
data: session.data,
|
|
485
|
-
toolArguments: toolCall.arguments,
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
// Update context with tool results
|
|
489
|
-
if (result.contextUpdate) {
|
|
490
|
-
await this.updateContext(
|
|
491
|
-
result.contextUpdate as Partial<TContext>
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Update collected data with tool results
|
|
496
|
-
if (result.dataUpdate) {
|
|
497
|
-
session = await this.updateData(session, result.dataUpdate);
|
|
498
|
-
logger.debug(
|
|
499
|
-
`[Agent] Tool updated collected data:`,
|
|
500
|
-
result.dataUpdate
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
logger.debug(
|
|
505
|
-
`[Agent] Executed dynamic tool: ${result.toolName} (success: ${result.success})`
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution (streaming)
|
|
512
|
-
const MAX_TOOL_LOOPS = 5;
|
|
513
|
-
let toolLoopCount = 0;
|
|
514
|
-
let hasToolCalls = toolCalls && toolCalls.length > 0;
|
|
515
|
-
|
|
516
|
-
while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
|
|
517
|
-
toolLoopCount++;
|
|
518
|
-
logger.debug(
|
|
519
|
-
`[Agent] Starting streaming tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
// Add tool execution results to history so AI knows what happened
|
|
523
|
-
const toolResultsEvents: Event[] = [];
|
|
524
|
-
for (const toolCall of toolCalls || []) {
|
|
525
|
-
const tool = this.findAvailableTool(
|
|
526
|
-
toolCall.toolName,
|
|
527
|
-
selectedRoute
|
|
528
|
-
);
|
|
529
|
-
if (tool) {
|
|
530
|
-
toolResultsEvents.push({
|
|
531
|
-
kind: EventKind.TOOL,
|
|
532
|
-
source: MessageRole.AGENT,
|
|
533
|
-
timestamp: new Date().toISOString(),
|
|
534
|
-
data: {
|
|
535
|
-
tool_calls: [
|
|
536
|
-
{
|
|
537
|
-
tool_id: toolCall.toolName,
|
|
538
|
-
arguments: toolCall.arguments,
|
|
539
|
-
result: {
|
|
540
|
-
data: "Tool executed successfully",
|
|
541
|
-
},
|
|
542
|
-
},
|
|
543
|
-
],
|
|
544
|
-
},
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Create updated history with tool results
|
|
550
|
-
const updatedHistory = [...history, ...toolResultsEvents];
|
|
551
|
-
|
|
552
|
-
// Make follow-up streaming AI call to see if more tools are needed
|
|
553
|
-
const followUpStream = this.options.provider.generateMessageStream({
|
|
554
|
-
prompt: responsePrompt,
|
|
555
|
-
history: updatedHistory,
|
|
556
|
-
context: effectiveContext,
|
|
557
|
-
tools: availableTools,
|
|
558
|
-
parameters: {
|
|
559
|
-
jsonSchema: responseSchema,
|
|
560
|
-
schemaName: "tool_followup",
|
|
561
|
-
},
|
|
562
|
-
signal,
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
let followUpToolCalls:
|
|
566
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
567
|
-
| undefined;
|
|
568
|
-
|
|
569
|
-
for await (const followUpChunk of followUpStream) {
|
|
570
|
-
// Extract tool calls from follow-up stream
|
|
571
|
-
if (followUpChunk.done && followUpChunk.structured?.toolCalls) {
|
|
572
|
-
followUpToolCalls = followUpChunk.structured.toolCalls;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
|
|
577
|
-
|
|
578
|
-
if (hasToolCalls) {
|
|
579
|
-
logger.debug(
|
|
580
|
-
`[Agent] Follow-up streaming call produced ${
|
|
581
|
-
followUpToolCalls!.length
|
|
582
|
-
} additional tool calls`
|
|
583
|
-
);
|
|
584
|
-
|
|
585
|
-
// Execute the follow-up tool calls
|
|
586
|
-
for (const toolCall of followUpToolCalls!) {
|
|
587
|
-
const tool = this.findAvailableTool(
|
|
588
|
-
toolCall.toolName,
|
|
589
|
-
selectedRoute
|
|
590
|
-
);
|
|
591
|
-
if (!tool) {
|
|
592
|
-
logger.warn(
|
|
593
|
-
`[Agent] Tool not found in streaming follow-up: ${toolCall.toolName}`
|
|
594
|
-
);
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
const toolExecutor = new ToolExecutor<TContext, unknown>();
|
|
599
|
-
const result = await toolExecutor.executeTool({
|
|
600
|
-
tool: tool,
|
|
601
|
-
context: effectiveContext,
|
|
602
|
-
updateContext: this.updateContext.bind(this),
|
|
603
|
-
history: updatedHistory,
|
|
604
|
-
data: session.data,
|
|
605
|
-
toolArguments: toolCall.arguments,
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
// Update context with follow-up tool results
|
|
609
|
-
if (result.contextUpdate) {
|
|
610
|
-
await this.updateContext(
|
|
611
|
-
result.contextUpdate as Partial<TContext>
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
if (result.dataUpdate) {
|
|
616
|
-
session = await this.updateData(session, result.dataUpdate);
|
|
617
|
-
logger.debug(
|
|
618
|
-
`[Agent] Streaming follow-up tool updated collected data:`,
|
|
619
|
-
result.dataUpdate
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
logger.debug(
|
|
624
|
-
`[Agent] Executed streaming follow-up tool: ${result.toolName} (success: ${result.success})`
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Update toolCalls for next iteration
|
|
629
|
-
toolCalls = followUpToolCalls;
|
|
630
|
-
} else {
|
|
631
|
-
logger.debug(
|
|
632
|
-
`[Agent] Streaming tool loop completed after ${toolLoopCount} iterations`
|
|
633
|
-
);
|
|
634
|
-
// Update toolCalls for final response
|
|
635
|
-
toolCalls = followUpToolCalls || [];
|
|
636
|
-
break;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
641
|
-
logger.warn(
|
|
642
|
-
`[Agent] Streaming tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// Extract collected data on final chunk
|
|
647
|
-
if (chunk.done && chunk.structured && nextStep.collect) {
|
|
648
|
-
const collectedData: Record<string, unknown> = {};
|
|
649
|
-
// The structured response includes both base fields and collected extraction fields
|
|
650
|
-
const structuredData = chunk.structured as AgentStructuredResponse &
|
|
651
|
-
Record<string, unknown>;
|
|
652
|
-
|
|
653
|
-
for (const field of nextStep.collect) {
|
|
654
|
-
if (field in structuredData) {
|
|
655
|
-
collectedData[field] = structuredData[field];
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Merge collected data into session
|
|
660
|
-
if (Object.keys(collectedData).length > 0) {
|
|
661
|
-
session = await this.updateData(session, collectedData);
|
|
662
|
-
logger.debug(`[Agent] Collected data:`, collectedData);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Extract any additional data from structured response on final chunk
|
|
667
|
-
if (
|
|
668
|
-
chunk.done &&
|
|
669
|
-
chunk.structured &&
|
|
670
|
-
typeof chunk.structured === "object" &&
|
|
671
|
-
"contextUpdate" in chunk.structured
|
|
672
|
-
) {
|
|
673
|
-
await this.updateContext(
|
|
674
|
-
(chunk.structured as { contextUpdate?: Partial<TContext> })
|
|
675
|
-
.contextUpdate as Partial<TContext>
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// Auto-save session step on final chunk
|
|
680
|
-
if (
|
|
681
|
-
chunk.done &&
|
|
682
|
-
this.persistenceManager &&
|
|
683
|
-
session.id &&
|
|
684
|
-
this.options.persistence?.autoSave !== false
|
|
685
|
-
) {
|
|
686
|
-
await this.persistenceManager.saveSessionState(session.id, session);
|
|
687
|
-
logger.debug(
|
|
688
|
-
`[Agent] Auto-saved session step to persistence: ${session.id}`
|
|
689
|
-
);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Execute finalize function on final chunk
|
|
693
|
-
if (chunk.done && session.currentRoute && session.currentStep) {
|
|
694
|
-
const currentRoute = this.routes.find(
|
|
695
|
-
(r) => r.id === session.currentRoute?.id
|
|
696
|
-
);
|
|
697
|
-
if (currentRoute) {
|
|
698
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
699
|
-
if (currentStep?.finalize) {
|
|
700
|
-
logger.debug(
|
|
701
|
-
`[Agent] Executing finalize for step: ${currentStep.id}`
|
|
702
|
-
);
|
|
703
|
-
await this.executePrepareFinalize(
|
|
704
|
-
currentStep.finalize,
|
|
705
|
-
effectiveContext,
|
|
706
|
-
session.data,
|
|
707
|
-
currentRoute,
|
|
708
|
-
currentStep
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// Update current session if we have one
|
|
715
|
-
if (chunk.done && this.currentSession) {
|
|
716
|
-
this.currentSession = session;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
yield {
|
|
720
|
-
delta: chunk.delta,
|
|
721
|
-
accumulated: chunk.accumulated,
|
|
722
|
-
done: chunk.done,
|
|
723
|
-
session, // Return updated session
|
|
724
|
-
toolCalls,
|
|
725
|
-
isRouteComplete,
|
|
726
|
-
metadata: chunk.metadata,
|
|
727
|
-
structured: chunk.structured,
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
} else if (isRouteComplete && selectedRoute) {
|
|
731
|
-
// Route is complete - generate completion message then check for onComplete transition
|
|
732
|
-
const lastUserMessage = getLastMessageFromHistory(history);
|
|
733
|
-
|
|
734
|
-
// Get endStep spec from route
|
|
735
|
-
const endStepSpec = selectedRoute.endStepSpec;
|
|
736
|
-
|
|
737
|
-
// Create a temporary step for completion message generation using endStep configuration
|
|
738
|
-
const completionStep = new Step<TContext, unknown>(selectedRoute.id, {
|
|
739
|
-
description: endStepSpec.description,
|
|
740
|
-
id: endStepSpec.id || END_ROUTE_ID,
|
|
741
|
-
collect: endStepSpec.collect,
|
|
742
|
-
requires: endStepSpec.requires,
|
|
743
|
-
prompt:
|
|
744
|
-
endStepSpec.prompt ||
|
|
745
|
-
"Summarize what was accomplished and confirm completion based on the conversation history and collected data",
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
// Build response schema for completion
|
|
749
|
-
const responseSchema = this.responseEngine.responseSchemaForRoute(
|
|
750
|
-
selectedRoute,
|
|
751
|
-
completionStep
|
|
752
|
-
);
|
|
753
|
-
const templateContext = {
|
|
754
|
-
context: effectiveContext,
|
|
755
|
-
session,
|
|
756
|
-
history,
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
// Build completion response prompt
|
|
760
|
-
const completionPrompt = await this.responseEngine.buildResponsePrompt({
|
|
761
|
-
route: selectedRoute,
|
|
762
|
-
currentStep: completionStep,
|
|
763
|
-
rules: selectedRoute.getRules(),
|
|
764
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
765
|
-
directives: undefined, // No directives for completion
|
|
766
|
-
history,
|
|
767
|
-
lastMessage: lastUserMessage,
|
|
768
|
-
agentOptions: this.options,
|
|
769
|
-
// Combine agent and route properties according to the specified logic
|
|
770
|
-
combinedGuidelines: [
|
|
771
|
-
...this.getGuidelines(),
|
|
772
|
-
...selectedRoute.getGuidelines(),
|
|
773
|
-
],
|
|
774
|
-
combinedTerms: this.mergeTerms(
|
|
775
|
-
this.getTerms(),
|
|
776
|
-
selectedRoute.getTerms()
|
|
777
|
-
),
|
|
778
|
-
context: effectiveContext,
|
|
779
|
-
session,
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// Stream completion message using AI provider
|
|
783
|
-
const stream = this.options.provider.generateMessageStream({
|
|
784
|
-
prompt: completionPrompt,
|
|
785
|
-
history,
|
|
786
|
-
context: effectiveContext,
|
|
787
|
-
signal,
|
|
788
|
-
parameters: {
|
|
789
|
-
jsonSchema: responseSchema,
|
|
790
|
-
schemaName: "completion_message_stream",
|
|
791
|
-
},
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
logger.debug(
|
|
795
|
-
`[Agent] Streaming completion message for route: ${selectedRoute.title}`
|
|
796
|
-
);
|
|
797
|
-
|
|
798
|
-
// Check for onComplete transition
|
|
799
|
-
const transitionConfig = await selectedRoute.evaluateOnComplete(
|
|
800
|
-
{ data: session.data },
|
|
801
|
-
effectiveContext
|
|
802
|
-
);
|
|
803
|
-
|
|
804
|
-
if (transitionConfig) {
|
|
805
|
-
// Find target route by ID or title
|
|
806
|
-
const targetRoute = this.routes.find(
|
|
807
|
-
(r) =>
|
|
808
|
-
r.id === transitionConfig.nextStep ||
|
|
809
|
-
r.title === transitionConfig.nextStep
|
|
810
|
-
);
|
|
811
|
-
|
|
812
|
-
if (targetRoute) {
|
|
813
|
-
const renderedCondition = await render(
|
|
814
|
-
transitionConfig.condition,
|
|
815
|
-
templateContext
|
|
816
|
-
);
|
|
817
|
-
// Set pending transition in session
|
|
818
|
-
session = {
|
|
819
|
-
...session,
|
|
820
|
-
pendingTransition: {
|
|
821
|
-
targetRouteId: targetRoute.id,
|
|
822
|
-
condition: renderedCondition,
|
|
823
|
-
reason: "route_complete",
|
|
824
|
-
},
|
|
825
|
-
};
|
|
826
|
-
logger.debug(
|
|
827
|
-
`[Agent] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`
|
|
828
|
-
);
|
|
829
|
-
} else {
|
|
830
|
-
logger.warn(
|
|
831
|
-
`[Agent] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`
|
|
832
|
-
);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// Set step to END_ROUTE marker
|
|
837
|
-
session = enterStep(session, END_ROUTE_ID, "Route completed");
|
|
838
|
-
logger.debug(
|
|
839
|
-
`[Agent] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`
|
|
840
|
-
);
|
|
841
|
-
|
|
842
|
-
// Stream completion chunks
|
|
843
|
-
for await (const chunk of stream) {
|
|
844
|
-
// Update current session if we have one
|
|
845
|
-
if (chunk.done && this.currentSession) {
|
|
846
|
-
this.currentSession = session;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
yield {
|
|
850
|
-
delta: chunk.delta,
|
|
851
|
-
accumulated: chunk.accumulated,
|
|
852
|
-
done: chunk.done,
|
|
853
|
-
session,
|
|
854
|
-
toolCalls: undefined,
|
|
855
|
-
isRouteComplete: true,
|
|
856
|
-
metadata: chunk.metadata,
|
|
857
|
-
structured: chunk.structured,
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
} else {
|
|
861
|
-
// Fallback: No routes defined, stream a simple response
|
|
862
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
863
|
-
history,
|
|
864
|
-
agentOptions: this.options,
|
|
865
|
-
terms: this.terms,
|
|
866
|
-
guidelines: this.guidelines,
|
|
867
|
-
context: effectiveContext,
|
|
868
|
-
session,
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
const stream = this.options.provider.generateMessageStream({
|
|
872
|
-
prompt: fallbackPrompt,
|
|
873
|
-
history,
|
|
874
|
-
context: effectiveContext,
|
|
875
|
-
signal,
|
|
876
|
-
parameters: {
|
|
877
|
-
jsonSchema: {
|
|
878
|
-
type: "object",
|
|
879
|
-
properties: {
|
|
880
|
-
message: { type: "string" },
|
|
881
|
-
},
|
|
882
|
-
required: ["message"],
|
|
883
|
-
additionalProperties: false,
|
|
884
|
-
},
|
|
885
|
-
schemaName: "fallback_stream_response",
|
|
886
|
-
},
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
for await (const chunk of stream) {
|
|
890
|
-
// Update current session if we have one
|
|
891
|
-
if (chunk.done && this.currentSession) {
|
|
892
|
-
this.currentSession = session;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
yield {
|
|
896
|
-
delta: chunk.delta,
|
|
897
|
-
accumulated: chunk.accumulated,
|
|
898
|
-
done: chunk.done,
|
|
899
|
-
session, // Return updated session
|
|
900
|
-
toolCalls: undefined,
|
|
901
|
-
isRouteComplete: false,
|
|
902
|
-
metadata: chunk.metadata,
|
|
903
|
-
structured: chunk.structured,
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
}
|
|
545
|
+
async *respondStream(params: RespondParams<TContext, TData>): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
546
|
+
// Delegate to ResponseModal
|
|
547
|
+
yield* this.responseModal.respondStream(params);
|
|
907
548
|
}
|
|
908
549
|
|
|
909
550
|
/**
|
|
910
551
|
* Generate a response based on history and context
|
|
911
552
|
*/
|
|
912
|
-
async respond
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
contextOverride?: Partial<TContext>;
|
|
917
|
-
signal?: AbortSignal;
|
|
918
|
-
}): Promise<AgentResponse<TData>> {
|
|
919
|
-
const { history: simpleHistory, contextOverride, signal } = params;
|
|
920
|
-
const history = normalizeHistory(simpleHistory);
|
|
921
|
-
|
|
922
|
-
// Get current context (may fetch from provider)
|
|
923
|
-
let currentContext = await this.getContext();
|
|
924
|
-
|
|
925
|
-
// Call beforeRespond hook if configured
|
|
926
|
-
if (this.options.hooks?.beforeRespond && currentContext !== undefined) {
|
|
927
|
-
currentContext = await this.options.hooks.beforeRespond(currentContext);
|
|
928
|
-
// Update stored context with the result from beforeRespond
|
|
929
|
-
this.context = currentContext;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// Merge context with override
|
|
933
|
-
const effectiveContext = {
|
|
934
|
-
...(currentContext as Record<string, unknown>),
|
|
935
|
-
...(contextOverride as Record<string, unknown>),
|
|
936
|
-
} as TContext;
|
|
937
|
-
|
|
938
|
-
// Initialize or get session (use current session if available)
|
|
939
|
-
let session =
|
|
940
|
-
cloneDeep(params.session) ||
|
|
941
|
-
cloneDeep(this.currentSession) ||
|
|
942
|
-
(await this.session.getOrCreate());
|
|
943
|
-
|
|
944
|
-
// PHASE 1: PREPARE - Execute prepare function if current step has one
|
|
945
|
-
if (session.currentRoute && session.currentStep) {
|
|
946
|
-
const currentRoute = this.routes.find(
|
|
947
|
-
(r) => r.id === session.currentRoute?.id
|
|
948
|
-
);
|
|
949
|
-
if (currentRoute) {
|
|
950
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
951
|
-
if (currentStep?.prepare) {
|
|
952
|
-
logger.debug(`[Agent] Executing prepare for step: ${currentStep.id}`);
|
|
953
|
-
await this.executePrepareFinalize(
|
|
954
|
-
currentStep.prepare,
|
|
955
|
-
effectiveContext,
|
|
956
|
-
session.data,
|
|
957
|
-
currentRoute,
|
|
958
|
-
currentStep
|
|
959
|
-
);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use (combined)
|
|
965
|
-
let selectedRoute: Route<TContext, unknown> | undefined;
|
|
966
|
-
let responseDirectives: string[] | undefined;
|
|
967
|
-
let selectedStep: Step<TContext, unknown> | undefined;
|
|
968
|
-
let isRouteComplete = false;
|
|
969
|
-
|
|
970
|
-
// Check for pending transition from previous route completion
|
|
971
|
-
if (session.pendingTransition) {
|
|
972
|
-
const targetRoute = this.routes.find(
|
|
973
|
-
(r) => r.id === session.pendingTransition?.targetRouteId
|
|
974
|
-
);
|
|
975
|
-
|
|
976
|
-
if (targetRoute) {
|
|
977
|
-
logger.debug(
|
|
978
|
-
`[Agent] Auto-transitioning from pending transition to route: ${targetRoute.title}`
|
|
979
|
-
);
|
|
980
|
-
// Clear pending transition and enter new route
|
|
981
|
-
session = {
|
|
982
|
-
...session,
|
|
983
|
-
pendingTransition: undefined,
|
|
984
|
-
};
|
|
985
|
-
session = enterRoute(session, targetRoute.id, targetRoute.title);
|
|
986
|
-
|
|
987
|
-
// Merge initial data if available
|
|
988
|
-
if (targetRoute.initialData) {
|
|
989
|
-
session = mergeCollected(session, targetRoute.initialData);
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
selectedRoute = targetRoute;
|
|
993
|
-
} else {
|
|
994
|
-
logger.warn(
|
|
995
|
-
`[Agent] Pending transition target route not found: ${session.pendingTransition.targetRouteId}`
|
|
996
|
-
);
|
|
997
|
-
// Clear invalid transition
|
|
998
|
-
session = {
|
|
999
|
-
...session,
|
|
1000
|
-
pendingTransition: undefined,
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
// If no pending transition or transition handled, do normal routing
|
|
1006
|
-
if (this.routes.length > 0 && !selectedRoute) {
|
|
1007
|
-
const orchestration = await this.routingEngine.decideRouteAndStep({
|
|
1008
|
-
routes: this.routes,
|
|
1009
|
-
session,
|
|
1010
|
-
history,
|
|
1011
|
-
agentOptions: this.options,
|
|
1012
|
-
provider: this.options.provider,
|
|
1013
|
-
context: effectiveContext,
|
|
1014
|
-
signal,
|
|
1015
|
-
});
|
|
1016
|
-
|
|
1017
|
-
selectedRoute = orchestration.selectedRoute;
|
|
1018
|
-
selectedStep = orchestration.selectedStep;
|
|
1019
|
-
responseDirectives = orchestration.responseDirectives;
|
|
1020
|
-
session = orchestration.session;
|
|
1021
|
-
isRouteComplete = orchestration.isRouteComplete || false;
|
|
1022
|
-
|
|
1023
|
-
// Log if route is complete
|
|
1024
|
-
if (isRouteComplete) {
|
|
1025
|
-
logger.debug(
|
|
1026
|
-
`[Agent] Route complete: all required data collected, END_ROUTE reached`
|
|
1027
|
-
);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
// PHASE 3: DETERMINE NEXT STEP - Use step from combined decision or get initial step
|
|
1032
|
-
let message: string;
|
|
1033
|
-
let toolCalls:
|
|
1034
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
1035
|
-
| undefined = undefined;
|
|
1036
|
-
let responsePrompt: string;
|
|
1037
|
-
let availableTools: Tool<TContext, unknown[], unknown, unknown>[] = [];
|
|
1038
|
-
let responseSchema: StructuredSchema | undefined;
|
|
1039
|
-
let nextStep: Step<TContext, unknown> | undefined;
|
|
1040
|
-
|
|
1041
|
-
// Get last user message (needed for both route and completion handling)
|
|
1042
|
-
const lastUserMessage = getLastMessageFromHistory(history);
|
|
1043
|
-
|
|
1044
|
-
if (selectedRoute && !isRouteComplete) {
|
|
1045
|
-
// If we have a selected step from the combined routing decision, use it
|
|
1046
|
-
if (selectedStep) {
|
|
1047
|
-
nextStep = selectedStep;
|
|
1048
|
-
} else {
|
|
1049
|
-
// New route or no step selected - get initial step or first valid step
|
|
1050
|
-
const candidates = this.routingEngine.getCandidateSteps(
|
|
1051
|
-
selectedRoute,
|
|
1052
|
-
undefined,
|
|
1053
|
-
session.data || {}
|
|
1054
|
-
);
|
|
1055
|
-
if (candidates.length > 0) {
|
|
1056
|
-
nextStep = candidates[0].step;
|
|
1057
|
-
logger.debug(
|
|
1058
|
-
`[Agent] Using first valid step: ${nextStep.id} for new route`
|
|
1059
|
-
);
|
|
1060
|
-
} else {
|
|
1061
|
-
// Fallback to initial step even if it should be skipped
|
|
1062
|
-
nextStep = selectedRoute.initialStep;
|
|
1063
|
-
logger.warn(
|
|
1064
|
-
`[Agent] No valid steps found, using initial step: ${nextStep.id}`
|
|
1065
|
-
);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// Update session with next step
|
|
1070
|
-
session = enterStep(session, nextStep.id, nextStep.description);
|
|
1071
|
-
logger.debug(`[Agent] Entered step: ${nextStep.id}`);
|
|
1072
|
-
|
|
1073
|
-
// PHASE 4: RESPONSE GENERATION - Generate message using selected route and step
|
|
1074
|
-
// Get last user message
|
|
1075
|
-
const lastUserMessage = getLastMessageFromHistory(history);
|
|
1076
|
-
|
|
1077
|
-
// Build response schema for this route (with collect fields from step)
|
|
1078
|
-
responseSchema = this.responseEngine.responseSchemaForRoute(
|
|
1079
|
-
selectedRoute,
|
|
1080
|
-
nextStep
|
|
1081
|
-
);
|
|
1082
|
-
|
|
1083
|
-
// Build response prompt
|
|
1084
|
-
responsePrompt = await this.responseEngine.buildResponsePrompt({
|
|
1085
|
-
route: selectedRoute,
|
|
1086
|
-
currentStep: nextStep,
|
|
1087
|
-
rules: selectedRoute.getRules(),
|
|
1088
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1089
|
-
directives: responseDirectives,
|
|
1090
|
-
history,
|
|
1091
|
-
lastMessage: lastUserMessage,
|
|
1092
|
-
agentOptions: this.options,
|
|
1093
|
-
// Combine agent and route properties according to the specified logic
|
|
1094
|
-
combinedGuidelines: [
|
|
1095
|
-
...this.getGuidelines(),
|
|
1096
|
-
...selectedRoute.getGuidelines(),
|
|
1097
|
-
],
|
|
1098
|
-
combinedTerms: this.mergeTerms(
|
|
1099
|
-
this.getTerms(),
|
|
1100
|
-
selectedRoute.getTerms()
|
|
1101
|
-
),
|
|
1102
|
-
context: effectiveContext,
|
|
1103
|
-
session,
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
|
-
// Collect available tools for AI
|
|
1107
|
-
availableTools = this.collectAvailableTools(
|
|
1108
|
-
selectedRoute,
|
|
1109
|
-
nextStep
|
|
1110
|
-
) as Tool<TContext, unknown[], unknown, unknown>[];
|
|
1111
|
-
} else {
|
|
1112
|
-
// No route selected - generate basic response without route context
|
|
1113
|
-
logger.debug(`[Agent] No route selected, generating basic response`);
|
|
1114
|
-
|
|
1115
|
-
// Build basic response prompt without route context
|
|
1116
|
-
responsePrompt = await this.responseEngine.buildFallbackPrompt({
|
|
1117
|
-
history,
|
|
1118
|
-
agentOptions: this.options,
|
|
1119
|
-
terms: this.getTerms(),
|
|
1120
|
-
guidelines: this.getGuidelines(),
|
|
1121
|
-
context: effectiveContext,
|
|
1122
|
-
session,
|
|
1123
|
-
});
|
|
1124
|
-
|
|
1125
|
-
// Use agent-level tools only
|
|
1126
|
-
availableTools = [...this.tools];
|
|
1127
|
-
responseSchema = undefined;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
// Generate message using AI provider (common for both route and no-route cases)
|
|
1131
|
-
const result = await this.options.provider.generateMessage({
|
|
1132
|
-
prompt: responsePrompt,
|
|
1133
|
-
history,
|
|
1134
|
-
context: effectiveContext,
|
|
1135
|
-
tools: availableTools,
|
|
1136
|
-
signal,
|
|
1137
|
-
parameters: responseSchema
|
|
1138
|
-
? {
|
|
1139
|
-
jsonSchema: responseSchema,
|
|
1140
|
-
schemaName: "response_output",
|
|
1141
|
-
}
|
|
1142
|
-
: undefined,
|
|
1143
|
-
});
|
|
1144
|
-
|
|
1145
|
-
message = result.structured?.message || result.message;
|
|
1146
|
-
|
|
1147
|
-
// Process dynamic tool calls from AI response (common for both route and no-route cases)
|
|
1148
|
-
if (result.structured?.toolCalls) {
|
|
1149
|
-
toolCalls = result.structured.toolCalls;
|
|
1150
|
-
|
|
1151
|
-
// Execute dynamic tool calls
|
|
1152
|
-
if (toolCalls.length > 0) {
|
|
1153
|
-
logger.debug(
|
|
1154
|
-
`[Agent] Executing ${toolCalls.length} dynamic tool calls`
|
|
1155
|
-
);
|
|
1156
|
-
|
|
1157
|
-
for (const toolCall of toolCalls) {
|
|
1158
|
-
const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
|
|
1159
|
-
if (!tool) {
|
|
1160
|
-
logger.warn(`[Agent] Tool not found: ${toolCall.toolName}`);
|
|
1161
|
-
continue;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
const toolExecutor = new ToolExecutor<TContext, unknown>();
|
|
1165
|
-
const toolResult = await toolExecutor.executeTool({
|
|
1166
|
-
tool: tool,
|
|
1167
|
-
context: effectiveContext,
|
|
1168
|
-
updateContext: this.updateContext.bind(this),
|
|
1169
|
-
history,
|
|
1170
|
-
data: session.data,
|
|
1171
|
-
toolArguments: toolCall.arguments,
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
// Update context with tool results
|
|
1175
|
-
if (toolResult.contextUpdate) {
|
|
1176
|
-
await this.updateContext(
|
|
1177
|
-
toolResult.contextUpdate as Partial<TContext>
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
// Update collected data with tool results
|
|
1182
|
-
if (toolResult.dataUpdate) {
|
|
1183
|
-
session = await this.updateData(session, toolResult.dataUpdate);
|
|
1184
|
-
logger.debug(
|
|
1185
|
-
`[Agent] Tool updated collected data:`,
|
|
1186
|
-
toolResult.dataUpdate
|
|
1187
|
-
);
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
logger.debug(
|
|
1191
|
-
`[Agent] Executed dynamic tool: ${toolResult.toolName} (success: ${toolResult.success})`
|
|
1192
|
-
);
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
// TOOL LOOP: Allow AI to make follow-up tool calls after initial tool execution
|
|
1198
|
-
const MAX_TOOL_LOOPS = 5;
|
|
1199
|
-
let toolLoopCount = 0;
|
|
1200
|
-
let hasToolCalls = toolCalls && toolCalls.length > 0;
|
|
1201
|
-
|
|
1202
|
-
while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
|
|
1203
|
-
toolLoopCount++;
|
|
1204
|
-
logger.debug(
|
|
1205
|
-
`[Agent] Starting tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`
|
|
1206
|
-
);
|
|
1207
|
-
|
|
1208
|
-
// Add tool execution results to history so AI knows what happened
|
|
1209
|
-
const toolResultsEvents: Event[] = [];
|
|
1210
|
-
for (const toolCall of toolCalls || []) {
|
|
1211
|
-
const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
|
|
1212
|
-
if (tool) {
|
|
1213
|
-
toolResultsEvents.push({
|
|
1214
|
-
kind: EventKind.TOOL,
|
|
1215
|
-
source: MessageRole.AGENT,
|
|
1216
|
-
timestamp: new Date().toISOString(),
|
|
1217
|
-
data: {
|
|
1218
|
-
tool_calls: [
|
|
1219
|
-
{
|
|
1220
|
-
tool_id: toolCall.toolName,
|
|
1221
|
-
arguments: toolCall.arguments,
|
|
1222
|
-
result: {
|
|
1223
|
-
data: "Tool executed successfully",
|
|
1224
|
-
},
|
|
1225
|
-
},
|
|
1226
|
-
],
|
|
1227
|
-
},
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
// Create updated history with tool results
|
|
1233
|
-
const updatedHistory = [...history, ...toolResultsEvents];
|
|
1234
|
-
|
|
1235
|
-
// Make follow-up AI call to see if more tools are needed
|
|
1236
|
-
const followUpResult = await this.options.provider.generateMessage({
|
|
1237
|
-
prompt: responsePrompt,
|
|
1238
|
-
history: updatedHistory,
|
|
1239
|
-
context: effectiveContext,
|
|
1240
|
-
tools: availableTools,
|
|
1241
|
-
parameters: {
|
|
1242
|
-
jsonSchema: responseSchema as StructuredSchema,
|
|
1243
|
-
schemaName: "tool_followup",
|
|
1244
|
-
},
|
|
1245
|
-
signal,
|
|
1246
|
-
});
|
|
1247
|
-
|
|
1248
|
-
// Check if follow-up call has more tool calls
|
|
1249
|
-
const followUpToolCalls = followUpResult.structured?.toolCalls;
|
|
1250
|
-
hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
|
|
1251
|
-
|
|
1252
|
-
if (hasToolCalls) {
|
|
1253
|
-
logger.debug(
|
|
1254
|
-
`[Agent] Follow-up call produced ${
|
|
1255
|
-
followUpToolCalls!.length
|
|
1256
|
-
} additional tool calls`
|
|
1257
|
-
);
|
|
1258
|
-
|
|
1259
|
-
// Execute the follow-up tool calls
|
|
1260
|
-
for (const toolCall of followUpToolCalls!) {
|
|
1261
|
-
const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
|
|
1262
|
-
if (!tool) {
|
|
1263
|
-
logger.warn(
|
|
1264
|
-
`[Agent] Tool not found in follow-up: ${toolCall.toolName}`
|
|
1265
|
-
);
|
|
1266
|
-
continue;
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
const toolExecutor = new ToolExecutor<TContext, unknown>();
|
|
1270
|
-
const toolResult = await toolExecutor.executeTool({
|
|
1271
|
-
tool: tool,
|
|
1272
|
-
context: effectiveContext,
|
|
1273
|
-
updateContext: this.updateContext.bind(this),
|
|
1274
|
-
history: updatedHistory,
|
|
1275
|
-
data: session.data,
|
|
1276
|
-
toolArguments: toolCall.arguments,
|
|
1277
|
-
});
|
|
1278
|
-
|
|
1279
|
-
// Update context with follow-up tool results
|
|
1280
|
-
if (toolResult.contextUpdate) {
|
|
1281
|
-
await this.updateContext(
|
|
1282
|
-
toolResult.contextUpdate as Partial<TContext>
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
if (toolResult.dataUpdate) {
|
|
1287
|
-
session = await this.updateData(session, toolResult.dataUpdate);
|
|
1288
|
-
logger.debug(
|
|
1289
|
-
`[Agent] Follow-up tool updated collected data:`,
|
|
1290
|
-
toolResult.dataUpdate
|
|
1291
|
-
);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
logger.debug(
|
|
1295
|
-
`[Agent] Executed follow-up tool: ${toolResult.toolName} (success: ${toolResult.success})`
|
|
1296
|
-
);
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
// Update toolCalls for next iteration or final response
|
|
1300
|
-
toolCalls = followUpToolCalls;
|
|
1301
|
-
} else {
|
|
1302
|
-
logger.debug(
|
|
1303
|
-
`[Agent] Tool loop completed after ${toolLoopCount} iterations`
|
|
1304
|
-
);
|
|
1305
|
-
// Update final message and toolCalls from follow-up result if no more tools
|
|
1306
|
-
message = followUpResult.structured?.message || followUpResult.message;
|
|
1307
|
-
toolCalls = followUpToolCalls || [];
|
|
1308
|
-
break;
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
1313
|
-
logger.warn(
|
|
1314
|
-
`[Agent] Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`
|
|
1315
|
-
);
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
// Extract collected data from final response (only for route-based interactions)
|
|
1319
|
-
if (selectedRoute && result.structured && nextStep?.collect) {
|
|
1320
|
-
const collectedData: Record<string, unknown> = {};
|
|
1321
|
-
// The structured response includes both base fields and collected extraction fields
|
|
1322
|
-
const structuredData = result.structured as AgentStructuredResponse &
|
|
1323
|
-
Record<string, unknown>;
|
|
1324
|
-
|
|
1325
|
-
for (const field of nextStep.collect) {
|
|
1326
|
-
if (field in structuredData) {
|
|
1327
|
-
collectedData[field] = structuredData[field];
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
553
|
+
async respond(params: RespondParams<TContext, TData>): Promise<AgentResponse<TData>> {
|
|
554
|
+
// Delegate to ResponseModal
|
|
555
|
+
return this.responseModal.respond(params);
|
|
556
|
+
}
|
|
1330
557
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
558
|
+
/**
|
|
559
|
+
* Get all routes
|
|
560
|
+
*/
|
|
561
|
+
getRoutes(): Route<TContext, TData>[] {
|
|
562
|
+
return [...this.routes];
|
|
563
|
+
}
|
|
1337
564
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
(result.structured as { contextUpdate?: Partial<TContext> })
|
|
1346
|
-
.contextUpdate as Partial<TContext>
|
|
1347
|
-
);
|
|
1348
|
-
}
|
|
565
|
+
/**
|
|
566
|
+
* Get agent options
|
|
567
|
+
* @internal Used by ResponseModal
|
|
568
|
+
*/
|
|
569
|
+
getAgentOptions(): AgentOptions<TContext, TData> {
|
|
570
|
+
return this.options;
|
|
571
|
+
}
|
|
1349
572
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
// Create a temporary step for completion message generation using endStep configuration
|
|
1358
|
-
const completionStep = new Step<TContext, unknown>(selectedRoute!.id, {
|
|
1359
|
-
description: endStepSpec.description,
|
|
1360
|
-
id: endStepSpec.id || END_ROUTE_ID,
|
|
1361
|
-
collect: endStepSpec.collect,
|
|
1362
|
-
requires: endStepSpec.requires,
|
|
1363
|
-
prompt:
|
|
1364
|
-
endStepSpec.prompt ||
|
|
1365
|
-
"Summarize what was accomplished and confirm completion based on the conversation history and collected data",
|
|
1366
|
-
});
|
|
573
|
+
/**
|
|
574
|
+
* Get routing engine
|
|
575
|
+
* @internal Used by ResponseModal
|
|
576
|
+
*/
|
|
577
|
+
getRoutingEngine(): RoutingEngine<TContext, TData> {
|
|
578
|
+
return this.routingEngine;
|
|
579
|
+
}
|
|
1367
580
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
session,
|
|
1376
|
-
history,
|
|
1377
|
-
};
|
|
1378
|
-
if (!selectedRoute) {
|
|
1379
|
-
throw new Error("Selected route is not defined");
|
|
1380
|
-
}
|
|
581
|
+
/**
|
|
582
|
+
* Get the updateData method bound to this agent
|
|
583
|
+
* @internal Used by ResponseModal
|
|
584
|
+
*/
|
|
585
|
+
getUpdateDataMethod(): (session: SessionState<TData>, dataUpdate: Partial<TData>) => Promise<SessionState<TData>> {
|
|
586
|
+
return this.updateData.bind(this);
|
|
587
|
+
}
|
|
1381
588
|
|
|
1382
|
-
// Build completion response prompt
|
|
1383
|
-
const completionPrompt = await this.responseEngine.buildResponsePrompt({
|
|
1384
|
-
route: selectedRoute,
|
|
1385
|
-
currentStep: completionStep,
|
|
1386
|
-
rules: selectedRoute.getRules(),
|
|
1387
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1388
|
-
directives: undefined, // No directives for completion
|
|
1389
|
-
history,
|
|
1390
|
-
lastMessage: lastUserMessage,
|
|
1391
|
-
agentOptions: this.options,
|
|
1392
|
-
// Combine agent and route properties according to the specified logic
|
|
1393
|
-
combinedGuidelines: [
|
|
1394
|
-
...this.getGuidelines(),
|
|
1395
|
-
...selectedRoute.getGuidelines(),
|
|
1396
|
-
],
|
|
1397
|
-
combinedTerms: this.mergeTerms(
|
|
1398
|
-
this.getTerms(),
|
|
1399
|
-
selectedRoute.getTerms()
|
|
1400
|
-
),
|
|
1401
|
-
context: effectiveContext,
|
|
1402
|
-
session,
|
|
1403
|
-
});
|
|
1404
589
|
|
|
1405
|
-
// Generate completion message using AI provider
|
|
1406
|
-
const completionResult = await this.options.provider.generateMessage({
|
|
1407
|
-
prompt: completionPrompt,
|
|
1408
|
-
history,
|
|
1409
|
-
context: effectiveContext,
|
|
1410
|
-
signal,
|
|
1411
|
-
parameters: {
|
|
1412
|
-
jsonSchema: responseSchema,
|
|
1413
|
-
schemaName: "completion_message",
|
|
1414
|
-
},
|
|
1415
|
-
});
|
|
1416
590
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
591
|
+
/**
|
|
592
|
+
* Get all terms
|
|
593
|
+
*/
|
|
594
|
+
getTerms(): Term<TContext, TData>[] {
|
|
595
|
+
return [...this.terms];
|
|
596
|
+
}
|
|
1422
597
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
598
|
+
/**
|
|
599
|
+
* Get all tools
|
|
600
|
+
*/
|
|
601
|
+
getTools(): Tool<TContext, TData, unknown[], unknown>[] {
|
|
602
|
+
return [...this.tools];
|
|
603
|
+
}
|
|
1428
604
|
|
|
1429
|
-
if (transitionConfig) {
|
|
1430
|
-
// Find target route by ID or title
|
|
1431
|
-
const targetRoute = this.routes.find(
|
|
1432
|
-
(r) =>
|
|
1433
|
-
r.id === transitionConfig.nextStep ||
|
|
1434
|
-
r.title === transitionConfig.nextStep
|
|
1435
|
-
);
|
|
1436
605
|
|
|
1437
|
-
if (targetRoute) {
|
|
1438
|
-
const renderedCondition = await render(
|
|
1439
|
-
transitionConfig.condition,
|
|
1440
|
-
templateContext
|
|
1441
|
-
);
|
|
1442
|
-
// Set pending transition in session
|
|
1443
|
-
session = {
|
|
1444
|
-
...session,
|
|
1445
|
-
pendingTransition: {
|
|
1446
|
-
targetRouteId: targetRoute.id,
|
|
1447
|
-
condition: renderedCondition,
|
|
1448
|
-
reason: "route_complete",
|
|
1449
|
-
},
|
|
1450
|
-
};
|
|
1451
|
-
logger.debug(
|
|
1452
|
-
`[Agent] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`
|
|
1453
|
-
);
|
|
1454
|
-
} else {
|
|
1455
|
-
logger.warn(
|
|
1456
|
-
`[Agent] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`
|
|
1457
|
-
);
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
606
|
|
|
1461
|
-
// Set step to END_ROUTE marker
|
|
1462
|
-
session = enterStep(session, END_ROUTE_ID, "Route completed");
|
|
1463
|
-
logger.debug(
|
|
1464
|
-
`[Agent] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`
|
|
1465
|
-
);
|
|
1466
|
-
} else {
|
|
1467
|
-
// Fallback: No routes defined, generate a simple response
|
|
1468
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
1469
|
-
history,
|
|
1470
|
-
agentOptions: this.options,
|
|
1471
|
-
terms: this.terms,
|
|
1472
|
-
guidelines: this.guidelines,
|
|
1473
|
-
context: effectiveContext,
|
|
1474
|
-
session,
|
|
1475
|
-
});
|
|
1476
607
|
|
|
1477
|
-
const result = await this.options.provider.generateMessage({
|
|
1478
|
-
prompt: fallbackPrompt,
|
|
1479
|
-
history,
|
|
1480
|
-
context: effectiveContext,
|
|
1481
|
-
signal,
|
|
1482
|
-
parameters: {
|
|
1483
|
-
jsonSchema: {
|
|
1484
|
-
type: "object",
|
|
1485
|
-
properties: {
|
|
1486
|
-
message: { type: "string" },
|
|
1487
|
-
},
|
|
1488
|
-
required: ["message"],
|
|
1489
|
-
additionalProperties: false,
|
|
1490
|
-
},
|
|
1491
|
-
schemaName: "fallback_response",
|
|
1492
|
-
},
|
|
1493
|
-
});
|
|
1494
608
|
|
|
1495
|
-
message = result.structured?.message || result.message;
|
|
1496
|
-
}
|
|
1497
609
|
|
|
1498
|
-
// Auto-save session step to persistence if configured
|
|
1499
|
-
if (
|
|
1500
|
-
this.persistenceManager &&
|
|
1501
|
-
session.id &&
|
|
1502
|
-
this.options.persistence?.autoSave !== false
|
|
1503
|
-
) {
|
|
1504
|
-
await this.persistenceManager.saveSessionState(session.id, session);
|
|
1505
|
-
logger.debug(
|
|
1506
|
-
`[Agent] Auto-saved session step to persistence: ${session.id}`
|
|
1507
|
-
);
|
|
1508
|
-
}
|
|
1509
610
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
if (currentRoute) {
|
|
1516
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
1517
|
-
if (currentStep?.finalize) {
|
|
1518
|
-
logger.debug(
|
|
1519
|
-
`[Agent] Executing finalize for step: ${currentStep.id}`
|
|
1520
|
-
);
|
|
1521
|
-
await this.executePrepareFinalize(
|
|
1522
|
-
currentStep.finalize,
|
|
1523
|
-
effectiveContext,
|
|
1524
|
-
session.data,
|
|
1525
|
-
currentRoute,
|
|
1526
|
-
currentStep
|
|
1527
|
-
);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// Update current session if we have one
|
|
1533
|
-
if (this.currentSession) {
|
|
1534
|
-
this.currentSession = session;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
return {
|
|
1538
|
-
message,
|
|
1539
|
-
session, // Return updated session with route/step info
|
|
1540
|
-
toolCalls,
|
|
1541
|
-
isRouteComplete, // Indicates if the route has reached END_ROUTE with all data collected
|
|
1542
|
-
};
|
|
611
|
+
/**
|
|
612
|
+
* Get all guidelines
|
|
613
|
+
*/
|
|
614
|
+
getGuidelines(): Guideline<TContext, TData>[] {
|
|
615
|
+
return [...this.guidelines];
|
|
1543
616
|
}
|
|
1544
617
|
|
|
1545
618
|
/**
|
|
1546
|
-
* Get
|
|
619
|
+
* Get the agent's knowledge base
|
|
1547
620
|
*/
|
|
1548
|
-
|
|
1549
|
-
return
|
|
621
|
+
getKnowledgeBase(): Record<string, unknown> {
|
|
622
|
+
return { ...this.knowledgeBase };
|
|
1550
623
|
}
|
|
1551
624
|
|
|
625
|
+
|
|
626
|
+
|
|
1552
627
|
/**
|
|
1553
|
-
* Get
|
|
628
|
+
* Get the persistence manager (if configured)
|
|
1554
629
|
*/
|
|
1555
|
-
|
|
1556
|
-
return
|
|
630
|
+
getPersistenceManager(): PersistenceManager<TData> | undefined {
|
|
631
|
+
return this.persistenceManager;
|
|
1557
632
|
}
|
|
1558
633
|
|
|
1559
634
|
/**
|
|
1560
|
-
*
|
|
635
|
+
* Check if persistence is enabled
|
|
1561
636
|
*/
|
|
1562
|
-
|
|
1563
|
-
return
|
|
637
|
+
hasPersistence(): boolean {
|
|
638
|
+
return this.persistenceManager !== undefined;
|
|
1564
639
|
}
|
|
1565
640
|
|
|
1566
641
|
/**
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
* @private
|
|
642
|
+
* Set the current session for convenience methods
|
|
643
|
+
* @param session - Session step to use for subsequent calls
|
|
1570
644
|
*/
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
route?: Route<TContext, unknown>
|
|
1574
|
-
): Tool<TContext, unknown[], unknown, unknown> | undefined {
|
|
1575
|
-
// Check route-level tools first (if route provided)
|
|
1576
|
-
if (route) {
|
|
1577
|
-
const routeTool = route
|
|
1578
|
-
.getTools()
|
|
1579
|
-
.find((tool) => tool.id === toolName || tool.name === toolName);
|
|
1580
|
-
if (routeTool) return routeTool;
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
// Fall back to agent-level tools
|
|
1584
|
-
return this.tools.find(
|
|
1585
|
-
(tool) => tool.id === toolName || tool.name === toolName
|
|
1586
|
-
);
|
|
645
|
+
setCurrentSession(session: SessionState): void {
|
|
646
|
+
this.currentSession = session;
|
|
1587
647
|
}
|
|
1588
648
|
|
|
1589
649
|
/**
|
|
1590
|
-
*
|
|
1591
|
-
* @private
|
|
650
|
+
* Get the current session (if set)
|
|
1592
651
|
*/
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
step?: Step<TContext, unknown>
|
|
1596
|
-
): Array<{
|
|
1597
|
-
id: string;
|
|
1598
|
-
name: string;
|
|
1599
|
-
description?: string;
|
|
1600
|
-
parameters?: unknown;
|
|
1601
|
-
}> {
|
|
1602
|
-
const availableTools = new Map<
|
|
1603
|
-
string,
|
|
1604
|
-
Tool<TContext, unknown[], unknown, unknown>
|
|
1605
|
-
>();
|
|
1606
|
-
|
|
1607
|
-
// Add agent-level tools
|
|
1608
|
-
this.tools.forEach((tool) => {
|
|
1609
|
-
availableTools.set(tool.id, tool);
|
|
1610
|
-
});
|
|
1611
|
-
|
|
1612
|
-
// Add route-level tools (these take precedence)
|
|
1613
|
-
if (route) {
|
|
1614
|
-
route.getTools().forEach((tool) => {
|
|
1615
|
-
availableTools.set(tool.id, tool);
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
// Filter by step-level allowed tools if specified
|
|
1620
|
-
if (step?.tools) {
|
|
1621
|
-
const allowedToolIds = new Set<string>();
|
|
1622
|
-
const stepTools: Tool<TContext, unknown[], unknown, unknown>[] = [];
|
|
1623
|
-
|
|
1624
|
-
for (const toolRef of step.tools) {
|
|
1625
|
-
if (typeof toolRef === "string") {
|
|
1626
|
-
// Reference to registered tool
|
|
1627
|
-
allowedToolIds.add(toolRef);
|
|
1628
|
-
} else {
|
|
1629
|
-
// Inline tool definition
|
|
1630
|
-
if (toolRef.id) {
|
|
1631
|
-
allowedToolIds.add(toolRef.id);
|
|
1632
|
-
stepTools.push(toolRef);
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
// If step specifies tools, only include those
|
|
1638
|
-
if (allowedToolIds.size > 0) {
|
|
1639
|
-
const filteredTools = new Map<
|
|
1640
|
-
string,
|
|
1641
|
-
Tool<TContext, unknown[], unknown, unknown>
|
|
1642
|
-
>();
|
|
1643
|
-
for (const toolId of allowedToolIds) {
|
|
1644
|
-
const tool = availableTools.get(toolId);
|
|
1645
|
-
if (tool) {
|
|
1646
|
-
filteredTools.set(toolId, tool);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
// Add inline tools
|
|
1650
|
-
stepTools.forEach((tool) => {
|
|
1651
|
-
if (tool.id) {
|
|
1652
|
-
filteredTools.set(tool.id, tool);
|
|
1653
|
-
}
|
|
1654
|
-
});
|
|
1655
|
-
availableTools.clear();
|
|
1656
|
-
filteredTools.forEach((tool, id) => availableTools.set(id, tool));
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// Convert to the format expected by AI providers
|
|
1661
|
-
return Array.from(availableTools.values()).map((tool) => ({
|
|
1662
|
-
id: tool.id,
|
|
1663
|
-
name: tool.name || tool.id,
|
|
1664
|
-
description: tool.description,
|
|
1665
|
-
parameters: tool.parameters,
|
|
1666
|
-
}));
|
|
652
|
+
getCurrentSession(): SessionState | undefined {
|
|
653
|
+
return this.currentSession;
|
|
1667
654
|
}
|
|
1668
655
|
|
|
1669
656
|
/**
|
|
1670
657
|
* Execute a prepare or finalize function/tool
|
|
1671
|
-
* @
|
|
658
|
+
* @internal Used by ResponseModal
|
|
1672
659
|
*/
|
|
1673
|
-
|
|
660
|
+
async executePrepareFinalize(
|
|
1674
661
|
prepareOrFinalize:
|
|
1675
662
|
| string
|
|
1676
|
-
| Tool<TContext, unknown[], unknown
|
|
1677
|
-
| ((context: TContext, data?: Partial<
|
|
663
|
+
| Tool<TContext, TData, unknown[], unknown>
|
|
664
|
+
| ((context: TContext, data?: Partial<TData>) => void | Promise<void>)
|
|
1678
665
|
| undefined,
|
|
1679
666
|
context: TContext,
|
|
1680
|
-
data?: Partial<
|
|
1681
|
-
route?: Route<TContext,
|
|
1682
|
-
step?: Step<TContext,
|
|
667
|
+
data?: Partial<TData>,
|
|
668
|
+
route?: Route<TContext, TData>,
|
|
669
|
+
step?: Step<TContext, TData>
|
|
1683
670
|
): Promise<void> {
|
|
1684
671
|
if (!prepareOrFinalize) return;
|
|
1685
672
|
|
|
@@ -1688,14 +675,11 @@ export class Agent<TContext = unknown> {
|
|
|
1688
675
|
await prepareOrFinalize(context, data);
|
|
1689
676
|
} else {
|
|
1690
677
|
// It's a tool reference - find and execute the tool
|
|
1691
|
-
let tool: Tool<TContext, unknown[], unknown
|
|
678
|
+
let tool: Tool<TContext, TData, unknown[], unknown> | undefined;
|
|
1692
679
|
|
|
1693
680
|
if (typeof prepareOrFinalize === "string") {
|
|
1694
681
|
// Tool ID - find it in available tools
|
|
1695
|
-
const availableTools = new Map<
|
|
1696
|
-
string,
|
|
1697
|
-
Tool<TContext, unknown[], unknown, unknown>
|
|
1698
|
-
>();
|
|
682
|
+
const availableTools = new Map<string, Tool<TContext, TData, unknown[], unknown>>();
|
|
1699
683
|
|
|
1700
684
|
// Add agent-level tools
|
|
1701
685
|
this.tools.forEach((t) => {
|
|
@@ -1727,11 +711,12 @@ export class Agent<TContext = unknown> {
|
|
|
1727
711
|
}
|
|
1728
712
|
|
|
1729
713
|
if (tool) {
|
|
1730
|
-
const toolExecutor = new ToolExecutor<TContext,
|
|
714
|
+
const toolExecutor = new ToolExecutor<TContext, TData>();
|
|
1731
715
|
const result = await toolExecutor.executeTool({
|
|
1732
716
|
tool,
|
|
1733
717
|
context,
|
|
1734
718
|
updateContext: this.updateContext.bind(this),
|
|
719
|
+
updateData: this.updateCollectedData.bind(this),
|
|
1735
720
|
history: [], // Empty history for prepare/finalize
|
|
1736
721
|
data,
|
|
1737
722
|
});
|
|
@@ -1744,86 +729,15 @@ export class Agent<TContext = unknown> {
|
|
|
1744
729
|
}
|
|
1745
730
|
} else {
|
|
1746
731
|
logger.warn(
|
|
1747
|
-
`[Agent] Tool not found for prepare/finalize: ${
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
: "inline tool"
|
|
732
|
+
`[Agent] Tool not found for prepare/finalize: ${typeof prepareOrFinalize === "string"
|
|
733
|
+
? prepareOrFinalize
|
|
734
|
+
: "inline tool"
|
|
1751
735
|
}`
|
|
1752
736
|
);
|
|
1753
737
|
}
|
|
1754
738
|
}
|
|
1755
739
|
}
|
|
1756
740
|
|
|
1757
|
-
/**
|
|
1758
|
-
* Get all guidelines
|
|
1759
|
-
*/
|
|
1760
|
-
getGuidelines(): Guideline<TContext>[] {
|
|
1761
|
-
return [...this.guidelines];
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
/**
|
|
1765
|
-
* Get the agent's knowledge base
|
|
1766
|
-
*/
|
|
1767
|
-
getKnowledgeBase(): Record<string, unknown> {
|
|
1768
|
-
return { ...this.knowledgeBase };
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
/**
|
|
1772
|
-
* Merge terms with route-specific taking precedence on conflicts
|
|
1773
|
-
* @private
|
|
1774
|
-
*/
|
|
1775
|
-
private mergeTerms(
|
|
1776
|
-
agentTerms: Term<TContext>[],
|
|
1777
|
-
routeTerms: Term<TContext>[]
|
|
1778
|
-
): Term<TContext>[] {
|
|
1779
|
-
const merged = new Map<string, Term<TContext>>();
|
|
1780
|
-
|
|
1781
|
-
// Add agent terms first
|
|
1782
|
-
agentTerms.forEach((term) => {
|
|
1783
|
-
const name =
|
|
1784
|
-
typeof term.name === "string" ? term.name : term.name.toString();
|
|
1785
|
-
merged.set(name, term);
|
|
1786
|
-
});
|
|
1787
|
-
|
|
1788
|
-
// Add route terms (these take precedence)
|
|
1789
|
-
routeTerms.forEach((term) => {
|
|
1790
|
-
const name =
|
|
1791
|
-
typeof term.name === "string" ? term.name : term.name.toString();
|
|
1792
|
-
merged.set(name, term);
|
|
1793
|
-
});
|
|
1794
|
-
|
|
1795
|
-
return Array.from(merged.values());
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
/**
|
|
1799
|
-
* Get the persistence manager (if configured)
|
|
1800
|
-
*/
|
|
1801
|
-
getPersistenceManager(): PersistenceManager | undefined {
|
|
1802
|
-
return this.persistenceManager;
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
/**
|
|
1806
|
-
* Check if persistence is enabled
|
|
1807
|
-
*/
|
|
1808
|
-
hasPersistence(): boolean {
|
|
1809
|
-
return this.persistenceManager !== undefined;
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
/**
|
|
1813
|
-
* Set the current session for convenience methods
|
|
1814
|
-
* @param session - Session step to use for subsequent calls
|
|
1815
|
-
*/
|
|
1816
|
-
setCurrentSession(session: SessionState): void {
|
|
1817
|
-
this.currentSession = session;
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
/**
|
|
1821
|
-
* Get the current session (if set)
|
|
1822
|
-
*/
|
|
1823
|
-
getCurrentSession(): SessionState | undefined {
|
|
1824
|
-
return this.currentSession;
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
741
|
/**
|
|
1828
742
|
* Clear the current session
|
|
1829
743
|
*/
|
|
@@ -1832,21 +746,20 @@ export class Agent<TContext = unknown> {
|
|
|
1832
746
|
}
|
|
1833
747
|
|
|
1834
748
|
/**
|
|
1835
|
-
* Get collected data from current session
|
|
749
|
+
* Get collected data from current session or agent-level collected data
|
|
1836
750
|
* @param routeId - Optional route ID to get data for (uses current route if not provided)
|
|
1837
|
-
* @returns The collected data from the current session
|
|
751
|
+
* @returns The collected data from the current session or agent-level data
|
|
1838
752
|
*/
|
|
1839
|
-
getData
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
return (
|
|
1845
|
-
(this.currentSession.dataByRoute?.[routeId] as Partial<TData>) ||
|
|
1846
|
-
({} as Partial<TData>)
|
|
1847
|
-
);
|
|
753
|
+
getData(): Partial<TData> {
|
|
754
|
+
// If we have a current session, use session data
|
|
755
|
+
if (this.currentSession) {
|
|
756
|
+
// With agent-level data, all routes share the same data structure
|
|
757
|
+
// No need for route-specific data access
|
|
758
|
+
return (this.currentSession.data) || {};
|
|
1848
759
|
}
|
|
1849
|
-
|
|
760
|
+
|
|
761
|
+
// Otherwise, return agent-level collected data
|
|
762
|
+
return this.getCollectedData();
|
|
1850
763
|
}
|
|
1851
764
|
|
|
1852
765
|
/**
|
|
@@ -1868,10 +781,10 @@ export class Agent<TContext = unknown> {
|
|
|
1868
781
|
*/
|
|
1869
782
|
async nextStepRoute(
|
|
1870
783
|
routeIdOrTitle: string,
|
|
1871
|
-
session?: SessionState
|
|
1872
|
-
condition?: Template<TContext,
|
|
784
|
+
session?: SessionState<TData>,
|
|
785
|
+
condition?: Template<TContext, TData>,
|
|
1873
786
|
history?: Event[]
|
|
1874
|
-
): Promise<SessionState
|
|
787
|
+
): Promise<SessionState<TData>> {
|
|
1875
788
|
const targetSession = session || this.currentSession;
|
|
1876
789
|
|
|
1877
790
|
if (!targetSession) {
|
|
@@ -1900,12 +813,12 @@ export class Agent<TContext = unknown> {
|
|
|
1900
813
|
};
|
|
1901
814
|
const renderedCondition = await render(condition, templateContext);
|
|
1902
815
|
|
|
1903
|
-
const updatedSession: SessionState = {
|
|
816
|
+
const updatedSession: SessionState<TData> = {
|
|
1904
817
|
...targetSession,
|
|
1905
818
|
pendingTransition: {
|
|
1906
819
|
targetRouteId: targetRoute.id,
|
|
1907
820
|
condition: renderedCondition,
|
|
1908
|
-
reason: "
|
|
821
|
+
reason: "route_complete",
|
|
1909
822
|
},
|
|
1910
823
|
};
|
|
1911
824
|
|
|
@@ -1915,7 +828,7 @@ export class Agent<TContext = unknown> {
|
|
|
1915
828
|
}
|
|
1916
829
|
|
|
1917
830
|
logger.debug(
|
|
1918
|
-
`[Agent] Set pending
|
|
831
|
+
`[Agent] Set pending transition to route: ${targetRoute.title}`
|
|
1919
832
|
);
|
|
1920
833
|
|
|
1921
834
|
return updatedSession;
|
|
@@ -1925,43 +838,27 @@ export class Agent<TContext = unknown> {
|
|
|
1925
838
|
* Simplified respond method using SessionManager
|
|
1926
839
|
* Automatically manages conversation history through the session
|
|
1927
840
|
*/
|
|
1928
|
-
async chat
|
|
841
|
+
async chat(
|
|
1929
842
|
message?: string,
|
|
1930
|
-
options?:
|
|
1931
|
-
history?: History; // Optional: override session history for this response
|
|
1932
|
-
contextOverride?: Partial<TContext>;
|
|
1933
|
-
signal?: AbortSignal;
|
|
1934
|
-
}
|
|
843
|
+
options?: GenerateOptions<TContext>
|
|
1935
844
|
): Promise<AgentResponse<TData>> {
|
|
1936
|
-
//
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
// Use provided history for this response only
|
|
1940
|
-
history = options.history;
|
|
1941
|
-
} else {
|
|
1942
|
-
// Add user message to session history if provided
|
|
1943
|
-
if (message) {
|
|
1944
|
-
await this.session.addMessage("user", message);
|
|
1945
|
-
}
|
|
1946
|
-
history = this.session.getHistory();
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
// Get or create session
|
|
1950
|
-
const session = await this.session.getOrCreate();
|
|
845
|
+
// Delegate to ResponseModal.generate()
|
|
846
|
+
return this.responseModal.generate(message, options);
|
|
847
|
+
}
|
|
1951
848
|
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
849
|
+
/**
|
|
850
|
+
* Modern streaming API - simple interface like chat() but returns a stream
|
|
851
|
+
* Automatically manages conversation history through the session
|
|
852
|
+
*/
|
|
853
|
+
async *stream(
|
|
854
|
+
message?: string,
|
|
855
|
+
options?: StreamOptions<TContext>
|
|
856
|
+
): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
857
|
+
// Delegate to ResponseModal with the same options structure as chat()
|
|
858
|
+
yield* this.responseModal.stream(message, {
|
|
859
|
+
history: options?.history,
|
|
1956
860
|
contextOverride: options?.contextOverride,
|
|
1957
861
|
signal: options?.signal,
|
|
1958
862
|
});
|
|
1959
|
-
|
|
1960
|
-
// Add agent response to session history (only if not using override history)
|
|
1961
|
-
if (!options?.history) {
|
|
1962
|
-
await this.session.addMessage("assistant", result.message);
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
return result as AgentResponse<TData>;
|
|
1966
863
|
}
|
|
1967
|
-
}
|
|
864
|
+
}
|