@falai/agent 0.1.0-alpha2
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 +797 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/src/adapters/MemoryAdapter.d.ts +47 -0
- package/dist/cjs/src/adapters/MemoryAdapter.d.ts.map +1 -0
- package/dist/cjs/src/adapters/MemoryAdapter.js +202 -0
- package/dist/cjs/src/adapters/MemoryAdapter.js.map +1 -0
- package/dist/cjs/src/adapters/MongoAdapter.d.ts +97 -0
- package/dist/cjs/src/adapters/MongoAdapter.d.ts.map +1 -0
- package/dist/cjs/src/adapters/MongoAdapter.js +168 -0
- package/dist/cjs/src/adapters/MongoAdapter.js.map +1 -0
- package/dist/cjs/src/adapters/OpenSearchAdapter.d.ts +169 -0
- package/dist/cjs/src/adapters/OpenSearchAdapter.d.ts.map +1 -0
- package/dist/cjs/src/adapters/OpenSearchAdapter.js +458 -0
- package/dist/cjs/src/adapters/OpenSearchAdapter.js.map +1 -0
- package/dist/cjs/src/adapters/PostgreSQLAdapter.d.ts +71 -0
- package/dist/cjs/src/adapters/PostgreSQLAdapter.d.ts.map +1 -0
- package/dist/cjs/src/adapters/PostgreSQLAdapter.js +260 -0
- package/dist/cjs/src/adapters/PostgreSQLAdapter.js.map +1 -0
- package/dist/cjs/src/adapters/PrismaAdapter.d.ts +115 -0
- package/dist/cjs/src/adapters/PrismaAdapter.d.ts.map +1 -0
- package/dist/cjs/src/adapters/PrismaAdapter.js +366 -0
- package/dist/cjs/src/adapters/PrismaAdapter.js.map +1 -0
- package/dist/cjs/src/adapters/RedisAdapter.d.ts +71 -0
- package/dist/cjs/src/adapters/RedisAdapter.d.ts.map +1 -0
- package/dist/cjs/src/adapters/RedisAdapter.js +231 -0
- package/dist/cjs/src/adapters/RedisAdapter.js.map +1 -0
- package/dist/cjs/src/adapters/SQLiteAdapter.d.ts +69 -0
- package/dist/cjs/src/adapters/SQLiteAdapter.d.ts.map +1 -0
- package/dist/cjs/src/adapters/SQLiteAdapter.js +312 -0
- package/dist/cjs/src/adapters/SQLiteAdapter.js.map +1 -0
- package/dist/cjs/src/adapters/index.d.ts +17 -0
- package/dist/cjs/src/adapters/index.d.ts.map +1 -0
- package/dist/cjs/src/adapters/index.js +21 -0
- package/dist/cjs/src/adapters/index.js.map +1 -0
- package/dist/cjs/src/constants/index.d.ts +10 -0
- package/dist/cjs/src/constants/index.d.ts.map +1 -0
- package/dist/cjs/src/constants/index.js +13 -0
- package/dist/cjs/src/constants/index.js.map +1 -0
- package/dist/cjs/src/core/Agent.d.ts +232 -0
- package/dist/cjs/src/core/Agent.d.ts.map +1 -0
- package/dist/cjs/src/core/Agent.js +741 -0
- package/dist/cjs/src/core/Agent.js.map +1 -0
- package/dist/cjs/src/core/Events.d.ts +26 -0
- package/dist/cjs/src/core/Events.d.ts.map +1 -0
- package/dist/cjs/src/core/Events.js +144 -0
- package/dist/cjs/src/core/Events.js.map +1 -0
- package/dist/cjs/src/core/PersistenceManager.d.ts +98 -0
- package/dist/cjs/src/core/PersistenceManager.d.ts.map +1 -0
- package/dist/cjs/src/core/PersistenceManager.js +261 -0
- package/dist/cjs/src/core/PersistenceManager.js.map +1 -0
- package/dist/cjs/src/core/PromptComposer.d.ts +27 -0
- package/dist/cjs/src/core/PromptComposer.d.ts.map +1 -0
- package/dist/cjs/src/core/PromptComposer.js +194 -0
- package/dist/cjs/src/core/PromptComposer.js.map +1 -0
- package/dist/cjs/src/core/ResponseEngine.d.ts +32 -0
- package/dist/cjs/src/core/ResponseEngine.d.ts.map +1 -0
- package/dist/cjs/src/core/ResponseEngine.js +202 -0
- package/dist/cjs/src/core/ResponseEngine.js.map +1 -0
- package/dist/cjs/src/core/ResponseModal.d.ts +222 -0
- package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -0
- package/dist/cjs/src/core/ResponseModal.js +1588 -0
- package/dist/cjs/src/core/ResponseModal.js.map +1 -0
- package/dist/cjs/src/core/ResponsePipeline.d.ts +175 -0
- package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -0
- package/dist/cjs/src/core/ResponsePipeline.js +549 -0
- package/dist/cjs/src/core/ResponsePipeline.js.map +1 -0
- package/dist/cjs/src/core/Route.d.ts +181 -0
- package/dist/cjs/src/core/Route.d.ts.map +1 -0
- package/dist/cjs/src/core/Route.js +541 -0
- package/dist/cjs/src/core/Route.js.map +1 -0
- package/dist/cjs/src/core/RoutingEngine.d.ts +159 -0
- package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -0
- package/dist/cjs/src/core/RoutingEngine.js +961 -0
- package/dist/cjs/src/core/RoutingEngine.js.map +1 -0
- package/dist/cjs/src/core/SessionManager.d.ts +94 -0
- package/dist/cjs/src/core/SessionManager.d.ts.map +1 -0
- package/dist/cjs/src/core/SessionManager.js +239 -0
- package/dist/cjs/src/core/SessionManager.js.map +1 -0
- package/dist/cjs/src/core/Step.d.ts +170 -0
- package/dist/cjs/src/core/Step.d.ts.map +1 -0
- package/dist/cjs/src/core/Step.js +448 -0
- package/dist/cjs/src/core/Step.js.map +1 -0
- package/dist/cjs/src/core/ToolManager.d.ts +234 -0
- package/dist/cjs/src/core/ToolManager.d.ts.map +1 -0
- package/dist/cjs/src/core/ToolManager.js +1117 -0
- package/dist/cjs/src/core/ToolManager.js.map +1 -0
- package/dist/cjs/src/index.d.ts +44 -0
- package/dist/cjs/src/index.d.ts.map +1 -0
- package/dist/cjs/src/index.js +88 -0
- package/dist/cjs/src/index.js.map +1 -0
- package/dist/cjs/src/providers/AnthropicProvider.d.ts +43 -0
- package/dist/cjs/src/providers/AnthropicProvider.d.ts.map +1 -0
- package/dist/cjs/src/providers/AnthropicProvider.js +377 -0
- package/dist/cjs/src/providers/AnthropicProvider.js.map +1 -0
- package/dist/cjs/src/providers/GeminiProvider.d.ts +58 -0
- package/dist/cjs/src/providers/GeminiProvider.d.ts.map +1 -0
- package/dist/cjs/src/providers/GeminiProvider.js +489 -0
- package/dist/cjs/src/providers/GeminiProvider.js.map +1 -0
- package/dist/cjs/src/providers/OpenAIProvider.d.ts +52 -0
- package/dist/cjs/src/providers/OpenAIProvider.d.ts.map +1 -0
- package/dist/cjs/src/providers/OpenAIProvider.js +395 -0
- package/dist/cjs/src/providers/OpenAIProvider.js.map +1 -0
- package/dist/cjs/src/providers/OpenRouterProvider.d.ts +56 -0
- package/dist/cjs/src/providers/OpenRouterProvider.d.ts.map +1 -0
- package/dist/cjs/src/providers/OpenRouterProvider.js +409 -0
- package/dist/cjs/src/providers/OpenRouterProvider.js.map +1 -0
- package/dist/cjs/src/providers/index.d.ts +13 -0
- package/dist/cjs/src/providers/index.d.ts.map +1 -0
- package/dist/cjs/src/providers/index.js +16 -0
- package/dist/cjs/src/providers/index.js.map +1 -0
- package/dist/cjs/src/types/agent.d.ts +181 -0
- package/dist/cjs/src/types/agent.d.ts.map +1 -0
- package/dist/cjs/src/types/agent.js +21 -0
- package/dist/cjs/src/types/agent.js.map +1 -0
- package/dist/cjs/src/types/ai.d.ts +143 -0
- package/dist/cjs/src/types/ai.d.ts.map +1 -0
- package/dist/cjs/src/types/ai.js +6 -0
- package/dist/cjs/src/types/ai.js.map +1 -0
- package/dist/cjs/src/types/history.d.ts +178 -0
- package/dist/cjs/src/types/history.d.ts.map +1 -0
- package/dist/cjs/src/types/history.js +33 -0
- package/dist/cjs/src/types/history.js.map +1 -0
- package/dist/cjs/src/types/index.d.ts +22 -0
- package/dist/cjs/src/types/index.d.ts.map +1 -0
- package/dist/cjs/src/types/index.js +37 -0
- package/dist/cjs/src/types/index.js.map +1 -0
- package/dist/cjs/src/types/persistence.d.ts +209 -0
- package/dist/cjs/src/types/persistence.d.ts.map +1 -0
- package/dist/cjs/src/types/persistence.js +7 -0
- package/dist/cjs/src/types/persistence.js.map +1 -0
- package/dist/cjs/src/types/route.d.ts +238 -0
- package/dist/cjs/src/types/route.d.ts.map +1 -0
- package/dist/cjs/src/types/route.js +6 -0
- package/dist/cjs/src/types/route.js.map +1 -0
- package/dist/cjs/src/types/routing.d.ts +16 -0
- package/dist/cjs/src/types/routing.d.ts.map +1 -0
- package/dist/cjs/src/types/routing.js +3 -0
- package/dist/cjs/src/types/routing.js.map +1 -0
- package/dist/cjs/src/types/schema.d.ts +22 -0
- package/dist/cjs/src/types/schema.d.ts.map +1 -0
- package/dist/cjs/src/types/schema.js +3 -0
- package/dist/cjs/src/types/schema.js.map +1 -0
- package/dist/cjs/src/types/session.d.ts +65 -0
- package/dist/cjs/src/types/session.d.ts.map +1 -0
- package/dist/cjs/src/types/session.js +6 -0
- package/dist/cjs/src/types/session.js.map +1 -0
- package/dist/cjs/src/types/template.d.ts +88 -0
- package/dist/cjs/src/types/template.d.ts.map +1 -0
- package/dist/cjs/src/types/template.js +3 -0
- package/dist/cjs/src/types/template.js.map +1 -0
- package/dist/cjs/src/types/tool.d.ts +130 -0
- package/dist/cjs/src/types/tool.d.ts.map +1 -0
- package/dist/cjs/src/types/tool.js +19 -0
- package/dist/cjs/src/types/tool.js.map +1 -0
- package/dist/cjs/src/utils/clone.d.ts +8 -0
- package/dist/cjs/src/utils/clone.d.ts.map +1 -0
- package/dist/cjs/src/utils/clone.js +32 -0
- package/dist/cjs/src/utils/clone.js.map +1 -0
- package/dist/cjs/src/utils/condition.d.ts +38 -0
- package/dist/cjs/src/utils/condition.d.ts.map +1 -0
- package/dist/cjs/src/utils/condition.js +168 -0
- package/dist/cjs/src/utils/condition.js.map +1 -0
- package/dist/cjs/src/utils/event.d.ts +6 -0
- package/dist/cjs/src/utils/event.d.ts.map +1 -0
- package/dist/cjs/src/utils/event.js +20 -0
- package/dist/cjs/src/utils/event.js.map +1 -0
- package/dist/cjs/src/utils/history.d.ts +60 -0
- package/dist/cjs/src/utils/history.d.ts.map +1 -0
- package/dist/cjs/src/utils/history.js +274 -0
- package/dist/cjs/src/utils/history.js.map +1 -0
- package/dist/cjs/src/utils/id.d.ts +25 -0
- package/dist/cjs/src/utils/id.d.ts.map +1 -0
- package/dist/cjs/src/utils/id.js +70 -0
- package/dist/cjs/src/utils/id.js.map +1 -0
- package/dist/cjs/src/utils/index.d.ts +15 -0
- package/dist/cjs/src/utils/index.d.ts.map +1 -0
- package/dist/cjs/src/utils/index.js +64 -0
- package/dist/cjs/src/utils/index.js.map +1 -0
- package/dist/cjs/src/utils/json.d.ts +16 -0
- package/dist/cjs/src/utils/json.d.ts.map +1 -0
- package/dist/cjs/src/utils/json.js +47 -0
- package/dist/cjs/src/utils/json.js.map +1 -0
- package/dist/cjs/src/utils/logger.d.ts +10 -0
- package/dist/cjs/src/utils/logger.d.ts.map +1 -0
- package/dist/cjs/src/utils/logger.js +23 -0
- package/dist/cjs/src/utils/logger.js.map +1 -0
- package/dist/cjs/src/utils/retry.d.ts +10 -0
- package/dist/cjs/src/utils/retry.d.ts.map +1 -0
- package/dist/cjs/src/utils/retry.js +76 -0
- package/dist/cjs/src/utils/retry.js.map +1 -0
- package/dist/cjs/src/utils/session.d.ts +51 -0
- package/dist/cjs/src/utils/session.d.ts.map +1 -0
- package/dist/cjs/src/utils/session.js +170 -0
- package/dist/cjs/src/utils/session.js.map +1 -0
- package/dist/cjs/src/utils/template.d.ts +155 -0
- package/dist/cjs/src/utils/template.d.ts.map +1 -0
- package/dist/cjs/src/utils/template.js +383 -0
- package/dist/cjs/src/utils/template.js.map +1 -0
- package/dist/src/adapters/MemoryAdapter.d.ts +47 -0
- package/dist/src/adapters/MemoryAdapter.d.ts.map +1 -0
- package/dist/src/adapters/MemoryAdapter.js +198 -0
- package/dist/src/adapters/MemoryAdapter.js.map +1 -0
- package/dist/src/adapters/MongoAdapter.d.ts +97 -0
- package/dist/src/adapters/MongoAdapter.d.ts.map +1 -0
- package/dist/src/adapters/MongoAdapter.js +164 -0
- package/dist/src/adapters/MongoAdapter.js.map +1 -0
- package/dist/src/adapters/OpenSearchAdapter.d.ts +169 -0
- package/dist/src/adapters/OpenSearchAdapter.d.ts.map +1 -0
- package/dist/src/adapters/OpenSearchAdapter.js +454 -0
- package/dist/src/adapters/OpenSearchAdapter.js.map +1 -0
- package/dist/src/adapters/PostgreSQLAdapter.d.ts +71 -0
- package/dist/src/adapters/PostgreSQLAdapter.d.ts.map +1 -0
- package/dist/src/adapters/PostgreSQLAdapter.js +256 -0
- package/dist/src/adapters/PostgreSQLAdapter.js.map +1 -0
- package/dist/src/adapters/PrismaAdapter.d.ts +115 -0
- package/dist/src/adapters/PrismaAdapter.d.ts.map +1 -0
- package/dist/src/adapters/PrismaAdapter.js +362 -0
- package/dist/src/adapters/PrismaAdapter.js.map +1 -0
- package/dist/src/adapters/RedisAdapter.d.ts +71 -0
- package/dist/src/adapters/RedisAdapter.d.ts.map +1 -0
- package/dist/src/adapters/RedisAdapter.js +227 -0
- package/dist/src/adapters/RedisAdapter.js.map +1 -0
- package/dist/src/adapters/SQLiteAdapter.d.ts +69 -0
- package/dist/src/adapters/SQLiteAdapter.d.ts.map +1 -0
- package/dist/src/adapters/SQLiteAdapter.js +308 -0
- package/dist/src/adapters/SQLiteAdapter.js.map +1 -0
- package/dist/src/adapters/index.d.ts +17 -0
- package/dist/src/adapters/index.d.ts.map +1 -0
- package/dist/src/adapters/index.js +11 -0
- package/dist/src/adapters/index.js.map +1 -0
- package/dist/src/constants/index.d.ts +10 -0
- package/dist/src/constants/index.d.ts.map +1 -0
- package/dist/src/constants/index.js +10 -0
- package/dist/src/constants/index.js.map +1 -0
- package/dist/src/core/Agent.d.ts +232 -0
- package/dist/src/core/Agent.d.ts.map +1 -0
- package/dist/src/core/Agent.js +737 -0
- package/dist/src/core/Agent.js.map +1 -0
- package/dist/src/core/Events.d.ts +26 -0
- package/dist/src/core/Events.d.ts.map +1 -0
- package/dist/src/core/Events.js +137 -0
- package/dist/src/core/Events.js.map +1 -0
- package/dist/src/core/PersistenceManager.d.ts +98 -0
- package/dist/src/core/PersistenceManager.d.ts.map +1 -0
- package/dist/src/core/PersistenceManager.js +257 -0
- package/dist/src/core/PersistenceManager.js.map +1 -0
- package/dist/src/core/PromptComposer.d.ts +27 -0
- package/dist/src/core/PromptComposer.d.ts.map +1 -0
- package/dist/src/core/PromptComposer.js +190 -0
- package/dist/src/core/PromptComposer.js.map +1 -0
- package/dist/src/core/ResponseEngine.d.ts +32 -0
- package/dist/src/core/ResponseEngine.d.ts.map +1 -0
- package/dist/src/core/ResponseEngine.js +198 -0
- package/dist/src/core/ResponseEngine.js.map +1 -0
- package/dist/src/core/ResponseModal.d.ts +222 -0
- package/dist/src/core/ResponseModal.d.ts.map +1 -0
- package/dist/src/core/ResponseModal.js +1583 -0
- package/dist/src/core/ResponseModal.js.map +1 -0
- package/dist/src/core/ResponsePipeline.d.ts +175 -0
- package/dist/src/core/ResponsePipeline.d.ts.map +1 -0
- package/dist/src/core/ResponsePipeline.js +545 -0
- package/dist/src/core/ResponsePipeline.js.map +1 -0
- package/dist/src/core/Route.d.ts +181 -0
- package/dist/src/core/Route.d.ts.map +1 -0
- package/dist/src/core/Route.js +537 -0
- package/dist/src/core/Route.js.map +1 -0
- package/dist/src/core/RoutingEngine.d.ts +159 -0
- package/dist/src/core/RoutingEngine.d.ts.map +1 -0
- package/dist/src/core/RoutingEngine.js +957 -0
- package/dist/src/core/RoutingEngine.js.map +1 -0
- package/dist/src/core/SessionManager.d.ts +94 -0
- package/dist/src/core/SessionManager.d.ts.map +1 -0
- package/dist/src/core/SessionManager.js +235 -0
- package/dist/src/core/SessionManager.js.map +1 -0
- package/dist/src/core/Step.d.ts +170 -0
- package/dist/src/core/Step.d.ts.map +1 -0
- package/dist/src/core/Step.js +444 -0
- package/dist/src/core/Step.js.map +1 -0
- package/dist/src/core/ToolManager.d.ts +234 -0
- package/dist/src/core/ToolManager.d.ts.map +1 -0
- package/dist/src/core/ToolManager.js +1111 -0
- package/dist/src/core/ToolManager.js.map +1 -0
- package/dist/src/index.d.ts +44 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +37 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/providers/AnthropicProvider.d.ts +43 -0
- package/dist/src/providers/AnthropicProvider.d.ts.map +1 -0
- package/dist/src/providers/AnthropicProvider.js +370 -0
- package/dist/src/providers/AnthropicProvider.js.map +1 -0
- package/dist/src/providers/GeminiProvider.d.ts +58 -0
- package/dist/src/providers/GeminiProvider.d.ts.map +1 -0
- package/dist/src/providers/GeminiProvider.js +485 -0
- package/dist/src/providers/GeminiProvider.js.map +1 -0
- package/dist/src/providers/OpenAIProvider.d.ts +52 -0
- package/dist/src/providers/OpenAIProvider.d.ts.map +1 -0
- package/dist/src/providers/OpenAIProvider.js +388 -0
- package/dist/src/providers/OpenAIProvider.js.map +1 -0
- package/dist/src/providers/OpenRouterProvider.d.ts +56 -0
- package/dist/src/providers/OpenRouterProvider.d.ts.map +1 -0
- package/dist/src/providers/OpenRouterProvider.js +402 -0
- package/dist/src/providers/OpenRouterProvider.js.map +1 -0
- package/dist/src/providers/index.d.ts +13 -0
- package/dist/src/providers/index.d.ts.map +1 -0
- package/dist/src/providers/index.js +9 -0
- package/dist/src/providers/index.js.map +1 -0
- package/dist/src/types/agent.d.ts +181 -0
- package/dist/src/types/agent.d.ts.map +1 -0
- package/dist/src/types/agent.js +18 -0
- package/dist/src/types/agent.js.map +1 -0
- package/dist/src/types/ai.d.ts +143 -0
- package/dist/src/types/ai.d.ts.map +1 -0
- package/dist/src/types/ai.js +5 -0
- package/dist/src/types/ai.js.map +1 -0
- package/dist/src/types/history.d.ts +178 -0
- package/dist/src/types/history.d.ts.map +1 -0
- package/dist/src/types/history.js +30 -0
- package/dist/src/types/history.js.map +1 -0
- package/dist/src/types/index.d.ts +22 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +12 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/persistence.d.ts +209 -0
- package/dist/src/types/persistence.d.ts.map +1 -0
- package/dist/src/types/persistence.js +6 -0
- package/dist/src/types/persistence.js.map +1 -0
- package/dist/src/types/route.d.ts +238 -0
- package/dist/src/types/route.d.ts.map +1 -0
- package/dist/src/types/route.js +5 -0
- package/dist/src/types/route.js.map +1 -0
- package/dist/src/types/routing.d.ts +16 -0
- package/dist/src/types/routing.d.ts.map +1 -0
- package/dist/src/types/routing.js +2 -0
- package/dist/src/types/routing.js.map +1 -0
- package/dist/src/types/schema.d.ts +22 -0
- package/dist/src/types/schema.d.ts.map +1 -0
- package/dist/src/types/schema.js +2 -0
- package/dist/src/types/schema.js.map +1 -0
- package/dist/src/types/session.d.ts +65 -0
- package/dist/src/types/session.d.ts.map +1 -0
- package/dist/src/types/session.js +5 -0
- package/dist/src/types/session.js.map +1 -0
- package/dist/src/types/template.d.ts +88 -0
- package/dist/src/types/template.d.ts.map +1 -0
- package/dist/src/types/template.js +2 -0
- package/dist/src/types/template.js.map +1 -0
- package/dist/src/types/tool.d.ts +130 -0
- package/dist/src/types/tool.d.ts.map +1 -0
- package/dist/src/types/tool.js +16 -0
- package/dist/src/types/tool.js.map +1 -0
- package/dist/src/utils/clone.d.ts +8 -0
- package/dist/src/utils/clone.d.ts.map +1 -0
- package/dist/src/utils/clone.js +29 -0
- package/dist/src/utils/clone.js.map +1 -0
- package/dist/src/utils/condition.d.ts +38 -0
- package/dist/src/utils/condition.d.ts.map +1 -0
- package/dist/src/utils/condition.js +161 -0
- package/dist/src/utils/condition.js.map +1 -0
- package/dist/src/utils/event.d.ts +6 -0
- package/dist/src/utils/event.d.ts.map +1 -0
- package/dist/src/utils/event.js +17 -0
- package/dist/src/utils/event.js.map +1 -0
- package/dist/src/utils/history.d.ts +60 -0
- package/dist/src/utils/history.d.ts.map +1 -0
- package/dist/src/utils/history.js +263 -0
- package/dist/src/utils/history.js.map +1 -0
- package/dist/src/utils/id.d.ts +25 -0
- package/dist/src/utils/id.d.ts.map +1 -0
- package/dist/src/utils/id.js +64 -0
- package/dist/src/utils/id.js.map +1 -0
- package/dist/src/utils/index.d.ts +15 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +23 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/json.d.ts +16 -0
- package/dist/src/utils/json.d.ts.map +1 -0
- package/dist/src/utils/json.js +43 -0
- package/dist/src/utils/json.js.map +1 -0
- package/dist/src/utils/logger.d.ts +10 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +17 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/retry.d.ts +10 -0
- package/dist/src/utils/retry.d.ts.map +1 -0
- package/dist/src/utils/retry.js +71 -0
- package/dist/src/utils/retry.js.map +1 -0
- package/dist/src/utils/session.d.ts +51 -0
- package/dist/src/utils/session.d.ts.map +1 -0
- package/dist/src/utils/session.js +160 -0
- package/dist/src/utils/session.js.map +1 -0
- package/dist/src/utils/template.d.ts +155 -0
- package/dist/src/utils/template.d.ts.map +1 -0
- package/dist/src/utils/template.js +374 -0
- package/dist/src/utils/template.js.map +1 -0
- package/docs/CONTRIBUTING.md +521 -0
- package/docs/README.md +228 -0
- package/docs/api/README.md +3258 -0
- package/docs/api/overview.md +1134 -0
- package/docs/architecture/data-extraction-flow.md +363 -0
- package/docs/core/agent/README.md +902 -0
- package/docs/core/agent/context-management.md +796 -0
- package/docs/core/agent/session-management.md +641 -0
- package/docs/core/ai-integration/prompt-composition.md +220 -0
- package/docs/core/ai-integration/providers.md +515 -0
- package/docs/core/ai-integration/response-processing.md +287 -0
- package/docs/core/conversation-flows/data-collection.md +623 -0
- package/docs/core/conversation-flows/route-dsl.md +502 -0
- package/docs/core/conversation-flows/routes.md +247 -0
- package/docs/core/conversation-flows/step-transitions.md +595 -0
- package/docs/core/conversation-flows/steps.md +154 -0
- package/docs/core/error-handling.md +638 -0
- package/docs/core/persistence/adapters.md +255 -0
- package/docs/core/persistence/session-storage.md +644 -0
- package/docs/core/routing/intelligent-routing.md +466 -0
- package/docs/core/tools/tool-definition.md +970 -0
- package/docs/core/tools/tool-scoping.md +819 -0
- package/docs/guides/advanced-patterns/publishing.md +186 -0
- package/docs/guides/error-handling-patterns.md +578 -0
- package/docs/guides/getting-started/README.md +696 -0
- package/docs/guides/migration/README.md +72 -0
- package/docs/guides/migration/flexible-routing-conditions.md +375 -0
- package/docs/guides/migration/response-modal-refactor.md +518 -0
- package/examples/advanced-patterns/knowledge-based-agent.ts +735 -0
- package/examples/advanced-patterns/persistent-onboarding.ts +728 -0
- package/examples/advanced-patterns/route-lifecycle-hooks.ts +556 -0
- package/examples/advanced-patterns/streaming-responses.ts +578 -0
- package/examples/ai-providers/anthropic-integration.ts +388 -0
- package/examples/ai-providers/openai-integration.ts +228 -0
- package/examples/condition-patterns/function-only-conditions.ts +365 -0
- package/examples/condition-patterns/mixed-array-conditions.ts +477 -0
- package/examples/condition-patterns/route-skipif-patterns.ts +468 -0
- package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
- package/examples/condition-patterns/string-only-conditions.ts +296 -0
- package/examples/conversation-flows/completion-transitions.ts +318 -0
- package/examples/core-concepts/basic-agent.ts +503 -0
- package/examples/core-concepts/modern-streaming-api.ts +309 -0
- package/examples/core-concepts/schema-driven-extraction.ts +332 -0
- package/examples/core-concepts/session-management.ts +494 -0
- package/examples/integrations/database-integration.ts +630 -0
- package/examples/integrations/healthcare-integration.ts +595 -0
- package/examples/integrations/search-integration.ts +530 -0
- package/examples/integrations/server-session-management.ts +307 -0
- package/examples/persistence/custom-adapter.ts +529 -0
- package/examples/persistence/database-persistence.ts +583 -0
- package/examples/persistence/memory-sessions.ts +495 -0
- package/examples/persistence/prisma-schema.example.prisma +74 -0
- package/examples/persistence/redis-persistence.ts +488 -0
- package/examples/tools/basic-tools.ts +765 -0
- package/examples/tools/data-enrichment-tools.ts +593 -0
- package/package.json +125 -0
- package/src/adapters/MemoryAdapter.ts +273 -0
- package/src/adapters/MongoAdapter.ts +304 -0
- package/src/adapters/OpenSearchAdapter.ts +670 -0
- package/src/adapters/PostgreSQLAdapter.ts +428 -0
- package/src/adapters/PrismaAdapter.ts +553 -0
- package/src/adapters/RedisAdapter.ts +377 -0
- package/src/adapters/SQLiteAdapter.ts +459 -0
- package/src/adapters/index.ts +43 -0
- package/src/constants/index.ts +10 -0
- package/src/core/Agent.ts +970 -0
- package/src/core/Events.ts +164 -0
- package/src/core/PersistenceManager.ts +353 -0
- package/src/core/PromptComposer.ts +253 -0
- package/src/core/ResponseEngine.ts +306 -0
- package/src/core/ResponseModal.ts +2050 -0
- package/src/core/ResponsePipeline.ts +864 -0
- package/src/core/Route.ts +677 -0
- package/src/core/RoutingEngine.ts +1396 -0
- package/src/core/SessionManager.ts +297 -0
- package/src/core/Step.ts +593 -0
- package/src/core/ToolManager.ts +1394 -0
- package/src/index.ts +155 -0
- package/src/providers/AnthropicProvider.ts +560 -0
- package/src/providers/GeminiProvider.ts +683 -0
- package/src/providers/OpenAIProvider.ts +602 -0
- package/src/providers/OpenRouterProvider.ts +613 -0
- package/src/providers/index.ts +16 -0
- package/src/types/agent.ts +196 -0
- package/src/types/ai.ts +158 -0
- package/src/types/history.ts +206 -0
- package/src/types/index.ts +119 -0
- package/src/types/persistence.ts +251 -0
- package/src/types/route.ts +272 -0
- package/src/types/routing.ts +18 -0
- package/src/types/schema.ts +23 -0
- package/src/types/session.ts +74 -0
- package/src/types/template.ts +104 -0
- package/src/types/tool.ts +174 -0
- package/src/utils/clone.ts +34 -0
- package/src/utils/condition.ts +190 -0
- package/src/utils/event.ts +16 -0
- package/src/utils/history.ts +306 -0
- package/src/utils/id.ts +73 -0
- package/src/utils/index.ts +69 -0
- package/src/utils/json.ts +46 -0
- package/src/utils/logger.ts +19 -0
- package/src/utils/retry.ts +97 -0
- package/src/utils/session.ts +204 -0
- package/src/utils/template.ts +444 -0
|
@@ -0,0 +1,1396 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Event,
|
|
3
|
+
AgentOptions,
|
|
4
|
+
StructuredSchema,
|
|
5
|
+
RoutingDecision,
|
|
6
|
+
SessionState,
|
|
7
|
+
AiProvider,
|
|
8
|
+
TemplateContext,
|
|
9
|
+
} from "../types";
|
|
10
|
+
import { enterRoute, mergeCollected } from "../utils";
|
|
11
|
+
import type { Route } from "./Route";
|
|
12
|
+
import type { Step } from "./Step";
|
|
13
|
+
import { PromptComposer } from "./PromptComposer";
|
|
14
|
+
import { END_ROUTE_ID } from "../constants";
|
|
15
|
+
import { createTemplateContext, getLastMessageFromHistory, logger } from "../utils";
|
|
16
|
+
|
|
17
|
+
export interface CandidateStep<TContext = unknown, TData = unknown> {
|
|
18
|
+
step: Step<TContext, TData>;
|
|
19
|
+
isRouteComplete?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RoutingDecisionOutput {
|
|
23
|
+
context: string;
|
|
24
|
+
routes: Record<string, number>;
|
|
25
|
+
selectedStepId?: string; // For active route, which step to transition to
|
|
26
|
+
stepReasoning?: string; // Why this step was selected
|
|
27
|
+
responseDirectives?: string[];
|
|
28
|
+
extractions?: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
value: any;
|
|
32
|
+
confidence?: number;
|
|
33
|
+
source?: "message" | "history";
|
|
34
|
+
}>;
|
|
35
|
+
contextUpdate?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RoutingEngineOptions {
|
|
39
|
+
allowRouteSwitch?: boolean;
|
|
40
|
+
switchThreshold?: number; // 0-100
|
|
41
|
+
maxCandidates?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface BuildStepSelectionPromptParams<
|
|
45
|
+
TContext = unknown,
|
|
46
|
+
TData = unknown
|
|
47
|
+
> {
|
|
48
|
+
route: Route<TContext, TData>;
|
|
49
|
+
currentStep: Step<TContext, TData> | undefined;
|
|
50
|
+
candidates: CandidateStep<TContext, TData>[];
|
|
51
|
+
data: Partial<TData>;
|
|
52
|
+
history: Event[];
|
|
53
|
+
lastMessage: string;
|
|
54
|
+
agentOptions?: AgentOptions<TContext, TData>;
|
|
55
|
+
context?: TContext;
|
|
56
|
+
session?: SessionState<TData>;
|
|
57
|
+
stepConditionContext?: string[]; // AI context strings from step conditions
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface BuildRoutingPromptParams<TContext = unknown, TData = unknown> {
|
|
61
|
+
history: Event[];
|
|
62
|
+
routes: Route<TContext, TData>[];
|
|
63
|
+
lastMessage: string;
|
|
64
|
+
agentOptions?: AgentOptions<TContext, TData>;
|
|
65
|
+
session?: SessionState<TData>;
|
|
66
|
+
activeRouteSteps?: Step<TContext, TData>[];
|
|
67
|
+
context?: TContext;
|
|
68
|
+
routeConditionContext?: string[]; // AI context strings from route conditions
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
72
|
+
constructor(private readonly options?: RoutingEngineOptions) { }
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Enter a route if not already in it, merging initial data
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
private enterRouteIfNeeded(
|
|
79
|
+
session: SessionState<TData>,
|
|
80
|
+
route: Route<TContext, TData>
|
|
81
|
+
): SessionState<TData> {
|
|
82
|
+
if (!session.currentRoute || session.currentRoute.id !== route.id) {
|
|
83
|
+
let updatedSession = enterRoute(session, route.id, route.title);
|
|
84
|
+
if (route.initialData) {
|
|
85
|
+
updatedSession = mergeCollected(updatedSession, route.initialData);
|
|
86
|
+
logger.debug(
|
|
87
|
+
`[RoutingEngine] Merged initial data for route ${route.title}:`,
|
|
88
|
+
route.initialData
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
logger.debug(`[RoutingEngine] Entered route: ${route.title}`);
|
|
92
|
+
return updatedSession;
|
|
93
|
+
}
|
|
94
|
+
return session;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Optimized decision for single-route scenarios
|
|
99
|
+
* Skips route scoring and only does step selection
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
private async decideSingleRouteStep(params: {
|
|
103
|
+
route: Route<TContext, TData>;
|
|
104
|
+
session: SessionState<TData>;
|
|
105
|
+
history: Event[];
|
|
106
|
+
agentOptions?: AgentOptions<TContext, TData>;
|
|
107
|
+
provider: AiProvider;
|
|
108
|
+
context: TContext;
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
selectedRoute?: Route<TContext, TData>;
|
|
112
|
+
selectedStep?: Step<TContext, TData>;
|
|
113
|
+
responseDirectives?: string[];
|
|
114
|
+
session: SessionState<TData>;
|
|
115
|
+
isRouteComplete?: boolean;
|
|
116
|
+
completedRoutes?: Route<TContext, TData>[];
|
|
117
|
+
}> {
|
|
118
|
+
const { route, session, history, agentOptions, provider, context, signal } =
|
|
119
|
+
params;
|
|
120
|
+
|
|
121
|
+
const selectedRoute = route;
|
|
122
|
+
|
|
123
|
+
// Enter route if not already in it (this may merge initial data)
|
|
124
|
+
const updatedSession = this.enterRouteIfNeeded(session, route);
|
|
125
|
+
|
|
126
|
+
// Check if this single route is complete (use updated session data)
|
|
127
|
+
const completedRoutes = route.isComplete(updatedSession.data || {}) ? [route] : [];
|
|
128
|
+
|
|
129
|
+
// Get candidate steps using new condition evaluation
|
|
130
|
+
const templateContext = createTemplateContext({
|
|
131
|
+
context,
|
|
132
|
+
session: updatedSession,
|
|
133
|
+
history,
|
|
134
|
+
data: updatedSession.data
|
|
135
|
+
});
|
|
136
|
+
const currentStep = updatedSession.currentStep
|
|
137
|
+
? route.getStep(updatedSession.currentStep.id)
|
|
138
|
+
: undefined;
|
|
139
|
+
const candidates = await this.getCandidateStepsWithConditions(
|
|
140
|
+
route,
|
|
141
|
+
currentStep,
|
|
142
|
+
templateContext
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (candidates.length === 0) {
|
|
146
|
+
logger.warn(`[RoutingEngine] Single-route: No valid steps found`);
|
|
147
|
+
return { selectedRoute, session: updatedSession };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// If only one candidate, check if it's a completion marker
|
|
151
|
+
if (candidates.length === 1) {
|
|
152
|
+
const candidate = candidates[0];
|
|
153
|
+
|
|
154
|
+
if (candidate.isRouteComplete) {
|
|
155
|
+
logger.debug(
|
|
156
|
+
`[RoutingEngine] Single-route: Route complete - all required fields collected or END_ROUTE reached`
|
|
157
|
+
);
|
|
158
|
+
// Don't return a selectedStep when route is complete - there's no step to enter
|
|
159
|
+
return {
|
|
160
|
+
selectedRoute,
|
|
161
|
+
selectedStep: undefined,
|
|
162
|
+
session: updatedSession,
|
|
163
|
+
isRouteComplete: true,
|
|
164
|
+
completedRoutes,
|
|
165
|
+
};
|
|
166
|
+
} else {
|
|
167
|
+
logger.debug(
|
|
168
|
+
`[RoutingEngine] Single-route: Only one valid step: ${candidate.step.id}`
|
|
169
|
+
);
|
|
170
|
+
return {
|
|
171
|
+
selectedRoute,
|
|
172
|
+
selectedStep: candidate.step,
|
|
173
|
+
session: updatedSession,
|
|
174
|
+
isRouteComplete: false,
|
|
175
|
+
completedRoutes,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// No candidates means route is likely complete or has no valid next steps
|
|
181
|
+
if (candidates.length === 0) {
|
|
182
|
+
const dataComplete = route.isComplete(updatedSession.data || {});
|
|
183
|
+
logger.debug(
|
|
184
|
+
`[RoutingEngine] Single-route: No valid steps found - ` +
|
|
185
|
+
`(data: ${dataComplete ? 'complete' : 'incomplete'}, marking as ${dataComplete ? 'complete' : 'incomplete'})`
|
|
186
|
+
);
|
|
187
|
+
return {
|
|
188
|
+
selectedRoute,
|
|
189
|
+
selectedStep: undefined,
|
|
190
|
+
session: updatedSession,
|
|
191
|
+
isRouteComplete: dataComplete,
|
|
192
|
+
completedRoutes,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Multiple candidates - use AI to select best step
|
|
197
|
+
const lastUserMessage = getLastMessageFromHistory(history);
|
|
198
|
+
|
|
199
|
+
// Collect AI context strings from step conditions
|
|
200
|
+
const stepConditionContext: string[] = [];
|
|
201
|
+
for (const candidate of candidates) {
|
|
202
|
+
const whenResult = await candidate.step.evaluateWhen(templateContext);
|
|
203
|
+
stepConditionContext.push(...whenResult.aiContextStrings);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check if any candidate is a completion marker (isRouteComplete = true)
|
|
207
|
+
const hasCompletionOption = candidates.some(c => c.isRouteComplete);
|
|
208
|
+
|
|
209
|
+
const stepPrompt = await this.buildStepSelectionPrompt({
|
|
210
|
+
route,
|
|
211
|
+
currentStep,
|
|
212
|
+
candidates,
|
|
213
|
+
data: updatedSession.data || {},
|
|
214
|
+
history,
|
|
215
|
+
lastMessage: lastUserMessage,
|
|
216
|
+
agentOptions,
|
|
217
|
+
context,
|
|
218
|
+
session: updatedSession,
|
|
219
|
+
stepConditionContext,
|
|
220
|
+
includeEndRoute: hasCompletionOption,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const stepSchema = this.buildStepSelectionSchema(
|
|
224
|
+
candidates.filter(c => !c.isRouteComplete).map((c) => c.step),
|
|
225
|
+
hasCompletionOption
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const stepResult = await provider.generateMessage<
|
|
229
|
+
TContext,
|
|
230
|
+
{
|
|
231
|
+
reasoning: string;
|
|
232
|
+
selectedStepId: string;
|
|
233
|
+
responseDirectives?: string[];
|
|
234
|
+
}
|
|
235
|
+
>({
|
|
236
|
+
prompt: stepPrompt,
|
|
237
|
+
history,
|
|
238
|
+
context,
|
|
239
|
+
signal,
|
|
240
|
+
parameters: {
|
|
241
|
+
jsonSchema: stepSchema,
|
|
242
|
+
schemaName: "step_selection",
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const selectedStepId = stepResult.structured?.selectedStepId;
|
|
247
|
+
|
|
248
|
+
// Check if AI selected END_ROUTE
|
|
249
|
+
if (selectedStepId === END_ROUTE_ID) {
|
|
250
|
+
logger.debug(
|
|
251
|
+
`[RoutingEngine] Single-route: AI selected END_ROUTE - completing route`
|
|
252
|
+
);
|
|
253
|
+
logger.debug(
|
|
254
|
+
`[RoutingEngine] Single-route: Reasoning: ${stepResult.structured?.reasoning}`
|
|
255
|
+
);
|
|
256
|
+
return {
|
|
257
|
+
selectedRoute,
|
|
258
|
+
selectedStep: undefined,
|
|
259
|
+
responseDirectives: stepResult.structured?.responseDirectives,
|
|
260
|
+
session: updatedSession,
|
|
261
|
+
isRouteComplete: true,
|
|
262
|
+
completedRoutes,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const selectedStep = candidates.find((c) => c.step.id === selectedStepId);
|
|
267
|
+
|
|
268
|
+
if (selectedStep) {
|
|
269
|
+
logger.debug(
|
|
270
|
+
`[RoutingEngine] Single-route: AI selected step: ${selectedStep.step.id}`
|
|
271
|
+
);
|
|
272
|
+
logger.debug(
|
|
273
|
+
`[RoutingEngine] Single-route: Reasoning: ${stepResult.structured?.reasoning}`
|
|
274
|
+
);
|
|
275
|
+
} else {
|
|
276
|
+
logger.warn(
|
|
277
|
+
`[RoutingEngine] Single-route: Invalid step ID returned, using first candidate`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
selectedRoute,
|
|
283
|
+
selectedStep: selectedStep?.step || candidates[0].step,
|
|
284
|
+
responseDirectives: stepResult.structured?.responseDirectives,
|
|
285
|
+
session: updatedSession,
|
|
286
|
+
completedRoutes,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Recursively traverse step chain to find first non-skipped step or END_ROUTE using new condition evaluation
|
|
292
|
+
* @private
|
|
293
|
+
*/
|
|
294
|
+
private async findFirstValidStepRecursiveWithConditions(
|
|
295
|
+
currentStep: Step<TContext, TData>,
|
|
296
|
+
templateContext: TemplateContext<TContext, TData>,
|
|
297
|
+
visited: Set<string>
|
|
298
|
+
): Promise<{
|
|
299
|
+
step?: Step<TContext, TData>;
|
|
300
|
+
isRouteComplete?: boolean;
|
|
301
|
+
aiContextStrings?: string[];
|
|
302
|
+
}> {
|
|
303
|
+
// Prevent infinite loops
|
|
304
|
+
if (visited.has(currentStep.id)) {
|
|
305
|
+
return { aiContextStrings: [] };
|
|
306
|
+
}
|
|
307
|
+
visited.add(currentStep.id);
|
|
308
|
+
|
|
309
|
+
const transitions = currentStep.getTransitions();
|
|
310
|
+
const allAiContextStrings: string[] = [];
|
|
311
|
+
|
|
312
|
+
for (const transition of transitions) {
|
|
313
|
+
const target = transition;
|
|
314
|
+
|
|
315
|
+
// Check for END_ROUTE transition
|
|
316
|
+
if (target && target.id === END_ROUTE_ID) {
|
|
317
|
+
// Found END_ROUTE - route is complete
|
|
318
|
+
return {
|
|
319
|
+
isRouteComplete: true,
|
|
320
|
+
aiContextStrings: allAiContextStrings,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!target) continue;
|
|
325
|
+
|
|
326
|
+
// Evaluate skipIf condition using new system
|
|
327
|
+
const skipResult = await target.evaluateSkipIf(templateContext);
|
|
328
|
+
allAiContextStrings.push(...skipResult.aiContextStrings);
|
|
329
|
+
|
|
330
|
+
// If target should NOT be skipped, we found our step
|
|
331
|
+
if (!skipResult.shouldSkip) {
|
|
332
|
+
logger.debug(
|
|
333
|
+
`[RoutingEngine] Found valid step after skipping: ${target.id}`
|
|
334
|
+
);
|
|
335
|
+
return {
|
|
336
|
+
step: target,
|
|
337
|
+
isRouteComplete: false,
|
|
338
|
+
aiContextStrings: allAiContextStrings,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Target should be skipped too - recurse deeper
|
|
343
|
+
logger.debug(
|
|
344
|
+
`[RoutingEngine] Skipping step ${target.id} (skipIf condition met), continuing traversal...`
|
|
345
|
+
);
|
|
346
|
+
const result = await this.findFirstValidStepRecursiveWithConditions(target, templateContext, visited);
|
|
347
|
+
|
|
348
|
+
// Collect AI context from recursive call
|
|
349
|
+
if (result.aiContextStrings) {
|
|
350
|
+
allAiContextStrings.push(...result.aiContextStrings);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// If we found something (a valid step or END_ROUTE), return it
|
|
354
|
+
if (result.step || result.isRouteComplete) {
|
|
355
|
+
return {
|
|
356
|
+
...result,
|
|
357
|
+
aiContextStrings: allAiContextStrings,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// No valid steps or END_ROUTE found in this branch
|
|
363
|
+
return { aiContextStrings: allAiContextStrings };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Identify valid next candidate steps using new condition evaluation system
|
|
370
|
+
* Returns step with isRouteComplete flag if route is complete (all steps skipped + has END_ROUTE transition)
|
|
371
|
+
*
|
|
372
|
+
* NEW: Automatically completes route when all required fields are collected
|
|
373
|
+
*/
|
|
374
|
+
async getCandidateStepsWithConditions(
|
|
375
|
+
route: Route<TContext, TData>,
|
|
376
|
+
currentStep: Step<TContext, TData> | undefined,
|
|
377
|
+
templateContext: TemplateContext<TContext, TData>
|
|
378
|
+
): Promise<CandidateStep<TContext, TData>[]> {
|
|
379
|
+
const candidates: CandidateStep<TContext, TData>[] = [];
|
|
380
|
+
const data = templateContext.data || {};
|
|
381
|
+
|
|
382
|
+
// Check if all required fields are collected
|
|
383
|
+
const allRequiredFieldsCollected = route.isComplete(data);
|
|
384
|
+
|
|
385
|
+
if (!currentStep) {
|
|
386
|
+
// Entering route for the first time
|
|
387
|
+
|
|
388
|
+
// If all required fields already collected, route is immediately complete
|
|
389
|
+
if (allRequiredFieldsCollected) {
|
|
390
|
+
logger.debug(
|
|
391
|
+
`[RoutingEngine] Route ${route.title} complete on entry: all required fields already collected`
|
|
392
|
+
);
|
|
393
|
+
// Return a completion marker - use initial step with completion flag
|
|
394
|
+
candidates.push({
|
|
395
|
+
step: route.initialStep,
|
|
396
|
+
isRouteComplete: true,
|
|
397
|
+
});
|
|
398
|
+
return candidates;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const initialStep = route.initialStep;
|
|
402
|
+
const skipResult = await initialStep.evaluateSkipIf(templateContext);
|
|
403
|
+
|
|
404
|
+
if (skipResult.shouldSkip) {
|
|
405
|
+
// Initial step should be skipped - recursively traverse to find first non-skipped step or END_ROUTE
|
|
406
|
+
const result = await this.findFirstValidStepRecursiveWithConditions(
|
|
407
|
+
initialStep,
|
|
408
|
+
templateContext,
|
|
409
|
+
new Set<string>()
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
if (result.isRouteComplete) {
|
|
413
|
+
// All steps are skipped and we reached END_ROUTE
|
|
414
|
+
logger.debug(
|
|
415
|
+
`[RoutingEngine] Route complete on entry: all steps skipped, END_ROUTE reached`
|
|
416
|
+
);
|
|
417
|
+
candidates.push({
|
|
418
|
+
step: initialStep,
|
|
419
|
+
isRouteComplete: true,
|
|
420
|
+
});
|
|
421
|
+
} else if (result.step) {
|
|
422
|
+
// Found a non-skipped step
|
|
423
|
+
candidates.push({
|
|
424
|
+
step: result.step,
|
|
425
|
+
isRouteComplete: result.isRouteComplete || false,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
// If no step found and not complete, fall through to return empty candidates
|
|
429
|
+
} else {
|
|
430
|
+
candidates.push({
|
|
431
|
+
step: initialStep,
|
|
432
|
+
isRouteComplete: false,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return candidates;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Check if all required fields are now collected (may have been collected during this step)
|
|
439
|
+
if (allRequiredFieldsCollected) {
|
|
440
|
+
// Required fields are complete - check if we should continue for optional fields
|
|
441
|
+
const transitions = currentStep.getTransitions();
|
|
442
|
+
const optionalFieldCandidates: CandidateStep<TContext, TData>[] = [];
|
|
443
|
+
|
|
444
|
+
for (const transition of transitions) {
|
|
445
|
+
const target = transition;
|
|
446
|
+
|
|
447
|
+
// Check for END_ROUTE transition
|
|
448
|
+
if (target && target.id === END_ROUTE_ID) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (!target) continue;
|
|
453
|
+
|
|
454
|
+
// Check if this step collects only optional fields
|
|
455
|
+
const collectsOnlyOptional = target.collect && target.collect.length > 0 &&
|
|
456
|
+
target.collect.every(field =>
|
|
457
|
+
route.optionalFields?.includes(field)
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
if (collectsOnlyOptional) {
|
|
461
|
+
// This step collects optional fields - it's a candidate
|
|
462
|
+
const skipResult = await target.evaluateSkipIf(templateContext);
|
|
463
|
+
if (!skipResult.shouldSkip) {
|
|
464
|
+
optionalFieldCandidates.push({
|
|
465
|
+
step: target,
|
|
466
|
+
isRouteComplete: false,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// If we have optional field candidates, include them along with END_ROUTE option
|
|
473
|
+
if (optionalFieldCandidates.length > 0) {
|
|
474
|
+
logger.debug(
|
|
475
|
+
`[RoutingEngine] Required fields complete, but ${optionalFieldCandidates.length} optional field steps available`
|
|
476
|
+
);
|
|
477
|
+
// Add optional field steps as candidates
|
|
478
|
+
candidates.push(...optionalFieldCandidates);
|
|
479
|
+
// Also add END_ROUTE as a candidate (AI can choose to skip optional fields)
|
|
480
|
+
candidates.push({
|
|
481
|
+
step: currentStep,
|
|
482
|
+
isRouteComplete: true,
|
|
483
|
+
});
|
|
484
|
+
return candidates;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// No optional fields to collect - route is complete
|
|
488
|
+
logger.debug(
|
|
489
|
+
`[RoutingEngine] Route ${route.title} complete: all required fields collected, no optional fields remain`
|
|
490
|
+
);
|
|
491
|
+
return [
|
|
492
|
+
{
|
|
493
|
+
step: currentStep,
|
|
494
|
+
isRouteComplete: true,
|
|
495
|
+
},
|
|
496
|
+
];
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Required fields not yet complete - continue normal step progression
|
|
500
|
+
const transitions = currentStep.getTransitions();
|
|
501
|
+
let hasEndRoute = false;
|
|
502
|
+
|
|
503
|
+
for (const transition of transitions) {
|
|
504
|
+
const target = transition;
|
|
505
|
+
|
|
506
|
+
// Check for END_ROUTE transition (no target step)
|
|
507
|
+
if (target && target.id === END_ROUTE_ID) {
|
|
508
|
+
hasEndRoute = true;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (!target) continue;
|
|
513
|
+
|
|
514
|
+
const skipResult = await target.evaluateSkipIf(templateContext);
|
|
515
|
+
|
|
516
|
+
if (skipResult.shouldSkip) {
|
|
517
|
+
logger.debug(
|
|
518
|
+
`[RoutingEngine] Skipping step ${target.id} (skipIf condition met)`
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
// Recursively traverse to find next valid step or END_ROUTE
|
|
522
|
+
const result = await this.findFirstValidStepRecursiveWithConditions(
|
|
523
|
+
target,
|
|
524
|
+
templateContext,
|
|
525
|
+
new Set<string>([currentStep.id]) // Already visited current step
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
if (result.isRouteComplete) {
|
|
529
|
+
hasEndRoute = true;
|
|
530
|
+
} else if (result.step) {
|
|
531
|
+
// Found a non-skipped step deeper in the chain
|
|
532
|
+
candidates.push({
|
|
533
|
+
step: result.step,
|
|
534
|
+
isRouteComplete: result.isRouteComplete || false,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
candidates.push({
|
|
541
|
+
step: target,
|
|
542
|
+
isRouteComplete: hasEndRoute || false,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// If no valid candidates found
|
|
547
|
+
if (candidates.length === 0) {
|
|
548
|
+
// If current step has END_ROUTE transition, the route is complete
|
|
549
|
+
if (hasEndRoute) {
|
|
550
|
+
logger.debug(
|
|
551
|
+
`[RoutingEngine] Route complete: all steps processed, END_ROUTE reached`
|
|
552
|
+
);
|
|
553
|
+
// Return current step with completion flag
|
|
554
|
+
return [
|
|
555
|
+
{
|
|
556
|
+
step: currentStep,
|
|
557
|
+
isRouteComplete: true,
|
|
558
|
+
},
|
|
559
|
+
];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Otherwise, stay in current step if it's still valid
|
|
563
|
+
const currentSkipResult = await currentStep.evaluateSkipIf(templateContext);
|
|
564
|
+
if (!currentSkipResult.shouldSkip) {
|
|
565
|
+
candidates.push({
|
|
566
|
+
step: currentStep,
|
|
567
|
+
isRouteComplete: hasEndRoute || false,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return candidates;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Full routing orchestration: builds prompt and schema, calls AI, selects route/step,
|
|
577
|
+
* and updates the session (including initialData merge when entering a new route).
|
|
578
|
+
*
|
|
579
|
+
* OPTIMIZATION: If there's only 1 route, skips route scoring and only does step selection.
|
|
580
|
+
* CROSS-ROUTE COMPLETION: Evaluates all routes for completion based on collected data.
|
|
581
|
+
*/
|
|
582
|
+
async decideRouteAndStep(params: {
|
|
583
|
+
routes: Route<TContext, TData>[];
|
|
584
|
+
session: SessionState<TData>;
|
|
585
|
+
history: Event[];
|
|
586
|
+
agentOptions?: AgentOptions<TContext, TData>;
|
|
587
|
+
provider: AiProvider;
|
|
588
|
+
context: TContext;
|
|
589
|
+
signal?: AbortSignal;
|
|
590
|
+
}): Promise<{
|
|
591
|
+
selectedRoute?: Route<TContext, TData>;
|
|
592
|
+
selectedStep?: Step<TContext, TData>;
|
|
593
|
+
responseDirectives?: string[];
|
|
594
|
+
session: SessionState<TData>;
|
|
595
|
+
isRouteComplete?: boolean;
|
|
596
|
+
completedRoutes?: Route<TContext, TData>[];
|
|
597
|
+
}> {
|
|
598
|
+
const {
|
|
599
|
+
routes,
|
|
600
|
+
session,
|
|
601
|
+
history,
|
|
602
|
+
agentOptions,
|
|
603
|
+
provider,
|
|
604
|
+
context,
|
|
605
|
+
signal,
|
|
606
|
+
} = params;
|
|
607
|
+
|
|
608
|
+
if (routes.length === 0) {
|
|
609
|
+
return { session };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// CROSS-ROUTE COMPLETION EVALUATION: Check all routes for completion
|
|
613
|
+
const completedRoutes = this.evaluateRouteCompletions(routes, session.data || {});
|
|
614
|
+
|
|
615
|
+
// Log completed routes
|
|
616
|
+
if (completedRoutes.length > 0) {
|
|
617
|
+
logger.debug(
|
|
618
|
+
`[RoutingEngine] Found ${completedRoutes.length} completed routes: ${completedRoutes.map(r => r.title).join(', ')}`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// OPTIMIZATION: Single route - skip route scoring, only do step selection
|
|
623
|
+
if (routes.length === 1) {
|
|
624
|
+
const result = await this.decideSingleRouteStep({
|
|
625
|
+
route: routes[0],
|
|
626
|
+
session,
|
|
627
|
+
history,
|
|
628
|
+
agentOptions,
|
|
629
|
+
provider,
|
|
630
|
+
context,
|
|
631
|
+
signal,
|
|
632
|
+
});
|
|
633
|
+
return {
|
|
634
|
+
...result,
|
|
635
|
+
completedRoutes,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const lastUserMessage = getLastMessageFromHistory(history);
|
|
640
|
+
const templateContext = createTemplateContext({
|
|
641
|
+
context,
|
|
642
|
+
session,
|
|
643
|
+
history,
|
|
644
|
+
data: session.data
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Apply route filtering with new condition evaluation system
|
|
648
|
+
const skipIfResult = await this.filterRoutesBySkipIf(routes, templateContext);
|
|
649
|
+
const whenResult = await this.filterRoutesByWhen(skipIfResult.eligibleRoutes, templateContext);
|
|
650
|
+
|
|
651
|
+
// Collect all AI context strings from route conditions
|
|
652
|
+
const routeConditionContext = [...skipIfResult.aiContextStrings, ...whenResult.aiContextStrings];
|
|
653
|
+
|
|
654
|
+
// Use filtered routes for further processing
|
|
655
|
+
const eligibleRoutes = whenResult.eligibleRoutes;
|
|
656
|
+
|
|
657
|
+
logger.debug(`[RoutingEngine] Route filtering: ${routes.length} total → ${skipIfResult.eligibleRoutes.length} after skipIf → ${eligibleRoutes.length} after when`);
|
|
658
|
+
|
|
659
|
+
let activeRouteSteps: Step<TContext, TData>[] | undefined;
|
|
660
|
+
let activeRoute: Route<TContext, TData> | undefined;
|
|
661
|
+
let isRouteComplete = false;
|
|
662
|
+
let updatedSession = session;
|
|
663
|
+
|
|
664
|
+
if (session.currentRoute) {
|
|
665
|
+
activeRoute = eligibleRoutes.find((r) => r.id === session.currentRoute?.id);
|
|
666
|
+
if (activeRoute) {
|
|
667
|
+
const currentStep = session.currentStep
|
|
668
|
+
? activeRoute.getStep(session.currentStep.id)
|
|
669
|
+
: undefined;
|
|
670
|
+
const activeTemplateContext = createTemplateContext({
|
|
671
|
+
...templateContext,
|
|
672
|
+
session: updatedSession,
|
|
673
|
+
data: updatedSession.data
|
|
674
|
+
});
|
|
675
|
+
const candidates = await this.getCandidateStepsWithConditions(
|
|
676
|
+
activeRoute,
|
|
677
|
+
currentStep,
|
|
678
|
+
activeTemplateContext
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
// Check if route is complete
|
|
682
|
+
// getCandidateStepsWithConditions now automatically handles completion when required fields are collected
|
|
683
|
+
if (candidates.length === 1 && candidates[0].isRouteComplete) {
|
|
684
|
+
isRouteComplete = true;
|
|
685
|
+
logger.debug(
|
|
686
|
+
`[RoutingEngine] Route ${activeRoute.title} is complete - all required fields collected or END_ROUTE reached`
|
|
687
|
+
);
|
|
688
|
+
// Don't include steps in routing if route is complete
|
|
689
|
+
activeRouteSteps = undefined;
|
|
690
|
+
} else if (candidates.length === 0) {
|
|
691
|
+
// No candidates - check if data is complete
|
|
692
|
+
const dataComplete = activeRoute.isComplete(updatedSession.data || {});
|
|
693
|
+
isRouteComplete = dataComplete;
|
|
694
|
+
logger.debug(
|
|
695
|
+
`[RoutingEngine] Route ${activeRoute.title} has no valid steps - ` +
|
|
696
|
+
`marking as ${isRouteComplete ? 'complete' : 'incomplete'}`
|
|
697
|
+
);
|
|
698
|
+
activeRouteSteps = undefined;
|
|
699
|
+
} else {
|
|
700
|
+
// Multiple candidates or single non-complete candidate
|
|
701
|
+
activeRouteSteps = candidates.map((c) => c.step);
|
|
702
|
+
logger.debug(
|
|
703
|
+
`[RoutingEngine] Found ${activeRouteSteps.length} candidate steps for active route`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const routingSchema = this.buildDynamicRoutingSchema(
|
|
710
|
+
eligibleRoutes,
|
|
711
|
+
undefined,
|
|
712
|
+
activeRouteSteps
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
const routingPrompt = await this.buildRoutingPrompt({
|
|
716
|
+
history,
|
|
717
|
+
routes: eligibleRoutes,
|
|
718
|
+
lastMessage: lastUserMessage,
|
|
719
|
+
agentOptions,
|
|
720
|
+
session,
|
|
721
|
+
activeRouteSteps,
|
|
722
|
+
context,
|
|
723
|
+
routeConditionContext, // Pass AI context strings from route conditions
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
const routingResult = await provider.generateMessage<
|
|
727
|
+
TContext,
|
|
728
|
+
RoutingDecisionOutput
|
|
729
|
+
>({
|
|
730
|
+
prompt: routingPrompt,
|
|
731
|
+
history,
|
|
732
|
+
context,
|
|
733
|
+
signal,
|
|
734
|
+
parameters: {
|
|
735
|
+
jsonSchema: routingSchema,
|
|
736
|
+
schemaName: "routing_output",
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
let selectedRoute: Route<TContext, TData> | undefined;
|
|
741
|
+
let selectedStep: Step<TContext, TData> | undefined;
|
|
742
|
+
let responseDirectives: string[] | undefined;
|
|
743
|
+
|
|
744
|
+
if (routingResult.structured?.routes) {
|
|
745
|
+
// Use cross-route completion evaluation to select optimal route
|
|
746
|
+
const optimalRoute = this.selectOptimalRoute(
|
|
747
|
+
eligibleRoutes,
|
|
748
|
+
updatedSession.data || {},
|
|
749
|
+
routingResult.structured.routes
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
// If no optimal route found, check why
|
|
753
|
+
if (!optimalRoute) {
|
|
754
|
+
if (eligibleRoutes.length === 0) {
|
|
755
|
+
// No routes passed filtering
|
|
756
|
+
logger.debug(
|
|
757
|
+
`[RoutingEngine] No eligible routes available - all routes filtered out`
|
|
758
|
+
);
|
|
759
|
+
selectedRoute = undefined;
|
|
760
|
+
} else {
|
|
761
|
+
// Routes exist but selectOptimalRoute returned undefined
|
|
762
|
+
// This means all routes are 100% complete
|
|
763
|
+
logger.debug(
|
|
764
|
+
`[RoutingEngine] No optimal route found - all ${eligibleRoutes.length} eligible routes are complete`
|
|
765
|
+
);
|
|
766
|
+
selectedRoute = undefined;
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
selectedRoute = optimalRoute;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
responseDirectives = routingResult.structured.responseDirectives;
|
|
773
|
+
|
|
774
|
+
if (
|
|
775
|
+
selectedRoute === activeRoute &&
|
|
776
|
+
routingResult.structured.selectedStepId &&
|
|
777
|
+
activeRoute
|
|
778
|
+
) {
|
|
779
|
+
selectedStep = activeRoute.getStep(
|
|
780
|
+
routingResult.structured.selectedStepId
|
|
781
|
+
);
|
|
782
|
+
if (selectedStep) {
|
|
783
|
+
logger.debug(
|
|
784
|
+
`[RoutingEngine] AI selected step: ${selectedStep.id} in active route`
|
|
785
|
+
);
|
|
786
|
+
logger.debug(
|
|
787
|
+
`[RoutingEngine] Step reasoning: ${routingResult.structured.stepReasoning}`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (selectedRoute) {
|
|
793
|
+
logger.debug(`[RoutingEngine] Selected route: ${selectedRoute.title}`);
|
|
794
|
+
updatedSession = this.enterRouteIfNeeded(updatedSession, selectedRoute);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return {
|
|
799
|
+
selectedRoute,
|
|
800
|
+
selectedStep,
|
|
801
|
+
responseDirectives,
|
|
802
|
+
session: updatedSession,
|
|
803
|
+
isRouteComplete,
|
|
804
|
+
completedRoutes,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Filter routes based on skipIf conditions
|
|
810
|
+
* @param routes - All available routes
|
|
811
|
+
* @param templateContext - Context for condition evaluation
|
|
812
|
+
* @returns Object with eligible routes and collected AI context strings
|
|
813
|
+
*/
|
|
814
|
+
async filterRoutesBySkipIf(
|
|
815
|
+
routes: Route<TContext, TData>[],
|
|
816
|
+
templateContext: TemplateContext<TContext, TData>
|
|
817
|
+
): Promise<{
|
|
818
|
+
eligibleRoutes: Route<TContext, TData>[];
|
|
819
|
+
aiContextStrings: string[];
|
|
820
|
+
}> {
|
|
821
|
+
const eligibleRoutes: Route<TContext, TData>[] = [];
|
|
822
|
+
const aiContextStrings: string[] = [];
|
|
823
|
+
|
|
824
|
+
for (const route of routes) {
|
|
825
|
+
const skipResult = await route.evaluateSkipIf(templateContext);
|
|
826
|
+
|
|
827
|
+
// Collect AI context strings from skipIf conditions
|
|
828
|
+
aiContextStrings.push(...skipResult.aiContextStrings);
|
|
829
|
+
|
|
830
|
+
// If route should not be skipped, it's eligible
|
|
831
|
+
if (!skipResult.programmaticResult) {
|
|
832
|
+
eligibleRoutes.push(route);
|
|
833
|
+
} else {
|
|
834
|
+
logger.debug(`[RoutingEngine] Skipping route ${route.title} (skipIf condition met)`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return { eligibleRoutes, aiContextStrings };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Filter routes based on when conditions
|
|
843
|
+
* @param routes - Routes that passed skipIf filtering
|
|
844
|
+
* @param templateContext - Context for condition evaluation
|
|
845
|
+
* @returns Object with eligible routes and collected AI context strings
|
|
846
|
+
*/
|
|
847
|
+
async filterRoutesByWhen(
|
|
848
|
+
routes: Route<TContext, TData>[],
|
|
849
|
+
templateContext: TemplateContext<TContext, TData>
|
|
850
|
+
): Promise<{
|
|
851
|
+
eligibleRoutes: Route<TContext, TData>[];
|
|
852
|
+
aiContextStrings: string[];
|
|
853
|
+
}> {
|
|
854
|
+
const eligibleRoutes: Route<TContext, TData>[] = [];
|
|
855
|
+
const aiContextStrings: string[] = [];
|
|
856
|
+
|
|
857
|
+
for (const route of routes) {
|
|
858
|
+
const whenResult = await route.evaluateWhen(templateContext);
|
|
859
|
+
|
|
860
|
+
// Collect AI context strings from when conditions
|
|
861
|
+
aiContextStrings.push(...whenResult.aiContextStrings);
|
|
862
|
+
|
|
863
|
+
// If route has no programmatic conditions or they evaluate to true, it's eligible
|
|
864
|
+
if (!whenResult.hasProgrammaticConditions || whenResult.programmaticResult) {
|
|
865
|
+
eligibleRoutes.push(route);
|
|
866
|
+
} else {
|
|
867
|
+
logger.debug(`[RoutingEngine] Route ${route.title} not eligible (when condition not met)`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return { eligibleRoutes, aiContextStrings };
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Evaluate all routes for completion based on collected data
|
|
876
|
+
* @param routes - All available routes
|
|
877
|
+
* @param data - Currently collected agent-level data
|
|
878
|
+
* @returns Array of routes that are complete
|
|
879
|
+
*/
|
|
880
|
+
evaluateRouteCompletions(routes: Route<TContext, TData>[], data: Partial<TData>): Route<TContext, TData>[] {
|
|
881
|
+
return routes.filter(route => route.isComplete(data));
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Get completion status for all routes
|
|
886
|
+
* @param routes - All available routes
|
|
887
|
+
* @param data - Currently collected agent-level data
|
|
888
|
+
* @returns Map of route ID to completion progress (0-1)
|
|
889
|
+
*/
|
|
890
|
+
getRouteCompletionStatus(routes: Route<TContext, TData>[], data: Partial<TData>): Map<string, number> {
|
|
891
|
+
const completionStatus = new Map<string, number>();
|
|
892
|
+
|
|
893
|
+
for (const route of routes) {
|
|
894
|
+
const progress = route.getCompletionProgress(data);
|
|
895
|
+
completionStatus.set(route.id, progress);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return completionStatus;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Find the best route to continue based on completion status and user intent
|
|
903
|
+
* Prioritizes routes that are partially complete but not finished
|
|
904
|
+
* IMPORTANT: Completed routes are excluded to prevent re-entering finished tasks
|
|
905
|
+
* @param routes - All available routes
|
|
906
|
+
* @param data - Currently collected agent-level data
|
|
907
|
+
* @param routeScores - AI-generated route scores from routing decision
|
|
908
|
+
* @returns Route that should be prioritized for continuation
|
|
909
|
+
*/
|
|
910
|
+
selectOptimalRoute(
|
|
911
|
+
routes: Route<TContext, TData>[],
|
|
912
|
+
data: Partial<TData>,
|
|
913
|
+
routeScores: Record<string, number>
|
|
914
|
+
): Route<TContext, TData> | undefined {
|
|
915
|
+
const completionStatus = this.getRouteCompletionStatus(routes, data);
|
|
916
|
+
|
|
917
|
+
// Create weighted scores combining AI intent scores with completion progress
|
|
918
|
+
const weightedScores: Array<{ route: Route<TContext, TData>; score: number }> = [];
|
|
919
|
+
|
|
920
|
+
for (const route of routes) {
|
|
921
|
+
const aiScore = routeScores[route.id] || 0;
|
|
922
|
+
const completionProgress = completionStatus.get(route.id) || 0;
|
|
923
|
+
|
|
924
|
+
// ALWAYS skip fully completed routes to prevent re-entering finished tasks
|
|
925
|
+
// Users should not be forced back into completed routes
|
|
926
|
+
if (completionProgress >= 1.0) {
|
|
927
|
+
logger.debug(
|
|
928
|
+
`[RoutingEngine] Excluding completed route: ${route.title} (100% complete)`
|
|
929
|
+
);
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Boost partially complete routes that match user intent
|
|
934
|
+
let weightedScore = aiScore;
|
|
935
|
+
if (completionProgress > 0 && completionProgress < 1.0) {
|
|
936
|
+
// Boost score for partially complete routes
|
|
937
|
+
weightedScore += (completionProgress * 20); // Up to 20 point boost
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
weightedScores.push({ route, score: weightedScore });
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Sort by weighted score and return the best option
|
|
944
|
+
weightedScores.sort((a, b) => b.score - a.score);
|
|
945
|
+
|
|
946
|
+
if (weightedScores.length > 0) {
|
|
947
|
+
logger.debug(
|
|
948
|
+
`[RoutingEngine] Selected optimal route: ${weightedScores[0].route.title} ` +
|
|
949
|
+
`(AI: ${routeScores[weightedScores[0].route.id]}, ` +
|
|
950
|
+
`Completion: ${(completionStatus.get(weightedScores[0].route.id) || 0) * 100}%, ` +
|
|
951
|
+
`Weighted: ${weightedScores[0].score})`
|
|
952
|
+
);
|
|
953
|
+
return weightedScores[0].route;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return undefined;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Build prompt for step selection within a single route
|
|
961
|
+
* @private
|
|
962
|
+
*/
|
|
963
|
+
private async buildStepSelectionPrompt(
|
|
964
|
+
params: BuildStepSelectionPromptParams<TContext, TData> & { includeEndRoute?: boolean }
|
|
965
|
+
): Promise<string> {
|
|
966
|
+
const {
|
|
967
|
+
route,
|
|
968
|
+
currentStep,
|
|
969
|
+
candidates,
|
|
970
|
+
data,
|
|
971
|
+
history,
|
|
972
|
+
lastMessage,
|
|
973
|
+
agentOptions,
|
|
974
|
+
context,
|
|
975
|
+
session,
|
|
976
|
+
stepConditionContext,
|
|
977
|
+
includeEndRoute = false,
|
|
978
|
+
} = params;
|
|
979
|
+
const templateContext = createTemplateContext({ context, session, history });
|
|
980
|
+
const pc = new PromptComposer<TContext, TData>(templateContext);
|
|
981
|
+
|
|
982
|
+
// Add agent metadata
|
|
983
|
+
if (agentOptions) {
|
|
984
|
+
await pc.addAgentMeta(agentOptions);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Add route context
|
|
988
|
+
await pc.addInstruction(
|
|
989
|
+
`Active Route: ${route.title}\nDescription: ${route.description || "N/A"}`
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
// Add current step context
|
|
993
|
+
if (currentStep) {
|
|
994
|
+
await pc.addInstruction(
|
|
995
|
+
`Current Step: ${currentStep.id}\nDescription: ${currentStep.description || "N/A"
|
|
996
|
+
}`
|
|
997
|
+
);
|
|
998
|
+
} else {
|
|
999
|
+
await pc.addInstruction("Current Step: None (entering route)");
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Add collected data context
|
|
1003
|
+
if (Object.keys(data).length > 0) {
|
|
1004
|
+
await pc.addInstruction(
|
|
1005
|
+
`Collected Data So Far:\n${JSON.stringify(data, null, 2)}`
|
|
1006
|
+
);
|
|
1007
|
+
} else {
|
|
1008
|
+
await pc.addInstruction("Collected Data: None yet");
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Add conversation history
|
|
1012
|
+
await pc.addInteractionHistory(history);
|
|
1013
|
+
await pc.addLastMessage(lastMessage);
|
|
1014
|
+
|
|
1015
|
+
// Add candidate steps with condition context
|
|
1016
|
+
const stepDescriptions = [];
|
|
1017
|
+
for (const candidate of candidates) {
|
|
1018
|
+
const idx = candidates.indexOf(candidate);
|
|
1019
|
+
const parts = [
|
|
1020
|
+
`${idx + 1}. Step ID: ${candidate.step.id}`,
|
|
1021
|
+
` Description: ${candidate.step.description || "N/A"}`,
|
|
1022
|
+
];
|
|
1023
|
+
|
|
1024
|
+
// Add when condition context
|
|
1025
|
+
if (candidate.step.when) {
|
|
1026
|
+
const whenResult = await candidate.step.evaluateWhen(templateContext);
|
|
1027
|
+
if (whenResult.aiContextStrings.length > 0) {
|
|
1028
|
+
parts.push(` When conditions: ${whenResult.aiContextStrings.join(", ")}`);
|
|
1029
|
+
} else if (typeof candidate.step.when === 'string') {
|
|
1030
|
+
parts.push(` When this step should be completed: ${candidate.step.when}`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (candidate.step.requires && candidate.step.requires.length > 0) {
|
|
1035
|
+
parts.push(` Required Data: ${candidate.step.requires.join(", ")}`);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (candidate.step.collect && candidate.step.collect.length > 0) {
|
|
1039
|
+
parts.push(` Collects: ${candidate.step.collect.join(", ")}`);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
stepDescriptions.push(parts.join("\n"));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
await pc.addInstruction(
|
|
1046
|
+
`Available Steps to Transition To:\n${stepDescriptions.join("\n\n")}`
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
// Add step condition context if available
|
|
1050
|
+
if (stepConditionContext && stepConditionContext.length > 0) {
|
|
1051
|
+
await pc.addInstruction(
|
|
1052
|
+
[
|
|
1053
|
+
"",
|
|
1054
|
+
"Additional step context from conditions:",
|
|
1055
|
+
...stepConditionContext.map(ctx => `- ${ctx}`),
|
|
1056
|
+
"",
|
|
1057
|
+
"Consider this context when selecting the most appropriate step.",
|
|
1058
|
+
].join("\n")
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Add decision prompt
|
|
1063
|
+
const decisionRules = [
|
|
1064
|
+
"Task: Decide which step to transition to based on:",
|
|
1065
|
+
"1. The user's current message and intent",
|
|
1066
|
+
"2. The conversation history and context",
|
|
1067
|
+
"3. The collected data we already have",
|
|
1068
|
+
"4. The conditions and requirements of each step",
|
|
1069
|
+
"5. The logical flow of the conversation",
|
|
1070
|
+
"",
|
|
1071
|
+
"Rules:",
|
|
1072
|
+
"- If a step has a condition, evaluate whether it's met based on context",
|
|
1073
|
+
"- If a step requires data we don't have, consider if we should collect it now",
|
|
1074
|
+
"- Choose the step that makes the most sense for moving the conversation forward",
|
|
1075
|
+
"- Steps with skipIf conditions that are met have already been filtered out",
|
|
1076
|
+
];
|
|
1077
|
+
|
|
1078
|
+
if (includeEndRoute) {
|
|
1079
|
+
decisionRules.push(
|
|
1080
|
+
"",
|
|
1081
|
+
`- You can select '${END_ROUTE_ID}' to complete this route if:`,
|
|
1082
|
+
" * All required data has been collected",
|
|
1083
|
+
" * The user's intent suggests they're done with this task",
|
|
1084
|
+
" * No further steps are needed to fulfill the user's request"
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
decisionRules.push(
|
|
1089
|
+
"",
|
|
1090
|
+
"Return ONLY JSON matching the provided schema."
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
await pc.addInstruction(decisionRules.join("\n"));
|
|
1094
|
+
|
|
1095
|
+
return pc.build();
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Build schema for step selection
|
|
1100
|
+
* @private
|
|
1101
|
+
*/
|
|
1102
|
+
private buildStepSelectionSchema(
|
|
1103
|
+
validSteps: Step<TContext, TData>[],
|
|
1104
|
+
includeEndRoute: boolean = false
|
|
1105
|
+
): StructuredSchema {
|
|
1106
|
+
const stepIds = validSteps.map((s) => s.id);
|
|
1107
|
+
|
|
1108
|
+
// Add END_ROUTE as an option if requested (when required fields are complete)
|
|
1109
|
+
if (includeEndRoute) {
|
|
1110
|
+
stepIds.push(END_ROUTE_ID);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
return {
|
|
1114
|
+
description:
|
|
1115
|
+
"Step transition decision based on conversation context and collected data",
|
|
1116
|
+
type: "object",
|
|
1117
|
+
properties: {
|
|
1118
|
+
reasoning: {
|
|
1119
|
+
type: "string",
|
|
1120
|
+
nullable: false,
|
|
1121
|
+
description: "Brief explanation of why this step was selected",
|
|
1122
|
+
},
|
|
1123
|
+
selectedStepId: {
|
|
1124
|
+
type: "string",
|
|
1125
|
+
nullable: false,
|
|
1126
|
+
description: includeEndRoute
|
|
1127
|
+
? `The ID of the selected step to transition to, or '${END_ROUTE_ID}' to complete the route`
|
|
1128
|
+
: "The ID of the selected step to transition to",
|
|
1129
|
+
enum: stepIds,
|
|
1130
|
+
},
|
|
1131
|
+
responseDirectives: {
|
|
1132
|
+
type: "array",
|
|
1133
|
+
items: { type: "string" },
|
|
1134
|
+
description:
|
|
1135
|
+
"Optional bullet points the response should address (concise)",
|
|
1136
|
+
},
|
|
1137
|
+
},
|
|
1138
|
+
required: ["reasoning", "selectedStepId"],
|
|
1139
|
+
additionalProperties: false,
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
buildDynamicRoutingSchema(
|
|
1144
|
+
routes: Route<TContext, TData>[],
|
|
1145
|
+
extrasSchema?: StructuredSchema,
|
|
1146
|
+
activeRouteSteps?: Step<TContext, TData>[]
|
|
1147
|
+
): StructuredSchema {
|
|
1148
|
+
const routeIds = routes.map((r) => r.id);
|
|
1149
|
+
const routeProperties: Record<string, StructuredSchema> = {};
|
|
1150
|
+
for (const id of routeIds) {
|
|
1151
|
+
routeProperties[id] = {
|
|
1152
|
+
type: "number",
|
|
1153
|
+
nullable: false,
|
|
1154
|
+
description: `Score for route ${id} based on direct evidence, context and semantic fit (0-100)`,
|
|
1155
|
+
minimum: 0,
|
|
1156
|
+
maximum: 100,
|
|
1157
|
+
} as StructuredSchema;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const base: StructuredSchema = {
|
|
1161
|
+
description:
|
|
1162
|
+
"Full intent analysis: score ALL available routes (0-100) using evidence and context",
|
|
1163
|
+
type: "object",
|
|
1164
|
+
properties: {
|
|
1165
|
+
context: {
|
|
1166
|
+
type: "string",
|
|
1167
|
+
nullable: false,
|
|
1168
|
+
description: "Brief summary of the user's intent/context",
|
|
1169
|
+
},
|
|
1170
|
+
routes: {
|
|
1171
|
+
type: "object",
|
|
1172
|
+
properties: routeProperties,
|
|
1173
|
+
required: routeIds,
|
|
1174
|
+
nullable: false,
|
|
1175
|
+
description: "Mapping of routeId to score (0-100)",
|
|
1176
|
+
},
|
|
1177
|
+
responseDirectives: {
|
|
1178
|
+
type: "array",
|
|
1179
|
+
items: { type: "string" },
|
|
1180
|
+
description:
|
|
1181
|
+
"Optional bullet points the response should address (concise)",
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
required: ["context", "routes"],
|
|
1185
|
+
additionalProperties: false,
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
// Add step selection fields if there's an active route with steps
|
|
1189
|
+
if (activeRouteSteps && activeRouteSteps.length > 0) {
|
|
1190
|
+
base.properties = base.properties || {};
|
|
1191
|
+
base.properties.selectedStepId = {
|
|
1192
|
+
type: "string",
|
|
1193
|
+
nullable: false,
|
|
1194
|
+
description:
|
|
1195
|
+
"The step ID to transition to within the active route (required if continuing in current route)",
|
|
1196
|
+
enum: activeRouteSteps.map((s) => s.id),
|
|
1197
|
+
};
|
|
1198
|
+
base.properties.stepReasoning = {
|
|
1199
|
+
type: "string",
|
|
1200
|
+
nullable: false,
|
|
1201
|
+
description: "Brief explanation of why this step was selected",
|
|
1202
|
+
};
|
|
1203
|
+
base.required = [
|
|
1204
|
+
...(base.required || []),
|
|
1205
|
+
"selectedStepId",
|
|
1206
|
+
"stepReasoning",
|
|
1207
|
+
];
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
if (extrasSchema) {
|
|
1211
|
+
base.properties = base.properties || {};
|
|
1212
|
+
base.properties.extractions = extrasSchema;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
return base;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
async buildRoutingPrompt(
|
|
1219
|
+
params: BuildRoutingPromptParams<TContext, TData>
|
|
1220
|
+
): Promise<string> {
|
|
1221
|
+
const {
|
|
1222
|
+
history,
|
|
1223
|
+
routes,
|
|
1224
|
+
lastMessage,
|
|
1225
|
+
agentOptions,
|
|
1226
|
+
session,
|
|
1227
|
+
activeRouteSteps,
|
|
1228
|
+
context,
|
|
1229
|
+
routeConditionContext,
|
|
1230
|
+
} = params;
|
|
1231
|
+
const templateContext = createTemplateContext({ context, session, history });
|
|
1232
|
+
const pc = new PromptComposer<TContext, TData>(templateContext);
|
|
1233
|
+
if (agentOptions) {
|
|
1234
|
+
await pc.addAgentMeta(agentOptions);
|
|
1235
|
+
}
|
|
1236
|
+
await pc.addInstruction(
|
|
1237
|
+
"Task: Intent analysis and route scoring (0-100). Score ALL listed routes."
|
|
1238
|
+
);
|
|
1239
|
+
|
|
1240
|
+
// Add session context if available
|
|
1241
|
+
if (session?.currentRoute) {
|
|
1242
|
+
const sessionInfo = [
|
|
1243
|
+
"Current conversation context:",
|
|
1244
|
+
`- Active route: ${session.currentRoute.title} (${session.currentRoute.id})`,
|
|
1245
|
+
];
|
|
1246
|
+
if (session.currentStep) {
|
|
1247
|
+
sessionInfo.push(`- Current step: ${session.currentStep.id}`);
|
|
1248
|
+
if (session.currentStep.description) {
|
|
1249
|
+
sessionInfo.push(` "${session.currentStep.description}"`);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (session.data && Object.keys(session.data).length > 0) {
|
|
1253
|
+
sessionInfo.push(`- Collected data: ${JSON.stringify(session.data)}`);
|
|
1254
|
+
}
|
|
1255
|
+
sessionInfo.push(
|
|
1256
|
+
"Note: User is mid-conversation. They may want to continue current route or switch to a new one based on their intent."
|
|
1257
|
+
);
|
|
1258
|
+
await pc.addInstruction(sessionInfo.join("\n"));
|
|
1259
|
+
|
|
1260
|
+
// Add cross-route completion status
|
|
1261
|
+
const completionStatus = this.getRouteCompletionStatus(routes, session.data || {});
|
|
1262
|
+
const completedRoutes = this.evaluateRouteCompletions(routes, session.data || {});
|
|
1263
|
+
|
|
1264
|
+
if (completionStatus.size > 0) {
|
|
1265
|
+
const statusInfo = [
|
|
1266
|
+
"",
|
|
1267
|
+
"Route completion status based on collected data:",
|
|
1268
|
+
];
|
|
1269
|
+
|
|
1270
|
+
for (const route of routes) {
|
|
1271
|
+
const progress = completionStatus.get(route.id) || 0;
|
|
1272
|
+
const isComplete = completedRoutes.includes(route);
|
|
1273
|
+
const progressPercent = Math.round(progress * 100);
|
|
1274
|
+
|
|
1275
|
+
statusInfo.push(
|
|
1276
|
+
`- ${route.title}: ${progressPercent}% complete${isComplete ? ' ✓ COMPLETE' : ''}`
|
|
1277
|
+
);
|
|
1278
|
+
|
|
1279
|
+
if (!isComplete && route.requiredFields) {
|
|
1280
|
+
const missingFields = route.getMissingRequiredFields(session.data || {});
|
|
1281
|
+
if (missingFields.length > 0) {
|
|
1282
|
+
statusInfo.push(` Missing: ${missingFields.join(', ')}`);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
statusInfo.push(
|
|
1288
|
+
"",
|
|
1289
|
+
"Consider route completion status when scoring. Partially complete routes may be good candidates for continuation."
|
|
1290
|
+
);
|
|
1291
|
+
|
|
1292
|
+
await pc.addInstruction(statusInfo.join("\n"));
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Add available steps for the active route
|
|
1296
|
+
if (activeRouteSteps && activeRouteSteps.length > 0) {
|
|
1297
|
+
const stepInfo = [
|
|
1298
|
+
"",
|
|
1299
|
+
"Available steps in active route (choose one to transition to):",
|
|
1300
|
+
];
|
|
1301
|
+
const activeStepConditionContext: string[] = [];
|
|
1302
|
+
|
|
1303
|
+
for (const step of activeRouteSteps) {
|
|
1304
|
+
const idx = activeRouteSteps.indexOf(step);
|
|
1305
|
+
stepInfo.push(`${idx + 1}. Step: ${step.id}`);
|
|
1306
|
+
if (step.description) {
|
|
1307
|
+
stepInfo.push(` Description: ${step.description}`);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// Collect AI context from step conditions
|
|
1311
|
+
if (step.when) {
|
|
1312
|
+
const whenResult = await step.evaluateWhen(templateContext);
|
|
1313
|
+
if (whenResult.aiContextStrings.length > 0) {
|
|
1314
|
+
stepInfo.push(` When conditions: ${whenResult.aiContextStrings.join(", ")}`);
|
|
1315
|
+
activeStepConditionContext.push(...whenResult.aiContextStrings);
|
|
1316
|
+
} else if (typeof step.when === 'string') {
|
|
1317
|
+
stepInfo.push(` When this step should be completed: ${step.when}`);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (step.requires && step.requires.length > 0) {
|
|
1322
|
+
stepInfo.push(` Required data: ${step.requires.join(", ")}`);
|
|
1323
|
+
}
|
|
1324
|
+
if (step.collect && step.collect.length > 0) {
|
|
1325
|
+
stepInfo.push(` Will collect: ${step.collect.join(", ")}`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
stepInfo.push("");
|
|
1329
|
+
stepInfo.push(
|
|
1330
|
+
"IMPORTANT: You MUST select a step to transition to. Evaluate which step makes the most sense based on:"
|
|
1331
|
+
);
|
|
1332
|
+
stepInfo.push("- The conversation flow and what's been collected");
|
|
1333
|
+
stepInfo.push("- What data is still needed vs already present");
|
|
1334
|
+
stepInfo.push("- The logical next step in the conversation");
|
|
1335
|
+
stepInfo.push("- Whether conditions for steps are met");
|
|
1336
|
+
await pc.addInstruction(stepInfo.join("\n"));
|
|
1337
|
+
|
|
1338
|
+
// Add active step condition context if available
|
|
1339
|
+
if (activeStepConditionContext.length > 0) {
|
|
1340
|
+
await pc.addInstruction(
|
|
1341
|
+
[
|
|
1342
|
+
"",
|
|
1343
|
+
"Additional context from step conditions:",
|
|
1344
|
+
...activeStepConditionContext.map(ctx => `- ${ctx}`),
|
|
1345
|
+
"",
|
|
1346
|
+
"Use this context to inform your step selection decision.",
|
|
1347
|
+
].join("\n")
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
await pc.addInteractionHistory(history);
|
|
1354
|
+
await pc.addLastMessage(lastMessage);
|
|
1355
|
+
await pc.addRoutingOverview(routes);
|
|
1356
|
+
|
|
1357
|
+
// Add route condition context if available
|
|
1358
|
+
if (routeConditionContext && routeConditionContext.length > 0) {
|
|
1359
|
+
await pc.addInstruction(
|
|
1360
|
+
[
|
|
1361
|
+
"",
|
|
1362
|
+
"Additional routing context from route conditions:",
|
|
1363
|
+
...routeConditionContext.map(ctx => `- ${ctx}`),
|
|
1364
|
+
"",
|
|
1365
|
+
"Consider this context when scoring routes for relevance.",
|
|
1366
|
+
].join("\n")
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
await pc.addInstruction(
|
|
1370
|
+
[
|
|
1371
|
+
"Scoring rules:",
|
|
1372
|
+
"- 90-100: explicit keywords + clear intent",
|
|
1373
|
+
"- 70-89: strong contextual evidence + relevant keywords",
|
|
1374
|
+
"- 50-69: moderate relevance",
|
|
1375
|
+
"- 30-49: weak connection or ambiguous",
|
|
1376
|
+
"- 0-29: minimal/none",
|
|
1377
|
+
"Return ONLY JSON matching the provided schema. Include scores for ALL routes.",
|
|
1378
|
+
].join("\n")
|
|
1379
|
+
);
|
|
1380
|
+
return pc.build();
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
decideRouteFromScores(output: RoutingDecision): {
|
|
1384
|
+
routeId: string;
|
|
1385
|
+
maxScore: number;
|
|
1386
|
+
} {
|
|
1387
|
+
// Optionally limit candidates and apply switching threshold
|
|
1388
|
+
const entries = Object.entries(output.routes).sort((a, b) => b[1] - a[1]);
|
|
1389
|
+
const limited = this.options?.maxCandidates
|
|
1390
|
+
? entries.slice(0, this.options.maxCandidates)
|
|
1391
|
+
: entries;
|
|
1392
|
+
const [topId, topScore] = limited[0] || ["", 0];
|
|
1393
|
+
// switchThreshold is enforced by caller when a current route exists
|
|
1394
|
+
return { routeId: topId, maxScore: topScore };
|
|
1395
|
+
}
|
|
1396
|
+
}
|