@falai/agent 0.6.2 → 0.6.4
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 +89 -56
- package/dist/cjs/constants/index.d.ts +6 -1
- package/dist/cjs/constants/index.d.ts.map +1 -1
- package/dist/cjs/constants/index.js +8 -3
- package/dist/cjs/constants/index.js.map +1 -1
- package/dist/cjs/core/Agent.d.ts +22 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +108 -21
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/Events.d.ts +13 -0
- package/dist/cjs/core/Events.d.ts.map +1 -1
- package/dist/cjs/core/Events.js +28 -14
- package/dist/cjs/core/Events.js.map +1 -1
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +4 -4
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts +6 -1
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/core/RoutingEngine.js +112 -37
- package/dist/cjs/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/core/State.d.ts +15 -5
- package/dist/cjs/core/State.d.ts.map +1 -1
- package/dist/cjs/core/State.js +24 -5
- package/dist/cjs/core/State.js.map +1 -1
- package/dist/cjs/core/Tool.d.ts +8 -1
- package/dist/cjs/core/Tool.d.ts.map +1 -1
- package/dist/cjs/core/Tool.js +25 -28
- package/dist/cjs/core/Tool.js.map +1 -1
- package/dist/cjs/core/Transition.js +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +5 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/route.d.ts +7 -1
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +12 -1
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/session.js +26 -5
- package/dist/cjs/types/session.js.map +1 -1
- package/dist/cjs/utils/logger.d.ts +10 -0
- package/dist/cjs/utils/logger.d.ts.map +1 -0
- package/dist/cjs/utils/logger.js +23 -0
- package/dist/cjs/utils/logger.js.map +1 -0
- package/dist/constants/index.d.ts +6 -1
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +6 -1
- package/dist/constants/index.js.map +1 -1
- package/dist/core/Agent.d.ts +22 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +108 -21
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/Events.d.ts +13 -0
- package/dist/core/Events.d.ts.map +1 -1
- package/dist/core/Events.js +28 -14
- package/dist/core/Events.js.map +1 -1
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +4 -4
- package/dist/core/Route.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts +6 -1
- package/dist/core/RoutingEngine.d.ts.map +1 -1
- package/dist/core/RoutingEngine.js +112 -37
- package/dist/core/RoutingEngine.js.map +1 -1
- package/dist/core/State.d.ts +15 -5
- package/dist/core/State.d.ts.map +1 -1
- package/dist/core/State.js +25 -6
- package/dist/core/State.js.map +1 -1
- package/dist/core/Tool.d.ts +8 -1
- package/dist/core/Tool.d.ts.map +1 -1
- package/dist/core/Tool.js +25 -28
- package/dist/core/Tool.js.map +1 -1
- package/dist/core/Transition.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +5 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js.map +1 -1
- package/dist/types/route.d.ts +7 -1
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/session.d.ts +12 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +26 -5
- package/dist/types/session.js.map +1 -1
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +17 -0
- package/dist/utils/logger.js.map +1 -0
- package/docs/{CONSTRUCTOR_OPTIONS.md → AGENT.md} +79 -7
- package/docs/API_REFERENCE.md +309 -18
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/DOCS.md +46 -22
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/README.md +13 -5
- package/docs/ROUTES.md +743 -0
- package/docs/STATES.md +798 -0
- package/examples/business-onboarding.ts +46 -5
- package/examples/company-qna-agent.ts +107 -1
- package/examples/custom-database-persistence.ts +44 -1
- package/examples/declarative-agent.ts +80 -37
- package/examples/domain-scoping.ts +91 -21
- package/examples/extracted-data-modification.ts +64 -2
- package/examples/healthcare-agent.ts +61 -4
- package/examples/openai-agent.ts +24 -2
- package/examples/opensearch-persistence.ts +26 -1
- package/examples/persistent-onboarding.ts +84 -18
- package/examples/prisma-persistence.ts +90 -16
- package/examples/redis-persistence.ts +89 -17
- package/examples/rules-prohibitions.ts +300 -139
- package/examples/streaming-agent.ts +60 -0
- package/examples/travel-agent.ts +66 -24
- package/package.json +3 -2
- package/src/constants/index.ts +6 -1
- package/src/core/Agent.ts +135 -21
- package/src/core/Events.ts +73 -10
- package/src/core/Route.ts +8 -4
- package/src/core/RoutingEngine.ts +150 -39
- package/src/core/State.ts +35 -10
- package/src/core/Tool.ts +67 -10
- package/src/core/Transition.ts +1 -1
- package/src/index.ts +1 -1
- package/src/types/agent.ts +5 -0
- package/src/types/route.ts +10 -1
- package/src/types/session.ts +42 -6
- package/src/utils/logger.ts +19 -0
package/src/core/Events.ts
CHANGED
|
@@ -81,17 +81,80 @@ export function createMessageEvent(
|
|
|
81
81
|
extracted?: Record<string, unknown>;
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
-
): Event<MessageEventData
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
84
|
+
): Event<MessageEventData>;
|
|
85
|
+
export function createMessageEvent(options: {
|
|
86
|
+
source: EventSource;
|
|
87
|
+
participantName: string;
|
|
88
|
+
message: string;
|
|
89
|
+
timestamp?: string;
|
|
90
|
+
session?: {
|
|
91
|
+
routeId?: string;
|
|
92
|
+
routeTitle?: string;
|
|
93
|
+
stateId?: string;
|
|
94
|
+
stateDescription?: string;
|
|
95
|
+
extracted?: Record<string, unknown>;
|
|
94
96
|
};
|
|
97
|
+
}): Event<MessageEventData>;
|
|
98
|
+
export function createMessageEvent(
|
|
99
|
+
sourceOrOptions:
|
|
100
|
+
| EventSource
|
|
101
|
+
| {
|
|
102
|
+
source: EventSource;
|
|
103
|
+
participantName: string;
|
|
104
|
+
message: string;
|
|
105
|
+
timestamp?: string;
|
|
106
|
+
session?: {
|
|
107
|
+
routeId?: string;
|
|
108
|
+
routeTitle?: string;
|
|
109
|
+
stateId?: string;
|
|
110
|
+
stateDescription?: string;
|
|
111
|
+
extracted?: Record<string, unknown>;
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
participantName?: string,
|
|
115
|
+
message?: string,
|
|
116
|
+
options?: {
|
|
117
|
+
timestamp?: string;
|
|
118
|
+
session?: {
|
|
119
|
+
routeId?: string;
|
|
120
|
+
routeTitle?: string;
|
|
121
|
+
stateId?: string;
|
|
122
|
+
stateDescription?: string;
|
|
123
|
+
extracted?: Record<string, unknown>;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
): Event<MessageEventData> {
|
|
127
|
+
if (typeof sourceOrOptions === "object") {
|
|
128
|
+
// New signature: createMessageEvent(options)
|
|
129
|
+
const {
|
|
130
|
+
source,
|
|
131
|
+
participantName: pName,
|
|
132
|
+
message: msg,
|
|
133
|
+
...restOptions
|
|
134
|
+
} = sourceOrOptions;
|
|
135
|
+
return {
|
|
136
|
+
kind: EventKind.MESSAGE,
|
|
137
|
+
source,
|
|
138
|
+
data: {
|
|
139
|
+
participant: { display_name: pName },
|
|
140
|
+
message: msg,
|
|
141
|
+
session: restOptions.session,
|
|
142
|
+
},
|
|
143
|
+
timestamp: restOptions.timestamp || new Date().toISOString(),
|
|
144
|
+
};
|
|
145
|
+
} else {
|
|
146
|
+
// Original signature: createMessageEvent(source, participantName, message, options)
|
|
147
|
+
return {
|
|
148
|
+
kind: EventKind.MESSAGE,
|
|
149
|
+
source: sourceOrOptions,
|
|
150
|
+
data: {
|
|
151
|
+
participant: { display_name: participantName! },
|
|
152
|
+
message: message!,
|
|
153
|
+
session: options?.session,
|
|
154
|
+
},
|
|
155
|
+
timestamp: options?.timestamp || new Date().toISOString(),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
95
158
|
}
|
|
96
159
|
|
|
97
160
|
/**
|
package/src/core/Route.ts
CHANGED
|
@@ -43,7 +43,11 @@ export class Route<TContext = unknown, TExtracted = unknown> {
|
|
|
43
43
|
this.prohibitions = options.prohibitions || ([] as string[]);
|
|
44
44
|
this.initialState = new State<TContext, TExtracted>(
|
|
45
45
|
this.id,
|
|
46
|
-
"Initial state"
|
|
46
|
+
options.initialState?.chatState || "Initial state",
|
|
47
|
+
options.initialState?.id,
|
|
48
|
+
options.initialState?.gather,
|
|
49
|
+
options.initialState?.skipIf,
|
|
50
|
+
options.initialState?.requiredData
|
|
47
51
|
);
|
|
48
52
|
this.routingExtrasSchema = options.routingExtrasSchema;
|
|
49
53
|
this.responseOutputSchema = options.responseOutputSchema;
|
|
@@ -70,8 +74,8 @@ export class Route<TContext = unknown, TExtracted = unknown> {
|
|
|
70
74
|
private buildSequentialSteps(
|
|
71
75
|
steps: Array<TransitionSpec<TContext, TExtracted>>
|
|
72
76
|
): void {
|
|
73
|
-
// Import
|
|
74
|
-
const
|
|
77
|
+
// Import END_STATE dynamically to avoid circular dependency
|
|
78
|
+
const END_STATE = Symbol.for("END_STATE");
|
|
75
79
|
|
|
76
80
|
let currentState: TransitionResult<TContext, TExtracted> =
|
|
77
81
|
this.initialState;
|
|
@@ -81,7 +85,7 @@ export class Route<TContext = unknown, TExtracted = unknown> {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
// End the route
|
|
84
|
-
currentState.transitionTo({ state:
|
|
88
|
+
currentState.transitionTo({ state: END_STATE });
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
/**
|
|
@@ -8,6 +8,7 @@ import type { AiProvider } from "../types/ai";
|
|
|
8
8
|
import { enterRoute, mergeExtracted } from "../types/session";
|
|
9
9
|
import { PromptComposer } from "./PromptComposer";
|
|
10
10
|
import { getLastMessageFromHistory } from "../utils/event";
|
|
11
|
+
import { logger } from "../utils/logger";
|
|
11
12
|
|
|
12
13
|
export interface RoutingDecisionOutput {
|
|
13
14
|
context: string;
|
|
@@ -69,12 +70,12 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
69
70
|
updatedSession = enterRoute(session, route.id, route.title);
|
|
70
71
|
if (route.initialData) {
|
|
71
72
|
updatedSession = mergeExtracted(updatedSession, route.initialData);
|
|
72
|
-
|
|
73
|
+
logger.debug(
|
|
73
74
|
`[RoutingEngine] Single-route: Merged initial data:`,
|
|
74
75
|
route.initialData
|
|
75
76
|
);
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
+
logger.debug(
|
|
78
79
|
`[RoutingEngine] Single-route: Entered route: ${route.title}`
|
|
79
80
|
);
|
|
80
81
|
}
|
|
@@ -90,7 +91,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
90
91
|
);
|
|
91
92
|
|
|
92
93
|
if (candidates.length === 0) {
|
|
93
|
-
|
|
94
|
+
logger.warn(`[RoutingEngine] Single-route: No valid states found`);
|
|
94
95
|
return { selectedRoute, session: updatedSession };
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -98,20 +99,27 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
98
99
|
if (candidates.length === 1) {
|
|
99
100
|
const isRouteComplete = candidates[0].isRouteComplete;
|
|
100
101
|
if (isRouteComplete) {
|
|
101
|
-
|
|
102
|
-
`[RoutingEngine] Single-route: Route complete - all data collected`
|
|
102
|
+
logger.debug(
|
|
103
|
+
`[RoutingEngine] Single-route: Route complete - all data collected, END_STATE reached`
|
|
103
104
|
);
|
|
105
|
+
// Don't return a selectedState when route is complete - there's no state to enter
|
|
106
|
+
return {
|
|
107
|
+
selectedRoute,
|
|
108
|
+
selectedState: undefined,
|
|
109
|
+
session: updatedSession,
|
|
110
|
+
isRouteComplete: true,
|
|
111
|
+
};
|
|
104
112
|
} else {
|
|
105
|
-
|
|
113
|
+
logger.debug(
|
|
106
114
|
`[RoutingEngine] Single-route: Only one valid state: ${candidates[0].state.id}`
|
|
107
115
|
);
|
|
116
|
+
return {
|
|
117
|
+
selectedRoute,
|
|
118
|
+
selectedState: candidates[0].state,
|
|
119
|
+
session: updatedSession,
|
|
120
|
+
isRouteComplete: false,
|
|
121
|
+
};
|
|
108
122
|
}
|
|
109
|
-
return {
|
|
110
|
-
selectedRoute,
|
|
111
|
-
selectedState: candidates[0].state,
|
|
112
|
-
session: updatedSession,
|
|
113
|
-
isRouteComplete,
|
|
114
|
-
};
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
// Multiple candidates - use AI to select best state
|
|
@@ -154,14 +162,14 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
154
162
|
)?.state;
|
|
155
163
|
|
|
156
164
|
if (selectedState) {
|
|
157
|
-
|
|
165
|
+
logger.debug(
|
|
158
166
|
`[RoutingEngine] Single-route: AI selected state: ${selectedState.id}`
|
|
159
167
|
);
|
|
160
|
-
|
|
168
|
+
logger.debug(
|
|
161
169
|
`[RoutingEngine] Single-route: Reasoning: ${stateResult.structured?.reasoning}`
|
|
162
170
|
);
|
|
163
171
|
} else {
|
|
164
|
-
|
|
172
|
+
logger.warn(
|
|
165
173
|
`[RoutingEngine] Single-route: Invalid state ID returned, using first candidate`
|
|
166
174
|
);
|
|
167
175
|
}
|
|
@@ -174,9 +182,76 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
174
182
|
};
|
|
175
183
|
}
|
|
176
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Recursively traverse state chain to find first non-skipped state or END_STATE
|
|
187
|
+
* @private
|
|
188
|
+
*/
|
|
189
|
+
private findFirstValidStateRecursive<TExtracted = unknown>(
|
|
190
|
+
currentState: State<TContext, TExtracted>,
|
|
191
|
+
extracted: Partial<TExtracted>,
|
|
192
|
+
visited: Set<string>
|
|
193
|
+
): {
|
|
194
|
+
state?: State<TContext, TExtracted>;
|
|
195
|
+
condition?: string;
|
|
196
|
+
isRouteComplete?: boolean;
|
|
197
|
+
} {
|
|
198
|
+
// Prevent infinite loops
|
|
199
|
+
if (visited.has(currentState.id)) {
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
202
|
+
visited.add(currentState.id);
|
|
203
|
+
|
|
204
|
+
const transitions = currentState.getTransitions();
|
|
205
|
+
|
|
206
|
+
for (const transition of transitions) {
|
|
207
|
+
const target = transition.getTarget();
|
|
208
|
+
|
|
209
|
+
// Check for END_STATE transition
|
|
210
|
+
if (
|
|
211
|
+
!target &&
|
|
212
|
+
transition.spec.state &&
|
|
213
|
+
typeof transition.spec.state === "symbol"
|
|
214
|
+
) {
|
|
215
|
+
// Found END_STATE - route is complete
|
|
216
|
+
return { isRouteComplete: true };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!target) continue;
|
|
220
|
+
|
|
221
|
+
// If target should NOT be skipped, we found our state
|
|
222
|
+
if (!target.shouldSkip(extracted)) {
|
|
223
|
+
logger.debug(
|
|
224
|
+
`[RoutingEngine] Found valid state after skipping: ${target.id}`
|
|
225
|
+
);
|
|
226
|
+
return {
|
|
227
|
+
state: target,
|
|
228
|
+
condition: transition.condition,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Target should be skipped too - recurse deeper
|
|
233
|
+
logger.debug(
|
|
234
|
+
`[RoutingEngine] Skipping state ${target.id} (skipIf condition met), continuing traversal...`
|
|
235
|
+
);
|
|
236
|
+
const result = this.findFirstValidStateRecursive(
|
|
237
|
+
target,
|
|
238
|
+
extracted,
|
|
239
|
+
visited
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// If we found something (a valid state or END_STATE), return it
|
|
243
|
+
if (result.state || result.isRouteComplete) {
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// No valid states or END_STATE found in this branch
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
|
|
177
252
|
/**
|
|
178
253
|
* Identify valid next candidate states based on current state and extracted data
|
|
179
|
-
* Returns state with isRouteComplete flag if route is complete (all states skipped + has
|
|
254
|
+
* Returns state with isRouteComplete flag if route is complete (all states skipped + has END_STATE transition)
|
|
180
255
|
*/
|
|
181
256
|
getCandidateStates<TExtracted = unknown>(
|
|
182
257
|
route: Route<TContext, TExtracted>,
|
|
@@ -200,18 +275,35 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
200
275
|
if (!currentState) {
|
|
201
276
|
const initialState = route.initialState;
|
|
202
277
|
if (initialState.shouldSkip(extracted)) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
278
|
+
// Initial state should be skipped - recursively traverse to find first non-skipped state or END_STATE
|
|
279
|
+
const result = this.findFirstValidStateRecursive(
|
|
280
|
+
initialState,
|
|
281
|
+
extracted,
|
|
282
|
+
new Set<string>()
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (result.isRouteComplete) {
|
|
286
|
+
// All states are skipped and we reached END_STATE
|
|
287
|
+
logger.debug(
|
|
288
|
+
`[RoutingEngine] Route complete on entry: all states skipped, END_STATE reached`
|
|
289
|
+
);
|
|
290
|
+
return [
|
|
291
|
+
{
|
|
292
|
+
state: initialState,
|
|
293
|
+
condition: "Route complete - all data collected on entry",
|
|
294
|
+
isRouteComplete: true,
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
} else if (result.state) {
|
|
298
|
+
// Found a non-skipped state
|
|
299
|
+
candidates.push({
|
|
300
|
+
state: result.state,
|
|
301
|
+
condition: result.condition,
|
|
302
|
+
requiredData: result.state.requiredData,
|
|
303
|
+
gatherFields: result.state.gatherFields,
|
|
304
|
+
});
|
|
214
305
|
}
|
|
306
|
+
// If no state found and not complete, fall through to return empty candidates
|
|
215
307
|
} else {
|
|
216
308
|
candidates.push({
|
|
217
309
|
state: initialState,
|
|
@@ -228,7 +320,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
228
320
|
for (const transition of transitions) {
|
|
229
321
|
const target = transition.getTarget();
|
|
230
322
|
|
|
231
|
-
// Check for
|
|
323
|
+
// Check for END_STATE transition (no target state)
|
|
232
324
|
if (
|
|
233
325
|
!target &&
|
|
234
326
|
transition.spec.state &&
|
|
@@ -241,9 +333,28 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
241
333
|
if (!target) continue;
|
|
242
334
|
|
|
243
335
|
if (target.shouldSkip(extracted)) {
|
|
244
|
-
|
|
336
|
+
logger.debug(
|
|
245
337
|
`[RoutingEngine] Skipping state ${target.id} (skipIf condition met)`
|
|
246
338
|
);
|
|
339
|
+
|
|
340
|
+
// Recursively traverse to find next valid state or END_STATE
|
|
341
|
+
const result = this.findFirstValidStateRecursive(
|
|
342
|
+
target,
|
|
343
|
+
extracted,
|
|
344
|
+
new Set<string>([currentState.id]) // Already visited current state
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if (result.isRouteComplete) {
|
|
348
|
+
hasEndRoute = true;
|
|
349
|
+
} else if (result.state) {
|
|
350
|
+
// Found a non-skipped state deeper in the chain
|
|
351
|
+
candidates.push({
|
|
352
|
+
state: result.state,
|
|
353
|
+
condition: result.condition || transition.condition,
|
|
354
|
+
requiredData: result.state.requiredData,
|
|
355
|
+
gatherFields: result.state.gatherFields,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
247
358
|
continue;
|
|
248
359
|
}
|
|
249
360
|
|
|
@@ -257,10 +368,10 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
257
368
|
|
|
258
369
|
// If no valid candidates found
|
|
259
370
|
if (candidates.length === 0) {
|
|
260
|
-
// If current state has
|
|
371
|
+
// If current state has END_STATE transition, the route is complete
|
|
261
372
|
if (hasEndRoute) {
|
|
262
|
-
|
|
263
|
-
`[RoutingEngine] Route complete: all states processed,
|
|
373
|
+
logger.debug(
|
|
374
|
+
`[RoutingEngine] Route complete: all states processed, END_STATE reached`
|
|
264
375
|
);
|
|
265
376
|
// Return current state with completion flag
|
|
266
377
|
return [
|
|
@@ -360,7 +471,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
360
471
|
// Check if route is complete
|
|
361
472
|
if (candidates.length === 1 && candidates[0].isRouteComplete) {
|
|
362
473
|
isRouteComplete = true;
|
|
363
|
-
|
|
474
|
+
logger.debug(
|
|
364
475
|
`[RoutingEngine] Route ${activeRoute.title} is complete - all data collected`
|
|
365
476
|
);
|
|
366
477
|
// Don't include states in routing if route is complete
|
|
@@ -373,7 +484,7 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
373
484
|
requiredData: c.requiredData,
|
|
374
485
|
gatherFields: c.gatherFields,
|
|
375
486
|
}));
|
|
376
|
-
|
|
487
|
+
logger.debug(
|
|
377
488
|
`[RoutingEngine] Found ${activeRouteStates.length} candidate states for active route`
|
|
378
489
|
);
|
|
379
490
|
}
|
|
@@ -432,17 +543,17 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
432
543
|
routingResult.structured.selectedStateId
|
|
433
544
|
);
|
|
434
545
|
if (selectedState) {
|
|
435
|
-
|
|
546
|
+
logger.debug(
|
|
436
547
|
`[RoutingEngine] AI selected state: ${selectedState.id} in active route`
|
|
437
548
|
);
|
|
438
|
-
|
|
549
|
+
logger.debug(
|
|
439
550
|
`[RoutingEngine] State reasoning: ${routingResult.structured.stateReasoning}`
|
|
440
551
|
);
|
|
441
552
|
}
|
|
442
553
|
}
|
|
443
554
|
|
|
444
555
|
if (selectedRoute) {
|
|
445
|
-
|
|
556
|
+
logger.debug(`[RoutingEngine] Selected route: ${selectedRoute.title}`);
|
|
446
557
|
if (
|
|
447
558
|
!session.currentRoute ||
|
|
448
559
|
session.currentRoute.id !== selectedRoute.id
|
|
@@ -457,12 +568,12 @@ export class RoutingEngine<TContext = unknown> {
|
|
|
457
568
|
updatedSession,
|
|
458
569
|
selectedRoute.initialData
|
|
459
570
|
);
|
|
460
|
-
|
|
571
|
+
logger.debug(
|
|
461
572
|
`[RoutingEngine] Merged initial data:`,
|
|
462
573
|
selectedRoute.initialData
|
|
463
574
|
);
|
|
464
575
|
}
|
|
465
|
-
|
|
576
|
+
logger.debug(`[RoutingEngine] Entered route: ${selectedRoute.title}`);
|
|
466
577
|
}
|
|
467
578
|
}
|
|
468
579
|
}
|
package/src/core/State.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
} from "../types/route";
|
|
10
10
|
import type { Guideline } from "../types/agent";
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { END_STATE } from "../constants";
|
|
13
13
|
import { Transition } from "./Transition";
|
|
14
14
|
import { generateStateId } from "../utils/id";
|
|
15
15
|
|
|
@@ -20,13 +20,13 @@ export class State<TContext = unknown, TExtracted = unknown> {
|
|
|
20
20
|
public readonly id: string;
|
|
21
21
|
private transitions: Transition<TContext, TExtracted>[] = [];
|
|
22
22
|
private guidelines: Guideline[] = [];
|
|
23
|
-
public
|
|
24
|
-
public
|
|
25
|
-
public
|
|
23
|
+
public gatherFields?: string[];
|
|
24
|
+
public skipIf?: (extracted: Partial<TExtracted>) => boolean;
|
|
25
|
+
public requiredData?: string[];
|
|
26
26
|
|
|
27
27
|
constructor(
|
|
28
28
|
public readonly routeId: string,
|
|
29
|
-
public
|
|
29
|
+
public description?: string,
|
|
30
30
|
customId?: string,
|
|
31
31
|
gatherFields?: string[],
|
|
32
32
|
skipIf?: (extracted: Partial<TExtracted>) => boolean,
|
|
@@ -39,6 +39,31 @@ export class State<TContext = unknown, TExtracted = unknown> {
|
|
|
39
39
|
this.requiredData = requiredData;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Configure the state properties after creation
|
|
44
|
+
* Useful for overriding initial state configuration
|
|
45
|
+
*/
|
|
46
|
+
configure(config: {
|
|
47
|
+
description?: string;
|
|
48
|
+
gatherFields?: string[];
|
|
49
|
+
skipIf?: (extracted: Partial<TExtracted>) => boolean;
|
|
50
|
+
requiredData?: string[];
|
|
51
|
+
}): this {
|
|
52
|
+
if (config.description !== undefined) {
|
|
53
|
+
this.description = config.description;
|
|
54
|
+
}
|
|
55
|
+
if (config.gatherFields !== undefined) {
|
|
56
|
+
this.gatherFields = config.gatherFields;
|
|
57
|
+
}
|
|
58
|
+
if (config.skipIf !== undefined) {
|
|
59
|
+
this.skipIf = config.skipIf;
|
|
60
|
+
}
|
|
61
|
+
if (config.requiredData !== undefined) {
|
|
62
|
+
this.requiredData = config.requiredData;
|
|
63
|
+
}
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
/**
|
|
43
68
|
* Create a transition from this state to another
|
|
44
69
|
*
|
|
@@ -48,15 +73,15 @@ export class State<TContext = unknown, TExtracted = unknown> {
|
|
|
48
73
|
transitionTo(
|
|
49
74
|
spec: TransitionSpec<TContext, TExtracted>
|
|
50
75
|
): TransitionResult<TContext, TExtracted> {
|
|
51
|
-
// Handle
|
|
76
|
+
// Handle END_STATE
|
|
52
77
|
if (
|
|
53
78
|
spec.state &&
|
|
54
79
|
typeof spec.state === "symbol" &&
|
|
55
|
-
spec.state ===
|
|
80
|
+
spec.state === END_STATE
|
|
56
81
|
) {
|
|
57
82
|
const endTransition = new Transition<TContext, TExtracted>(
|
|
58
83
|
this.getRef(),
|
|
59
|
-
{ state:
|
|
84
|
+
{ state: END_STATE, condition: spec.condition }
|
|
60
85
|
);
|
|
61
86
|
this.transitions.push(endTransition);
|
|
62
87
|
|
|
@@ -161,7 +186,7 @@ export class State<TContext = unknown, TExtracted = unknown> {
|
|
|
161
186
|
}
|
|
162
187
|
|
|
163
188
|
/**
|
|
164
|
-
* Create a terminal state reference (for
|
|
189
|
+
* Create a terminal state reference (for END_STATE)
|
|
165
190
|
*/
|
|
166
191
|
private createTerminalRef(): TransitionResult<TContext, TExtracted> {
|
|
167
192
|
const terminalRef: StateRef = {
|
|
@@ -172,7 +197,7 @@ export class State<TContext = unknown, TExtracted = unknown> {
|
|
|
172
197
|
return {
|
|
173
198
|
...terminalRef,
|
|
174
199
|
transitionTo: () => {
|
|
175
|
-
throw new Error("Cannot transition from
|
|
200
|
+
throw new Error("Cannot transition from END_STATE state");
|
|
176
201
|
},
|
|
177
202
|
};
|
|
178
203
|
}
|
package/src/core/Tool.ts
CHANGED
|
@@ -28,7 +28,11 @@ import { generateToolId } from "../utils/id";
|
|
|
28
28
|
* );
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
export function defineTool<
|
|
31
|
+
export function defineTool<
|
|
32
|
+
TContext = unknown,
|
|
33
|
+
TArgs extends unknown[] = unknown[],
|
|
34
|
+
TResult = unknown
|
|
35
|
+
>(
|
|
32
36
|
name: string,
|
|
33
37
|
handler: ToolHandler<TContext, TArgs, TResult>,
|
|
34
38
|
options?: {
|
|
@@ -36,17 +40,70 @@ export function defineTool<TContext, TArgs extends unknown[], TResult>(
|
|
|
36
40
|
description?: string;
|
|
37
41
|
parameters?: unknown;
|
|
38
42
|
}
|
|
43
|
+
): ToolRef<TContext, TArgs, TResult>;
|
|
44
|
+
export function defineTool<
|
|
45
|
+
TContext = unknown,
|
|
46
|
+
TArgs extends unknown[] = unknown[],
|
|
47
|
+
TResult = unknown
|
|
48
|
+
>(options: {
|
|
49
|
+
name: string;
|
|
50
|
+
handler: ToolHandler<TContext, TArgs, TResult>;
|
|
51
|
+
id?: string;
|
|
52
|
+
description?: string;
|
|
53
|
+
parameters?: unknown;
|
|
54
|
+
}): ToolRef<TContext, TArgs, TResult>;
|
|
55
|
+
export function defineTool<
|
|
56
|
+
TContext = unknown,
|
|
57
|
+
TArgs extends unknown[] = unknown[],
|
|
58
|
+
TResult = unknown
|
|
59
|
+
>(
|
|
60
|
+
nameOrOptions:
|
|
61
|
+
| string
|
|
62
|
+
| {
|
|
63
|
+
name: string;
|
|
64
|
+
handler: ToolHandler<TContext, TArgs, TResult>;
|
|
65
|
+
id?: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
parameters?: unknown;
|
|
68
|
+
},
|
|
69
|
+
handler?: ToolHandler<TContext, TArgs, TResult>,
|
|
70
|
+
options?: {
|
|
71
|
+
id?: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
parameters?: unknown;
|
|
74
|
+
}
|
|
39
75
|
): ToolRef<TContext, TArgs, TResult> {
|
|
40
|
-
|
|
41
|
-
|
|
76
|
+
if (typeof nameOrOptions === "string") {
|
|
77
|
+
// Original signature: defineTool(name, handler, options)
|
|
78
|
+
const name = nameOrOptions;
|
|
79
|
+
const id = options?.id || generateToolId(name);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
id,
|
|
83
|
+
name,
|
|
84
|
+
handler: handler!,
|
|
85
|
+
description: options?.description,
|
|
86
|
+
parameters: options?.parameters,
|
|
87
|
+
};
|
|
88
|
+
} else {
|
|
89
|
+
// New signature: defineTool(options)
|
|
90
|
+
const {
|
|
91
|
+
name,
|
|
92
|
+
handler: newHandler,
|
|
93
|
+
id,
|
|
94
|
+
description,
|
|
95
|
+
parameters,
|
|
96
|
+
} = nameOrOptions;
|
|
97
|
+
const toolId = id || generateToolId(name);
|
|
42
98
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
99
|
+
return {
|
|
100
|
+
id: toolId,
|
|
101
|
+
name,
|
|
102
|
+
handler: newHandler,
|
|
103
|
+
description,
|
|
104
|
+
parameters,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
50
107
|
}
|
|
51
108
|
|
|
52
109
|
/**
|
package/src/core/Transition.ts
CHANGED
|
@@ -55,7 +55,7 @@ export class Transition<TContext = unknown, TExtracted = unknown> {
|
|
|
55
55
|
}
|
|
56
56
|
if (this.spec.state) {
|
|
57
57
|
if (typeof this.spec.state === "symbol") {
|
|
58
|
-
parts.push("state:
|
|
58
|
+
parts.push("state: END_STATE");
|
|
59
59
|
} else {
|
|
60
60
|
parts.push(`state: ${this.spec.state.id}`);
|
|
61
61
|
}
|
package/src/index.ts
CHANGED
|
@@ -62,7 +62,7 @@ export type {
|
|
|
62
62
|
} from "./adapters/OpenSearchAdapter";
|
|
63
63
|
|
|
64
64
|
// Constants
|
|
65
|
-
export {
|
|
65
|
+
export { END_STATE, END_STATE_ID } from "./constants";
|
|
66
66
|
|
|
67
67
|
// Utils
|
|
68
68
|
export { generateRouteId, generateStateId, generateToolId } from "./utils/id";
|
package/src/types/agent.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { AiProvider } from "./ai";
|
|
|
6
6
|
import type { ToolRef } from "./tool";
|
|
7
7
|
import type { RouteOptions } from "./route";
|
|
8
8
|
import type { PersistenceConfig } from "./persistence";
|
|
9
|
+
import type { SessionState } from "./session";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Composition mode determines how the agent processes and structures responses
|
|
@@ -74,8 +75,12 @@ export interface AgentOptions<TContext = unknown> {
|
|
|
74
75
|
goal?: string;
|
|
75
76
|
/** Optional personality/tone instructions used in prompts */
|
|
76
77
|
personality?: string;
|
|
78
|
+
/** Enable debug logging */
|
|
79
|
+
debug?: boolean;
|
|
77
80
|
/** Default context data available to the agent */
|
|
78
81
|
context?: TContext;
|
|
82
|
+
/** Optional current session for convenience methods */
|
|
83
|
+
session?: SessionState;
|
|
79
84
|
/** Context provider function for always-fresh context (alternative to static context) */
|
|
80
85
|
contextProvider?: ContextProvider<TContext>;
|
|
81
86
|
/** Lifecycle hooks for context management */
|
package/src/types/route.ts
CHANGED
|
@@ -66,10 +66,19 @@ export interface RouteOptions<TExtracted = unknown> {
|
|
|
66
66
|
initialData?: Partial<TExtracted>;
|
|
67
67
|
/**
|
|
68
68
|
* NEW: Sequential steps for simple linear flows
|
|
69
|
-
* If provided, automatically chains the steps from initialState to
|
|
69
|
+
* If provided, automatically chains the steps from initialState to END_STATE
|
|
70
70
|
* For complex flows with branching, build the state machine manually instead
|
|
71
71
|
*/
|
|
72
72
|
steps?: TransitionSpec<unknown, TExtracted>[];
|
|
73
|
+
/**
|
|
74
|
+
* Configure the initial state (optional)
|
|
75
|
+
* Accepts full TransitionSpec configuration (id, chatState, gather, skipIf, etc.)
|
|
76
|
+
* Note: toolState and state properties are ignored for initial state
|
|
77
|
+
*/
|
|
78
|
+
initialState?: Omit<
|
|
79
|
+
TransitionSpec<unknown, TExtracted>,
|
|
80
|
+
"toolState" | "state" | "condition"
|
|
81
|
+
>;
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
/**
|