@falai/agent 0.4.0 → 0.5.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 +21 -74
- package/dist/cjs/core/Agent.d.ts +22 -29
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +465 -275
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/Events.d.ts +10 -1
- package/dist/cjs/core/Events.d.ts.map +1 -1
- package/dist/cjs/core/Events.js +3 -2
- package/dist/cjs/core/Events.js.map +1 -1
- package/dist/cjs/core/PersistenceManager.d.ts +19 -0
- package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/core/PersistenceManager.js +57 -0
- package/dist/cjs/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/core/PromptComposer.d.ts +24 -0
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -0
- package/dist/cjs/core/PromptComposer.js +127 -0
- package/dist/cjs/core/PromptComposer.js.map +1 -0
- package/dist/cjs/core/ResponseEngine.d.ts +19 -0
- package/dist/cjs/core/ResponseEngine.d.ts.map +1 -0
- package/dist/cjs/core/ResponseEngine.js +51 -0
- package/dist/cjs/core/ResponseEngine.js.map +1 -0
- package/dist/cjs/core/Route.d.ts +18 -12
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +15 -9
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts +38 -0
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -0
- package/dist/cjs/core/RoutingEngine.js +110 -0
- package/dist/cjs/core/RoutingEngine.js.map +1 -0
- package/dist/cjs/core/State.d.ts +15 -4
- package/dist/cjs/core/State.d.ts.map +1 -1
- package/dist/cjs/core/State.js +21 -2
- package/dist/cjs/core/State.js.map +1 -1
- package/dist/cjs/core/ToolExecutor.d.ts +29 -0
- package/dist/cjs/core/ToolExecutor.d.ts.map +1 -0
- package/dist/cjs/core/ToolExecutor.js +73 -0
- package/dist/cjs/core/ToolExecutor.js.map +1 -0
- package/dist/cjs/core/Transition.d.ts +5 -5
- package/dist/cjs/core/Transition.d.ts.map +1 -1
- package/dist/cjs/core/Transition.js.map +1 -1
- package/dist/cjs/index.d.ts +6 -8
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +8 -10
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +10 -13
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +12 -8
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +10 -53
- package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.js +10 -53
- package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +13 -9
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/ai.d.ts +8 -2
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/history.d.ts +8 -0
- package/dist/cjs/types/history.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +0 -3
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +1 -3
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/route.d.ts +39 -4
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/types/routing.d.ts +16 -0
- package/dist/cjs/types/routing.d.ts.map +1 -0
- package/dist/cjs/types/routing.js +3 -0
- package/dist/cjs/types/routing.js.map +1 -0
- package/dist/cjs/types/schema.d.ts +22 -0
- package/dist/cjs/types/schema.d.ts.map +1 -0
- package/dist/cjs/types/schema.js +3 -0
- package/dist/cjs/types/schema.js.map +1 -0
- package/dist/cjs/types/session.d.ts +72 -0
- package/dist/cjs/types/session.d.ts.map +1 -0
- package/dist/cjs/types/session.js +140 -0
- package/dist/cjs/types/session.js.map +1 -0
- package/dist/cjs/types/tool.d.ts +11 -5
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/utils/id.d.ts +0 -5
- package/dist/cjs/utils/id.d.ts.map +1 -1
- package/dist/cjs/utils/id.js +0 -10
- package/dist/cjs/utils/id.js.map +1 -1
- package/dist/cjs/utils/schema.d.ts +17 -0
- package/dist/cjs/utils/schema.d.ts.map +1 -0
- package/dist/cjs/utils/schema.js +32 -0
- package/dist/cjs/utils/schema.js.map +1 -0
- package/dist/core/Agent.d.ts +22 -29
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +465 -275
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/Events.d.ts +10 -1
- package/dist/core/Events.d.ts.map +1 -1
- package/dist/core/Events.js +3 -2
- package/dist/core/Events.js.map +1 -1
- package/dist/core/PersistenceManager.d.ts +19 -0
- package/dist/core/PersistenceManager.d.ts.map +1 -1
- package/dist/core/PersistenceManager.js +57 -0
- package/dist/core/PersistenceManager.js.map +1 -1
- package/dist/core/PromptComposer.d.ts +24 -0
- package/dist/core/PromptComposer.d.ts.map +1 -0
- package/dist/core/PromptComposer.js +123 -0
- package/dist/core/PromptComposer.js.map +1 -0
- package/dist/core/ResponseEngine.d.ts +19 -0
- package/dist/core/ResponseEngine.d.ts.map +1 -0
- package/dist/core/ResponseEngine.js +47 -0
- package/dist/core/ResponseEngine.js.map +1 -0
- package/dist/core/Route.d.ts +18 -12
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +15 -9
- package/dist/core/Route.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts +38 -0
- package/dist/core/RoutingEngine.d.ts.map +1 -0
- package/dist/core/RoutingEngine.js +106 -0
- package/dist/core/RoutingEngine.js.map +1 -0
- package/dist/core/State.d.ts +15 -4
- package/dist/core/State.d.ts.map +1 -1
- package/dist/core/State.js +21 -2
- package/dist/core/State.js.map +1 -1
- package/dist/core/ToolExecutor.d.ts +29 -0
- package/dist/core/ToolExecutor.d.ts.map +1 -0
- package/dist/core/ToolExecutor.js +69 -0
- package/dist/core/ToolExecutor.js.map +1 -0
- package/dist/core/Transition.d.ts +5 -5
- package/dist/core/Transition.d.ts.map +1 -1
- package/dist/core/Transition.js.map +1 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +10 -13
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +12 -8
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +10 -53
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/OpenRouterProvider.js +10 -53
- package/dist/providers/OpenRouterProvider.js.map +1 -1
- package/dist/types/agent.d.ts +13 -9
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/ai.d.ts +8 -2
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/history.d.ts +8 -0
- package/dist/types/history.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/route.d.ts +39 -4
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/routing.d.ts +16 -0
- package/dist/types/routing.d.ts.map +1 -0
- package/dist/types/routing.js +2 -0
- package/dist/types/routing.js.map +1 -0
- package/dist/types/schema.d.ts +22 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +2 -0
- package/dist/types/schema.js.map +1 -0
- package/dist/types/session.d.ts +72 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +132 -0
- package/dist/types/session.js.map +1 -0
- package/dist/types/tool.d.ts +11 -5
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/id.d.ts +0 -5
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +0 -9
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/schema.d.ts +17 -0
- package/dist/utils/schema.d.ts.map +1 -0
- package/dist/utils/schema.js +27 -0
- package/dist/utils/schema.js.map +1 -0
- package/docs/ADAPTERS.md +83 -3
- package/docs/API_REFERENCE.md +95 -104
- package/docs/ARCHITECTURE.md +284 -286
- package/docs/CONSTRUCTOR_OPTIONS.md +192 -135
- package/docs/CONTEXT_MANAGEMENT.md +311 -28
- package/docs/CONTRIBUTING.md +1 -1
- package/docs/DOMAINS.md +61 -0
- package/docs/GETTING_STARTED.md +177 -88
- package/docs/PERSISTENCE.md +170 -23
- package/docs/README.md +7 -10
- package/examples/business-onboarding.ts +21 -9
- package/examples/company-qna-agent.ts +508 -0
- package/examples/declarative-agent.ts +143 -26
- package/examples/domain-scoping.ts +31 -10
- package/examples/extracted-data-modification.ts +415 -0
- package/examples/healthcare-agent.ts +194 -90
- package/examples/openai-agent.ts +67 -25
- package/examples/opensearch-persistence.ts +455 -151
- package/examples/persistent-onboarding.ts +162 -96
- package/examples/prisma-persistence.ts +371 -125
- package/examples/redis-persistence.ts +393 -23
- package/examples/rules-prohibitions.ts +32 -11
- package/examples/streaming-agent.ts +61 -13
- package/examples/travel-agent.ts +266 -133
- package/package.json +1 -1
- package/src/core/Agent.ts +679 -332
- package/src/core/Events.ts +12 -2
- package/src/core/PersistenceManager.ts +83 -0
- package/src/core/PromptComposer.ts +143 -0
- package/src/core/ResponseEngine.ts +82 -0
- package/src/core/Route.ts +32 -17
- package/src/core/RoutingEngine.ts +165 -0
- package/src/core/State.ts +55 -15
- package/src/core/ToolExecutor.ts +117 -0
- package/src/core/Transition.ts +5 -5
- package/src/index.ts +12 -21
- package/src/providers/AnthropicProvider.ts +10 -13
- package/src/providers/GeminiProvider.ts +12 -8
- package/src/providers/OpenAIProvider.ts +10 -56
- package/src/providers/OpenRouterProvider.ts +10 -56
- package/src/types/agent.ts +16 -10
- package/src/types/ai.ts +6 -2
- package/src/types/history.ts +8 -0
- package/src/types/index.ts +0 -11
- package/src/types/route.ts +41 -5
- package/src/types/routing.ts +18 -0
- package/src/types/schema.ts +23 -0
- package/src/types/session.ts +207 -0
- package/src/types/tool.ts +29 -7
- package/src/utils/id.ts +0 -10
- package/src/utils/schema.ts +32 -0
- package/dist/cjs/core/ConditionEvaluator.d.ts +0 -72
- package/dist/cjs/core/ConditionEvaluator.d.ts.map +0 -1
- package/dist/cjs/core/ConditionEvaluator.js +0 -272
- package/dist/cjs/core/ConditionEvaluator.js.map +0 -1
- package/dist/cjs/core/Observation.d.ts +0 -24
- package/dist/cjs/core/Observation.d.ts.map +0 -1
- package/dist/cjs/core/Observation.js +0 -39
- package/dist/cjs/core/Observation.js.map +0 -1
- package/dist/cjs/core/PreparationEngine.d.ts +0 -105
- package/dist/cjs/core/PreparationEngine.d.ts.map +0 -1
- package/dist/cjs/core/PreparationEngine.js +0 -320
- package/dist/cjs/core/PreparationEngine.js.map +0 -1
- package/dist/cjs/core/PromptBuilder.d.ts +0 -136
- package/dist/cjs/core/PromptBuilder.d.ts.map +0 -1
- package/dist/cjs/core/PromptBuilder.js +0 -421
- package/dist/cjs/core/PromptBuilder.js.map +0 -1
- package/dist/cjs/types/observation.d.ts +0 -27
- package/dist/cjs/types/observation.d.ts.map +0 -1
- package/dist/cjs/types/observation.js +0 -6
- package/dist/cjs/types/observation.js.map +0 -1
- package/dist/cjs/types/prompt.d.ts +0 -46
- package/dist/cjs/types/prompt.d.ts.map +0 -1
- package/dist/cjs/types/prompt.js +0 -19
- package/dist/cjs/types/prompt.js.map +0 -1
- package/dist/core/ConditionEvaluator.d.ts +0 -72
- package/dist/core/ConditionEvaluator.d.ts.map +0 -1
- package/dist/core/ConditionEvaluator.js +0 -268
- package/dist/core/ConditionEvaluator.js.map +0 -1
- package/dist/core/Observation.d.ts +0 -24
- package/dist/core/Observation.d.ts.map +0 -1
- package/dist/core/Observation.js +0 -35
- package/dist/core/Observation.js.map +0 -1
- package/dist/core/PreparationEngine.d.ts +0 -105
- package/dist/core/PreparationEngine.d.ts.map +0 -1
- package/dist/core/PreparationEngine.js +0 -316
- package/dist/core/PreparationEngine.js.map +0 -1
- package/dist/core/PromptBuilder.d.ts +0 -136
- package/dist/core/PromptBuilder.d.ts.map +0 -1
- package/dist/core/PromptBuilder.js +0 -417
- package/dist/core/PromptBuilder.js.map +0 -1
- package/dist/types/observation.d.ts +0 -27
- package/dist/types/observation.d.ts.map +0 -1
- package/dist/types/observation.js +0 -5
- package/dist/types/observation.js.map +0 -1
- package/dist/types/prompt.d.ts +0 -46
- package/dist/types/prompt.d.ts.map +0 -1
- package/dist/types/prompt.js +0 -16
- package/dist/types/prompt.js.map +0 -1
- package/docs/STRUCTURE.md +0 -58
- package/src/core/ConditionEvaluator.ts +0 -381
- package/src/core/Observation.ts +0 -47
- package/src/core/PreparationEngine.ts +0 -500
- package/src/core/PromptBuilder.ts +0 -617
- package/src/types/observation.ts +0 -29
- package/src/types/prompt.ts +0 -49
package/src/core/Agent.ts
CHANGED
|
@@ -2,22 +2,41 @@
|
|
|
2
2
|
* Core Agent implementation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
Term,
|
|
8
|
-
Guideline,
|
|
9
|
-
GuidelineMatch,
|
|
10
|
-
Capability,
|
|
11
|
-
} from "../types/agent";
|
|
12
|
-
import type { Event, StateRef } from "../types/index";
|
|
5
|
+
import type { AgentOptions, Term, Guideline, Capability } from "../types/agent";
|
|
6
|
+
import type { Event, StateRef, MessageEventData } from "../types/index";
|
|
13
7
|
import type { RouteOptions } from "../types/route";
|
|
8
|
+
import type { RoutingDecisionOutput } from "./RoutingEngine";
|
|
9
|
+
import type { SessionState } from "../types/session";
|
|
10
|
+
import type { AgentStructuredResponse } from "../types/ai";
|
|
11
|
+
import {
|
|
12
|
+
createSession,
|
|
13
|
+
enterRoute,
|
|
14
|
+
enterState,
|
|
15
|
+
mergeExtracted,
|
|
16
|
+
} from "../types/session";
|
|
17
|
+
import { EventKind } from "../types/history";
|
|
18
|
+
import { PromptComposer } from "./PromptComposer";
|
|
14
19
|
|
|
15
20
|
import { Route } from "./Route";
|
|
21
|
+
import { State } from "./State";
|
|
16
22
|
import { DomainRegistry } from "./DomainRegistry";
|
|
17
|
-
import { PromptBuilder } from "./PromptBuilder";
|
|
18
|
-
import { Observation } from "./Observation";
|
|
19
23
|
import { PersistenceManager } from "./PersistenceManager";
|
|
20
|
-
import {
|
|
24
|
+
import { RoutingEngine } from "./RoutingEngine";
|
|
25
|
+
import { ResponseEngine } from "./ResponseEngine";
|
|
26
|
+
import { ToolExecutor } from "./ToolExecutor";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Helper to extract last message from history
|
|
30
|
+
*/
|
|
31
|
+
function getLastMessageFromHistory(history: Event[]): string {
|
|
32
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
33
|
+
const event = history[i];
|
|
34
|
+
if (event.kind === EventKind.MESSAGE) {
|
|
35
|
+
return (event.data as MessageEventData).message;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
21
40
|
|
|
22
41
|
/**
|
|
23
42
|
* Main Agent class with generic context support
|
|
@@ -26,12 +45,13 @@ export class Agent<TContext = unknown> {
|
|
|
26
45
|
private terms: Term[] = [];
|
|
27
46
|
private guidelines: Guideline[] = [];
|
|
28
47
|
private capabilities: Capability[] = [];
|
|
29
|
-
|
|
30
|
-
private
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
private routes: Route<TContext, any>[] = [];
|
|
31
50
|
private domainRegistry = new DomainRegistry();
|
|
32
51
|
private context: TContext | undefined;
|
|
33
52
|
private persistenceManager: PersistenceManager | undefined;
|
|
34
|
-
private
|
|
53
|
+
private routingEngine: RoutingEngine<TContext>;
|
|
54
|
+
private responseEngine: ResponseEngine<TContext>;
|
|
35
55
|
|
|
36
56
|
/**
|
|
37
57
|
* Dynamic domain property - populated via addDomain
|
|
@@ -39,11 +59,6 @@ export class Agent<TContext = unknown> {
|
|
|
39
59
|
public readonly domain: Record<string, Record<string, unknown>> = {};
|
|
40
60
|
|
|
41
61
|
constructor(private readonly options: AgentOptions<TContext>) {
|
|
42
|
-
// Initialize with default values
|
|
43
|
-
if (!this.options.maxEngineIterations) {
|
|
44
|
-
this.options.maxEngineIterations = 1;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
62
|
// Validate context configuration
|
|
48
63
|
if (options.context !== undefined && options.contextProvider) {
|
|
49
64
|
throw new Error(
|
|
@@ -54,8 +69,13 @@ export class Agent<TContext = unknown> {
|
|
|
54
69
|
// Initialize context if provided
|
|
55
70
|
this.context = options.context;
|
|
56
71
|
|
|
57
|
-
// Initialize
|
|
58
|
-
this.
|
|
72
|
+
// Initialize routing and response engines
|
|
73
|
+
this.routingEngine = new RoutingEngine<TContext>({
|
|
74
|
+
maxCandidates: 5,
|
|
75
|
+
allowRouteSwitch: true,
|
|
76
|
+
switchThreshold: 70,
|
|
77
|
+
});
|
|
78
|
+
this.responseEngine = new ResponseEngine<TContext>();
|
|
59
79
|
|
|
60
80
|
// Initialize persistence if configured
|
|
61
81
|
if (options.persistence) {
|
|
@@ -91,27 +111,7 @@ export class Agent<TContext = unknown> {
|
|
|
91
111
|
|
|
92
112
|
if (options.routes) {
|
|
93
113
|
options.routes.forEach((routeOptions) => {
|
|
94
|
-
this.createRoute(routeOptions);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (options.observations) {
|
|
99
|
-
options.observations.forEach((obsOptions) => {
|
|
100
|
-
const obs = this.createObservation(obsOptions.description);
|
|
101
|
-
|
|
102
|
-
// If route refs were provided, resolve and disambiguate
|
|
103
|
-
if (obsOptions.routeRefs && obsOptions.routeRefs.length > 0) {
|
|
104
|
-
const resolvedRoutes = obsOptions.routeRefs
|
|
105
|
-
.map((ref) => {
|
|
106
|
-
// Try to find route by ID or title
|
|
107
|
-
return this.routes.find((r) => r.id === ref || r.title === ref);
|
|
108
|
-
})
|
|
109
|
-
.filter((r): r is Route<TContext> => r !== undefined);
|
|
110
|
-
|
|
111
|
-
if (resolvedRoutes.length > 0) {
|
|
112
|
-
obs.disambiguate(resolvedRoutes);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
114
|
+
this.createRoute<unknown>(routeOptions);
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -139,9 +139,12 @@ export class Agent<TContext = unknown> {
|
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
141
|
* Create a new route (journey)
|
|
142
|
+
* @template TExtracted - Type of data extracted throughout the route
|
|
142
143
|
*/
|
|
143
|
-
createRoute
|
|
144
|
-
|
|
144
|
+
createRoute<TExtracted = unknown>(
|
|
145
|
+
options: RouteOptions<TExtracted>
|
|
146
|
+
): Route<TContext, TExtracted> {
|
|
147
|
+
const route = new Route<TContext, TExtracted>(options);
|
|
145
148
|
this.routes.push(route);
|
|
146
149
|
return route;
|
|
147
150
|
}
|
|
@@ -179,25 +182,34 @@ export class Agent<TContext = unknown> {
|
|
|
179
182
|
return this;
|
|
180
183
|
}
|
|
181
184
|
|
|
182
|
-
/**
|
|
183
|
-
* Create an observation for disambiguation
|
|
184
|
-
*/
|
|
185
|
-
createObservation(description: string): Observation {
|
|
186
|
-
const observation = new Observation({ description });
|
|
187
|
-
this.observations.push(observation);
|
|
188
|
-
return observation;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
185
|
/**
|
|
192
186
|
* Add a domain with its tools/methods
|
|
187
|
+
* Automatically tags all ToolRef objects with their domain name for security enforcement
|
|
193
188
|
*/
|
|
194
189
|
addDomain<TName extends string, TDomain extends Record<string, unknown>>(
|
|
195
190
|
name: TName,
|
|
196
191
|
domainObject: TDomain
|
|
197
192
|
): void {
|
|
198
|
-
this
|
|
193
|
+
// Tag all tools in this domain with the domain name for security enforcement
|
|
194
|
+
const taggedDomain = { ...domainObject };
|
|
195
|
+
for (const key in taggedDomain) {
|
|
196
|
+
const value = taggedDomain[key];
|
|
197
|
+
// Check if value is a ToolRef (has handler, id, name properties)
|
|
198
|
+
if (
|
|
199
|
+
value &&
|
|
200
|
+
typeof value === "object" &&
|
|
201
|
+
"handler" in value &&
|
|
202
|
+
"id" in value &&
|
|
203
|
+
"name" in value
|
|
204
|
+
) {
|
|
205
|
+
// Tag the tool with its domain name
|
|
206
|
+
(value as Record<string, unknown>).domainName = name;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.domainRegistry.register(name, taggedDomain);
|
|
199
211
|
// Attach to the domain property for easy access
|
|
200
|
-
this.domain[name] =
|
|
212
|
+
this.domain[name] = taggedDomain;
|
|
201
213
|
}
|
|
202
214
|
|
|
203
215
|
/**
|
|
@@ -219,6 +231,36 @@ export class Agent<TContext = unknown> {
|
|
|
219
231
|
}
|
|
220
232
|
}
|
|
221
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Update extracted data in session with lifecycle hook support
|
|
236
|
+
* Triggers the onExtractedUpdate lifecycle hook if configured
|
|
237
|
+
* @internal
|
|
238
|
+
*/
|
|
239
|
+
private async updateExtracted<TExtracted = unknown>(
|
|
240
|
+
session: SessionState<TExtracted>,
|
|
241
|
+
extractedUpdate: Partial<TExtracted>
|
|
242
|
+
): Promise<SessionState<TExtracted>> {
|
|
243
|
+
const previousExtracted = { ...session.extracted };
|
|
244
|
+
|
|
245
|
+
// Merge new extracted data
|
|
246
|
+
let newExtracted = {
|
|
247
|
+
...session.extracted,
|
|
248
|
+
...extractedUpdate,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Trigger lifecycle hook if configured
|
|
252
|
+
if (this.options.hooks?.onExtractedUpdate) {
|
|
253
|
+
const updatedExtracted = (await this.options.hooks.onExtractedUpdate(
|
|
254
|
+
newExtracted,
|
|
255
|
+
previousExtracted
|
|
256
|
+
)) as Partial<TExtracted>;
|
|
257
|
+
newExtracted = updatedExtracted;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Return updated session
|
|
261
|
+
return mergeExtracted(session, newExtracted);
|
|
262
|
+
}
|
|
263
|
+
|
|
222
264
|
/**
|
|
223
265
|
* Get current context (fetches from provider if configured)
|
|
224
266
|
* @internal
|
|
@@ -233,20 +275,71 @@ export class Agent<TContext = unknown> {
|
|
|
233
275
|
return this.context;
|
|
234
276
|
}
|
|
235
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Determine the next state in a route based on extracted data
|
|
280
|
+
* @internal
|
|
281
|
+
*/
|
|
282
|
+
private getNextState<TExtracted = unknown>(
|
|
283
|
+
route: Route<TContext, TExtracted>,
|
|
284
|
+
currentState: State<TContext, TExtracted> | undefined,
|
|
285
|
+
extracted: Partial<TExtracted>
|
|
286
|
+
): State<TContext, TExtracted> {
|
|
287
|
+
// If no current state, start from initial state
|
|
288
|
+
if (!currentState) {
|
|
289
|
+
// Check if initial state should be skipped
|
|
290
|
+
if (route.initialState.shouldSkip(extracted)) {
|
|
291
|
+
return this.getNextState(route, route.initialState, extracted);
|
|
292
|
+
}
|
|
293
|
+
return route.initialState;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Get transitions from current state
|
|
297
|
+
const transitions = currentState.getTransitions();
|
|
298
|
+
|
|
299
|
+
// If no transitions, stay in current state
|
|
300
|
+
if (transitions.length === 0) {
|
|
301
|
+
return currentState;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Try to find the next state to transition to
|
|
305
|
+
for (const transition of transitions) {
|
|
306
|
+
const target = transition.getTarget();
|
|
307
|
+
if (!target) continue;
|
|
308
|
+
|
|
309
|
+
// Check if target state should be skipped
|
|
310
|
+
if (target.shouldSkip(extracted)) {
|
|
311
|
+
// Recursively find next non-skipped state
|
|
312
|
+
return this.getNextState(route, target, extracted);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Check if target state has required data
|
|
316
|
+
if (!target.hasRequiredData(extracted)) {
|
|
317
|
+
// Cannot enter this state yet, stay in current state
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Found valid next state
|
|
322
|
+
return target;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// No valid transition found, stay in current state
|
|
326
|
+
return currentState;
|
|
327
|
+
}
|
|
328
|
+
|
|
236
329
|
/**
|
|
237
330
|
* Generate a response based on history and context as a stream
|
|
238
331
|
*/
|
|
239
332
|
async *respondStream(params: {
|
|
240
333
|
history: Event[];
|
|
241
334
|
state?: StateRef;
|
|
335
|
+
session?: SessionState;
|
|
242
336
|
contextOverride?: Partial<TContext>;
|
|
243
337
|
signal?: AbortSignal;
|
|
244
338
|
}): AsyncGenerator<{
|
|
245
339
|
delta: string;
|
|
246
340
|
accumulated: string;
|
|
247
341
|
done: boolean;
|
|
248
|
-
|
|
249
|
-
state?: { id: string; description?: string } | null;
|
|
342
|
+
session?: SessionState;
|
|
250
343
|
toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
|
|
251
344
|
}> {
|
|
252
345
|
const { history, contextOverride, signal } = params;
|
|
@@ -262,171 +355,307 @@ export class Agent<TContext = unknown> {
|
|
|
262
355
|
}
|
|
263
356
|
|
|
264
357
|
// Merge context with override
|
|
265
|
-
|
|
358
|
+
const effectiveContext = {
|
|
266
359
|
...(currentContext as Record<string, unknown>),
|
|
267
360
|
...(contextOverride as Record<string, unknown>),
|
|
268
361
|
} as TContext;
|
|
269
362
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
// 1. Matched guidelines with associated tools
|
|
273
|
-
// 2. State machine transitions with toolState
|
|
274
|
-
//
|
|
275
|
-
// The AI will NEVER see these tools - they execute before message generation
|
|
276
|
-
const preparationResult = await this.preparationEngine.prepare({
|
|
277
|
-
history,
|
|
278
|
-
currentState: params.state,
|
|
279
|
-
context: effectiveContext,
|
|
280
|
-
routes: this.routes,
|
|
281
|
-
guidelines: this.guidelines,
|
|
282
|
-
maxIterations: this.options.maxEngineIterations || 1,
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Update context with results from tool executions
|
|
286
|
-
effectiveContext = preparationResult.finalContext;
|
|
363
|
+
// Initialize or get session
|
|
364
|
+
let session = params.session || createSession();
|
|
287
365
|
|
|
288
|
-
//
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
366
|
+
// PHASE 1: TOOL EXECUTION - Execute tools if current state has toolState
|
|
367
|
+
if (session.currentRoute && session.currentState) {
|
|
368
|
+
const currentRoute = this.routes.find(
|
|
369
|
+
(r) => r.id === session.currentRoute?.id
|
|
292
370
|
);
|
|
371
|
+
if (currentRoute) {
|
|
372
|
+
const currentState = currentRoute.getState(session.currentState.id);
|
|
373
|
+
if (currentState) {
|
|
374
|
+
const transitions = currentState.getTransitions();
|
|
375
|
+
const toolTransition = transitions.find((t) => t.spec.toolState);
|
|
376
|
+
|
|
377
|
+
if (toolTransition?.spec.toolState) {
|
|
378
|
+
const toolExecutor = new ToolExecutor<TContext, unknown>();
|
|
379
|
+
// Get allowed domains from current route for security enforcement
|
|
380
|
+
const allowedDomains = currentRoute.getDomains();
|
|
381
|
+
const result = await toolExecutor.executeTool(
|
|
382
|
+
toolTransition.spec.toolState,
|
|
383
|
+
effectiveContext,
|
|
384
|
+
this.updateContext.bind(this),
|
|
385
|
+
history,
|
|
386
|
+
session.extracted,
|
|
387
|
+
allowedDomains
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// Update context with tool results
|
|
391
|
+
if (result.contextUpdate) {
|
|
392
|
+
await this.updateContext(
|
|
393
|
+
result.contextUpdate as Partial<TContext>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Update extracted data with tool results
|
|
398
|
+
if (result.extractedUpdate) {
|
|
399
|
+
session = await this.updateExtracted(
|
|
400
|
+
session,
|
|
401
|
+
result.extractedUpdate
|
|
402
|
+
);
|
|
403
|
+
console.log(
|
|
404
|
+
`[Agent] Tool updated extracted data:`,
|
|
405
|
+
result.extractedUpdate
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.log(
|
|
410
|
+
`[Agent] Executed tool: ${result.toolName} (success: ${result.success})`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
293
415
|
}
|
|
294
416
|
|
|
295
|
-
//
|
|
296
|
-
|
|
417
|
+
// PHASE 2: ROUTING - Determine which route to use
|
|
418
|
+
let selectedRoute: Route<TContext> | undefined;
|
|
419
|
+
let responseDirectives: string[] | undefined;
|
|
297
420
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
name: this.options.name,
|
|
302
|
-
description: this.options.description,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
421
|
+
if (this.routes.length > 0) {
|
|
422
|
+
// Get last user message
|
|
423
|
+
const lastUserMessage = getLastMessageFromHistory(history);
|
|
305
424
|
|
|
306
|
-
|
|
307
|
-
|
|
425
|
+
// Build routing schema
|
|
426
|
+
const routingSchema = this.routingEngine.buildDynamicRoutingSchema(
|
|
427
|
+
this.routes
|
|
428
|
+
);
|
|
308
429
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
430
|
+
// Build routing prompt with session context
|
|
431
|
+
const routingPrompt = this.routingEngine.buildRoutingPrompt(
|
|
432
|
+
history,
|
|
433
|
+
this.routes,
|
|
434
|
+
lastUserMessage,
|
|
435
|
+
{
|
|
436
|
+
name: this.options.name,
|
|
437
|
+
goal: this.options.goal,
|
|
438
|
+
description: this.options.description,
|
|
439
|
+
personality: this.options.personality,
|
|
440
|
+
},
|
|
441
|
+
session // Pass session for context-aware routing
|
|
442
|
+
);
|
|
313
443
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
444
|
+
// Call AI to score routes (non-streaming for routing decision)
|
|
445
|
+
const routingResult = await this.options.ai.generateMessage<
|
|
446
|
+
TContext,
|
|
447
|
+
RoutingDecisionOutput
|
|
448
|
+
>({
|
|
449
|
+
prompt: routingPrompt,
|
|
450
|
+
history,
|
|
451
|
+
context: effectiveContext,
|
|
452
|
+
signal,
|
|
453
|
+
parameters: {
|
|
454
|
+
jsonSchema: routingSchema,
|
|
455
|
+
schemaName: "routing_output",
|
|
456
|
+
},
|
|
457
|
+
});
|
|
324
458
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
459
|
+
// Select best route from scores
|
|
460
|
+
if (routingResult.structured?.routes) {
|
|
461
|
+
const decision = this.routingEngine.decideRouteFromScores({
|
|
462
|
+
context: routingResult.structured.context,
|
|
463
|
+
routes: routingResult.structured.routes,
|
|
464
|
+
responseDirectives: routingResult.structured.responseDirectives,
|
|
465
|
+
});
|
|
466
|
+
selectedRoute = this.routes.find((r) => r.id === decision.routeId);
|
|
467
|
+
responseDirectives = routingResult.structured.responseDirectives;
|
|
468
|
+
|
|
469
|
+
if (selectedRoute) {
|
|
470
|
+
console.log(
|
|
471
|
+
`[Agent] Selected route: ${selectedRoute.title} (score: ${decision.maxScore})`
|
|
472
|
+
);
|
|
329
473
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
474
|
+
// Update session with selected route (if changed)
|
|
475
|
+
if (
|
|
476
|
+
!session.currentRoute ||
|
|
477
|
+
session.currentRoute.id !== selectedRoute.id
|
|
478
|
+
) {
|
|
479
|
+
session = enterRoute(
|
|
480
|
+
session,
|
|
481
|
+
selectedRoute.id,
|
|
482
|
+
selectedRoute.title
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
// Merge initial data if provided by the route
|
|
486
|
+
if (selectedRoute.initialData) {
|
|
487
|
+
session = mergeExtracted(session, selectedRoute.initialData);
|
|
488
|
+
console.log(
|
|
489
|
+
`[Agent] Merged initial data:`,
|
|
490
|
+
selectedRoute.initialData
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
console.log(`[Agent] Entered route: ${selectedRoute.title}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
344
497
|
}
|
|
345
498
|
}
|
|
346
499
|
|
|
347
|
-
//
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
500
|
+
// PHASE 3: RESPONSE - Stream message using selected route
|
|
501
|
+
if (selectedRoute) {
|
|
502
|
+
// Determine next state based on current extracted data
|
|
503
|
+
const currentStateRef = session.currentState;
|
|
504
|
+
const currentState = currentStateRef
|
|
505
|
+
? selectedRoute.getState(currentStateRef.id)
|
|
506
|
+
: undefined;
|
|
507
|
+
const nextState = this.getNextState(
|
|
508
|
+
selectedRoute,
|
|
509
|
+
currentState,
|
|
510
|
+
session.extracted
|
|
358
511
|
);
|
|
359
|
-
}
|
|
360
512
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// Add JSON response schema instructions
|
|
366
|
-
promptBuilder.addJsonResponseSchema();
|
|
367
|
-
|
|
368
|
-
// Build final prompt
|
|
369
|
-
const prompt = promptBuilder.build();
|
|
370
|
-
|
|
371
|
-
// Generate message stream using AI provider with JSON mode enabled
|
|
372
|
-
const stream = this.options.ai.generateMessageStream({
|
|
373
|
-
prompt,
|
|
374
|
-
history,
|
|
375
|
-
context: effectiveContext,
|
|
376
|
-
signal,
|
|
377
|
-
parameters: {
|
|
378
|
-
jsonMode: true,
|
|
379
|
-
},
|
|
380
|
-
});
|
|
513
|
+
// Update session with next state
|
|
514
|
+
session = enterState(session, nextState.id, nextState.description);
|
|
515
|
+
console.log(`[Agent] Entered state: ${nextState.id}`);
|
|
381
516
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
517
|
+
// Get last user message
|
|
518
|
+
const lastUserMessage = getLastMessageFromHistory(history);
|
|
519
|
+
|
|
520
|
+
// Build response schema for this route (with gather fields from state)
|
|
521
|
+
const responseSchema = this.responseEngine.responseSchemaForRoute(
|
|
522
|
+
selectedRoute,
|
|
523
|
+
nextState
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// Build response prompt
|
|
527
|
+
const responsePrompt = this.responseEngine.buildResponsePrompt(
|
|
528
|
+
selectedRoute,
|
|
529
|
+
selectedRoute.getRules(),
|
|
530
|
+
selectedRoute.getProhibitions(),
|
|
531
|
+
responseDirectives,
|
|
532
|
+
history,
|
|
533
|
+
lastUserMessage,
|
|
534
|
+
{
|
|
535
|
+
name: this.options.name,
|
|
536
|
+
goal: this.options.goal,
|
|
537
|
+
description: this.options.description,
|
|
538
|
+
personality: this.options.personality,
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
// Generate message stream using AI provider
|
|
543
|
+
const stream = this.options.ai.generateMessageStream({
|
|
544
|
+
prompt: responsePrompt,
|
|
545
|
+
history,
|
|
546
|
+
context: effectiveContext,
|
|
547
|
+
signal,
|
|
548
|
+
parameters: {
|
|
549
|
+
jsonSchema: responseSchema,
|
|
550
|
+
schemaName: "response_stream_output",
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Stream chunks to caller
|
|
555
|
+
for await (const chunk of stream) {
|
|
556
|
+
const toolCalls:
|
|
557
|
+
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
558
|
+
| undefined = undefined;
|
|
559
|
+
|
|
560
|
+
// Extract gathered data on final chunk
|
|
561
|
+
if (chunk.done && chunk.structured && nextState.gatherFields) {
|
|
562
|
+
const gatheredData: Record<string, unknown> = {};
|
|
563
|
+
// The structured response includes both base fields and gathered extraction fields
|
|
564
|
+
const structuredData = chunk.structured as AgentStructuredResponse &
|
|
565
|
+
Record<string, unknown>;
|
|
566
|
+
|
|
567
|
+
for (const field of nextState.gatherFields) {
|
|
568
|
+
if (field in structuredData) {
|
|
569
|
+
gatheredData[field] = structuredData[field];
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Merge gathered data into session
|
|
574
|
+
if (Object.keys(gatheredData).length > 0) {
|
|
575
|
+
session = await this.updateExtracted(session, gatheredData);
|
|
576
|
+
console.log(`[Agent] Extracted data:`, gatheredData);
|
|
402
577
|
}
|
|
403
578
|
}
|
|
404
579
|
|
|
405
|
-
//
|
|
406
|
-
if (
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
580
|
+
// Extract any additional data from structured response on final chunk
|
|
581
|
+
if (
|
|
582
|
+
chunk.done &&
|
|
583
|
+
chunk.structured &&
|
|
584
|
+
typeof chunk.structured === "object" &&
|
|
585
|
+
"contextUpdate" in chunk.structured
|
|
586
|
+
) {
|
|
587
|
+
await this.updateContext(
|
|
588
|
+
(chunk.structured as { contextUpdate?: Partial<TContext> })
|
|
589
|
+
.contextUpdate as Partial<TContext>
|
|
590
|
+
);
|
|
411
591
|
}
|
|
412
592
|
|
|
413
|
-
//
|
|
593
|
+
// Auto-save session state on final chunk
|
|
414
594
|
if (
|
|
415
|
-
chunk.
|
|
416
|
-
|
|
595
|
+
chunk.done &&
|
|
596
|
+
this.persistenceManager &&
|
|
597
|
+
session.metadata?.sessionId &&
|
|
598
|
+
this.options.persistence?.autoSave !== false
|
|
417
599
|
) {
|
|
418
|
-
|
|
600
|
+
await this.persistenceManager.saveSessionState(
|
|
601
|
+
session.metadata.sessionId,
|
|
602
|
+
session
|
|
603
|
+
);
|
|
604
|
+
console.log(
|
|
605
|
+
`[Agent] Auto-saved session state to persistence: ${session.metadata.sessionId}`
|
|
606
|
+
);
|
|
419
607
|
}
|
|
608
|
+
|
|
609
|
+
yield {
|
|
610
|
+
delta: chunk.delta,
|
|
611
|
+
accumulated: chunk.accumulated,
|
|
612
|
+
done: chunk.done,
|
|
613
|
+
session, // Return updated session
|
|
614
|
+
toolCalls,
|
|
615
|
+
};
|
|
420
616
|
}
|
|
617
|
+
} else {
|
|
618
|
+
// Fallback: No routes defined, stream a simple response
|
|
619
|
+
const fallbackPrompt = new PromptComposer<TContext>()
|
|
620
|
+
.addAgentMeta({
|
|
621
|
+
name: this.options.name,
|
|
622
|
+
goal: this.options.goal,
|
|
623
|
+
description: this.options.description,
|
|
624
|
+
})
|
|
625
|
+
.addPersonality(this.options.personality)
|
|
626
|
+
.addInteractionHistory(history)
|
|
627
|
+
.addGlossary(this.terms)
|
|
628
|
+
.addGuidelines(this.guidelines)
|
|
629
|
+
.addCapabilities(this.capabilities)
|
|
630
|
+
.build();
|
|
631
|
+
|
|
632
|
+
const stream = this.options.ai.generateMessageStream({
|
|
633
|
+
prompt: fallbackPrompt,
|
|
634
|
+
history,
|
|
635
|
+
context: effectiveContext,
|
|
636
|
+
signal,
|
|
637
|
+
parameters: {
|
|
638
|
+
jsonSchema: {
|
|
639
|
+
type: "object",
|
|
640
|
+
properties: {
|
|
641
|
+
message: { type: "string" },
|
|
642
|
+
},
|
|
643
|
+
required: ["message"],
|
|
644
|
+
additionalProperties: false,
|
|
645
|
+
},
|
|
646
|
+
schemaName: "fallback_stream_response",
|
|
647
|
+
},
|
|
648
|
+
});
|
|
421
649
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
650
|
+
for await (const chunk of stream) {
|
|
651
|
+
yield {
|
|
652
|
+
delta: chunk.delta,
|
|
653
|
+
accumulated: chunk.accumulated,
|
|
654
|
+
done: chunk.done,
|
|
655
|
+
session, // Return updated session
|
|
656
|
+
toolCalls: undefined,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
430
659
|
}
|
|
431
660
|
}
|
|
432
661
|
|
|
@@ -436,12 +665,12 @@ export class Agent<TContext = unknown> {
|
|
|
436
665
|
async respond(params: {
|
|
437
666
|
history: Event[];
|
|
438
667
|
state?: StateRef;
|
|
668
|
+
session?: SessionState;
|
|
439
669
|
contextOverride?: Partial<TContext>;
|
|
440
670
|
signal?: AbortSignal;
|
|
441
671
|
}): Promise<{
|
|
442
672
|
message: string;
|
|
443
|
-
|
|
444
|
-
state?: { id: string; description?: string } | null;
|
|
673
|
+
session?: SessionState;
|
|
445
674
|
toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
|
|
446
675
|
}> {
|
|
447
676
|
const { history, contextOverride, signal } = params;
|
|
@@ -457,169 +686,294 @@ export class Agent<TContext = unknown> {
|
|
|
457
686
|
}
|
|
458
687
|
|
|
459
688
|
// Merge context with override
|
|
460
|
-
|
|
689
|
+
const effectiveContext = {
|
|
461
690
|
...(currentContext as Record<string, unknown>),
|
|
462
691
|
...(contextOverride as Record<string, unknown>),
|
|
463
692
|
} as TContext;
|
|
464
693
|
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
// 1. Matched guidelines with associated tools
|
|
468
|
-
// 2. State machine transitions with toolState
|
|
469
|
-
//
|
|
470
|
-
// The AI will NEVER see these tools - they execute before message generation
|
|
471
|
-
const preparationResult = await this.preparationEngine.prepare({
|
|
472
|
-
history,
|
|
473
|
-
currentState: params.state,
|
|
474
|
-
context: effectiveContext,
|
|
475
|
-
routes: this.routes,
|
|
476
|
-
guidelines: this.guidelines,
|
|
477
|
-
maxIterations: this.options.maxEngineIterations || 1,
|
|
478
|
-
});
|
|
694
|
+
// Initialize or get session
|
|
695
|
+
let session = params.session || createSession();
|
|
479
696
|
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (preparationResult.toolExecutions.length > 0) {
|
|
485
|
-
console.log(
|
|
486
|
-
`[Agent] Preparation complete: ${preparationResult.toolExecutions.length} tools executed in ${preparationResult.iterations.length} iterations`
|
|
697
|
+
// PHASE 1: TOOL EXECUTION - Execute tools if current state has toolState
|
|
698
|
+
if (session.currentRoute && session.currentState) {
|
|
699
|
+
const currentRoute = this.routes.find(
|
|
700
|
+
(r) => r.id === session.currentRoute?.id
|
|
487
701
|
);
|
|
702
|
+
if (currentRoute) {
|
|
703
|
+
const currentState = currentRoute.getState(session.currentState.id);
|
|
704
|
+
if (currentState) {
|
|
705
|
+
const transitions = currentState.getTransitions();
|
|
706
|
+
const toolTransition = transitions.find((t) => t.spec.toolState);
|
|
707
|
+
|
|
708
|
+
if (toolTransition?.spec.toolState) {
|
|
709
|
+
const toolExecutor = new ToolExecutor<TContext, unknown>();
|
|
710
|
+
// Get allowed domains from current route for security enforcement
|
|
711
|
+
const allowedDomains = currentRoute.getDomains();
|
|
712
|
+
const result = await toolExecutor.executeTool(
|
|
713
|
+
toolTransition.spec.toolState,
|
|
714
|
+
effectiveContext,
|
|
715
|
+
this.updateContext.bind(this),
|
|
716
|
+
history,
|
|
717
|
+
session.extracted,
|
|
718
|
+
allowedDomains
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
// Update context with tool results
|
|
722
|
+
if (result.contextUpdate) {
|
|
723
|
+
await this.updateContext(
|
|
724
|
+
result.contextUpdate as Partial<TContext>
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Update extracted data with tool results
|
|
729
|
+
if (result.extractedUpdate) {
|
|
730
|
+
session = await this.updateExtracted(
|
|
731
|
+
session,
|
|
732
|
+
result.extractedUpdate
|
|
733
|
+
);
|
|
734
|
+
console.log(
|
|
735
|
+
`[Agent] Tool updated extracted data:`,
|
|
736
|
+
result.extractedUpdate
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
console.log(
|
|
741
|
+
`[Agent] Executed tool: ${result.toolName} (success: ${result.success})`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
488
746
|
}
|
|
489
747
|
|
|
490
|
-
//
|
|
491
|
-
|
|
748
|
+
// PHASE 2: ROUTING - Determine which route to use
|
|
749
|
+
let selectedRoute: Route<TContext> | undefined;
|
|
750
|
+
let responseDirectives: string[] | undefined;
|
|
492
751
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
name: this.options.name,
|
|
497
|
-
description: this.options.description,
|
|
498
|
-
});
|
|
499
|
-
}
|
|
752
|
+
if (this.routes.length > 0) {
|
|
753
|
+
// Get last user message
|
|
754
|
+
const lastUserMessage = getLastMessageFromHistory(history);
|
|
500
755
|
|
|
501
|
-
|
|
502
|
-
|
|
756
|
+
// Build routing schema
|
|
757
|
+
const routingSchema = this.routingEngine.buildDynamicRoutingSchema(
|
|
758
|
+
this.routes
|
|
759
|
+
);
|
|
503
760
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
761
|
+
// Build routing prompt with session context
|
|
762
|
+
const routingPrompt = this.routingEngine.buildRoutingPrompt(
|
|
763
|
+
history,
|
|
764
|
+
this.routes,
|
|
765
|
+
lastUserMessage,
|
|
766
|
+
{
|
|
767
|
+
name: this.options.name,
|
|
768
|
+
goal: this.options.goal,
|
|
769
|
+
description: this.options.description,
|
|
770
|
+
personality: this.options.personality,
|
|
771
|
+
},
|
|
772
|
+
session // Pass session for context-aware routing
|
|
773
|
+
);
|
|
508
774
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
775
|
+
// Call AI to score routes
|
|
776
|
+
const routingResult = await this.options.ai.generateMessage<
|
|
777
|
+
TContext,
|
|
778
|
+
RoutingDecisionOutput
|
|
779
|
+
>({
|
|
780
|
+
prompt: routingPrompt,
|
|
781
|
+
history,
|
|
782
|
+
context: effectiveContext,
|
|
783
|
+
signal,
|
|
784
|
+
parameters: {
|
|
785
|
+
jsonSchema: routingSchema,
|
|
786
|
+
schemaName: "routing_output",
|
|
787
|
+
},
|
|
788
|
+
});
|
|
519
789
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
790
|
+
// Select best route from scores
|
|
791
|
+
if (routingResult.structured?.routes) {
|
|
792
|
+
const decision = this.routingEngine.decideRouteFromScores({
|
|
793
|
+
context: routingResult.structured.context,
|
|
794
|
+
routes: routingResult.structured.routes,
|
|
795
|
+
responseDirectives: routingResult.structured.responseDirectives,
|
|
796
|
+
});
|
|
797
|
+
selectedRoute = this.routes.find((r) => r.id === decision.routeId);
|
|
798
|
+
responseDirectives = routingResult.structured.responseDirectives;
|
|
524
799
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
800
|
+
if (selectedRoute) {
|
|
801
|
+
console.log(
|
|
802
|
+
`[Agent] Selected route: ${selectedRoute.title} (score: ${decision.maxScore})`
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
// Update session with selected route (if changed)
|
|
806
|
+
if (
|
|
807
|
+
!session.currentRoute ||
|
|
808
|
+
session.currentRoute.id !== selectedRoute.id
|
|
809
|
+
) {
|
|
810
|
+
session = enterRoute(
|
|
811
|
+
session,
|
|
812
|
+
selectedRoute.id,
|
|
813
|
+
selectedRoute.title
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
// Merge initial data if provided by the route
|
|
817
|
+
if (selectedRoute.initialData) {
|
|
818
|
+
session = mergeExtracted(session, selectedRoute.initialData);
|
|
819
|
+
console.log(
|
|
820
|
+
`[Agent] Merged initial data:`,
|
|
821
|
+
selectedRoute.initialData
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
console.log(`[Agent] Entered route: ${selectedRoute.title}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
539
828
|
}
|
|
540
829
|
}
|
|
541
830
|
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
831
|
+
// PHASE 3: RESPONSE - Generate message using selected route
|
|
832
|
+
let message: string;
|
|
833
|
+
const toolCalls:
|
|
834
|
+
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
835
|
+
| undefined = undefined;
|
|
836
|
+
|
|
837
|
+
if (selectedRoute) {
|
|
838
|
+
// Determine next state based on current extracted data
|
|
839
|
+
const currentStateRef = session.currentState;
|
|
840
|
+
const currentState = currentStateRef
|
|
841
|
+
? selectedRoute.getState(currentStateRef.id)
|
|
842
|
+
: undefined;
|
|
843
|
+
const nextState = this.getNextState(
|
|
844
|
+
selectedRoute,
|
|
845
|
+
currentState,
|
|
846
|
+
session.extracted
|
|
553
847
|
);
|
|
554
|
-
}
|
|
555
848
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
// Add JSON response schema instructions
|
|
561
|
-
promptBuilder.addJsonResponseSchema();
|
|
562
|
-
|
|
563
|
-
// Build final prompt
|
|
564
|
-
const prompt = promptBuilder.build();
|
|
565
|
-
|
|
566
|
-
// Generate message using AI provider with JSON mode enabled
|
|
567
|
-
const result = await this.options.ai.generateMessage({
|
|
568
|
-
prompt,
|
|
569
|
-
history,
|
|
570
|
-
context: effectiveContext,
|
|
571
|
-
signal,
|
|
572
|
-
parameters: {
|
|
573
|
-
jsonMode: true,
|
|
574
|
-
},
|
|
575
|
-
});
|
|
849
|
+
// Update session with next state
|
|
850
|
+
session = enterState(session, nextState.id, nextState.description);
|
|
851
|
+
console.log(`[Agent] Entered state: ${nextState.id}`);
|
|
576
852
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
let route: { id: string; title: string } | null = null;
|
|
580
|
-
let state: { id: string; description?: string } | null = null;
|
|
581
|
-
let toolCalls:
|
|
582
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
583
|
-
| undefined;
|
|
853
|
+
// Get last user message
|
|
854
|
+
const lastUserMessage = getLastMessageFromHistory(history);
|
|
584
855
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
856
|
+
// Build response schema for this route (with gather fields from state)
|
|
857
|
+
const responseSchema = this.responseEngine.responseSchemaForRoute(
|
|
858
|
+
selectedRoute,
|
|
859
|
+
nextState
|
|
860
|
+
);
|
|
588
861
|
|
|
589
|
-
//
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
)
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
862
|
+
// Build response prompt
|
|
863
|
+
const responsePrompt = this.responseEngine.buildResponsePrompt(
|
|
864
|
+
selectedRoute,
|
|
865
|
+
selectedRoute.getRules(),
|
|
866
|
+
selectedRoute.getProhibitions(),
|
|
867
|
+
responseDirectives,
|
|
868
|
+
history,
|
|
869
|
+
lastUserMessage,
|
|
870
|
+
{
|
|
871
|
+
name: this.options.name,
|
|
872
|
+
goal: this.options.goal,
|
|
873
|
+
description: this.options.description,
|
|
874
|
+
personality: this.options.personality,
|
|
599
875
|
}
|
|
600
|
-
|
|
876
|
+
);
|
|
601
877
|
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
878
|
+
// Generate message using AI provider
|
|
879
|
+
const result = await this.options.ai.generateMessage({
|
|
880
|
+
prompt: responsePrompt,
|
|
881
|
+
history,
|
|
882
|
+
context: effectiveContext,
|
|
883
|
+
signal,
|
|
884
|
+
parameters: {
|
|
885
|
+
jsonSchema: responseSchema,
|
|
886
|
+
schemaName: "response_output",
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
message = result.structured?.message || result.message;
|
|
891
|
+
|
|
892
|
+
// Extract gathered data from response
|
|
893
|
+
if (result.structured && nextState.gatherFields) {
|
|
894
|
+
const gatheredData: Record<string, unknown> = {};
|
|
895
|
+
// The structured response includes both base fields and gathered extraction fields
|
|
896
|
+
const structuredData = result.structured as AgentStructuredResponse &
|
|
897
|
+
Record<string, unknown>;
|
|
898
|
+
|
|
899
|
+
for (const field of nextState.gatherFields) {
|
|
900
|
+
if (field in structuredData) {
|
|
901
|
+
gatheredData[field] = structuredData[field];
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Merge gathered data into session
|
|
906
|
+
if (Object.keys(gatheredData).length > 0) {
|
|
907
|
+
session = mergeExtracted(session, gatheredData);
|
|
908
|
+
console.log(`[Agent] Extracted data:`, gatheredData);
|
|
909
|
+
}
|
|
608
910
|
}
|
|
609
911
|
|
|
610
|
-
// Extract
|
|
912
|
+
// Extract any additional data from structured response
|
|
611
913
|
if (
|
|
612
|
-
result.structured
|
|
613
|
-
result.structured
|
|
914
|
+
result.structured &&
|
|
915
|
+
typeof result.structured === "object" &&
|
|
916
|
+
"contextUpdate" in result.structured
|
|
614
917
|
) {
|
|
615
|
-
|
|
918
|
+
await this.updateContext(
|
|
919
|
+
(result.structured as { contextUpdate?: Partial<TContext> })
|
|
920
|
+
.contextUpdate as Partial<TContext>
|
|
921
|
+
);
|
|
616
922
|
}
|
|
923
|
+
} else {
|
|
924
|
+
// Fallback: No routes defined, generate a simple response
|
|
925
|
+
const fallbackPrompt = new PromptComposer<TContext>()
|
|
926
|
+
.addAgentMeta({
|
|
927
|
+
name: this.options.name,
|
|
928
|
+
goal: this.options.goal,
|
|
929
|
+
description: this.options.description,
|
|
930
|
+
})
|
|
931
|
+
.addPersonality(this.options.personality)
|
|
932
|
+
.addInteractionHistory(history)
|
|
933
|
+
.addGlossary(this.terms)
|
|
934
|
+
.addGuidelines(this.guidelines)
|
|
935
|
+
.addCapabilities(this.capabilities)
|
|
936
|
+
.build();
|
|
937
|
+
|
|
938
|
+
const result = await this.options.ai.generateMessage({
|
|
939
|
+
prompt: fallbackPrompt,
|
|
940
|
+
history,
|
|
941
|
+
context: effectiveContext,
|
|
942
|
+
signal,
|
|
943
|
+
parameters: {
|
|
944
|
+
jsonSchema: {
|
|
945
|
+
type: "object",
|
|
946
|
+
properties: {
|
|
947
|
+
message: { type: "string" },
|
|
948
|
+
},
|
|
949
|
+
required: ["message"],
|
|
950
|
+
additionalProperties: false,
|
|
951
|
+
},
|
|
952
|
+
schemaName: "fallback_response",
|
|
953
|
+
},
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
message = result.structured?.message || result.message;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Auto-save session state to persistence if configured
|
|
960
|
+
if (
|
|
961
|
+
this.persistenceManager &&
|
|
962
|
+
session.metadata?.sessionId &&
|
|
963
|
+
this.options.persistence?.autoSave !== false
|
|
964
|
+
) {
|
|
965
|
+
await this.persistenceManager.saveSessionState(
|
|
966
|
+
session.metadata.sessionId,
|
|
967
|
+
session
|
|
968
|
+
);
|
|
969
|
+
console.log(
|
|
970
|
+
`[Agent] Auto-saved session state to persistence: ${session.metadata.sessionId}`
|
|
971
|
+
);
|
|
617
972
|
}
|
|
618
973
|
|
|
619
974
|
return {
|
|
620
975
|
message,
|
|
621
|
-
|
|
622
|
-
state: state || undefined,
|
|
976
|
+
session, // Return updated session with route/state info
|
|
623
977
|
toolCalls,
|
|
624
978
|
};
|
|
625
979
|
}
|
|
@@ -627,7 +981,7 @@ export class Agent<TContext = unknown> {
|
|
|
627
981
|
/**
|
|
628
982
|
* Get all routes
|
|
629
983
|
*/
|
|
630
|
-
getRoutes(): Route<TContext>[] {
|
|
984
|
+
getRoutes(): Route<TContext, unknown>[] {
|
|
631
985
|
return [...this.routes];
|
|
632
986
|
}
|
|
633
987
|
|
|
@@ -652,13 +1006,6 @@ export class Agent<TContext = unknown> {
|
|
|
652
1006
|
return [...this.capabilities];
|
|
653
1007
|
}
|
|
654
1008
|
|
|
655
|
-
/**
|
|
656
|
-
* Get all observations
|
|
657
|
-
*/
|
|
658
|
-
getObservations(): Observation[] {
|
|
659
|
-
return [...this.observations];
|
|
660
|
-
}
|
|
661
|
-
|
|
662
1009
|
/**
|
|
663
1010
|
* Get the domain registry
|
|
664
1011
|
*/
|