@falai/agent 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +78 -5
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/Route.d.ts +2 -3
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +8 -7
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/core/SessionManager.d.ts +9 -0
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +11 -0
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +78 -5
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/Route.d.ts +2 -3
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +8 -7
- package/dist/core/Route.js.map +1 -1
- package/dist/core/SessionManager.d.ts +9 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +11 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/package.json +3 -2
- package/src/core/ResponseModal.ts +88 -4
- package/src/core/Route.ts +8 -7
- package/src/core/SessionManager.ts +12 -0
|
@@ -139,6 +139,8 @@ interface ResponseContext<TContext = unknown, TData = unknown> {
|
|
|
139
139
|
batchStoppedReason?: StoppedReason;
|
|
140
140
|
/** Step that caused batch to stop (if applicable) */
|
|
141
141
|
batchStoppedAtStep?: StepOptions<TContext, TData>;
|
|
142
|
+
/** AbortSignal for cancellation propagation */
|
|
143
|
+
signal?: AbortSignal;
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
/**
|
|
@@ -260,20 +262,28 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
260
262
|
|
|
261
263
|
// Stream response using existing respondStream method
|
|
262
264
|
let finalMessage = "";
|
|
265
|
+
let finalizedSession: SessionState<TData> | undefined;
|
|
263
266
|
for await (const chunk of this.respondStream({
|
|
264
267
|
history,
|
|
265
268
|
session,
|
|
266
269
|
contextOverride: options?.contextOverride,
|
|
267
270
|
signal: options?.signal,
|
|
268
271
|
})) {
|
|
269
|
-
// Accumulate the final message
|
|
272
|
+
// Accumulate the final message and capture finalized session
|
|
270
273
|
if (chunk.done) {
|
|
271
274
|
finalMessage = chunk.accumulated;
|
|
275
|
+
finalizedSession = chunk.session;
|
|
272
276
|
}
|
|
273
277
|
|
|
274
278
|
yield chunk;
|
|
275
279
|
}
|
|
276
280
|
|
|
281
|
+
// Sync finalized session to agent.session.current (skip in override-history mode)
|
|
282
|
+
// Must happen BEFORE addMessage so the assistant message is added on top of the synced session state
|
|
283
|
+
if (!options?.history && finalizedSession) {
|
|
284
|
+
this.agent.session.syncSession(finalizedSession);
|
|
285
|
+
}
|
|
286
|
+
|
|
277
287
|
// Add agent response to session history (only if not using override history)
|
|
278
288
|
if (!options?.history && finalMessage) {
|
|
279
289
|
await this.agent.session.addMessage("assistant", finalMessage);
|
|
@@ -320,6 +330,12 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
320
330
|
signal: options?.signal,
|
|
321
331
|
});
|
|
322
332
|
|
|
333
|
+
// Sync finalized session to agent.session.current (skip in override-history mode)
|
|
334
|
+
// Must happen BEFORE addMessage so the assistant message is added on top of the synced session state
|
|
335
|
+
if (!options?.history && result.session) {
|
|
336
|
+
this.agent.session.syncSession(result.session);
|
|
337
|
+
}
|
|
338
|
+
|
|
323
339
|
// Add agent response to session history (only if not using override history)
|
|
324
340
|
if (!options?.history) {
|
|
325
341
|
await this.agent.session.addMessage("assistant", result.message);
|
|
@@ -469,6 +485,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
469
485
|
batchSteps: routingResult.batchSteps,
|
|
470
486
|
batchStoppedReason: routingResult.batchStoppedReason,
|
|
471
487
|
batchStoppedAtStep: routingResult.batchStoppedAtStep,
|
|
488
|
+
signal,
|
|
472
489
|
};
|
|
473
490
|
} catch (error) {
|
|
474
491
|
// Re-throw ResponseGenerationError as-is, wrap others
|
|
@@ -711,6 +728,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
711
728
|
isRouteComplete,
|
|
712
729
|
batchSteps,
|
|
713
730
|
batchStoppedReason,
|
|
731
|
+
signal,
|
|
714
732
|
} = responseContext;
|
|
715
733
|
let session = initialSession;
|
|
716
734
|
|
|
@@ -758,7 +776,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
758
776
|
history,
|
|
759
777
|
context: effectiveContext,
|
|
760
778
|
historyEvents,
|
|
761
|
-
signal
|
|
779
|
+
signal,
|
|
762
780
|
});
|
|
763
781
|
|
|
764
782
|
message = result.message;
|
|
@@ -786,7 +804,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
786
804
|
context: effectiveContext,
|
|
787
805
|
history,
|
|
788
806
|
historyEvents,
|
|
789
|
-
signal
|
|
807
|
+
signal,
|
|
790
808
|
});
|
|
791
809
|
|
|
792
810
|
// Set step to END_ROUTE marker
|
|
@@ -1517,7 +1535,11 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1517
1535
|
}
|
|
1518
1536
|
|
|
1519
1537
|
// Collect data from response
|
|
1520
|
-
|
|
1538
|
+
// Use follow-up structured data from tool loop when available, fall back to original result
|
|
1539
|
+
const dataSource = toolResult.structured
|
|
1540
|
+
? { structured: toolResult.structured }
|
|
1541
|
+
: result;
|
|
1542
|
+
session = await this.collectDataFromResponse({ result: dataSource, selectedRoute, nextStep, session });
|
|
1521
1543
|
|
|
1522
1544
|
return { message, toolCalls, session };
|
|
1523
1545
|
}
|
|
@@ -1769,6 +1791,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1769
1791
|
session: SessionState<TData>;
|
|
1770
1792
|
finalToolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
|
|
1771
1793
|
finalMessage?: string;
|
|
1794
|
+
structured?: AgentStructuredResponse;
|
|
1772
1795
|
}> {
|
|
1773
1796
|
try {
|
|
1774
1797
|
const { context, history, selectedRoute, responsePrompt, availableTools, responseSchema, signal } = params;
|
|
@@ -1857,6 +1880,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1857
1880
|
let toolLoopCount = 0;
|
|
1858
1881
|
let hasToolCalls = toolCalls && toolCalls.length > 0;
|
|
1859
1882
|
let finalMessage: string | undefined;
|
|
1883
|
+
let followUpStructured: AgentStructuredResponse | undefined;
|
|
1860
1884
|
|
|
1861
1885
|
while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
|
|
1862
1886
|
toolLoopCount++;
|
|
@@ -1998,6 +2022,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1998
2022
|
logger.debug(`[ResponseModal] Tool loop completed after ${toolLoopCount} iterations`);
|
|
1999
2023
|
// Update final message and toolCalls from follow-up result if no more tools
|
|
2000
2024
|
finalMessage = followUpResult.structured?.message || followUpResult.message;
|
|
2025
|
+
followUpStructured = followUpResult.structured;
|
|
2001
2026
|
toolCalls = followUpToolCalls || [];
|
|
2002
2027
|
break;
|
|
2003
2028
|
}
|
|
@@ -2007,6 +2032,64 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2007
2032
|
logger.warn(`[ResponseModal] Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`);
|
|
2008
2033
|
}
|
|
2009
2034
|
|
|
2035
|
+
// If tools were executed but no final text message was produced,
|
|
2036
|
+
// make one more LLM call to generate a proper text response from tool results.
|
|
2037
|
+
// This prevents the original tool-invocation message (e.g. "Let me check...")
|
|
2038
|
+
// from being returned as the final user-facing response.
|
|
2039
|
+
if (!finalMessage && toolLoopCount > 0) {
|
|
2040
|
+
logger.debug(`[ResponseModal] No final message after tool loop, making additional LLM call for text response`);
|
|
2041
|
+
|
|
2042
|
+
// Build tool result history for the final call
|
|
2043
|
+
const finalToolResultHistoryItems: HistoryItem[] = [];
|
|
2044
|
+
for (const toolCall of toolCalls || []) {
|
|
2045
|
+
finalToolResultHistoryItems.push({
|
|
2046
|
+
role: "assistant" as const,
|
|
2047
|
+
content: null,
|
|
2048
|
+
tool_calls: [{
|
|
2049
|
+
id: toolCall.toolName,
|
|
2050
|
+
name: toolCall.toolName,
|
|
2051
|
+
arguments: toolCall.arguments,
|
|
2052
|
+
}],
|
|
2053
|
+
});
|
|
2054
|
+
finalToolResultHistoryItems.push({
|
|
2055
|
+
role: "tool" as const,
|
|
2056
|
+
tool_call_id: toolCall.toolName,
|
|
2057
|
+
name: toolCall.toolName,
|
|
2058
|
+
content: toolResultsMap.get(toolCall.toolName) || "Tool executed successfully",
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
const finalHistory = [...history, ...finalToolResultHistoryItems];
|
|
2063
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
2064
|
+
|
|
2065
|
+
try {
|
|
2066
|
+
const textResult = await agentOptions.provider.generateMessage({
|
|
2067
|
+
prompt: responsePrompt + "\n\nProvide a text response to the user based on the tool results. Do not call any tools.",
|
|
2068
|
+
history: finalHistory,
|
|
2069
|
+
context,
|
|
2070
|
+
tools: [], // No tools - force text response
|
|
2071
|
+
parameters: responseSchema ? {
|
|
2072
|
+
jsonSchema: responseSchema,
|
|
2073
|
+
schemaName: "tool_final_text",
|
|
2074
|
+
} : undefined,
|
|
2075
|
+
signal,
|
|
2076
|
+
});
|
|
2077
|
+
|
|
2078
|
+
finalMessage = textResult.structured?.message || textResult.message;
|
|
2079
|
+
if (textResult.structured) {
|
|
2080
|
+
followUpStructured = textResult.structured;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
logger.debug(`[ResponseModal] Generated final text response after tool loop:`, {
|
|
2084
|
+
hasMessage: !!finalMessage,
|
|
2085
|
+
messageLength: finalMessage?.length || 0,
|
|
2086
|
+
});
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
logger.error(`[ResponseModal] Failed to generate final text response after tool loop:`, error);
|
|
2089
|
+
// finalMessage remains undefined; caller will use original message as fallback
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2010
2093
|
logger.debug(`[ResponseModal] Tool loop completed:`, {
|
|
2011
2094
|
totalIterations: toolLoopCount,
|
|
2012
2095
|
hasFinalMessage: !!finalMessage,
|
|
@@ -2018,6 +2101,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2018
2101
|
session,
|
|
2019
2102
|
finalToolCalls: toolCalls,
|
|
2020
2103
|
finalMessage,
|
|
2104
|
+
structured: followUpStructured,
|
|
2021
2105
|
};
|
|
2022
2106
|
} catch (error) {
|
|
2023
2107
|
throw ResponseGenerationError.fromError(error, 'tool_execution', params, {
|
package/src/core/Route.ts
CHANGED
|
@@ -567,9 +567,8 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
567
567
|
* @param data - Currently collected agent-level data
|
|
568
568
|
* @returns true if all required fields are present, false otherwise
|
|
569
569
|
*
|
|
570
|
-
* Note: Routes with no requiredFields
|
|
571
|
-
* based on data
|
|
572
|
-
* are always complete (optional data doesn't block completion).
|
|
570
|
+
* Note: Routes with no requiredFields (whether they have optionalFields or not)
|
|
571
|
+
* are never complete based on data — they can only complete via END_ROUTE.
|
|
573
572
|
*/
|
|
574
573
|
isComplete(data: Partial<TData>): boolean {
|
|
575
574
|
// If route has required fields, check if they're all collected
|
|
@@ -580,9 +579,10 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
580
579
|
});
|
|
581
580
|
}
|
|
582
581
|
|
|
583
|
-
// If route has optional fields but no required fields, it's
|
|
582
|
+
// If route has optional fields but no required fields, it's NOT complete
|
|
583
|
+
// Optional-only routes can only complete via END_ROUTE
|
|
584
584
|
if (this.optionalFields && this.optionalFields.length > 0) {
|
|
585
|
-
return
|
|
585
|
+
return false;
|
|
586
586
|
}
|
|
587
587
|
|
|
588
588
|
// No required or optional fields - route doesn't complete based on data
|
|
@@ -623,9 +623,10 @@ export class Route<TContext = unknown, TData = unknown> {
|
|
|
623
623
|
return completedFields.length / this.requiredFields.length;
|
|
624
624
|
}
|
|
625
625
|
|
|
626
|
-
// If route has optional fields but no required fields, it's
|
|
626
|
+
// If route has optional fields but no required fields, it's NOT complete
|
|
627
|
+
// Optional-only routes can only complete via END_ROUTE, progress is 0
|
|
627
628
|
if (this.optionalFields && this.optionalFields.length > 0) {
|
|
628
|
-
return
|
|
629
|
+
return 0;
|
|
629
630
|
}
|
|
630
631
|
|
|
631
632
|
// No required or optional fields - route doesn't complete based on data
|
|
@@ -298,6 +298,18 @@ export class SessionManager<TData = unknown> {
|
|
|
298
298
|
return newSession;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Sync the session state without triggering persistence save.
|
|
303
|
+
* Used by modern APIs (stream, generate, chat) to push the finalized session
|
|
304
|
+
* back to `currentSession` after completion. Persistence is handled separately
|
|
305
|
+
* by `finalizeSession()`.
|
|
306
|
+
*
|
|
307
|
+
* @internal Called from ResponseModal after stream()/generate() completion
|
|
308
|
+
*/
|
|
309
|
+
syncSession(session: SessionState<TData>): void {
|
|
310
|
+
this.currentSession = session;
|
|
311
|
+
}
|
|
312
|
+
|
|
301
313
|
/**
|
|
302
314
|
* Get the persistence manager (for testing purposes)
|
|
303
315
|
*/
|