@falai/agent 1.2.8 → 2.0.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 +40 -886
- package/dist/adapters/MemoryAdapter.js +2 -2
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/MongoAdapter.js +2 -2
- package/dist/adapters/MongoAdapter.js.map +1 -1
- package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenSearchAdapter.js +9 -7
- package/dist/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts +14 -0
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +25 -9
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/PrismaAdapter.js +5 -5
- package/dist/adapters/PrismaAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.js +2 -2
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts +17 -0
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +30 -11
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.js +2 -2
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/MongoAdapter.js +2 -2
- package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.js +9 -7
- package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +14 -0
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +25 -9
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/PrismaAdapter.js +5 -5
- package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +2 -2
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts +17 -0
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +30 -11
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/constants/index.d.ts +0 -9
- package/dist/cjs/constants/index.d.ts.map +1 -1
- package/dist/cjs/constants/index.js +2 -11
- package/dist/cjs/constants/index.js.map +1 -1
- package/dist/cjs/core/Agent.d.ts +119 -153
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +471 -324
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +107 -0
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -0
- package/dist/cjs/core/AutoChainExecutor.js +297 -0
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -0
- package/dist/cjs/core/BranchEvaluator.d.ts +54 -0
- package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/BranchEvaluator.js +130 -0
- package/dist/cjs/core/BranchEvaluator.js.map +1 -0
- package/dist/cjs/core/DirectiveBus.d.ts +88 -0
- package/dist/cjs/core/DirectiveBus.d.ts.map +1 -0
- package/dist/cjs/core/DirectiveBus.js +196 -0
- package/dist/cjs/core/DirectiveBus.js.map +1 -0
- package/dist/cjs/core/DirectiveChainTracker.d.ts +49 -0
- package/dist/cjs/core/DirectiveChainTracker.d.ts.map +1 -0
- package/dist/cjs/core/DirectiveChainTracker.js +121 -0
- package/dist/cjs/core/DirectiveChainTracker.js.map +1 -0
- package/dist/cjs/core/Flow.d.ts +186 -0
- package/dist/cjs/core/Flow.d.ts.map +1 -0
- package/dist/cjs/core/Flow.js +550 -0
- package/dist/cjs/core/Flow.js.map +1 -0
- package/dist/cjs/core/FlowRouter.d.ts +182 -0
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -0
- package/dist/cjs/core/{RoutingEngine.js → FlowRouter.js} +323 -306
- package/dist/cjs/core/FlowRouter.js.map +1 -0
- package/dist/cjs/core/PersistenceManager.d.ts +2 -2
- package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/core/PersistenceManager.js +7 -7
- package/dist/cjs/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/core/PromptComposer.d.ts +21 -8
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/core/PromptComposer.js +182 -105
- package/dist/cjs/core/PromptComposer.js.map +1 -1
- package/dist/cjs/core/PromptSectionCache.d.ts +1 -1
- package/dist/cjs/core/PromptSectionCache.js +1 -1
- package/dist/cjs/core/ResponseEngine.d.ts +18 -8
- package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/core/ResponseEngine.js +38 -36
- package/dist/cjs/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +73 -56
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +1191 -1014
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +124 -26
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +509 -136
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SignalEvaluator.d.ts +86 -0
- package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/SignalEvaluator.js +333 -0
- package/dist/cjs/core/SignalEvaluator.js.map +1 -0
- package/dist/cjs/core/SignalProcessor.d.ts +152 -0
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -0
- package/dist/cjs/core/SignalProcessor.js +562 -0
- package/dist/cjs/core/SignalProcessor.js.map +1 -0
- package/dist/cjs/core/Step.d.ts +43 -32
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +221 -126
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts +2 -2
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +44 -13
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +174 -91
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/core/createAgent.d.ts +35 -0
- package/dist/cjs/core/createAgent.d.ts.map +1 -0
- package/dist/cjs/core/createAgent.js +39 -0
- package/dist/cjs/core/createAgent.js.map +1 -0
- package/dist/cjs/core/flow-namespace.d.ts +49 -0
- package/dist/cjs/core/flow-namespace.d.ts.map +1 -0
- package/dist/cjs/core/flow-namespace.js +171 -0
- package/dist/cjs/core/flow-namespace.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -14
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +18 -22
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +183 -54
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js +0 -6
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/ai.d.ts +3 -3
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/errors.d.ts +15 -0
- package/dist/cjs/types/errors.d.ts.map +1 -0
- package/dist/cjs/types/errors.js +22 -0
- package/dist/cjs/types/errors.js.map +1 -0
- package/dist/cjs/types/flow.d.ts +513 -0
- package/dist/cjs/types/flow.d.ts.map +1 -0
- package/dist/cjs/types/{route.js → flow.js} +2 -2
- package/dist/cjs/types/flow.js.map +1 -0
- package/dist/cjs/types/index.d.ts +7 -6
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +6 -2
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/persistence.d.ts +11 -7
- package/dist/cjs/types/persistence.d.ts.map +1 -1
- package/dist/cjs/types/routing.d.ts +1 -1
- package/dist/cjs/types/routing.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +24 -23
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/signals.d.ts +248 -0
- package/dist/cjs/types/signals.d.ts.map +1 -0
- package/dist/cjs/types/signals.js +11 -0
- package/dist/cjs/types/signals.js.map +1 -0
- package/dist/cjs/types/template.d.ts +2 -8
- package/dist/cjs/types/template.d.ts.map +1 -1
- package/dist/cjs/types/tool.d.ts +36 -29
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js +1 -1
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/condition.d.ts +7 -1
- package/dist/cjs/utils/condition.d.ts.map +1 -1
- package/dist/cjs/utils/condition.js.map +1 -1
- package/dist/cjs/utils/id.d.ts +13 -5
- package/dist/cjs/utils/id.d.ts.map +1 -1
- package/dist/cjs/utils/id.js +24 -10
- package/dist/cjs/utils/id.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +2 -2
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +7 -3
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +44 -5
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +197 -38
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/constants/index.d.ts +0 -9
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +3 -9
- package/dist/constants/index.js.map +1 -1
- package/dist/core/Agent.d.ts +119 -153
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +472 -325
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts +107 -0
- package/dist/core/AutoChainExecutor.d.ts.map +1 -0
- package/dist/core/AutoChainExecutor.js +293 -0
- package/dist/core/AutoChainExecutor.js.map +1 -0
- package/dist/core/BranchEvaluator.d.ts +54 -0
- package/dist/core/BranchEvaluator.d.ts.map +1 -0
- package/dist/core/BranchEvaluator.js +126 -0
- package/dist/core/BranchEvaluator.js.map +1 -0
- package/dist/core/DirectiveBus.d.ts +88 -0
- package/dist/core/DirectiveBus.d.ts.map +1 -0
- package/dist/core/DirectiveBus.js +192 -0
- package/dist/core/DirectiveBus.js.map +1 -0
- package/dist/core/DirectiveChainTracker.d.ts +49 -0
- package/dist/core/DirectiveChainTracker.d.ts.map +1 -0
- package/dist/core/DirectiveChainTracker.js +117 -0
- package/dist/core/DirectiveChainTracker.js.map +1 -0
- package/dist/core/Flow.d.ts +186 -0
- package/dist/core/Flow.d.ts.map +1 -0
- package/dist/core/Flow.js +546 -0
- package/dist/core/Flow.js.map +1 -0
- package/dist/core/FlowRouter.d.ts +182 -0
- package/dist/core/FlowRouter.d.ts.map +1 -0
- package/dist/core/{RoutingEngine.js → FlowRouter.js} +322 -305
- package/dist/core/FlowRouter.js.map +1 -0
- package/dist/core/PersistenceManager.d.ts +2 -2
- package/dist/core/PersistenceManager.d.ts.map +1 -1
- package/dist/core/PersistenceManager.js +7 -7
- package/dist/core/PersistenceManager.js.map +1 -1
- package/dist/core/PromptComposer.d.ts +21 -8
- package/dist/core/PromptComposer.d.ts.map +1 -1
- package/dist/core/PromptComposer.js +183 -106
- package/dist/core/PromptComposer.js.map +1 -1
- package/dist/core/PromptSectionCache.d.ts +1 -1
- package/dist/core/PromptSectionCache.js +1 -1
- package/dist/core/ResponseEngine.d.ts +18 -8
- package/dist/core/ResponseEngine.d.ts.map +1 -1
- package/dist/core/ResponseEngine.js +38 -36
- package/dist/core/ResponseEngine.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +73 -56
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +1193 -1016
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +124 -26
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +509 -137
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SignalEvaluator.d.ts +86 -0
- package/dist/core/SignalEvaluator.d.ts.map +1 -0
- package/dist/core/SignalEvaluator.js +326 -0
- package/dist/core/SignalEvaluator.js.map +1 -0
- package/dist/core/SignalProcessor.d.ts +152 -0
- package/dist/core/SignalProcessor.d.ts.map +1 -0
- package/dist/core/SignalProcessor.js +555 -0
- package/dist/core/SignalProcessor.js.map +1 -0
- package/dist/core/Step.d.ts +43 -32
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +220 -126
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts +2 -2
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/core/StreamingToolExecutor.js.map +1 -1
- package/dist/core/ToolManager.d.ts +44 -13
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +174 -91
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/core/createAgent.d.ts +35 -0
- package/dist/core/createAgent.d.ts.map +1 -0
- package/dist/core/createAgent.js +36 -0
- package/dist/core/createAgent.js.map +1 -0
- package/dist/core/flow-namespace.d.ts +49 -0
- package/dist/core/flow-namespace.d.ts.map +1 -0
- package/dist/core/flow-namespace.js +168 -0
- package/dist/core/flow-namespace.js.map +1 -0
- package/dist/index.d.ts +11 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -12
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +183 -54
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js +0 -6
- package/dist/types/agent.js.map +1 -1
- package/dist/types/ai.d.ts +3 -3
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +18 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/flow.d.ts +513 -0
- package/dist/types/flow.d.ts.map +1 -0
- package/dist/types/flow.js +5 -0
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +7 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/persistence.d.ts +11 -7
- package/dist/types/persistence.d.ts.map +1 -1
- package/dist/types/routing.d.ts +1 -1
- package/dist/types/routing.d.ts.map +1 -1
- package/dist/types/session.d.ts +24 -23
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/signals.d.ts +248 -0
- package/dist/types/signals.d.ts.map +1 -0
- package/dist/types/signals.js +10 -0
- package/dist/types/signals.js.map +1 -0
- package/dist/types/template.d.ts +2 -8
- package/dist/types/template.d.ts.map +1 -1
- package/dist/types/tool.d.ts +36 -29
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js +1 -1
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/condition.d.ts +7 -1
- package/dist/utils/condition.d.ts.map +1 -1
- package/dist/utils/condition.js.map +1 -1
- package/dist/utils/id.d.ts +13 -5
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +22 -9
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/session.d.ts +44 -5
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +193 -37
- package/dist/utils/session.js.map +1 -1
- package/docs/README.md +15 -202
- package/docs/concepts/architecture.md +281 -0
- package/docs/concepts/directives.md +400 -0
- package/docs/concepts/pipeline.md +399 -0
- package/docs/guides/branching.md +263 -0
- package/docs/guides/compaction.md +163 -0
- package/docs/guides/conditions.md +167 -0
- package/docs/guides/error-handling.md +176 -0
- package/docs/guides/flow-control.md +409 -0
- package/docs/guides/instructions.md +210 -0
- package/docs/guides/persistence.md +182 -0
- package/docs/guides/streaming.md +137 -0
- package/docs/migration/README.md +15 -0
- package/docs/migration/route-to-flow.md +560 -0
- package/docs/migration/v1-to-v2.md +909 -0
- package/docs/reference/adapters.md +481 -0
- package/docs/reference/branches.md +241 -0
- package/docs/reference/create-agent.md +186 -0
- package/docs/reference/directive.md +243 -0
- package/docs/reference/errors.md +122 -0
- package/docs/reference/flow.md +238 -0
- package/docs/reference/instruction.md +177 -0
- package/docs/reference/pre-directive.md +131 -0
- package/docs/reference/providers.md +227 -0
- package/docs/reference/signals.md +356 -0
- package/docs/reference/step.md +339 -0
- package/docs/reference/tool.md +269 -0
- package/docs/start/01-install.md +81 -0
- package/docs/start/02-first-agent.md +196 -0
- package/docs/start/03-collect-data.md +222 -0
- package/docs/start/04-add-tools.md +276 -0
- package/docs/start/05-go-to-production.md +216 -0
- package/examples/01-quickstart.ts +20 -0
- package/examples/02-data-extraction.ts +90 -0
- package/examples/03-tools.ts +136 -0
- package/examples/04-instructions.ts +100 -0
- package/examples/05-branching.ts +140 -0
- package/examples/06-flow-control.ts +103 -0
- package/examples/07-streaming.ts +69 -0
- package/examples/08-persistence.ts +98 -0
- package/examples/09-signals.ts +144 -0
- package/examples/tsconfig.json +30 -0
- package/package.json +2 -1
- package/src/adapters/MemoryAdapter.ts +3 -3
- package/src/adapters/MongoAdapter.ts +3 -3
- package/src/adapters/OpenSearchAdapter.ts +10 -8
- package/src/adapters/PostgreSQLAdapter.ts +26 -10
- package/src/adapters/PrismaAdapter.ts +6 -6
- package/src/adapters/RedisAdapter.ts +3 -3
- package/src/adapters/SQLiteAdapter.ts +31 -12
- package/src/constants/index.ts +2 -10
- package/src/core/Agent.ts +585 -374
- package/src/core/AutoChainExecutor.ts +440 -0
- package/src/core/BranchEvaluator.ts +167 -0
- package/src/core/DirectiveBus.ts +248 -0
- package/src/core/DirectiveChainTracker.ts +144 -0
- package/src/core/Flow.ts +666 -0
- package/src/core/{RoutingEngine.ts → FlowRouter.ts} +385 -365
- package/src/core/PersistenceManager.ts +8 -8
- package/src/core/PromptComposer.ts +209 -140
- package/src/core/PromptSectionCache.ts +1 -1
- package/src/core/ResponseEngine.ts +61 -46
- package/src/core/ResponseModal.ts +1453 -1240
- package/src/core/ResponsePipeline.ts +655 -175
- package/src/core/SignalEvaluator.ts +420 -0
- package/src/core/SignalProcessor.ts +723 -0
- package/src/core/Step.ts +279 -176
- package/src/core/StreamingToolExecutor.ts +4 -4
- package/src/core/ToolManager.ts +200 -97
- package/src/core/createAgent.ts +40 -0
- package/src/core/flow-namespace.ts +219 -0
- package/src/index.ts +42 -36
- package/src/types/agent.ts +182 -53
- package/src/types/ai.ts +3 -3
- package/src/types/errors.ts +18 -0
- package/src/types/flow.ts +590 -0
- package/src/types/index.ts +43 -16
- package/src/types/persistence.ts +12 -8
- package/src/types/routing.ts +1 -1
- package/src/types/session.ts +26 -23
- package/src/types/signals.ts +321 -0
- package/src/types/template.ts +3 -11
- package/src/types/tool.ts +50 -42
- package/src/utils/condition.ts +13 -4
- package/src/utils/id.ts +27 -9
- package/src/utils/index.ts +6 -2
- package/src/utils/session.ts +238 -42
- package/dist/cjs/core/BatchExecutor.d.ts +0 -359
- package/dist/cjs/core/BatchExecutor.d.ts.map +0 -1
- package/dist/cjs/core/BatchExecutor.js +0 -861
- package/dist/cjs/core/BatchExecutor.js.map +0 -1
- package/dist/cjs/core/BatchPromptBuilder.d.ts +0 -89
- package/dist/cjs/core/BatchPromptBuilder.d.ts.map +0 -1
- package/dist/cjs/core/BatchPromptBuilder.js +0 -223
- package/dist/cjs/core/BatchPromptBuilder.js.map +0 -1
- package/dist/cjs/core/Route.d.ts +0 -180
- package/dist/cjs/core/Route.d.ts.map +0 -1
- package/dist/cjs/core/Route.js +0 -542
- package/dist/cjs/core/Route.js.map +0 -1
- package/dist/cjs/core/RoutingEngine.d.ts +0 -185
- package/dist/cjs/core/RoutingEngine.d.ts.map +0 -1
- package/dist/cjs/core/RoutingEngine.js.map +0 -1
- package/dist/cjs/types/route.d.ts +0 -336
- package/dist/cjs/types/route.d.ts.map +0 -1
- package/dist/cjs/types/route.js.map +0 -1
- package/dist/core/BatchExecutor.d.ts +0 -359
- package/dist/core/BatchExecutor.d.ts.map +0 -1
- package/dist/core/BatchExecutor.js +0 -856
- package/dist/core/BatchExecutor.js.map +0 -1
- package/dist/core/BatchPromptBuilder.d.ts +0 -89
- package/dist/core/BatchPromptBuilder.d.ts.map +0 -1
- package/dist/core/BatchPromptBuilder.js +0 -219
- package/dist/core/BatchPromptBuilder.js.map +0 -1
- package/dist/core/Route.d.ts +0 -180
- package/dist/core/Route.d.ts.map +0 -1
- package/dist/core/Route.js +0 -538
- package/dist/core/Route.js.map +0 -1
- package/dist/core/RoutingEngine.d.ts +0 -185
- package/dist/core/RoutingEngine.d.ts.map +0 -1
- package/dist/core/RoutingEngine.js.map +0 -1
- package/dist/types/route.d.ts +0 -336
- package/dist/types/route.d.ts.map +0 -1
- package/dist/types/route.js +0 -5
- package/dist/types/route.js.map +0 -1
- package/docs/CONTRIBUTING.md +0 -521
- package/docs/api/README.md +0 -3299
- package/docs/api/overview.md +0 -1410
- package/docs/architecture/data-extraction-flow.md +0 -360
- package/docs/architecture/multi-step-execution.md +0 -277
- package/docs/core/agent/README.md +0 -938
- package/docs/core/agent/context-management.md +0 -796
- package/docs/core/agent/rules-and-prohibitions.md +0 -113
- package/docs/core/agent/session-management.md +0 -693
- package/docs/core/ai-integration/prompt-composition.md +0 -355
- package/docs/core/ai-integration/providers.md +0 -515
- package/docs/core/ai-integration/response-processing.md +0 -433
- package/docs/core/conversation-flows/data-collection.md +0 -772
- package/docs/core/conversation-flows/route-dsl.md +0 -509
- package/docs/core/conversation-flows/routes.md +0 -249
- package/docs/core/conversation-flows/step-transitions.md +0 -731
- package/docs/core/conversation-flows/steps.md +0 -268
- package/docs/core/error-handling.md +0 -830
- package/docs/core/persistence/adapters.md +0 -255
- package/docs/core/persistence/session-storage.md +0 -656
- package/docs/core/routing/intelligent-routing.md +0 -470
- package/docs/core/tools/enhanced-tool.md +0 -186
- package/docs/core/tools/streaming-execution.md +0 -161
- package/docs/core/tools/tool-definition.md +0 -970
- package/docs/core/tools/tool-scoping.md +0 -819
- package/docs/guides/advanced-patterns/publishing.md +0 -186
- package/docs/guides/context-compaction.md +0 -96
- package/docs/guides/error-handling-patterns.md +0 -578
- package/docs/guides/getting-started/README.md +0 -795
- package/docs/guides/migration/README.md +0 -101
- package/docs/guides/migration/flexible-routing-conditions.md +0 -375
- package/docs/guides/migration/multi-step-execution.md +0 -393
- package/docs/guides/migration/response-modal-refactor.md +0 -518
- package/docs/guides/prompt-optimization.md +0 -164
- package/examples/advanced-patterns/context-compaction.ts +0 -223
- package/examples/advanced-patterns/knowledge-based-agent.ts +0 -735
- package/examples/advanced-patterns/persistent-onboarding.ts +0 -728
- package/examples/advanced-patterns/route-lifecycle-hooks.ts +0 -556
- package/examples/advanced-patterns/streaming-responses.ts +0 -656
- package/examples/ai-providers/anthropic-integration.ts +0 -388
- package/examples/ai-providers/openai-integration.ts +0 -228
- package/examples/condition-patterns/function-only-conditions.ts +0 -365
- package/examples/condition-patterns/mixed-array-conditions.ts +0 -477
- package/examples/condition-patterns/route-skipif-patterns.ts +0 -468
- package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
- package/examples/condition-patterns/string-only-conditions.ts +0 -296
- package/examples/conversation-flows/completion-transitions.ts +0 -318
- package/examples/core-concepts/basic-agent.ts +0 -503
- package/examples/core-concepts/modern-streaming-api.ts +0 -309
- package/examples/core-concepts/schema-driven-extraction.ts +0 -332
- package/examples/core-concepts/session-management.ts +0 -494
- package/examples/integrations/database-integration.ts +0 -631
- package/examples/integrations/healthcare-integration.ts +0 -595
- package/examples/integrations/search-integration.ts +0 -530
- package/examples/integrations/server-session-management.ts +0 -307
- package/examples/persistence/custom-adapter.ts +0 -526
- package/examples/persistence/database-persistence.ts +0 -583
- package/examples/persistence/memory-sessions.ts +0 -495
- package/examples/persistence/prisma-schema.example.prisma +0 -74
- package/examples/persistence/redis-persistence.ts +0 -488
- package/examples/tools/basic-tools.ts +0 -765
- package/examples/tools/data-enrichment-tools.ts +0 -593
- package/examples/tools/enhanced-tool-metadata.ts +0 -268
- package/examples/tools/streaming-tool-execution.ts +0 -283
- package/src/core/BatchExecutor.ts +0 -1187
- package/src/core/BatchPromptBuilder.ts +0 -299
- package/src/core/Route.ts +0 -678
- package/src/types/route.ts +0 -392
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { MessageRole } from "../types";
|
|
2
|
-
import {
|
|
2
|
+
import { enterFlow, mergeCollected, isFlowCompletedThisSession } from "../utils";
|
|
3
3
|
import { PromptComposer } from "./PromptComposer";
|
|
4
|
-
import { END_ROUTE_ID } from "../constants";
|
|
5
4
|
import { createTemplateContext, getLastMessageFromHistory, logger, eventsToHistory } from "../utils";
|
|
6
|
-
export class
|
|
5
|
+
export class FlowRouter {
|
|
7
6
|
constructor(options) {
|
|
8
7
|
this.options = options;
|
|
9
8
|
}
|
|
10
9
|
/**
|
|
11
10
|
* Check whether the history contains any user messages.
|
|
12
|
-
* Used to detect "session resume" scenarios where a
|
|
11
|
+
* Used to detect "session resume" scenarios where a flow/step was
|
|
13
12
|
* pre-set programmatically and the conversation starts with only
|
|
14
13
|
* system messages (or no messages at all).
|
|
15
14
|
* @private
|
|
@@ -19,46 +18,46 @@ export class RoutingEngine {
|
|
|
19
18
|
}
|
|
20
19
|
/**
|
|
21
20
|
* Handle the "session resume" fast-path: when a session already has a
|
|
22
|
-
* pre-set
|
|
21
|
+
* pre-set currentFlow (and optionally currentStep) and the conversation
|
|
23
22
|
* history contains no user messages, honor the pre-set position instead
|
|
24
|
-
* of running AI
|
|
23
|
+
* of running AI flow/step selection.
|
|
25
24
|
*
|
|
26
25
|
* Returns `undefined` when the fast-path does not apply.
|
|
27
26
|
* @private
|
|
28
27
|
*/
|
|
29
28
|
async handleSessionResume(params) {
|
|
30
|
-
const {
|
|
29
|
+
const { flows, session, history, context } = params;
|
|
31
30
|
// Fast-path only applies when:
|
|
32
|
-
// 1. Session already has a
|
|
31
|
+
// 1. Session already has a currentFlow set
|
|
33
32
|
// 2. There are no user messages in the history (system-only or empty)
|
|
34
|
-
if (!session.
|
|
33
|
+
if (!session.currentFlow || this.hasUserMessages(history)) {
|
|
35
34
|
return undefined;
|
|
36
35
|
}
|
|
37
|
-
// Find the pre-set
|
|
38
|
-
const
|
|
39
|
-
if (!
|
|
40
|
-
logger.warn(`[
|
|
36
|
+
// Find the pre-set flow among available flows
|
|
37
|
+
const presetFlow = flows.find((r) => r.id === session.currentFlow.id);
|
|
38
|
+
if (!presetFlow) {
|
|
39
|
+
logger.warn(`[FlowConfigurationError] Pre-set flow not found: session references flow "${session.currentFlow.id}" which does not exist among available flows. Falling back to normal routing. Remove the stale session reference or register the missing flow.`);
|
|
41
40
|
return undefined;
|
|
42
41
|
}
|
|
43
|
-
logger.debug(`[
|
|
44
|
-
// Enter
|
|
45
|
-
const updatedSession = this.
|
|
46
|
-
// Evaluate cross-
|
|
47
|
-
const
|
|
42
|
+
logger.debug(`[FlowRouter] Session resume: honoring pre-set flow '${presetFlow.title}' (no user messages in history)`);
|
|
43
|
+
// Enter flow if needed (merges initialData, no-op if already entered)
|
|
44
|
+
const updatedSession = this.enterFlowIfNeeded(session, presetFlow);
|
|
45
|
+
// Evaluate cross-flow completions
|
|
46
|
+
const completedFlows = this.evaluateFlowCompletions(flows, updatedSession.data || {});
|
|
48
47
|
// If a currentStep is also pre-set, honor it — stay on that step
|
|
49
48
|
if (session.currentStep) {
|
|
50
|
-
const presetStep =
|
|
49
|
+
const presetStep = presetFlow.getStep(session.currentStep.id);
|
|
51
50
|
if (presetStep) {
|
|
52
|
-
logger.debug(`[
|
|
51
|
+
logger.debug(`[FlowRouter] Session resume: honoring pre-set step '${presetStep.id}'`);
|
|
53
52
|
return {
|
|
54
|
-
|
|
53
|
+
selectedFlow: presetFlow,
|
|
55
54
|
selectedStep: presetStep,
|
|
56
55
|
session: updatedSession,
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
isFlowComplete: false,
|
|
57
|
+
completedFlows,
|
|
59
58
|
};
|
|
60
59
|
}
|
|
61
|
-
logger.warn(`[
|
|
60
|
+
logger.warn(`[FlowConfigurationError] Pre-set step not found: session references step "${session.currentStep.id}" which does not exist in flow "${presetFlow.title}". Resolving from initial step. Remove the stale step reference or register the missing step.`);
|
|
62
61
|
}
|
|
63
62
|
// No currentStep pre-set (or it wasn't found) — resolve from initialStep
|
|
64
63
|
// using the standard candidate logic (handles skipIf, etc.)
|
|
@@ -68,67 +67,97 @@ export class RoutingEngine {
|
|
|
68
67
|
history,
|
|
69
68
|
data: updatedSession.data,
|
|
70
69
|
});
|
|
71
|
-
const candidates = await this.getCandidateStepsWithConditions(
|
|
70
|
+
const candidates = await this.getCandidateStepsWithConditions(presetFlow, undefined, // No current step — start from beginning
|
|
72
71
|
templateContext);
|
|
73
72
|
if (candidates.length === 0) {
|
|
74
|
-
logger.warn(`[
|
|
73
|
+
logger.warn(`[FlowConfigurationError] No valid steps found: all steps in flow "${presetFlow.title}" are skipped by their conditions. Check skip/when conditions on the flow's steps.`);
|
|
75
74
|
return {
|
|
76
|
-
|
|
75
|
+
selectedFlow: presetFlow,
|
|
77
76
|
selectedStep: undefined,
|
|
78
77
|
session: updatedSession,
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
isFlowComplete: false,
|
|
79
|
+
completedFlows,
|
|
81
80
|
};
|
|
82
81
|
}
|
|
83
82
|
const candidate = candidates[0];
|
|
84
|
-
if (candidate.
|
|
85
|
-
logger.debug(`[
|
|
83
|
+
if (candidate.isFlowComplete) {
|
|
84
|
+
logger.debug(`[FlowRouter] Session resume: flow '${presetFlow.title}' is already complete`);
|
|
86
85
|
return {
|
|
87
|
-
|
|
86
|
+
selectedFlow: presetFlow,
|
|
88
87
|
selectedStep: undefined,
|
|
89
88
|
session: updatedSession,
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
isFlowComplete: true,
|
|
90
|
+
completedFlows,
|
|
92
91
|
};
|
|
93
92
|
}
|
|
94
|
-
logger.debug(`[
|
|
93
|
+
logger.debug(`[FlowRouter] Session resume: resolved initial step '${candidate.step.id}'`);
|
|
95
94
|
return {
|
|
96
|
-
|
|
95
|
+
selectedFlow: presetFlow,
|
|
97
96
|
selectedStep: candidate.step,
|
|
98
97
|
session: updatedSession,
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
isFlowComplete: false,
|
|
99
|
+
completedFlows,
|
|
101
100
|
};
|
|
102
101
|
}
|
|
103
102
|
/**
|
|
104
|
-
* Enter a
|
|
103
|
+
* Enter a flow if not already in it, merging initial data.
|
|
104
|
+
*
|
|
105
|
+
* When the flow is `reentrant` and was previously completed in this
|
|
106
|
+
* session, clears every field declared in the flow's `requiredFields`
|
|
107
|
+
* and `optionalFields` before entering — so the flow starts fresh from
|
|
108
|
+
* its initial step instead of being instantly marked complete by stale
|
|
109
|
+
* data. Fields not owned by this flow are preserved.
|
|
110
|
+
*
|
|
105
111
|
* @private
|
|
106
112
|
*/
|
|
107
|
-
|
|
108
|
-
if (!session.
|
|
109
|
-
let
|
|
113
|
+
enterFlowIfNeeded(session, route) {
|
|
114
|
+
if (!session.currentFlow || session.currentFlow.id !== route.id) {
|
|
115
|
+
let workingSession = session;
|
|
116
|
+
// Re-entry into a `reentrant` flow that previously completed:
|
|
117
|
+
// clear owned fields so completion logic doesn't short-circuit on
|
|
118
|
+
// stale data from the prior run.
|
|
119
|
+
const previouslyCompleted = isFlowCompletedThisSession(session, route.id);
|
|
120
|
+
if (previouslyCompleted && route.reentrant) {
|
|
121
|
+
const ownedFields = [
|
|
122
|
+
...(route.requiredFields ?? []),
|
|
123
|
+
...(route.optionalFields ?? []),
|
|
124
|
+
];
|
|
125
|
+
if (ownedFields.length > 0) {
|
|
126
|
+
const owned = new Set(ownedFields);
|
|
127
|
+
const filtered = {};
|
|
128
|
+
for (const key of Object.keys(session.data ?? {})) {
|
|
129
|
+
if (!owned.has(key)) {
|
|
130
|
+
filtered[key] =
|
|
131
|
+
session.data[key];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
workingSession = { ...session, data: filtered };
|
|
135
|
+
logger.debug(`[FlowRouter] Re-entering reentrant flow ${route.title}: cleared ${ownedFields.length} owned field(s)`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
let updatedSession = enterFlow(workingSession, route.id, route.title);
|
|
110
139
|
if (route.initialData) {
|
|
111
140
|
updatedSession = mergeCollected(updatedSession, route.initialData);
|
|
112
|
-
logger.debug(`[
|
|
141
|
+
logger.debug(`[FlowRouter] Merged initial data for flow ${route.title}:`, route.initialData);
|
|
113
142
|
}
|
|
114
|
-
logger.debug(`[
|
|
115
|
-
this.options?.
|
|
143
|
+
logger.debug(`[FlowRouter] Entered flow: ${route.title}`);
|
|
144
|
+
this.options?.onFlowSwitch?.();
|
|
116
145
|
return updatedSession;
|
|
117
146
|
}
|
|
118
147
|
return session;
|
|
119
148
|
}
|
|
120
149
|
/**
|
|
121
|
-
* Optimized decision for single-
|
|
122
|
-
* Skips
|
|
150
|
+
* Optimized decision for single-flow scenarios
|
|
151
|
+
* Skips flow scoring and only does step selection
|
|
123
152
|
* @private
|
|
124
153
|
*/
|
|
125
|
-
async
|
|
154
|
+
async decideSingleFlowStep(params) {
|
|
126
155
|
const { route, session, history, agentOptions, provider, context, signal } = params;
|
|
127
|
-
const
|
|
128
|
-
// Enter
|
|
129
|
-
const updatedSession = this.
|
|
130
|
-
// Check if this single
|
|
131
|
-
const
|
|
156
|
+
const selectedFlow = route;
|
|
157
|
+
// Enter flow if not already in it (this may merge initial data)
|
|
158
|
+
const updatedSession = this.enterFlowIfNeeded(session, route);
|
|
159
|
+
// Check if this single flow is complete (use updated session data)
|
|
160
|
+
const completedFlows = route.isComplete(updatedSession.data || {}) ? [route] : [];
|
|
132
161
|
// Get candidate steps using new condition evaluation
|
|
133
162
|
const templateContext = createTemplateContext({
|
|
134
163
|
context,
|
|
@@ -141,44 +170,43 @@ export class RoutingEngine {
|
|
|
141
170
|
: undefined;
|
|
142
171
|
const candidates = await this.getCandidateStepsWithConditions(route, currentStep, templateContext);
|
|
143
172
|
if (candidates.length === 0) {
|
|
144
|
-
logger.warn(`[
|
|
145
|
-
return {
|
|
173
|
+
logger.warn(`[FlowConfigurationError] No valid steps found: all candidates in the single-flow agent are skipped. Check step skip/when conditions.`);
|
|
174
|
+
return { selectedFlow, session: updatedSession };
|
|
146
175
|
}
|
|
147
176
|
// If only one candidate, check if it's a completion marker
|
|
148
177
|
if (candidates.length === 1) {
|
|
149
178
|
const candidate = candidates[0];
|
|
150
|
-
if (candidate.
|
|
151
|
-
logger.debug(`[
|
|
152
|
-
// Don't return a selectedStep when
|
|
179
|
+
if (candidate.isFlowComplete) {
|
|
180
|
+
logger.debug(`[FlowRouter] Single-flow: Flow complete - all required fields collected or last step reached`);
|
|
181
|
+
// Don't return a selectedStep when flow is complete - there's no step to enter
|
|
153
182
|
return {
|
|
154
|
-
|
|
183
|
+
selectedFlow,
|
|
155
184
|
selectedStep: undefined,
|
|
156
185
|
session: updatedSession,
|
|
157
|
-
|
|
158
|
-
|
|
186
|
+
isFlowComplete: true,
|
|
187
|
+
completedFlows,
|
|
159
188
|
};
|
|
160
189
|
}
|
|
161
190
|
else {
|
|
162
|
-
logger.debug(`[
|
|
191
|
+
logger.debug(`[FlowRouter] Single-flow: Only one valid step: ${candidate.step.id}`);
|
|
163
192
|
return {
|
|
164
|
-
|
|
193
|
+
selectedFlow,
|
|
165
194
|
selectedStep: candidate.step,
|
|
166
195
|
session: updatedSession,
|
|
167
|
-
|
|
168
|
-
|
|
196
|
+
isFlowComplete: false,
|
|
197
|
+
completedFlows,
|
|
169
198
|
};
|
|
170
199
|
}
|
|
171
200
|
}
|
|
172
|
-
// No candidates means
|
|
173
|
-
// Don't mark as complete based on data alone — only END_ROUTE completes a route
|
|
201
|
+
// No candidates means flow has no valid next steps (edge case)
|
|
174
202
|
if (candidates.length === 0) {
|
|
175
|
-
logger.debug(`[
|
|
203
|
+
logger.debug(`[FlowRouter] Single-flow: No valid candidate steps found`);
|
|
176
204
|
return {
|
|
177
|
-
|
|
205
|
+
selectedFlow,
|
|
178
206
|
selectedStep: undefined,
|
|
179
207
|
session: updatedSession,
|
|
180
|
-
|
|
181
|
-
|
|
208
|
+
isFlowComplete: false,
|
|
209
|
+
completedFlows,
|
|
182
210
|
};
|
|
183
211
|
}
|
|
184
212
|
// Multiple candidates - use AI to select best step
|
|
@@ -189,10 +217,10 @@ export class RoutingEngine {
|
|
|
189
217
|
const whenResult = await candidate.step.evaluateWhen(templateContext);
|
|
190
218
|
stepConditionContext.push(...whenResult.aiContextStrings);
|
|
191
219
|
}
|
|
192
|
-
// Check if any candidate is a completion marker (
|
|
193
|
-
const hasCompletionOption = candidates.some(c => c.
|
|
220
|
+
// Check if any candidate is a completion marker (isFlowComplete = true)
|
|
221
|
+
const hasCompletionOption = candidates.some(c => c.isFlowComplete);
|
|
194
222
|
const stepPrompt = await this.buildStepSelectionPrompt({
|
|
195
|
-
route,
|
|
223
|
+
flow: route,
|
|
196
224
|
currentStep,
|
|
197
225
|
candidates,
|
|
198
226
|
data: updatedSession.data || {},
|
|
@@ -202,9 +230,9 @@ export class RoutingEngine {
|
|
|
202
230
|
context,
|
|
203
231
|
session: updatedSession,
|
|
204
232
|
stepConditionContext,
|
|
205
|
-
|
|
233
|
+
includeCompletion: hasCompletionOption,
|
|
206
234
|
});
|
|
207
|
-
const stepSchema = this.buildStepSelectionSchema(candidates.filter(c => !c.
|
|
235
|
+
const stepSchema = this.buildStepSelectionSchema(candidates.filter(c => !c.isFlowComplete).map((c) => c.step), hasCompletionOption);
|
|
208
236
|
const stepResult = await provider.generateMessage({
|
|
209
237
|
prompt: stepPrompt,
|
|
210
238
|
history: eventsToHistory(history),
|
|
@@ -216,37 +244,37 @@ export class RoutingEngine {
|
|
|
216
244
|
},
|
|
217
245
|
});
|
|
218
246
|
const selectedStepId = stepResult.structured?.selectedStepId;
|
|
219
|
-
// Check if AI selected
|
|
220
|
-
if (selectedStepId ===
|
|
221
|
-
logger.debug(`[
|
|
222
|
-
logger.debug(`[
|
|
247
|
+
// Check if AI selected flow completion
|
|
248
|
+
if (selectedStepId === '__COMPLETE__') {
|
|
249
|
+
logger.debug(`[FlowRouter] Single-flow: AI selected flow completion`);
|
|
250
|
+
logger.debug(`[FlowRouter] Single-flow: Reasoning: ${stepResult.structured?.reasoning}`);
|
|
223
251
|
return {
|
|
224
|
-
|
|
252
|
+
selectedFlow,
|
|
225
253
|
selectedStep: undefined,
|
|
226
254
|
responseDirectives: stepResult.structured?.responseDirectives,
|
|
227
255
|
session: updatedSession,
|
|
228
|
-
|
|
229
|
-
|
|
256
|
+
isFlowComplete: true,
|
|
257
|
+
completedFlows,
|
|
230
258
|
};
|
|
231
259
|
}
|
|
232
260
|
const selectedStep = candidates.find((c) => c.step.id === selectedStepId);
|
|
233
261
|
if (selectedStep) {
|
|
234
|
-
logger.debug(`[
|
|
235
|
-
logger.debug(`[
|
|
262
|
+
logger.debug(`[FlowRouter] Single-flow: AI selected step: ${selectedStep.step.id}`);
|
|
263
|
+
logger.debug(`[FlowRouter] Single-flow: Reasoning: ${stepResult.structured?.reasoning}`);
|
|
236
264
|
}
|
|
237
265
|
else {
|
|
238
|
-
logger.warn(`[
|
|
266
|
+
logger.warn(`[FlowConfigurationError] Invalid step ID returned: AI router returned a step ID that does not match any candidate. Falling back to first candidate. Check flow step ids and router configuration.`);
|
|
239
267
|
}
|
|
240
268
|
return {
|
|
241
|
-
|
|
269
|
+
selectedFlow,
|
|
242
270
|
selectedStep: selectedStep?.step || candidates[0].step,
|
|
243
271
|
responseDirectives: stepResult.structured?.responseDirectives,
|
|
244
272
|
session: updatedSession,
|
|
245
|
-
|
|
273
|
+
completedFlows,
|
|
246
274
|
};
|
|
247
275
|
}
|
|
248
276
|
/**
|
|
249
|
-
* Recursively traverse step chain to find first non-skipped step
|
|
277
|
+
* Recursively traverse step chain to find first non-skipped step using new condition evaluation
|
|
250
278
|
* @private
|
|
251
279
|
*/
|
|
252
280
|
async findFirstValidStepRecursiveWithConditions(currentStep, templateContext, visited) {
|
|
@@ -257,76 +285,78 @@ export class RoutingEngine {
|
|
|
257
285
|
visited.add(currentStep.id);
|
|
258
286
|
const transitions = currentStep.getTransitions();
|
|
259
287
|
const allAiContextStrings = [];
|
|
288
|
+
// No transitions means implicit terminus — flow is complete
|
|
289
|
+
if (transitions.length === 0) {
|
|
290
|
+
return {
|
|
291
|
+
isFlowComplete: true,
|
|
292
|
+
aiContextStrings: allAiContextStrings,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
260
295
|
for (const transition of transitions) {
|
|
261
296
|
const target = transition;
|
|
262
|
-
// Check for END_ROUTE transition
|
|
263
|
-
if (target && target.id === END_ROUTE_ID) {
|
|
264
|
-
// Found END_ROUTE - route is complete
|
|
265
|
-
return {
|
|
266
|
-
isRouteComplete: true,
|
|
267
|
-
aiContextStrings: allAiContextStrings,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
297
|
if (!target)
|
|
271
298
|
continue;
|
|
272
|
-
// Evaluate
|
|
273
|
-
const skipResult = await target.
|
|
299
|
+
// Evaluate skip condition (code-only, if-shape)
|
|
300
|
+
const skipResult = await target.evaluateSkip(templateContext);
|
|
274
301
|
allAiContextStrings.push(...skipResult.aiContextStrings);
|
|
275
302
|
// If target should NOT be skipped, we found our step
|
|
276
303
|
if (!skipResult.shouldSkip) {
|
|
277
|
-
logger.debug(`[
|
|
304
|
+
logger.debug(`[FlowRouter] Found valid step after skipping: ${target.id}`);
|
|
278
305
|
return {
|
|
279
306
|
step: target,
|
|
280
|
-
|
|
307
|
+
isFlowComplete: false,
|
|
281
308
|
aiContextStrings: allAiContextStrings,
|
|
282
309
|
};
|
|
283
310
|
}
|
|
284
311
|
// Target should be skipped too - recurse deeper
|
|
285
|
-
logger.debug(`[
|
|
312
|
+
logger.debug(`[FlowRouter] Skipping step ${target.id} (skipIf condition met), continuing traversal...`);
|
|
286
313
|
const result = await this.findFirstValidStepRecursiveWithConditions(target, templateContext, visited);
|
|
287
314
|
// Collect AI context from recursive call
|
|
288
315
|
if (result.aiContextStrings) {
|
|
289
316
|
allAiContextStrings.push(...result.aiContextStrings);
|
|
290
317
|
}
|
|
291
|
-
// If we found something (a valid step or
|
|
292
|
-
if (result.step || result.
|
|
318
|
+
// If we found something (a valid step or flow complete), return it
|
|
319
|
+
if (result.step || result.isFlowComplete) {
|
|
293
320
|
return {
|
|
294
321
|
...result,
|
|
295
322
|
aiContextStrings: allAiContextStrings,
|
|
296
323
|
};
|
|
297
324
|
}
|
|
298
325
|
}
|
|
299
|
-
// No valid steps
|
|
300
|
-
return {
|
|
326
|
+
// No valid steps found in this branch — all skipped with no further transitions
|
|
327
|
+
return {
|
|
328
|
+
isFlowComplete: true,
|
|
329
|
+
aiContextStrings: allAiContextStrings,
|
|
330
|
+
};
|
|
301
331
|
}
|
|
302
332
|
/**
|
|
303
333
|
* Identify valid next candidate steps using new condition evaluation system
|
|
304
|
-
* Returns step with
|
|
334
|
+
* Returns step with isFlowComplete flag if flow is complete (all steps skipped or no transitions remain)
|
|
305
335
|
*
|
|
306
|
-
*
|
|
336
|
+
* Flow completion is implicit: when the last step has no transitions, the flow is done.
|
|
307
337
|
*/
|
|
308
338
|
async getCandidateStepsWithConditions(route, currentStep, templateContext) {
|
|
309
339
|
const candidates = [];
|
|
310
340
|
if (!currentStep) {
|
|
311
|
-
// Entering
|
|
341
|
+
// Entering flow for the first time — always start the step flow
|
|
312
342
|
const initialStep = route.initialStep;
|
|
313
|
-
const skipResult = await initialStep.
|
|
343
|
+
const skipResult = await initialStep.evaluateSkip(templateContext);
|
|
314
344
|
if (skipResult.shouldSkip) {
|
|
315
|
-
// Initial step should be skipped - recursively traverse to find first non-skipped step
|
|
345
|
+
// Initial step should be skipped - recursively traverse to find first non-skipped step
|
|
316
346
|
const result = await this.findFirstValidStepRecursiveWithConditions(initialStep, templateContext, new Set());
|
|
317
|
-
if (result.
|
|
318
|
-
// All steps are skipped and
|
|
319
|
-
logger.debug(`[
|
|
347
|
+
if (result.isFlowComplete) {
|
|
348
|
+
// All steps are skipped and no transitions remain
|
|
349
|
+
logger.debug(`[FlowRouter] Flow complete on entry: all steps skipped, no transitions remain`);
|
|
320
350
|
candidates.push({
|
|
321
351
|
step: initialStep,
|
|
322
|
-
|
|
352
|
+
isFlowComplete: true,
|
|
323
353
|
});
|
|
324
354
|
}
|
|
325
355
|
else if (result.step) {
|
|
326
356
|
// Found a non-skipped step
|
|
327
357
|
candidates.push({
|
|
328
358
|
step: result.step,
|
|
329
|
-
|
|
359
|
+
isFlowComplete: result.isFlowComplete || false,
|
|
330
360
|
});
|
|
331
361
|
}
|
|
332
362
|
// If no step found and not complete, fall through to return empty candidates
|
|
@@ -334,87 +364,84 @@ export class RoutingEngine {
|
|
|
334
364
|
else {
|
|
335
365
|
candidates.push({
|
|
336
366
|
step: initialStep,
|
|
337
|
-
|
|
367
|
+
isFlowComplete: false,
|
|
338
368
|
});
|
|
339
369
|
}
|
|
340
370
|
return candidates;
|
|
341
371
|
}
|
|
342
|
-
// Continue normal step progression —
|
|
372
|
+
// Continue normal step progression — flows complete when last step has no transitions (implicit terminus)
|
|
343
373
|
const transitions = currentStep.getTransitions();
|
|
344
|
-
|
|
374
|
+
// No transitions means this is the last step — flow is complete
|
|
375
|
+
if (transitions.length === 0) {
|
|
376
|
+
logger.debug(`[FlowRouter] Flow complete: current step has no transitions (implicit terminus)`);
|
|
377
|
+
return [
|
|
378
|
+
{
|
|
379
|
+
step: currentStep,
|
|
380
|
+
isFlowComplete: true,
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
}
|
|
345
384
|
for (const transition of transitions) {
|
|
346
385
|
const target = transition;
|
|
347
|
-
// Check for END_ROUTE transition (no target step)
|
|
348
|
-
if (target && target.id === END_ROUTE_ID) {
|
|
349
|
-
hasEndRoute = true;
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
386
|
if (!target)
|
|
353
387
|
continue;
|
|
354
|
-
const skipResult = await target.
|
|
388
|
+
const skipResult = await target.evaluateSkip(templateContext);
|
|
355
389
|
if (skipResult.shouldSkip) {
|
|
356
|
-
logger.debug(`[
|
|
357
|
-
// Recursively traverse to find next valid step
|
|
390
|
+
logger.debug(`[FlowRouter] Skipping step ${target.id} (skip condition met)`);
|
|
391
|
+
// Recursively traverse to find next valid step
|
|
358
392
|
const result = await this.findFirstValidStepRecursiveWithConditions(target, templateContext, new Set([currentStep.id]) // Already visited current step
|
|
359
393
|
);
|
|
360
|
-
if (result.
|
|
361
|
-
|
|
394
|
+
if (result.isFlowComplete) {
|
|
395
|
+
// All forward paths lead to terminus
|
|
396
|
+
candidates.push({
|
|
397
|
+
step: currentStep,
|
|
398
|
+
isFlowComplete: true,
|
|
399
|
+
});
|
|
362
400
|
}
|
|
363
401
|
else if (result.step) {
|
|
364
402
|
// Found a non-skipped step deeper in the chain
|
|
365
403
|
candidates.push({
|
|
366
404
|
step: result.step,
|
|
367
|
-
|
|
405
|
+
isFlowComplete: false,
|
|
368
406
|
});
|
|
369
407
|
}
|
|
370
408
|
continue;
|
|
371
409
|
}
|
|
372
410
|
candidates.push({
|
|
373
411
|
step: target,
|
|
374
|
-
|
|
412
|
+
isFlowComplete: false,
|
|
375
413
|
});
|
|
376
414
|
}
|
|
377
|
-
// If no valid candidates found
|
|
415
|
+
// If no valid candidates found after evaluating all transitions
|
|
378
416
|
if (candidates.length === 0) {
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return [
|
|
384
|
-
{
|
|
385
|
-
step: currentStep,
|
|
386
|
-
isRouteComplete: true,
|
|
387
|
-
},
|
|
388
|
-
];
|
|
389
|
-
}
|
|
390
|
-
// Otherwise, stay in current step if it's still valid
|
|
391
|
-
const currentSkipResult = await currentStep.evaluateSkipIf(templateContext);
|
|
392
|
-
if (!currentSkipResult.shouldSkip) {
|
|
393
|
-
candidates.push({
|
|
417
|
+
// All transitions were skipped — flow is complete
|
|
418
|
+
logger.debug(`[FlowRouter] Flow complete: all transitions skipped`);
|
|
419
|
+
return [
|
|
420
|
+
{
|
|
394
421
|
step: currentStep,
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
422
|
+
isFlowComplete: true,
|
|
423
|
+
},
|
|
424
|
+
];
|
|
398
425
|
}
|
|
399
426
|
return candidates;
|
|
400
427
|
}
|
|
401
428
|
/**
|
|
402
429
|
* Full routing orchestration: builds prompt and schema, calls AI, selects route/step,
|
|
403
|
-
* and updates the session (including initialData merge when entering a new
|
|
430
|
+
* and updates the session (including initialData merge when entering a new flow).
|
|
404
431
|
*
|
|
405
432
|
* OPTIMIZATION: If there's only 1 route, skips route scoring and only does step selection.
|
|
406
|
-
* CROSS-
|
|
433
|
+
* CROSS-FLOW COMPLETION: Evaluates all flows for completion based on collected data.
|
|
407
434
|
*/
|
|
408
|
-
async
|
|
409
|
-
const {
|
|
410
|
-
if (
|
|
435
|
+
async decideFlowAndStep(params) {
|
|
436
|
+
const { flows, session, history, agentOptions, provider, context, signal, } = params;
|
|
437
|
+
if (flows.length === 0) {
|
|
411
438
|
return { session };
|
|
412
439
|
}
|
|
413
|
-
// SESSION RESUME: If the session has a pre-set
|
|
440
|
+
// SESSION RESUME: If the session has a pre-set flow and there are no user
|
|
414
441
|
// messages in the history, honor the pre-set position without AI routing.
|
|
415
442
|
// This supports programmatic session setup and persistence-based resume.
|
|
416
443
|
const resumeResult = await this.handleSessionResume({
|
|
417
|
-
|
|
444
|
+
flows,
|
|
418
445
|
session,
|
|
419
446
|
history,
|
|
420
447
|
context,
|
|
@@ -422,16 +449,24 @@ export class RoutingEngine {
|
|
|
422
449
|
if (resumeResult) {
|
|
423
450
|
return resumeResult;
|
|
424
451
|
}
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
452
|
+
// Exclude flows that completed earlier in this session unless explicitly
|
|
453
|
+
// re-entrant. A completed flow surrenders the conversation back to
|
|
454
|
+
// routing — it cannot be re-selected without `flow.reentrant: true`.
|
|
455
|
+
const reEntryFiltered = flows.filter((f) => !isFlowCompletedThisSession(session, f.id) || f.reentrant === true);
|
|
456
|
+
const excludedFlows = flows.length - reEntryFiltered.length;
|
|
457
|
+
if (excludedFlows > 0) {
|
|
458
|
+
logger.debug(`[FlowRouter] Excluded ${excludedFlows} completed (non-reentrant) flow(s) from routing candidates`);
|
|
459
|
+
}
|
|
460
|
+
// CROSS-FLOW COMPLETION EVALUATION: Check all eligible flows for completion
|
|
461
|
+
const completedFlows = this.evaluateFlowCompletions(reEntryFiltered, session.data || {});
|
|
462
|
+
// Log completed flows
|
|
463
|
+
if (completedFlows.length > 0) {
|
|
464
|
+
logger.debug(`[FlowRouter] Found ${completedFlows.length} completed routes: ${completedFlows.map(r => r.title).join(', ')}`);
|
|
465
|
+
}
|
|
466
|
+
// OPTIMIZATION: Single flow - skip flow scoring, only do step selection
|
|
467
|
+
if (reEntryFiltered.length === 1) {
|
|
468
|
+
const result = await this.decideSingleFlowStep({
|
|
469
|
+
route: reEntryFiltered[0],
|
|
435
470
|
session,
|
|
436
471
|
history,
|
|
437
472
|
agentOptions,
|
|
@@ -441,9 +476,15 @@ export class RoutingEngine {
|
|
|
441
476
|
});
|
|
442
477
|
return {
|
|
443
478
|
...result,
|
|
444
|
-
|
|
479
|
+
completedFlows,
|
|
445
480
|
};
|
|
446
481
|
}
|
|
482
|
+
// No eligible flows after re-entry filtering — caller falls back to the
|
|
483
|
+
// generic non-flow response path.
|
|
484
|
+
if (reEntryFiltered.length === 0) {
|
|
485
|
+
logger.debug(`[FlowRouter] All flows completed and none are reentrant — releasing session to fallback`);
|
|
486
|
+
return { session, completedFlows };
|
|
487
|
+
}
|
|
447
488
|
const lastUserMessage = getLastMessageFromHistory(history);
|
|
448
489
|
const templateContext = createTemplateContext({
|
|
449
490
|
context,
|
|
@@ -451,56 +492,55 @@ export class RoutingEngine {
|
|
|
451
492
|
history,
|
|
452
493
|
data: session.data
|
|
453
494
|
});
|
|
454
|
-
// Apply
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
// Use filtered routes for further processing
|
|
495
|
+
// Apply flow filtering with new condition evaluation system
|
|
496
|
+
const whenResult = await this.filterFlowsByWhen(reEntryFiltered, templateContext);
|
|
497
|
+
// Use filtered flows for further processing
|
|
458
498
|
const eligibleRoutes = whenResult.eligibleRoutes;
|
|
459
|
-
logger.debug(`[
|
|
460
|
-
let
|
|
461
|
-
let
|
|
462
|
-
let
|
|
499
|
+
logger.debug(`[FlowRouter] Flow filtering: ${flows.length} total → ${reEntryFiltered.length} after re-entry filter → ${eligibleRoutes.length} after when`);
|
|
500
|
+
let activeFlowSteps;
|
|
501
|
+
let activeFlow;
|
|
502
|
+
let isFlowComplete = false;
|
|
463
503
|
let updatedSession = session;
|
|
464
|
-
if (session.
|
|
465
|
-
|
|
466
|
-
if (
|
|
504
|
+
if (session.currentFlow) {
|
|
505
|
+
activeFlow = eligibleRoutes.find((r) => r.id === session.currentFlow?.id);
|
|
506
|
+
if (activeFlow) {
|
|
467
507
|
const currentStep = session.currentStep
|
|
468
|
-
?
|
|
508
|
+
? activeFlow.getStep(session.currentStep.id)
|
|
469
509
|
: undefined;
|
|
470
510
|
const activeTemplateContext = createTemplateContext({
|
|
471
511
|
...templateContext,
|
|
472
512
|
session: updatedSession,
|
|
473
513
|
data: updatedSession.data
|
|
474
514
|
});
|
|
475
|
-
const candidates = await this.getCandidateStepsWithConditions(
|
|
476
|
-
// Check if
|
|
515
|
+
const candidates = await this.getCandidateStepsWithConditions(activeFlow, currentStep, activeTemplateContext);
|
|
516
|
+
// Check if flow is complete
|
|
477
517
|
// getCandidateStepsWithConditions now automatically handles completion when required fields are collected
|
|
478
|
-
if (candidates.length === 1 && candidates[0].
|
|
479
|
-
|
|
480
|
-
logger.debug(`[
|
|
518
|
+
if (candidates.length === 1 && candidates[0].isFlowComplete) {
|
|
519
|
+
isFlowComplete = true;
|
|
520
|
+
logger.debug(`[FlowRouter] Flow ${activeFlow.title} is complete - all required fields collected or last step reached`);
|
|
481
521
|
// Don't include steps in routing if route is complete
|
|
482
|
-
|
|
522
|
+
activeFlowSteps = undefined;
|
|
483
523
|
}
|
|
484
524
|
else if (candidates.length === 0) {
|
|
485
|
-
// No candidates available — don't end
|
|
486
|
-
logger.debug(`[
|
|
487
|
-
|
|
525
|
+
// No candidates available — don't end flow based on data alone
|
|
526
|
+
logger.debug(`[FlowRouter] Flow ${activeFlow.title} has no valid candidate steps`);
|
|
527
|
+
activeFlowSteps = undefined;
|
|
488
528
|
}
|
|
489
529
|
else {
|
|
490
530
|
// Multiple candidates or single non-complete candidate
|
|
491
|
-
|
|
492
|
-
logger.debug(`[
|
|
531
|
+
activeFlowSteps = candidates.map((c) => c.step);
|
|
532
|
+
logger.debug(`[FlowRouter] Found ${activeFlowSteps.length} candidate steps for active route`);
|
|
493
533
|
}
|
|
494
534
|
}
|
|
495
535
|
}
|
|
496
|
-
const routingSchema = this.
|
|
536
|
+
const routingSchema = this.buildDynamicFlowSchema(eligibleRoutes, undefined, activeFlowSteps);
|
|
497
537
|
const routingPrompt = await this.buildRoutingPrompt({
|
|
498
538
|
history,
|
|
499
|
-
|
|
539
|
+
flows: eligibleRoutes,
|
|
500
540
|
lastMessage: lastUserMessage,
|
|
501
541
|
agentOptions,
|
|
502
542
|
session,
|
|
503
|
-
|
|
543
|
+
activeFlowSteps,
|
|
504
544
|
context,
|
|
505
545
|
});
|
|
506
546
|
const routingResult = await provider.generateMessage({
|
|
@@ -513,115 +553,92 @@ export class RoutingEngine {
|
|
|
513
553
|
schemaName: "routing_output",
|
|
514
554
|
},
|
|
515
555
|
});
|
|
516
|
-
let
|
|
556
|
+
let selectedFlow;
|
|
517
557
|
let selectedStep;
|
|
518
558
|
let responseDirectives;
|
|
519
|
-
if (routingResult.structured?.
|
|
520
|
-
// Use cross-
|
|
521
|
-
const optimalRoute = this.
|
|
522
|
-
// If no optimal
|
|
559
|
+
if (routingResult.structured?.flows) {
|
|
560
|
+
// Use cross-flow completion evaluation to select optimal flow
|
|
561
|
+
const optimalRoute = this.selectOptimalFlow(eligibleRoutes, updatedSession.data || {}, routingResult.structured.flows, updatedSession.currentFlow?.id);
|
|
562
|
+
// If no optimal flow found, check why
|
|
523
563
|
if (!optimalRoute) {
|
|
524
564
|
if (eligibleRoutes.length === 0) {
|
|
525
|
-
// No
|
|
526
|
-
logger.debug(`[
|
|
527
|
-
|
|
565
|
+
// No flows passed filtering
|
|
566
|
+
logger.debug(`[FlowRouter] No eligible flows available - all flows filtered out`);
|
|
567
|
+
selectedFlow = undefined;
|
|
528
568
|
}
|
|
529
569
|
else {
|
|
530
|
-
// Routes exist but
|
|
531
|
-
// This means all
|
|
532
|
-
logger.debug(`[
|
|
533
|
-
|
|
570
|
+
// Routes exist but selectOptimalFlow returned undefined
|
|
571
|
+
// This means all flows are 100% complete
|
|
572
|
+
logger.debug(`[FlowRouter] No optimal route found - all ${eligibleRoutes.length} eligible routes are complete`);
|
|
573
|
+
selectedFlow = undefined;
|
|
534
574
|
}
|
|
535
575
|
}
|
|
536
576
|
else {
|
|
537
|
-
|
|
577
|
+
selectedFlow = optimalRoute;
|
|
538
578
|
}
|
|
539
579
|
responseDirectives = routingResult.structured.responseDirectives;
|
|
540
|
-
if (
|
|
580
|
+
if (selectedFlow === activeFlow &&
|
|
541
581
|
routingResult.structured.selectedStepId &&
|
|
542
|
-
|
|
543
|
-
selectedStep =
|
|
582
|
+
activeFlow) {
|
|
583
|
+
selectedStep = activeFlow.getStep(routingResult.structured.selectedStepId);
|
|
544
584
|
if (selectedStep) {
|
|
545
|
-
logger.debug(`[
|
|
546
|
-
logger.debug(`[
|
|
585
|
+
logger.debug(`[FlowRouter] AI selected step: ${selectedStep.id} in active route`);
|
|
586
|
+
logger.debug(`[FlowRouter] Step reasoning: ${routingResult.structured.stepReasoning}`);
|
|
547
587
|
}
|
|
548
588
|
}
|
|
549
|
-
if (
|
|
550
|
-
logger.debug(`[
|
|
551
|
-
updatedSession = this.
|
|
589
|
+
if (selectedFlow) {
|
|
590
|
+
logger.debug(`[FlowRouter] Selected route: ${selectedFlow.title}`);
|
|
591
|
+
updatedSession = this.enterFlowIfNeeded(updatedSession, selectedFlow);
|
|
552
592
|
}
|
|
553
593
|
}
|
|
554
594
|
return {
|
|
555
|
-
|
|
595
|
+
selectedFlow,
|
|
556
596
|
selectedStep,
|
|
557
597
|
responseDirectives,
|
|
558
598
|
session: updatedSession,
|
|
559
|
-
|
|
560
|
-
|
|
599
|
+
isFlowComplete,
|
|
600
|
+
completedFlows,
|
|
561
601
|
};
|
|
562
602
|
}
|
|
563
603
|
/**
|
|
564
|
-
* Filter
|
|
565
|
-
* @param routes -
|
|
566
|
-
* @param templateContext - Context for condition evaluation
|
|
567
|
-
* @returns Object with eligible routes and collected AI context strings
|
|
568
|
-
*/
|
|
569
|
-
async filterRoutesBySkipIf(routes, templateContext) {
|
|
570
|
-
const eligibleRoutes = [];
|
|
571
|
-
const aiContextStrings = [];
|
|
572
|
-
for (const route of routes) {
|
|
573
|
-
const skipResult = await route.evaluateSkipIf(templateContext);
|
|
574
|
-
// Collect AI context strings from skipIf conditions
|
|
575
|
-
aiContextStrings.push(...skipResult.aiContextStrings);
|
|
576
|
-
// If route should not be skipped, it's eligible
|
|
577
|
-
if (!skipResult.programmaticResult) {
|
|
578
|
-
eligibleRoutes.push(route);
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
logger.debug(`[RoutingEngine] Skipping route ${route.title} (skipIf condition met)`);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
return { eligibleRoutes, aiContextStrings };
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Filter routes based on when conditions
|
|
588
|
-
* @param routes - Routes that passed skipIf filtering
|
|
604
|
+
* Filter flows based on when conditions
|
|
605
|
+
* @param routes - Flows that passed skipIf filtering
|
|
589
606
|
* @param templateContext - Context for condition evaluation
|
|
590
|
-
* @returns Object with eligible
|
|
607
|
+
* @returns Object with eligible flows and collected AI context strings
|
|
591
608
|
*/
|
|
592
|
-
async
|
|
609
|
+
async filterFlowsByWhen(routes, templateContext) {
|
|
593
610
|
const eligibleRoutes = [];
|
|
594
611
|
const aiContextStrings = [];
|
|
595
612
|
for (const route of routes) {
|
|
596
613
|
const whenResult = await route.evaluateWhen(templateContext);
|
|
597
614
|
// Collect AI context strings from when conditions
|
|
598
615
|
aiContextStrings.push(...whenResult.aiContextStrings);
|
|
599
|
-
// If
|
|
616
|
+
// If flow has no programmatic conditions or they evaluate to true, it's eligible
|
|
600
617
|
if (!whenResult.hasProgrammaticConditions || whenResult.programmaticResult) {
|
|
601
618
|
eligibleRoutes.push(route);
|
|
602
619
|
}
|
|
603
620
|
else {
|
|
604
|
-
logger.debug(`[
|
|
621
|
+
logger.debug(`[FlowRouter] Flow ${route.title} not eligible (when condition not met)`);
|
|
605
622
|
}
|
|
606
623
|
}
|
|
607
624
|
return { eligibleRoutes, aiContextStrings };
|
|
608
625
|
}
|
|
609
626
|
/**
|
|
610
|
-
* Evaluate all
|
|
611
|
-
* @param routes - All available
|
|
627
|
+
* Evaluate all flows for completion based on collected data
|
|
628
|
+
* @param routes - All available flows
|
|
612
629
|
* @param data - Currently collected agent-level data
|
|
613
|
-
* @returns Array of
|
|
630
|
+
* @returns Array of flows that are complete
|
|
614
631
|
*/
|
|
615
|
-
|
|
632
|
+
evaluateFlowCompletions(routes, data) {
|
|
616
633
|
return routes.filter(route => route.isComplete(data));
|
|
617
634
|
}
|
|
618
635
|
/**
|
|
619
|
-
* Get completion status for all
|
|
620
|
-
* @param routes - All available
|
|
636
|
+
* Get completion status for all flows
|
|
637
|
+
* @param routes - All available flows
|
|
621
638
|
* @param data - Currently collected agent-level data
|
|
622
|
-
* @returns Map of
|
|
639
|
+
* @returns Map of flow ID to completion progress (0-1)
|
|
623
640
|
*/
|
|
624
|
-
|
|
641
|
+
getFlowCompletionStatus(routes, data) {
|
|
625
642
|
const completionStatus = new Map();
|
|
626
643
|
for (const route of routes) {
|
|
627
644
|
const progress = route.getCompletionProgress(data);
|
|
@@ -630,28 +647,28 @@ export class RoutingEngine {
|
|
|
630
647
|
return completionStatus;
|
|
631
648
|
}
|
|
632
649
|
/**
|
|
633
|
-
* Find the best
|
|
634
|
-
* Prioritizes
|
|
635
|
-
* IMPORTANT: Completed
|
|
636
|
-
* @param routes - All available
|
|
650
|
+
* Find the best flow to continue based on completion status and user intent
|
|
651
|
+
* Prioritizes flows that are partially complete but not finished
|
|
652
|
+
* IMPORTANT: Completed flows are excluded to prevent re-entering finished tasks
|
|
653
|
+
* @param routes - All available flows
|
|
637
654
|
* @param data - Currently collected agent-level data
|
|
638
655
|
* @param routeScores - AI-generated route scores from routing decision
|
|
639
|
-
* @returns
|
|
656
|
+
* @returns Flow that should be prioritized for continuation
|
|
640
657
|
*/
|
|
641
|
-
|
|
642
|
-
const completionStatus = this.
|
|
643
|
-
const switchMargin = this.options?.
|
|
658
|
+
selectOptimalFlow(routes, data, routeScores, currentRouteId) {
|
|
659
|
+
const completionStatus = this.getFlowCompletionStatus(routes, data);
|
|
660
|
+
const switchMargin = this.options?.flowSwitchMargin ?? 15;
|
|
644
661
|
// Create weighted scores combining AI intent scores with completion progress
|
|
645
662
|
const weightedScores = [];
|
|
646
663
|
for (const route of routes) {
|
|
647
664
|
const aiScore = routeScores[route.id] || 0;
|
|
648
665
|
const completionProgress = completionStatus.get(route.id) || 0;
|
|
649
|
-
// ALWAYS skip fully completed
|
|
666
|
+
// ALWAYS skip fully completed flows to prevent re-entering finished tasks
|
|
650
667
|
if (completionProgress >= 1.0) {
|
|
651
|
-
logger.debug(`[
|
|
668
|
+
logger.debug(`[FlowRouter] Excluding completed flow: ${route.title} (100% complete)`);
|
|
652
669
|
continue;
|
|
653
670
|
}
|
|
654
|
-
// Boost partially complete
|
|
671
|
+
// Boost partially complete flows that match user intent
|
|
655
672
|
let weightedScore = aiScore;
|
|
656
673
|
if (completionProgress > 0 && completionProgress < 1.0) {
|
|
657
674
|
weightedScore += (completionProgress * 20); // Up to 20 point boost
|
|
@@ -664,48 +681,48 @@ export class RoutingEngine {
|
|
|
664
681
|
return undefined;
|
|
665
682
|
}
|
|
666
683
|
// Apply sticky routing: if there's a current route, only switch if the
|
|
667
|
-
// best alternative exceeds the current
|
|
684
|
+
// best alternative exceeds the current flow's score by the configured margin
|
|
668
685
|
if (currentRouteId) {
|
|
669
686
|
const currentEntry = weightedScores.find(e => e.route.id === currentRouteId);
|
|
670
687
|
const bestEntry = weightedScores[0];
|
|
671
688
|
if (currentEntry && bestEntry.route.id !== currentRouteId) {
|
|
672
689
|
if (bestEntry.score < currentEntry.score + switchMargin) {
|
|
673
|
-
logger.debug(`[
|
|
690
|
+
logger.debug(`[FlowRouter] Staying on current flow: ${currentEntry.route.title} ` +
|
|
674
691
|
`(current: ${currentEntry.score}, best alternative: ${bestEntry.score}, ` +
|
|
675
692
|
`margin required: ${switchMargin})`);
|
|
676
693
|
return currentEntry.route;
|
|
677
694
|
}
|
|
678
|
-
logger.debug(`[
|
|
695
|
+
logger.debug(`[FlowRouter] Switching flow: ${currentEntry.route.title} → ${bestEntry.route.title} ` +
|
|
679
696
|
`(current: ${currentEntry.score}, alternative: ${bestEntry.score}, ` +
|
|
680
697
|
`margin: ${switchMargin})`);
|
|
681
698
|
}
|
|
682
699
|
}
|
|
683
|
-
logger.debug(`[
|
|
700
|
+
logger.debug(`[FlowRouter] Selected optimal route: ${weightedScores[0].route.title} ` +
|
|
684
701
|
`(AI: ${routeScores[weightedScores[0].route.id]}, ` +
|
|
685
702
|
`Completion: ${(completionStatus.get(weightedScores[0].route.id) || 0) * 100}%, ` +
|
|
686
703
|
`Weighted: ${weightedScores[0].score})`);
|
|
687
704
|
return weightedScores[0].route;
|
|
688
705
|
}
|
|
689
706
|
/**
|
|
690
|
-
* Build prompt for step selection within a single
|
|
707
|
+
* Build prompt for step selection within a single flow
|
|
691
708
|
* @private
|
|
692
709
|
*/
|
|
693
710
|
async buildStepSelectionPrompt(params) {
|
|
694
|
-
const { route, currentStep, candidates, data, history, lastMessage, agentOptions, context, session, stepConditionContext,
|
|
711
|
+
const { flow: route, currentStep, candidates, data, history, lastMessage, agentOptions, context, session, stepConditionContext, includeCompletion = false, } = params;
|
|
695
712
|
const templateContext = createTemplateContext({ context, session, history });
|
|
696
713
|
const pc = new PromptComposer(templateContext, this.options?.promptSectionCache);
|
|
697
714
|
// Add agent metadata
|
|
698
715
|
if (agentOptions) {
|
|
699
716
|
await pc.addAgentMeta(agentOptions);
|
|
700
717
|
}
|
|
701
|
-
// Add
|
|
702
|
-
await pc.addInstruction(`Active
|
|
718
|
+
// Add flow context
|
|
719
|
+
await pc.addInstruction(`Active Flow: ${route.title}\nDescription: ${route.description || "N/A"}`);
|
|
703
720
|
// Add current step context
|
|
704
721
|
if (currentStep) {
|
|
705
722
|
await pc.addInstruction(`Current Step: ${currentStep.id}\nDescription: ${currentStep.description || "N/A"}`);
|
|
706
723
|
}
|
|
707
724
|
else {
|
|
708
|
-
await pc.addInstruction("Current Step: None (entering
|
|
725
|
+
await pc.addInstruction("Current Step: None (entering flow)");
|
|
709
726
|
}
|
|
710
727
|
// Add collected data context
|
|
711
728
|
if (Object.keys(data).length > 0) {
|
|
@@ -769,8 +786,8 @@ export class RoutingEngine {
|
|
|
769
786
|
"- Choose the step that makes the most sense for moving the conversation forward",
|
|
770
787
|
"- Steps with skipIf conditions that are met have already been filtered out",
|
|
771
788
|
];
|
|
772
|
-
if (
|
|
773
|
-
decisionRules.push("", `- You can select '
|
|
789
|
+
if (includeCompletion) {
|
|
790
|
+
decisionRules.push("", `- You can select '__COMPLETE__' to complete this flow if:`, " * All required data has been collected", " * The user's intent suggests they're done with this task", " * No further steps are needed to fulfill the user's request");
|
|
774
791
|
}
|
|
775
792
|
decisionRules.push("", "Return ONLY JSON matching the provided schema.");
|
|
776
793
|
await pc.addInstruction(decisionRules.join("\n"));
|
|
@@ -780,11 +797,11 @@ export class RoutingEngine {
|
|
|
780
797
|
* Build schema for step selection
|
|
781
798
|
* @private
|
|
782
799
|
*/
|
|
783
|
-
buildStepSelectionSchema(validSteps,
|
|
800
|
+
buildStepSelectionSchema(validSteps, includeCompletion = false) {
|
|
784
801
|
const stepIds = validSteps.map((s) => s.id);
|
|
785
|
-
// Add
|
|
786
|
-
if (
|
|
787
|
-
stepIds.push(
|
|
802
|
+
// Add completion option if requested (when required fields are complete)
|
|
803
|
+
if (includeCompletion) {
|
|
804
|
+
stepIds.push('__COMPLETE__');
|
|
788
805
|
}
|
|
789
806
|
return {
|
|
790
807
|
description: "Step transition decision based on conversation context and collected data",
|
|
@@ -798,8 +815,8 @@ export class RoutingEngine {
|
|
|
798
815
|
selectedStepId: {
|
|
799
816
|
type: "string",
|
|
800
817
|
nullable: false,
|
|
801
|
-
description:
|
|
802
|
-
?
|
|
818
|
+
description: includeCompletion
|
|
819
|
+
? "The ID of the selected step to transition to, or '__COMPLETE__' to complete the flow"
|
|
803
820
|
: "The ID of the selected step to transition to",
|
|
804
821
|
enum: stepIds,
|
|
805
822
|
},
|
|
@@ -813,20 +830,20 @@ export class RoutingEngine {
|
|
|
813
830
|
additionalProperties: false,
|
|
814
831
|
};
|
|
815
832
|
}
|
|
816
|
-
|
|
833
|
+
buildDynamicFlowSchema(routes, extrasSchema, activeFlowSteps) {
|
|
817
834
|
const routeIds = routes.map((r) => r.id);
|
|
818
835
|
const routeProperties = {};
|
|
819
836
|
for (const id of routeIds) {
|
|
820
837
|
routeProperties[id] = {
|
|
821
838
|
type: "number",
|
|
822
839
|
nullable: false,
|
|
823
|
-
description: `Score for
|
|
840
|
+
description: `Score for flow ${id} based on direct evidence, context and semantic fit (0-100)`,
|
|
824
841
|
minimum: 0,
|
|
825
842
|
maximum: 100,
|
|
826
843
|
};
|
|
827
844
|
}
|
|
828
845
|
const base = {
|
|
829
|
-
description: "Full intent analysis: score ALL available
|
|
846
|
+
description: "Full intent analysis: score ALL available flows (0-100) using evidence and context",
|
|
830
847
|
type: "object",
|
|
831
848
|
properties: {
|
|
832
849
|
context: {
|
|
@@ -834,12 +851,12 @@ export class RoutingEngine {
|
|
|
834
851
|
nullable: false,
|
|
835
852
|
description: "Brief summary of the user's intent/context",
|
|
836
853
|
},
|
|
837
|
-
|
|
854
|
+
flows: {
|
|
838
855
|
type: "object",
|
|
839
856
|
properties: routeProperties,
|
|
840
857
|
required: routeIds,
|
|
841
858
|
nullable: false,
|
|
842
|
-
description: "Mapping of
|
|
859
|
+
description: "Mapping of flowId to score (0-100)",
|
|
843
860
|
},
|
|
844
861
|
responseDirectives: {
|
|
845
862
|
type: "array",
|
|
@@ -847,17 +864,17 @@ export class RoutingEngine {
|
|
|
847
864
|
description: "Optional bullet points the response should address (concise)",
|
|
848
865
|
},
|
|
849
866
|
},
|
|
850
|
-
required: ["context", "
|
|
867
|
+
required: ["context", "flows"],
|
|
851
868
|
additionalProperties: false,
|
|
852
869
|
};
|
|
853
|
-
// Add step selection fields if there's an active
|
|
854
|
-
if (
|
|
870
|
+
// Add step selection fields if there's an active flow with steps
|
|
871
|
+
if (activeFlowSteps && activeFlowSteps.length > 0) {
|
|
855
872
|
base.properties = base.properties || {};
|
|
856
873
|
base.properties.selectedStepId = {
|
|
857
874
|
type: "string",
|
|
858
875
|
nullable: false,
|
|
859
|
-
description: "The step ID to transition to within the active
|
|
860
|
-
enum:
|
|
876
|
+
description: "The step ID to transition to within the active flow (required if continuing in current flow)",
|
|
877
|
+
enum: activeFlowSteps.map((s) => s.id),
|
|
861
878
|
};
|
|
862
879
|
base.properties.stepReasoning = {
|
|
863
880
|
type: "string",
|
|
@@ -877,7 +894,7 @@ export class RoutingEngine {
|
|
|
877
894
|
return base;
|
|
878
895
|
}
|
|
879
896
|
async buildRoutingPrompt(params) {
|
|
880
|
-
const { history, routes, lastMessage, agentOptions, session,
|
|
897
|
+
const { history, flows: routes, lastMessage, agentOptions, session, activeFlowSteps, context, } = params;
|
|
881
898
|
const templateContext = createTemplateContext({ context, session, history });
|
|
882
899
|
const pc = new PromptComposer(templateContext, this.options?.promptSectionCache);
|
|
883
900
|
if (agentOptions) {
|
|
@@ -885,10 +902,10 @@ export class RoutingEngine {
|
|
|
885
902
|
}
|
|
886
903
|
await pc.addInstruction("Task: Intent analysis and route scoring (0-100). Score ALL listed routes.");
|
|
887
904
|
// Add session context if available
|
|
888
|
-
if (session?.
|
|
905
|
+
if (session?.currentFlow) {
|
|
889
906
|
const sessionInfo = [
|
|
890
907
|
"Current conversation context:",
|
|
891
|
-
`- Active route: ${session.
|
|
908
|
+
`- Active route: ${session.currentFlow.title} (${session.currentFlow.id})`,
|
|
892
909
|
];
|
|
893
910
|
if (session.currentStep) {
|
|
894
911
|
sessionInfo.push(`- Current step: ${session.currentStep.id}`);
|
|
@@ -902,16 +919,16 @@ export class RoutingEngine {
|
|
|
902
919
|
sessionInfo.push("Note: User is mid-conversation. They may want to continue current route or switch to a new one based on their intent.");
|
|
903
920
|
await pc.addInstruction(sessionInfo.join("\n"));
|
|
904
921
|
// Add cross-route completion status
|
|
905
|
-
const completionStatus = this.
|
|
906
|
-
const
|
|
922
|
+
const completionStatus = this.getFlowCompletionStatus(routes, session.data || {});
|
|
923
|
+
const completedFlows = this.evaluateFlowCompletions(routes, session.data || {});
|
|
907
924
|
if (completionStatus.size > 0) {
|
|
908
925
|
const statusInfo = [
|
|
909
926
|
"",
|
|
910
|
-
"
|
|
927
|
+
"Flow completion status based on collected data:",
|
|
911
928
|
];
|
|
912
929
|
for (const route of routes) {
|
|
913
930
|
const progress = completionStatus.get(route.id) || 0;
|
|
914
|
-
const isComplete =
|
|
931
|
+
const isComplete = completedFlows.includes(route);
|
|
915
932
|
const progressPercent = Math.round(progress * 100);
|
|
916
933
|
statusInfo.push(`- ${route.title}: ${progressPercent}% complete${isComplete ? ' ✓ COMPLETE' : ''}`);
|
|
917
934
|
if (!isComplete && route.requiredFields) {
|
|
@@ -925,14 +942,14 @@ export class RoutingEngine {
|
|
|
925
942
|
await pc.addInstruction(statusInfo.join("\n"));
|
|
926
943
|
}
|
|
927
944
|
// Add available steps for the active route
|
|
928
|
-
if (
|
|
945
|
+
if (activeFlowSteps && activeFlowSteps.length > 0) {
|
|
929
946
|
const stepInfo = [
|
|
930
947
|
"",
|
|
931
948
|
"Available steps in active route (choose one to transition to):",
|
|
932
949
|
];
|
|
933
950
|
const activeStepConditionContext = [];
|
|
934
|
-
for (const step of
|
|
935
|
-
const idx =
|
|
951
|
+
for (const step of activeFlowSteps) {
|
|
952
|
+
const idx = activeFlowSteps.indexOf(step);
|
|
936
953
|
stepInfo.push(`${idx + 1}. Step: ${step.id}`);
|
|
937
954
|
if (step.description) {
|
|
938
955
|
stepInfo.push(` Description: ${step.description}`);
|
|
@@ -976,7 +993,7 @@ export class RoutingEngine {
|
|
|
976
993
|
}
|
|
977
994
|
await pc.addInteractionHistory(history);
|
|
978
995
|
await pc.addLastMessage(lastMessage);
|
|
979
|
-
await pc.
|
|
996
|
+
await pc.addFlowOverview(routes);
|
|
980
997
|
await pc.addInstruction([
|
|
981
998
|
"Scoring rules:",
|
|
982
999
|
"- 90-100: explicit keywords + clear intent",
|
|
@@ -989,4 +1006,4 @@ export class RoutingEngine {
|
|
|
989
1006
|
return pc.build();
|
|
990
1007
|
}
|
|
991
1008
|
}
|
|
992
|
-
//# sourceMappingURL=
|
|
1009
|
+
//# sourceMappingURL=FlowRouter.js.map
|