@falai/agent 1.2.8 → 2.0.1
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/providers/AnthropicProvider.d.ts +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +1 -1
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +1 -1
- package/dist/cjs/providers/OpenAIProvider.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/providers/AnthropicProvider.d.ts +1 -1
- package/dist/providers/AnthropicProvider.js +1 -1
- package/dist/providers/GeminiProvider.d.ts +1 -1
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +1 -1
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +1 -1
- package/dist/providers/OpenAIProvider.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 +22 -200
- 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 +14 -0
- package/docs/migration/route-to-flow.md +561 -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/providers/AnthropicProvider.ts +2 -2
- package/src/providers/GeminiProvider.ts +2 -2
- package/src/providers/OpenAIProvider.ts +2 -2
- 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
|
@@ -7,23 +7,22 @@ import type {
|
|
|
7
7
|
TemplateContext,
|
|
8
8
|
} from "../types";
|
|
9
9
|
import { MessageRole } from "../types";
|
|
10
|
-
import {
|
|
11
|
-
import type {
|
|
10
|
+
import { enterFlow, mergeCollected, isFlowCompletedThisSession } from "../utils";
|
|
11
|
+
import type { Flow } from "./Flow";
|
|
12
12
|
import type { Step } from "./Step";
|
|
13
13
|
import { PromptComposer } from "./PromptComposer";
|
|
14
14
|
import { PromptSectionCache } from "./PromptSectionCache";
|
|
15
|
-
import { END_ROUTE_ID } from "../constants";
|
|
16
15
|
import { createTemplateContext, getLastMessageFromHistory, logger, eventsToHistory } from "../utils";
|
|
17
16
|
|
|
18
17
|
export interface CandidateStep<TContext = unknown, TData = unknown> {
|
|
19
18
|
step: Step<TContext, TData>;
|
|
20
|
-
|
|
19
|
+
isFlowComplete?: boolean;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
export interface
|
|
22
|
+
export interface FlowRoutingDecisionOutput {
|
|
24
23
|
context: string;
|
|
25
|
-
|
|
26
|
-
selectedStepId?: string; // For active
|
|
24
|
+
flows: Record<string, number>;
|
|
25
|
+
selectedStepId?: string; // For active flow, which step to transition to
|
|
27
26
|
stepReasoning?: string; // Why this step was selected
|
|
28
27
|
responseDirectives?: string[];
|
|
29
28
|
extractions?: Array<{
|
|
@@ -36,18 +35,18 @@ export interface RoutingDecisionOutput {
|
|
|
36
35
|
contextUpdate?: Record<string, unknown>;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
export interface
|
|
38
|
+
export interface FlowRouterOptions {
|
|
40
39
|
/**
|
|
41
|
-
* Score margin the best alternative
|
|
42
|
-
* by before the agent switches
|
|
40
|
+
* Score margin the best alternative flow must exceed the current flow's score
|
|
41
|
+
* by before the agent switches flows. Prevents flip-flopping on marginal differences.
|
|
43
42
|
* @default 15
|
|
44
43
|
*/
|
|
45
|
-
|
|
44
|
+
flowSwitchMargin?: number;
|
|
46
45
|
/**
|
|
47
|
-
* Callback invoked when the active
|
|
48
|
-
* Used by Agent to invalidate
|
|
46
|
+
* Callback invoked when the active flow changes.
|
|
47
|
+
* Used by Agent to invalidate flow-dependent prompt cache sections.
|
|
49
48
|
*/
|
|
50
|
-
|
|
49
|
+
onFlowSwitch?: () => void;
|
|
51
50
|
/**
|
|
52
51
|
* Shared prompt section cache for memoizing static prompt sections.
|
|
53
52
|
*/
|
|
@@ -58,7 +57,7 @@ export interface BuildStepSelectionPromptParams<
|
|
|
58
57
|
TContext = unknown,
|
|
59
58
|
TData = unknown
|
|
60
59
|
> {
|
|
61
|
-
|
|
60
|
+
flow: Flow<TContext, TData>;
|
|
62
61
|
currentStep: Step<TContext, TData> | undefined;
|
|
63
62
|
candidates: CandidateStep<TContext, TData>[];
|
|
64
63
|
data: Partial<TData>;
|
|
@@ -72,20 +71,20 @@ export interface BuildStepSelectionPromptParams<
|
|
|
72
71
|
|
|
73
72
|
export interface BuildRoutingPromptParams<TContext = unknown, TData = unknown> {
|
|
74
73
|
history: Event[];
|
|
75
|
-
|
|
74
|
+
flows: Flow<TContext, TData>[];
|
|
76
75
|
lastMessage: string;
|
|
77
76
|
agentOptions?: AgentOptions<TContext, TData>;
|
|
78
77
|
session?: SessionState<TData>;
|
|
79
|
-
|
|
78
|
+
activeFlowSteps?: Step<TContext, TData>[];
|
|
80
79
|
context?: TContext;
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
export class
|
|
84
|
-
constructor(private readonly options?:
|
|
82
|
+
export class FlowRouter<TContext = unknown, TData = unknown> {
|
|
83
|
+
constructor(private readonly options?: FlowRouterOptions) { }
|
|
85
84
|
|
|
86
85
|
/**
|
|
87
86
|
* Check whether the history contains any user messages.
|
|
88
|
-
* Used to detect "session resume" scenarios where a
|
|
87
|
+
* Used to detect "session resume" scenarios where a flow/step was
|
|
89
88
|
* pre-set programmatically and the conversation starts with only
|
|
90
89
|
* system messages (or no messages at all).
|
|
91
90
|
* @private
|
|
@@ -98,75 +97,75 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
98
97
|
|
|
99
98
|
/**
|
|
100
99
|
* Handle the "session resume" fast-path: when a session already has a
|
|
101
|
-
* pre-set
|
|
100
|
+
* pre-set currentFlow (and optionally currentStep) and the conversation
|
|
102
101
|
* history contains no user messages, honor the pre-set position instead
|
|
103
|
-
* of running AI
|
|
102
|
+
* of running AI flow/step selection.
|
|
104
103
|
*
|
|
105
104
|
* Returns `undefined` when the fast-path does not apply.
|
|
106
105
|
* @private
|
|
107
106
|
*/
|
|
108
107
|
private async handleSessionResume(params: {
|
|
109
|
-
|
|
108
|
+
flows: Flow<TContext, TData>[];
|
|
110
109
|
session: SessionState<TData>;
|
|
111
110
|
history: Event[];
|
|
112
111
|
context: TContext;
|
|
113
112
|
}): Promise<{
|
|
114
|
-
|
|
113
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
115
114
|
selectedStep?: Step<TContext, TData>;
|
|
116
115
|
session: SessionState<TData>;
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
isFlowComplete?: boolean;
|
|
117
|
+
completedFlows?: Flow<TContext, TData>[];
|
|
119
118
|
} | undefined> {
|
|
120
|
-
const {
|
|
119
|
+
const { flows, session, history, context } = params;
|
|
121
120
|
|
|
122
121
|
// Fast-path only applies when:
|
|
123
|
-
// 1. Session already has a
|
|
122
|
+
// 1. Session already has a currentFlow set
|
|
124
123
|
// 2. There are no user messages in the history (system-only or empty)
|
|
125
|
-
if (!session.
|
|
124
|
+
if (!session.currentFlow || this.hasUserMessages(history)) {
|
|
126
125
|
return undefined;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
// Find the pre-set
|
|
130
|
-
const
|
|
131
|
-
(r) => r.id === session.
|
|
128
|
+
// Find the pre-set flow among available flows
|
|
129
|
+
const presetFlow = flows.find(
|
|
130
|
+
(r) => r.id === session.currentFlow!.id
|
|
132
131
|
);
|
|
133
132
|
|
|
134
|
-
if (!
|
|
133
|
+
if (!presetFlow) {
|
|
135
134
|
logger.warn(
|
|
136
|
-
`[
|
|
135
|
+
`[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.`
|
|
137
136
|
);
|
|
138
137
|
return undefined;
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
logger.debug(
|
|
142
|
-
`[
|
|
141
|
+
`[FlowRouter] Session resume: honoring pre-set flow '${presetFlow.title}' (no user messages in history)`
|
|
143
142
|
);
|
|
144
143
|
|
|
145
|
-
// Enter
|
|
146
|
-
const updatedSession = this.
|
|
144
|
+
// Enter flow if needed (merges initialData, no-op if already entered)
|
|
145
|
+
const updatedSession = this.enterFlowIfNeeded(session, presetFlow);
|
|
147
146
|
|
|
148
|
-
// Evaluate cross-
|
|
149
|
-
const
|
|
147
|
+
// Evaluate cross-flow completions
|
|
148
|
+
const completedFlows = this.evaluateFlowCompletions(flows, updatedSession.data || {});
|
|
150
149
|
|
|
151
150
|
// If a currentStep is also pre-set, honor it — stay on that step
|
|
152
151
|
if (session.currentStep) {
|
|
153
|
-
const presetStep =
|
|
152
|
+
const presetStep = presetFlow.getStep(session.currentStep.id);
|
|
154
153
|
|
|
155
154
|
if (presetStep) {
|
|
156
155
|
logger.debug(
|
|
157
|
-
`[
|
|
156
|
+
`[FlowRouter] Session resume: honoring pre-set step '${presetStep.id}'`
|
|
158
157
|
);
|
|
159
158
|
return {
|
|
160
|
-
|
|
159
|
+
selectedFlow: presetFlow,
|
|
161
160
|
selectedStep: presetStep,
|
|
162
161
|
session: updatedSession,
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
isFlowComplete: false,
|
|
163
|
+
completedFlows,
|
|
165
164
|
};
|
|
166
165
|
}
|
|
167
166
|
|
|
168
167
|
logger.warn(
|
|
169
|
-
`[
|
|
168
|
+
`[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.`
|
|
170
169
|
);
|
|
171
170
|
}
|
|
172
171
|
|
|
@@ -180,82 +179,116 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
180
179
|
});
|
|
181
180
|
|
|
182
181
|
const candidates = await this.getCandidateStepsWithConditions(
|
|
183
|
-
|
|
182
|
+
presetFlow,
|
|
184
183
|
undefined, // No current step — start from beginning
|
|
185
184
|
templateContext
|
|
186
185
|
);
|
|
187
186
|
|
|
188
187
|
if (candidates.length === 0) {
|
|
189
188
|
logger.warn(
|
|
190
|
-
`[
|
|
189
|
+
`[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.`
|
|
191
190
|
);
|
|
192
191
|
return {
|
|
193
|
-
|
|
192
|
+
selectedFlow: presetFlow,
|
|
194
193
|
selectedStep: undefined,
|
|
195
194
|
session: updatedSession,
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
isFlowComplete: false,
|
|
196
|
+
completedFlows,
|
|
198
197
|
};
|
|
199
198
|
}
|
|
200
199
|
|
|
201
200
|
const candidate = candidates[0];
|
|
202
201
|
|
|
203
|
-
if (candidate.
|
|
202
|
+
if (candidate.isFlowComplete) {
|
|
204
203
|
logger.debug(
|
|
205
|
-
`[
|
|
204
|
+
`[FlowRouter] Session resume: flow '${presetFlow.title}' is already complete`
|
|
206
205
|
);
|
|
207
206
|
return {
|
|
208
|
-
|
|
207
|
+
selectedFlow: presetFlow,
|
|
209
208
|
selectedStep: undefined,
|
|
210
209
|
session: updatedSession,
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
isFlowComplete: true,
|
|
211
|
+
completedFlows,
|
|
213
212
|
};
|
|
214
213
|
}
|
|
215
214
|
|
|
216
215
|
logger.debug(
|
|
217
|
-
`[
|
|
216
|
+
`[FlowRouter] Session resume: resolved initial step '${candidate.step.id}'`
|
|
218
217
|
);
|
|
219
218
|
return {
|
|
220
|
-
|
|
219
|
+
selectedFlow: presetFlow,
|
|
221
220
|
selectedStep: candidate.step,
|
|
222
221
|
session: updatedSession,
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
isFlowComplete: false,
|
|
223
|
+
completedFlows,
|
|
225
224
|
};
|
|
226
225
|
}
|
|
227
226
|
|
|
228
227
|
/**
|
|
229
|
-
* Enter a
|
|
228
|
+
* Enter a flow if not already in it, merging initial data.
|
|
229
|
+
*
|
|
230
|
+
* When the flow is `reentrant` and was previously completed in this
|
|
231
|
+
* session, clears every field declared in the flow's `requiredFields`
|
|
232
|
+
* and `optionalFields` before entering — so the flow starts fresh from
|
|
233
|
+
* its initial step instead of being instantly marked complete by stale
|
|
234
|
+
* data. Fields not owned by this flow are preserved.
|
|
235
|
+
*
|
|
230
236
|
* @private
|
|
231
237
|
*/
|
|
232
|
-
private
|
|
238
|
+
private enterFlowIfNeeded(
|
|
233
239
|
session: SessionState<TData>,
|
|
234
|
-
route:
|
|
240
|
+
route: Flow<TContext, TData>
|
|
235
241
|
): SessionState<TData> {
|
|
236
|
-
if (!session.
|
|
237
|
-
let
|
|
242
|
+
if (!session.currentFlow || session.currentFlow.id !== route.id) {
|
|
243
|
+
let workingSession = session;
|
|
244
|
+
|
|
245
|
+
// Re-entry into a `reentrant` flow that previously completed:
|
|
246
|
+
// clear owned fields so completion logic doesn't short-circuit on
|
|
247
|
+
// stale data from the prior run.
|
|
248
|
+
const previouslyCompleted = isFlowCompletedThisSession(session, route.id);
|
|
249
|
+
if (previouslyCompleted && route.reentrant) {
|
|
250
|
+
const ownedFields = [
|
|
251
|
+
...(route.requiredFields ?? []),
|
|
252
|
+
...(route.optionalFields ?? []),
|
|
253
|
+
];
|
|
254
|
+
if (ownedFields.length > 0) {
|
|
255
|
+
const owned = new Set<keyof TData>(ownedFields);
|
|
256
|
+
const filtered: Partial<TData> = {};
|
|
257
|
+
for (const key of Object.keys(session.data ?? {}) as (keyof TData)[]) {
|
|
258
|
+
if (!owned.has(key)) {
|
|
259
|
+
(filtered as Record<keyof TData, unknown>)[key] =
|
|
260
|
+
(session.data as Record<keyof TData, unknown>)[key];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
workingSession = { ...session, data: filtered };
|
|
264
|
+
logger.debug(
|
|
265
|
+
`[FlowRouter] Re-entering reentrant flow ${route.title}: cleared ${ownedFields.length} owned field(s)`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let updatedSession = enterFlow(workingSession, route.id, route.title);
|
|
238
271
|
if (route.initialData) {
|
|
239
272
|
updatedSession = mergeCollected(updatedSession, route.initialData);
|
|
240
273
|
logger.debug(
|
|
241
|
-
`[
|
|
274
|
+
`[FlowRouter] Merged initial data for flow ${route.title}:`,
|
|
242
275
|
route.initialData
|
|
243
276
|
);
|
|
244
277
|
}
|
|
245
|
-
logger.debug(`[
|
|
246
|
-
this.options?.
|
|
278
|
+
logger.debug(`[FlowRouter] Entered flow: ${route.title}`);
|
|
279
|
+
this.options?.onFlowSwitch?.();
|
|
247
280
|
return updatedSession;
|
|
248
281
|
}
|
|
249
282
|
return session;
|
|
250
283
|
}
|
|
251
284
|
|
|
252
285
|
/**
|
|
253
|
-
* Optimized decision for single-
|
|
254
|
-
* Skips
|
|
286
|
+
* Optimized decision for single-flow scenarios
|
|
287
|
+
* Skips flow scoring and only does step selection
|
|
255
288
|
* @private
|
|
256
289
|
*/
|
|
257
|
-
private async
|
|
258
|
-
route:
|
|
290
|
+
private async decideSingleFlowStep(params: {
|
|
291
|
+
route: Flow<TContext, TData>;
|
|
259
292
|
session: SessionState<TData>;
|
|
260
293
|
history: Event[];
|
|
261
294
|
agentOptions?: AgentOptions<TContext, TData>;
|
|
@@ -263,23 +296,23 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
263
296
|
context: TContext;
|
|
264
297
|
signal?: AbortSignal;
|
|
265
298
|
}): Promise<{
|
|
266
|
-
|
|
299
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
267
300
|
selectedStep?: Step<TContext, TData>;
|
|
268
301
|
responseDirectives?: string[];
|
|
269
302
|
session: SessionState<TData>;
|
|
270
|
-
|
|
271
|
-
|
|
303
|
+
isFlowComplete?: boolean;
|
|
304
|
+
completedFlows?: Flow<TContext, TData>[];
|
|
272
305
|
}> {
|
|
273
306
|
const { route, session, history, agentOptions, provider, context, signal } =
|
|
274
307
|
params;
|
|
275
308
|
|
|
276
|
-
const
|
|
309
|
+
const selectedFlow = route;
|
|
277
310
|
|
|
278
|
-
// Enter
|
|
279
|
-
const updatedSession = this.
|
|
311
|
+
// Enter flow if not already in it (this may merge initial data)
|
|
312
|
+
const updatedSession = this.enterFlowIfNeeded(session, route);
|
|
280
313
|
|
|
281
|
-
// Check if this single
|
|
282
|
-
const
|
|
314
|
+
// Check if this single flow is complete (use updated session data)
|
|
315
|
+
const completedFlows = route.isComplete(updatedSession.data || {}) ? [route] : [];
|
|
283
316
|
|
|
284
317
|
// Get candidate steps using new condition evaluation
|
|
285
318
|
const templateContext = createTemplateContext({
|
|
@@ -298,52 +331,51 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
298
331
|
);
|
|
299
332
|
|
|
300
333
|
if (candidates.length === 0) {
|
|
301
|
-
logger.warn(`[
|
|
302
|
-
return {
|
|
334
|
+
logger.warn(`[FlowConfigurationError] No valid steps found: all candidates in the single-flow agent are skipped. Check step skip/when conditions.`);
|
|
335
|
+
return { selectedFlow, session: updatedSession };
|
|
303
336
|
}
|
|
304
337
|
|
|
305
338
|
// If only one candidate, check if it's a completion marker
|
|
306
339
|
if (candidates.length === 1) {
|
|
307
340
|
const candidate = candidates[0];
|
|
308
341
|
|
|
309
|
-
if (candidate.
|
|
342
|
+
if (candidate.isFlowComplete) {
|
|
310
343
|
logger.debug(
|
|
311
|
-
`[
|
|
344
|
+
`[FlowRouter] Single-flow: Flow complete - all required fields collected or last step reached`
|
|
312
345
|
);
|
|
313
|
-
// Don't return a selectedStep when
|
|
346
|
+
// Don't return a selectedStep when flow is complete - there's no step to enter
|
|
314
347
|
return {
|
|
315
|
-
|
|
348
|
+
selectedFlow,
|
|
316
349
|
selectedStep: undefined,
|
|
317
350
|
session: updatedSession,
|
|
318
|
-
|
|
319
|
-
|
|
351
|
+
isFlowComplete: true,
|
|
352
|
+
completedFlows,
|
|
320
353
|
};
|
|
321
354
|
} else {
|
|
322
355
|
logger.debug(
|
|
323
|
-
`[
|
|
356
|
+
`[FlowRouter] Single-flow: Only one valid step: ${candidate.step.id}`
|
|
324
357
|
);
|
|
325
358
|
return {
|
|
326
|
-
|
|
359
|
+
selectedFlow,
|
|
327
360
|
selectedStep: candidate.step,
|
|
328
361
|
session: updatedSession,
|
|
329
|
-
|
|
330
|
-
|
|
362
|
+
isFlowComplete: false,
|
|
363
|
+
completedFlows,
|
|
331
364
|
};
|
|
332
365
|
}
|
|
333
366
|
}
|
|
334
367
|
|
|
335
|
-
// No candidates means
|
|
336
|
-
// Don't mark as complete based on data alone — only END_ROUTE completes a route
|
|
368
|
+
// No candidates means flow has no valid next steps (edge case)
|
|
337
369
|
if (candidates.length === 0) {
|
|
338
370
|
logger.debug(
|
|
339
|
-
`[
|
|
371
|
+
`[FlowRouter] Single-flow: No valid candidate steps found`
|
|
340
372
|
);
|
|
341
373
|
return {
|
|
342
|
-
|
|
374
|
+
selectedFlow,
|
|
343
375
|
selectedStep: undefined,
|
|
344
376
|
session: updatedSession,
|
|
345
|
-
|
|
346
|
-
|
|
377
|
+
isFlowComplete: false,
|
|
378
|
+
completedFlows,
|
|
347
379
|
};
|
|
348
380
|
}
|
|
349
381
|
|
|
@@ -357,11 +389,11 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
357
389
|
stepConditionContext.push(...whenResult.aiContextStrings);
|
|
358
390
|
}
|
|
359
391
|
|
|
360
|
-
// Check if any candidate is a completion marker (
|
|
361
|
-
const hasCompletionOption = candidates.some(c => c.
|
|
392
|
+
// Check if any candidate is a completion marker (isFlowComplete = true)
|
|
393
|
+
const hasCompletionOption = candidates.some(c => c.isFlowComplete);
|
|
362
394
|
|
|
363
395
|
const stepPrompt = await this.buildStepSelectionPrompt({
|
|
364
|
-
route,
|
|
396
|
+
flow: route,
|
|
365
397
|
currentStep,
|
|
366
398
|
candidates,
|
|
367
399
|
data: updatedSession.data || {},
|
|
@@ -371,11 +403,11 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
371
403
|
context,
|
|
372
404
|
session: updatedSession,
|
|
373
405
|
stepConditionContext,
|
|
374
|
-
|
|
406
|
+
includeCompletion: hasCompletionOption,
|
|
375
407
|
});
|
|
376
408
|
|
|
377
409
|
const stepSchema = this.buildStepSelectionSchema(
|
|
378
|
-
candidates.filter(c => !c.
|
|
410
|
+
candidates.filter(c => !c.isFlowComplete).map((c) => c.step),
|
|
379
411
|
hasCompletionOption
|
|
380
412
|
);
|
|
381
413
|
|
|
@@ -399,21 +431,21 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
399
431
|
|
|
400
432
|
const selectedStepId = stepResult.structured?.selectedStepId;
|
|
401
433
|
|
|
402
|
-
// Check if AI selected
|
|
403
|
-
if (selectedStepId ===
|
|
434
|
+
// Check if AI selected flow completion
|
|
435
|
+
if (selectedStepId === '__COMPLETE__') {
|
|
404
436
|
logger.debug(
|
|
405
|
-
`[
|
|
437
|
+
`[FlowRouter] Single-flow: AI selected flow completion`
|
|
406
438
|
);
|
|
407
439
|
logger.debug(
|
|
408
|
-
`[
|
|
440
|
+
`[FlowRouter] Single-flow: Reasoning: ${stepResult.structured?.reasoning}`
|
|
409
441
|
);
|
|
410
442
|
return {
|
|
411
|
-
|
|
443
|
+
selectedFlow,
|
|
412
444
|
selectedStep: undefined,
|
|
413
445
|
responseDirectives: stepResult.structured?.responseDirectives,
|
|
414
446
|
session: updatedSession,
|
|
415
|
-
|
|
416
|
-
|
|
447
|
+
isFlowComplete: true,
|
|
448
|
+
completedFlows,
|
|
417
449
|
};
|
|
418
450
|
}
|
|
419
451
|
|
|
@@ -421,28 +453,28 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
421
453
|
|
|
422
454
|
if (selectedStep) {
|
|
423
455
|
logger.debug(
|
|
424
|
-
`[
|
|
456
|
+
`[FlowRouter] Single-flow: AI selected step: ${selectedStep.step.id}`
|
|
425
457
|
);
|
|
426
458
|
logger.debug(
|
|
427
|
-
`[
|
|
459
|
+
`[FlowRouter] Single-flow: Reasoning: ${stepResult.structured?.reasoning}`
|
|
428
460
|
);
|
|
429
461
|
} else {
|
|
430
462
|
logger.warn(
|
|
431
|
-
`[
|
|
463
|
+
`[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.`
|
|
432
464
|
);
|
|
433
465
|
}
|
|
434
466
|
|
|
435
467
|
return {
|
|
436
|
-
|
|
468
|
+
selectedFlow,
|
|
437
469
|
selectedStep: selectedStep?.step || candidates[0].step,
|
|
438
470
|
responseDirectives: stepResult.structured?.responseDirectives,
|
|
439
471
|
session: updatedSession,
|
|
440
|
-
|
|
472
|
+
completedFlows,
|
|
441
473
|
};
|
|
442
474
|
}
|
|
443
475
|
|
|
444
476
|
/**
|
|
445
|
-
* Recursively traverse step chain to find first non-skipped step
|
|
477
|
+
* Recursively traverse step chain to find first non-skipped step using new condition evaluation
|
|
446
478
|
* @private
|
|
447
479
|
*/
|
|
448
480
|
private async findFirstValidStepRecursiveWithConditions(
|
|
@@ -451,7 +483,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
451
483
|
visited: Set<string>
|
|
452
484
|
): Promise<{
|
|
453
485
|
step?: Step<TContext, TData>;
|
|
454
|
-
|
|
486
|
+
isFlowComplete?: boolean;
|
|
455
487
|
aiContextStrings?: string[];
|
|
456
488
|
}> {
|
|
457
489
|
// Prevent infinite loops
|
|
@@ -463,39 +495,38 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
463
495
|
const transitions = currentStep.getTransitions();
|
|
464
496
|
const allAiContextStrings: string[] = [];
|
|
465
497
|
|
|
498
|
+
// No transitions means implicit terminus — flow is complete
|
|
499
|
+
if (transitions.length === 0) {
|
|
500
|
+
return {
|
|
501
|
+
isFlowComplete: true,
|
|
502
|
+
aiContextStrings: allAiContextStrings,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
466
506
|
for (const transition of transitions) {
|
|
467
507
|
const target = transition;
|
|
468
508
|
|
|
469
|
-
// Check for END_ROUTE transition
|
|
470
|
-
if (target && target.id === END_ROUTE_ID) {
|
|
471
|
-
// Found END_ROUTE - route is complete
|
|
472
|
-
return {
|
|
473
|
-
isRouteComplete: true,
|
|
474
|
-
aiContextStrings: allAiContextStrings,
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
509
|
if (!target) continue;
|
|
479
510
|
|
|
480
|
-
// Evaluate
|
|
481
|
-
const skipResult = await target.
|
|
511
|
+
// Evaluate skip condition (code-only, if-shape)
|
|
512
|
+
const skipResult = await target.evaluateSkip(templateContext);
|
|
482
513
|
allAiContextStrings.push(...skipResult.aiContextStrings);
|
|
483
514
|
|
|
484
515
|
// If target should NOT be skipped, we found our step
|
|
485
516
|
if (!skipResult.shouldSkip) {
|
|
486
517
|
logger.debug(
|
|
487
|
-
`[
|
|
518
|
+
`[FlowRouter] Found valid step after skipping: ${target.id}`
|
|
488
519
|
);
|
|
489
520
|
return {
|
|
490
521
|
step: target,
|
|
491
|
-
|
|
522
|
+
isFlowComplete: false,
|
|
492
523
|
aiContextStrings: allAiContextStrings,
|
|
493
524
|
};
|
|
494
525
|
}
|
|
495
526
|
|
|
496
527
|
// Target should be skipped too - recurse deeper
|
|
497
528
|
logger.debug(
|
|
498
|
-
`[
|
|
529
|
+
`[FlowRouter] Skipping step ${target.id} (skipIf condition met), continuing traversal...`
|
|
499
530
|
);
|
|
500
531
|
const result = await this.findFirstValidStepRecursiveWithConditions(target, templateContext, visited);
|
|
501
532
|
|
|
@@ -504,8 +535,8 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
504
535
|
allAiContextStrings.push(...result.aiContextStrings);
|
|
505
536
|
}
|
|
506
537
|
|
|
507
|
-
// If we found something (a valid step or
|
|
508
|
-
if (result.step || result.
|
|
538
|
+
// If we found something (a valid step or flow complete), return it
|
|
539
|
+
if (result.step || result.isFlowComplete) {
|
|
509
540
|
return {
|
|
510
541
|
...result,
|
|
511
542
|
aiContextStrings: allAiContextStrings,
|
|
@@ -513,101 +544,114 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
513
544
|
}
|
|
514
545
|
}
|
|
515
546
|
|
|
516
|
-
// No valid steps
|
|
517
|
-
return {
|
|
547
|
+
// No valid steps found in this branch — all skipped with no further transitions
|
|
548
|
+
return {
|
|
549
|
+
isFlowComplete: true,
|
|
550
|
+
aiContextStrings: allAiContextStrings,
|
|
551
|
+
};
|
|
518
552
|
}
|
|
519
553
|
|
|
520
554
|
|
|
521
555
|
|
|
522
556
|
/**
|
|
523
557
|
* Identify valid next candidate steps using new condition evaluation system
|
|
524
|
-
* Returns step with
|
|
558
|
+
* Returns step with isFlowComplete flag if flow is complete (all steps skipped or no transitions remain)
|
|
525
559
|
*
|
|
526
|
-
*
|
|
560
|
+
* Flow completion is implicit: when the last step has no transitions, the flow is done.
|
|
527
561
|
*/
|
|
528
562
|
async getCandidateStepsWithConditions(
|
|
529
|
-
route:
|
|
563
|
+
route: Flow<TContext, TData>,
|
|
530
564
|
currentStep: Step<TContext, TData> | undefined,
|
|
531
565
|
templateContext: TemplateContext<TContext, TData>
|
|
532
566
|
): Promise<CandidateStep<TContext, TData>[]> {
|
|
533
567
|
const candidates: CandidateStep<TContext, TData>[] = [];
|
|
534
568
|
|
|
535
569
|
if (!currentStep) {
|
|
536
|
-
// Entering
|
|
570
|
+
// Entering flow for the first time — always start the step flow
|
|
537
571
|
|
|
538
572
|
const initialStep = route.initialStep;
|
|
539
|
-
const skipResult = await initialStep.
|
|
573
|
+
const skipResult = await initialStep.evaluateSkip(templateContext);
|
|
540
574
|
|
|
541
575
|
if (skipResult.shouldSkip) {
|
|
542
|
-
// Initial step should be skipped - recursively traverse to find first non-skipped step
|
|
576
|
+
// Initial step should be skipped - recursively traverse to find first non-skipped step
|
|
543
577
|
const result = await this.findFirstValidStepRecursiveWithConditions(
|
|
544
578
|
initialStep,
|
|
545
579
|
templateContext,
|
|
546
580
|
new Set<string>()
|
|
547
581
|
);
|
|
548
582
|
|
|
549
|
-
if (result.
|
|
550
|
-
// All steps are skipped and
|
|
583
|
+
if (result.isFlowComplete) {
|
|
584
|
+
// All steps are skipped and no transitions remain
|
|
551
585
|
logger.debug(
|
|
552
|
-
`[
|
|
586
|
+
`[FlowRouter] Flow complete on entry: all steps skipped, no transitions remain`
|
|
553
587
|
);
|
|
554
588
|
candidates.push({
|
|
555
589
|
step: initialStep,
|
|
556
|
-
|
|
590
|
+
isFlowComplete: true,
|
|
557
591
|
});
|
|
558
592
|
} else if (result.step) {
|
|
559
593
|
// Found a non-skipped step
|
|
560
594
|
candidates.push({
|
|
561
595
|
step: result.step,
|
|
562
|
-
|
|
596
|
+
isFlowComplete: result.isFlowComplete || false,
|
|
563
597
|
});
|
|
564
598
|
}
|
|
565
599
|
// If no step found and not complete, fall through to return empty candidates
|
|
566
600
|
} else {
|
|
567
601
|
candidates.push({
|
|
568
602
|
step: initialStep,
|
|
569
|
-
|
|
603
|
+
isFlowComplete: false,
|
|
570
604
|
});
|
|
571
605
|
}
|
|
572
606
|
return candidates;
|
|
573
607
|
}
|
|
574
608
|
|
|
575
|
-
// Continue normal step progression —
|
|
609
|
+
// Continue normal step progression — flows complete when last step has no transitions (implicit terminus)
|
|
576
610
|
const transitions = currentStep.getTransitions();
|
|
577
|
-
|
|
611
|
+
|
|
612
|
+
// No transitions means this is the last step — flow is complete
|
|
613
|
+
if (transitions.length === 0) {
|
|
614
|
+
logger.debug(
|
|
615
|
+
`[FlowRouter] Flow complete: current step has no transitions (implicit terminus)`
|
|
616
|
+
);
|
|
617
|
+
return [
|
|
618
|
+
{
|
|
619
|
+
step: currentStep,
|
|
620
|
+
isFlowComplete: true,
|
|
621
|
+
},
|
|
622
|
+
];
|
|
623
|
+
}
|
|
578
624
|
|
|
579
625
|
for (const transition of transitions) {
|
|
580
626
|
const target = transition;
|
|
581
627
|
|
|
582
|
-
// Check for END_ROUTE transition (no target step)
|
|
583
|
-
if (target && target.id === END_ROUTE_ID) {
|
|
584
|
-
hasEndRoute = true;
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
628
|
if (!target) continue;
|
|
589
629
|
|
|
590
|
-
const skipResult = await target.
|
|
630
|
+
const skipResult = await target.evaluateSkip(templateContext);
|
|
591
631
|
|
|
592
632
|
if (skipResult.shouldSkip) {
|
|
593
633
|
logger.debug(
|
|
594
|
-
`[
|
|
634
|
+
`[FlowRouter] Skipping step ${target.id} (skip condition met)`
|
|
595
635
|
);
|
|
596
636
|
|
|
597
|
-
// Recursively traverse to find next valid step
|
|
637
|
+
// Recursively traverse to find next valid step
|
|
598
638
|
const result = await this.findFirstValidStepRecursiveWithConditions(
|
|
599
639
|
target,
|
|
600
640
|
templateContext,
|
|
601
641
|
new Set<string>([currentStep.id]) // Already visited current step
|
|
602
642
|
);
|
|
603
643
|
|
|
604
|
-
if (result.
|
|
605
|
-
|
|
644
|
+
if (result.isFlowComplete) {
|
|
645
|
+
// All forward paths lead to terminus
|
|
646
|
+
candidates.push({
|
|
647
|
+
step: currentStep,
|
|
648
|
+
isFlowComplete: true,
|
|
649
|
+
});
|
|
606
650
|
} else if (result.step) {
|
|
607
651
|
// Found a non-skipped step deeper in the chain
|
|
608
652
|
candidates.push({
|
|
609
653
|
step: result.step,
|
|
610
|
-
|
|
654
|
+
isFlowComplete: false,
|
|
611
655
|
});
|
|
612
656
|
}
|
|
613
657
|
continue;
|
|
@@ -615,34 +659,22 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
615
659
|
|
|
616
660
|
candidates.push({
|
|
617
661
|
step: target,
|
|
618
|
-
|
|
662
|
+
isFlowComplete: false,
|
|
619
663
|
});
|
|
620
664
|
}
|
|
621
665
|
|
|
622
|
-
// If no valid candidates found
|
|
666
|
+
// If no valid candidates found after evaluating all transitions
|
|
623
667
|
if (candidates.length === 0) {
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
return [
|
|
631
|
-
{
|
|
632
|
-
step: currentStep,
|
|
633
|
-
isRouteComplete: true,
|
|
634
|
-
},
|
|
635
|
-
];
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Otherwise, stay in current step if it's still valid
|
|
639
|
-
const currentSkipResult = await currentStep.evaluateSkipIf(templateContext);
|
|
640
|
-
if (!currentSkipResult.shouldSkip) {
|
|
641
|
-
candidates.push({
|
|
668
|
+
// All transitions were skipped — flow is complete
|
|
669
|
+
logger.debug(
|
|
670
|
+
`[FlowRouter] Flow complete: all transitions skipped`
|
|
671
|
+
);
|
|
672
|
+
return [
|
|
673
|
+
{
|
|
642
674
|
step: currentStep,
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
|
|
675
|
+
isFlowComplete: true,
|
|
676
|
+
},
|
|
677
|
+
];
|
|
646
678
|
}
|
|
647
679
|
|
|
648
680
|
return candidates;
|
|
@@ -650,13 +682,13 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
650
682
|
|
|
651
683
|
/**
|
|
652
684
|
* Full routing orchestration: builds prompt and schema, calls AI, selects route/step,
|
|
653
|
-
* and updates the session (including initialData merge when entering a new
|
|
685
|
+
* and updates the session (including initialData merge when entering a new flow).
|
|
654
686
|
*
|
|
655
687
|
* OPTIMIZATION: If there's only 1 route, skips route scoring and only does step selection.
|
|
656
|
-
* CROSS-
|
|
688
|
+
* CROSS-FLOW COMPLETION: Evaluates all flows for completion based on collected data.
|
|
657
689
|
*/
|
|
658
|
-
async
|
|
659
|
-
|
|
690
|
+
async decideFlowAndStep(params: {
|
|
691
|
+
flows: Flow<TContext, TData>[];
|
|
660
692
|
session: SessionState<TData>;
|
|
661
693
|
history: Event[];
|
|
662
694
|
agentOptions?: AgentOptions<TContext, TData>;
|
|
@@ -664,15 +696,15 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
664
696
|
context: TContext;
|
|
665
697
|
signal?: AbortSignal;
|
|
666
698
|
}): Promise<{
|
|
667
|
-
|
|
699
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
668
700
|
selectedStep?: Step<TContext, TData>;
|
|
669
701
|
responseDirectives?: string[];
|
|
670
702
|
session: SessionState<TData>;
|
|
671
|
-
|
|
672
|
-
|
|
703
|
+
isFlowComplete?: boolean;
|
|
704
|
+
completedFlows?: Flow<TContext, TData>[];
|
|
673
705
|
}> {
|
|
674
706
|
const {
|
|
675
|
-
|
|
707
|
+
flows,
|
|
676
708
|
session,
|
|
677
709
|
history,
|
|
678
710
|
agentOptions,
|
|
@@ -681,15 +713,15 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
681
713
|
signal,
|
|
682
714
|
} = params;
|
|
683
715
|
|
|
684
|
-
if (
|
|
716
|
+
if (flows.length === 0) {
|
|
685
717
|
return { session };
|
|
686
718
|
}
|
|
687
719
|
|
|
688
|
-
// SESSION RESUME: If the session has a pre-set
|
|
720
|
+
// SESSION RESUME: If the session has a pre-set flow and there are no user
|
|
689
721
|
// messages in the history, honor the pre-set position without AI routing.
|
|
690
722
|
// This supports programmatic session setup and persistence-based resume.
|
|
691
723
|
const resumeResult = await this.handleSessionResume({
|
|
692
|
-
|
|
724
|
+
flows,
|
|
693
725
|
session,
|
|
694
726
|
history,
|
|
695
727
|
context,
|
|
@@ -698,20 +730,33 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
698
730
|
return resumeResult;
|
|
699
731
|
}
|
|
700
732
|
|
|
701
|
-
//
|
|
702
|
-
|
|
733
|
+
// Exclude flows that completed earlier in this session unless explicitly
|
|
734
|
+
// re-entrant. A completed flow surrenders the conversation back to
|
|
735
|
+
// routing — it cannot be re-selected without `flow.reentrant: true`.
|
|
736
|
+
const reEntryFiltered = flows.filter(
|
|
737
|
+
(f) => !isFlowCompletedThisSession(session, f.id) || f.reentrant === true
|
|
738
|
+
);
|
|
739
|
+
const excludedFlows = flows.length - reEntryFiltered.length;
|
|
740
|
+
if (excludedFlows > 0) {
|
|
741
|
+
logger.debug(
|
|
742
|
+
`[FlowRouter] Excluded ${excludedFlows} completed (non-reentrant) flow(s) from routing candidates`
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// CROSS-FLOW COMPLETION EVALUATION: Check all eligible flows for completion
|
|
747
|
+
const completedFlows = this.evaluateFlowCompletions(reEntryFiltered, session.data || {});
|
|
703
748
|
|
|
704
|
-
// Log completed
|
|
705
|
-
if (
|
|
749
|
+
// Log completed flows
|
|
750
|
+
if (completedFlows.length > 0) {
|
|
706
751
|
logger.debug(
|
|
707
|
-
`[
|
|
752
|
+
`[FlowRouter] Found ${completedFlows.length} completed routes: ${completedFlows.map(r => r.title).join(', ')}`
|
|
708
753
|
);
|
|
709
754
|
}
|
|
710
755
|
|
|
711
|
-
// OPTIMIZATION: Single
|
|
712
|
-
if (
|
|
713
|
-
const result = await this.
|
|
714
|
-
route:
|
|
756
|
+
// OPTIMIZATION: Single flow - skip flow scoring, only do step selection
|
|
757
|
+
if (reEntryFiltered.length === 1) {
|
|
758
|
+
const result = await this.decideSingleFlowStep({
|
|
759
|
+
route: reEntryFiltered[0],
|
|
715
760
|
session,
|
|
716
761
|
history,
|
|
717
762
|
agentOptions,
|
|
@@ -721,10 +766,19 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
721
766
|
});
|
|
722
767
|
return {
|
|
723
768
|
...result,
|
|
724
|
-
|
|
769
|
+
completedFlows,
|
|
725
770
|
};
|
|
726
771
|
}
|
|
727
772
|
|
|
773
|
+
// No eligible flows after re-entry filtering — caller falls back to the
|
|
774
|
+
// generic non-flow response path.
|
|
775
|
+
if (reEntryFiltered.length === 0) {
|
|
776
|
+
logger.debug(
|
|
777
|
+
`[FlowRouter] All flows completed and none are reentrant — releasing session to fallback`
|
|
778
|
+
);
|
|
779
|
+
return { session, completedFlows };
|
|
780
|
+
}
|
|
781
|
+
|
|
728
782
|
const lastUserMessage = getLastMessageFromHistory(history);
|
|
729
783
|
const templateContext = createTemplateContext({
|
|
730
784
|
context,
|
|
@@ -733,25 +787,24 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
733
787
|
data: session.data
|
|
734
788
|
});
|
|
735
789
|
|
|
736
|
-
// Apply
|
|
737
|
-
const
|
|
738
|
-
const whenResult = await this.filterRoutesByWhen(skipIfResult.eligibleRoutes, templateContext);
|
|
790
|
+
// Apply flow filtering with new condition evaluation system
|
|
791
|
+
const whenResult = await this.filterFlowsByWhen(reEntryFiltered, templateContext);
|
|
739
792
|
|
|
740
|
-
// Use filtered
|
|
793
|
+
// Use filtered flows for further processing
|
|
741
794
|
const eligibleRoutes = whenResult.eligibleRoutes;
|
|
742
795
|
|
|
743
|
-
logger.debug(`[
|
|
796
|
+
logger.debug(`[FlowRouter] Flow filtering: ${flows.length} total → ${reEntryFiltered.length} after re-entry filter → ${eligibleRoutes.length} after when`);
|
|
744
797
|
|
|
745
|
-
let
|
|
746
|
-
let
|
|
747
|
-
let
|
|
798
|
+
let activeFlowSteps: Step<TContext, TData>[] | undefined;
|
|
799
|
+
let activeFlow: Flow<TContext, TData> | undefined;
|
|
800
|
+
let isFlowComplete = false;
|
|
748
801
|
let updatedSession = session;
|
|
749
802
|
|
|
750
|
-
if (session.
|
|
751
|
-
|
|
752
|
-
if (
|
|
803
|
+
if (session.currentFlow) {
|
|
804
|
+
activeFlow = eligibleRoutes.find((r) => r.id === session.currentFlow?.id);
|
|
805
|
+
if (activeFlow) {
|
|
753
806
|
const currentStep = session.currentStep
|
|
754
|
-
?
|
|
807
|
+
? activeFlow.getStep(session.currentStep.id)
|
|
755
808
|
: undefined;
|
|
756
809
|
const activeTemplateContext = createTemplateContext({
|
|
757
810
|
...templateContext,
|
|
@@ -759,55 +812,55 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
759
812
|
data: updatedSession.data
|
|
760
813
|
});
|
|
761
814
|
const candidates = await this.getCandidateStepsWithConditions(
|
|
762
|
-
|
|
815
|
+
activeFlow,
|
|
763
816
|
currentStep,
|
|
764
817
|
activeTemplateContext
|
|
765
818
|
);
|
|
766
819
|
|
|
767
|
-
// Check if
|
|
820
|
+
// Check if flow is complete
|
|
768
821
|
// getCandidateStepsWithConditions now automatically handles completion when required fields are collected
|
|
769
|
-
if (candidates.length === 1 && candidates[0].
|
|
770
|
-
|
|
822
|
+
if (candidates.length === 1 && candidates[0].isFlowComplete) {
|
|
823
|
+
isFlowComplete = true;
|
|
771
824
|
logger.debug(
|
|
772
|
-
`[
|
|
825
|
+
`[FlowRouter] Flow ${activeFlow.title} is complete - all required fields collected or last step reached`
|
|
773
826
|
);
|
|
774
827
|
// Don't include steps in routing if route is complete
|
|
775
|
-
|
|
828
|
+
activeFlowSteps = undefined;
|
|
776
829
|
} else if (candidates.length === 0) {
|
|
777
|
-
// No candidates available — don't end
|
|
830
|
+
// No candidates available — don't end flow based on data alone
|
|
778
831
|
logger.debug(
|
|
779
|
-
`[
|
|
832
|
+
`[FlowRouter] Flow ${activeFlow.title} has no valid candidate steps`
|
|
780
833
|
);
|
|
781
|
-
|
|
834
|
+
activeFlowSteps = undefined;
|
|
782
835
|
} else {
|
|
783
836
|
// Multiple candidates or single non-complete candidate
|
|
784
|
-
|
|
837
|
+
activeFlowSteps = candidates.map((c) => c.step);
|
|
785
838
|
logger.debug(
|
|
786
|
-
`[
|
|
839
|
+
`[FlowRouter] Found ${activeFlowSteps.length} candidate steps for active route`
|
|
787
840
|
);
|
|
788
841
|
}
|
|
789
842
|
}
|
|
790
843
|
}
|
|
791
844
|
|
|
792
|
-
const routingSchema = this.
|
|
845
|
+
const routingSchema = this.buildDynamicFlowSchema(
|
|
793
846
|
eligibleRoutes,
|
|
794
847
|
undefined,
|
|
795
|
-
|
|
848
|
+
activeFlowSteps
|
|
796
849
|
);
|
|
797
850
|
|
|
798
851
|
const routingPrompt = await this.buildRoutingPrompt({
|
|
799
852
|
history,
|
|
800
|
-
|
|
853
|
+
flows: eligibleRoutes,
|
|
801
854
|
lastMessage: lastUserMessage,
|
|
802
855
|
agentOptions,
|
|
803
856
|
session,
|
|
804
|
-
|
|
857
|
+
activeFlowSteps,
|
|
805
858
|
context,
|
|
806
859
|
});
|
|
807
860
|
|
|
808
861
|
const routingResult = await provider.generateMessage<
|
|
809
862
|
TContext,
|
|
810
|
-
|
|
863
|
+
FlowRoutingDecisionOutput
|
|
811
864
|
>({
|
|
812
865
|
prompt: routingPrompt,
|
|
813
866
|
history: eventsToHistory(history),
|
|
@@ -819,122 +872,89 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
819
872
|
},
|
|
820
873
|
});
|
|
821
874
|
|
|
822
|
-
let
|
|
875
|
+
let selectedFlow: Flow<TContext, TData> | undefined;
|
|
823
876
|
let selectedStep: Step<TContext, TData> | undefined;
|
|
824
877
|
let responseDirectives: string[] | undefined;
|
|
825
878
|
|
|
826
|
-
if (routingResult.structured?.
|
|
827
|
-
// Use cross-
|
|
828
|
-
const optimalRoute = this.
|
|
879
|
+
if (routingResult.structured?.flows) {
|
|
880
|
+
// Use cross-flow completion evaluation to select optimal flow
|
|
881
|
+
const optimalRoute = this.selectOptimalFlow(
|
|
829
882
|
eligibleRoutes,
|
|
830
883
|
updatedSession.data || {},
|
|
831
|
-
routingResult.structured.
|
|
832
|
-
updatedSession.
|
|
884
|
+
routingResult.structured.flows,
|
|
885
|
+
updatedSession.currentFlow?.id
|
|
833
886
|
);
|
|
834
887
|
|
|
835
|
-
// If no optimal
|
|
888
|
+
// If no optimal flow found, check why
|
|
836
889
|
if (!optimalRoute) {
|
|
837
890
|
if (eligibleRoutes.length === 0) {
|
|
838
|
-
// No
|
|
891
|
+
// No flows passed filtering
|
|
839
892
|
logger.debug(
|
|
840
|
-
`[
|
|
893
|
+
`[FlowRouter] No eligible flows available - all flows filtered out`
|
|
841
894
|
);
|
|
842
|
-
|
|
895
|
+
selectedFlow = undefined;
|
|
843
896
|
} else {
|
|
844
|
-
// Routes exist but
|
|
845
|
-
// This means all
|
|
897
|
+
// Routes exist but selectOptimalFlow returned undefined
|
|
898
|
+
// This means all flows are 100% complete
|
|
846
899
|
logger.debug(
|
|
847
|
-
`[
|
|
900
|
+
`[FlowRouter] No optimal route found - all ${eligibleRoutes.length} eligible routes are complete`
|
|
848
901
|
);
|
|
849
|
-
|
|
902
|
+
selectedFlow = undefined;
|
|
850
903
|
}
|
|
851
904
|
} else {
|
|
852
|
-
|
|
905
|
+
selectedFlow = optimalRoute;
|
|
853
906
|
}
|
|
854
907
|
|
|
855
908
|
responseDirectives = routingResult.structured.responseDirectives;
|
|
856
909
|
|
|
857
910
|
if (
|
|
858
|
-
|
|
911
|
+
selectedFlow === activeFlow &&
|
|
859
912
|
routingResult.structured.selectedStepId &&
|
|
860
|
-
|
|
913
|
+
activeFlow
|
|
861
914
|
) {
|
|
862
|
-
selectedStep =
|
|
915
|
+
selectedStep = activeFlow.getStep(
|
|
863
916
|
routingResult.structured.selectedStepId
|
|
864
917
|
);
|
|
865
918
|
if (selectedStep) {
|
|
866
919
|
logger.debug(
|
|
867
|
-
`[
|
|
920
|
+
`[FlowRouter] AI selected step: ${selectedStep.id} in active route`
|
|
868
921
|
);
|
|
869
922
|
logger.debug(
|
|
870
|
-
`[
|
|
923
|
+
`[FlowRouter] Step reasoning: ${routingResult.structured.stepReasoning}`
|
|
871
924
|
);
|
|
872
925
|
}
|
|
873
926
|
}
|
|
874
927
|
|
|
875
|
-
if (
|
|
876
|
-
logger.debug(`[
|
|
877
|
-
updatedSession = this.
|
|
928
|
+
if (selectedFlow) {
|
|
929
|
+
logger.debug(`[FlowRouter] Selected route: ${selectedFlow.title}`);
|
|
930
|
+
updatedSession = this.enterFlowIfNeeded(updatedSession, selectedFlow);
|
|
878
931
|
}
|
|
879
932
|
}
|
|
880
933
|
|
|
881
934
|
return {
|
|
882
|
-
|
|
935
|
+
selectedFlow,
|
|
883
936
|
selectedStep,
|
|
884
937
|
responseDirectives,
|
|
885
938
|
session: updatedSession,
|
|
886
|
-
|
|
887
|
-
|
|
939
|
+
isFlowComplete,
|
|
940
|
+
completedFlows,
|
|
888
941
|
};
|
|
889
942
|
}
|
|
890
943
|
|
|
891
944
|
/**
|
|
892
|
-
* Filter
|
|
893
|
-
* @param routes -
|
|
945
|
+
* Filter flows based on when conditions
|
|
946
|
+
* @param routes - Flows that passed skipIf filtering
|
|
894
947
|
* @param templateContext - Context for condition evaluation
|
|
895
|
-
* @returns Object with eligible
|
|
948
|
+
* @returns Object with eligible flows and collected AI context strings
|
|
896
949
|
*/
|
|
897
|
-
async
|
|
898
|
-
routes:
|
|
950
|
+
async filterFlowsByWhen(
|
|
951
|
+
routes: Flow<TContext, TData>[],
|
|
899
952
|
templateContext: TemplateContext<TContext, TData>
|
|
900
953
|
): Promise<{
|
|
901
|
-
eligibleRoutes:
|
|
954
|
+
eligibleRoutes: Flow<TContext, TData>[];
|
|
902
955
|
aiContextStrings: string[];
|
|
903
956
|
}> {
|
|
904
|
-
const eligibleRoutes:
|
|
905
|
-
const aiContextStrings: string[] = [];
|
|
906
|
-
|
|
907
|
-
for (const route of routes) {
|
|
908
|
-
const skipResult = await route.evaluateSkipIf(templateContext);
|
|
909
|
-
|
|
910
|
-
// Collect AI context strings from skipIf conditions
|
|
911
|
-
aiContextStrings.push(...skipResult.aiContextStrings);
|
|
912
|
-
|
|
913
|
-
// If route should not be skipped, it's eligible
|
|
914
|
-
if (!skipResult.programmaticResult) {
|
|
915
|
-
eligibleRoutes.push(route);
|
|
916
|
-
} else {
|
|
917
|
-
logger.debug(`[RoutingEngine] Skipping route ${route.title} (skipIf condition met)`);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
return { eligibleRoutes, aiContextStrings };
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
* Filter routes based on when conditions
|
|
926
|
-
* @param routes - Routes that passed skipIf filtering
|
|
927
|
-
* @param templateContext - Context for condition evaluation
|
|
928
|
-
* @returns Object with eligible routes and collected AI context strings
|
|
929
|
-
*/
|
|
930
|
-
async filterRoutesByWhen(
|
|
931
|
-
routes: Route<TContext, TData>[],
|
|
932
|
-
templateContext: TemplateContext<TContext, TData>
|
|
933
|
-
): Promise<{
|
|
934
|
-
eligibleRoutes: Route<TContext, TData>[];
|
|
935
|
-
aiContextStrings: string[];
|
|
936
|
-
}> {
|
|
937
|
-
const eligibleRoutes: Route<TContext, TData>[] = [];
|
|
957
|
+
const eligibleRoutes: Flow<TContext, TData>[] = [];
|
|
938
958
|
const aiContextStrings: string[] = [];
|
|
939
959
|
|
|
940
960
|
for (const route of routes) {
|
|
@@ -943,11 +963,11 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
943
963
|
// Collect AI context strings from when conditions
|
|
944
964
|
aiContextStrings.push(...whenResult.aiContextStrings);
|
|
945
965
|
|
|
946
|
-
// If
|
|
966
|
+
// If flow has no programmatic conditions or they evaluate to true, it's eligible
|
|
947
967
|
if (!whenResult.hasProgrammaticConditions || whenResult.programmaticResult) {
|
|
948
968
|
eligibleRoutes.push(route);
|
|
949
969
|
} else {
|
|
950
|
-
logger.debug(`[
|
|
970
|
+
logger.debug(`[FlowRouter] Flow ${route.title} not eligible (when condition not met)`);
|
|
951
971
|
}
|
|
952
972
|
}
|
|
953
973
|
|
|
@@ -955,22 +975,22 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
955
975
|
}
|
|
956
976
|
|
|
957
977
|
/**
|
|
958
|
-
* Evaluate all
|
|
959
|
-
* @param routes - All available
|
|
978
|
+
* Evaluate all flows for completion based on collected data
|
|
979
|
+
* @param routes - All available flows
|
|
960
980
|
* @param data - Currently collected agent-level data
|
|
961
|
-
* @returns Array of
|
|
981
|
+
* @returns Array of flows that are complete
|
|
962
982
|
*/
|
|
963
|
-
|
|
983
|
+
evaluateFlowCompletions(routes: Flow<TContext, TData>[], data: Partial<TData>): Flow<TContext, TData>[] {
|
|
964
984
|
return routes.filter(route => route.isComplete(data));
|
|
965
985
|
}
|
|
966
986
|
|
|
967
987
|
/**
|
|
968
|
-
* Get completion status for all
|
|
969
|
-
* @param routes - All available
|
|
988
|
+
* Get completion status for all flows
|
|
989
|
+
* @param routes - All available flows
|
|
970
990
|
* @param data - Currently collected agent-level data
|
|
971
|
-
* @returns Map of
|
|
991
|
+
* @returns Map of flow ID to completion progress (0-1)
|
|
972
992
|
*/
|
|
973
|
-
|
|
993
|
+
getFlowCompletionStatus(routes: Flow<TContext, TData>[], data: Partial<TData>): Map<string, number> {
|
|
974
994
|
const completionStatus = new Map<string, number>();
|
|
975
995
|
|
|
976
996
|
for (const route of routes) {
|
|
@@ -982,39 +1002,39 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
982
1002
|
}
|
|
983
1003
|
|
|
984
1004
|
/**
|
|
985
|
-
* Find the best
|
|
986
|
-
* Prioritizes
|
|
987
|
-
* IMPORTANT: Completed
|
|
988
|
-
* @param routes - All available
|
|
1005
|
+
* Find the best flow to continue based on completion status and user intent
|
|
1006
|
+
* Prioritizes flows that are partially complete but not finished
|
|
1007
|
+
* IMPORTANT: Completed flows are excluded to prevent re-entering finished tasks
|
|
1008
|
+
* @param routes - All available flows
|
|
989
1009
|
* @param data - Currently collected agent-level data
|
|
990
1010
|
* @param routeScores - AI-generated route scores from routing decision
|
|
991
|
-
* @returns
|
|
1011
|
+
* @returns Flow that should be prioritized for continuation
|
|
992
1012
|
*/
|
|
993
|
-
|
|
994
|
-
routes:
|
|
1013
|
+
selectOptimalFlow(
|
|
1014
|
+
routes: Flow<TContext, TData>[],
|
|
995
1015
|
data: Partial<TData>,
|
|
996
1016
|
routeScores: Record<string, number>,
|
|
997
1017
|
currentRouteId?: string
|
|
998
|
-
):
|
|
999
|
-
const completionStatus = this.
|
|
1000
|
-
const switchMargin = this.options?.
|
|
1018
|
+
): Flow<TContext, TData> | undefined {
|
|
1019
|
+
const completionStatus = this.getFlowCompletionStatus(routes, data);
|
|
1020
|
+
const switchMargin = this.options?.flowSwitchMargin ?? 15;
|
|
1001
1021
|
|
|
1002
1022
|
// Create weighted scores combining AI intent scores with completion progress
|
|
1003
|
-
const weightedScores: Array<{ route:
|
|
1023
|
+
const weightedScores: Array<{ route: Flow<TContext, TData>; score: number }> = [];
|
|
1004
1024
|
|
|
1005
1025
|
for (const route of routes) {
|
|
1006
1026
|
const aiScore = routeScores[route.id] || 0;
|
|
1007
1027
|
const completionProgress = completionStatus.get(route.id) || 0;
|
|
1008
1028
|
|
|
1009
|
-
// ALWAYS skip fully completed
|
|
1029
|
+
// ALWAYS skip fully completed flows to prevent re-entering finished tasks
|
|
1010
1030
|
if (completionProgress >= 1.0) {
|
|
1011
1031
|
logger.debug(
|
|
1012
|
-
`[
|
|
1032
|
+
`[FlowRouter] Excluding completed flow: ${route.title} (100% complete)`
|
|
1013
1033
|
);
|
|
1014
1034
|
continue;
|
|
1015
1035
|
}
|
|
1016
1036
|
|
|
1017
|
-
// Boost partially complete
|
|
1037
|
+
// Boost partially complete flows that match user intent
|
|
1018
1038
|
let weightedScore = aiScore;
|
|
1019
1039
|
if (completionProgress > 0 && completionProgress < 1.0) {
|
|
1020
1040
|
weightedScore += (completionProgress * 20); // Up to 20 point boost
|
|
@@ -1031,7 +1051,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1031
1051
|
}
|
|
1032
1052
|
|
|
1033
1053
|
// Apply sticky routing: if there's a current route, only switch if the
|
|
1034
|
-
// best alternative exceeds the current
|
|
1054
|
+
// best alternative exceeds the current flow's score by the configured margin
|
|
1035
1055
|
if (currentRouteId) {
|
|
1036
1056
|
const currentEntry = weightedScores.find(e => e.route.id === currentRouteId);
|
|
1037
1057
|
const bestEntry = weightedScores[0];
|
|
@@ -1039,14 +1059,14 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1039
1059
|
if (currentEntry && bestEntry.route.id !== currentRouteId) {
|
|
1040
1060
|
if (bestEntry.score < currentEntry.score + switchMargin) {
|
|
1041
1061
|
logger.debug(
|
|
1042
|
-
`[
|
|
1062
|
+
`[FlowRouter] Staying on current flow: ${currentEntry.route.title} ` +
|
|
1043
1063
|
`(current: ${currentEntry.score}, best alternative: ${bestEntry.score}, ` +
|
|
1044
1064
|
`margin required: ${switchMargin})`
|
|
1045
1065
|
);
|
|
1046
1066
|
return currentEntry.route;
|
|
1047
1067
|
}
|
|
1048
1068
|
logger.debug(
|
|
1049
|
-
`[
|
|
1069
|
+
`[FlowRouter] Switching flow: ${currentEntry.route.title} → ${bestEntry.route.title} ` +
|
|
1050
1070
|
`(current: ${currentEntry.score}, alternative: ${bestEntry.score}, ` +
|
|
1051
1071
|
`margin: ${switchMargin})`
|
|
1052
1072
|
);
|
|
@@ -1054,7 +1074,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1054
1074
|
}
|
|
1055
1075
|
|
|
1056
1076
|
logger.debug(
|
|
1057
|
-
`[
|
|
1077
|
+
`[FlowRouter] Selected optimal route: ${weightedScores[0].route.title} ` +
|
|
1058
1078
|
`(AI: ${routeScores[weightedScores[0].route.id]}, ` +
|
|
1059
1079
|
`Completion: ${(completionStatus.get(weightedScores[0].route.id) || 0) * 100}%, ` +
|
|
1060
1080
|
`Weighted: ${weightedScores[0].score})`
|
|
@@ -1063,14 +1083,14 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1063
1083
|
}
|
|
1064
1084
|
|
|
1065
1085
|
/**
|
|
1066
|
-
* Build prompt for step selection within a single
|
|
1086
|
+
* Build prompt for step selection within a single flow
|
|
1067
1087
|
* @private
|
|
1068
1088
|
*/
|
|
1069
1089
|
private async buildStepSelectionPrompt(
|
|
1070
|
-
params: BuildStepSelectionPromptParams<TContext, TData> & {
|
|
1090
|
+
params: BuildStepSelectionPromptParams<TContext, TData> & { includeCompletion?: boolean }
|
|
1071
1091
|
): Promise<string> {
|
|
1072
1092
|
const {
|
|
1073
|
-
route,
|
|
1093
|
+
flow: route,
|
|
1074
1094
|
currentStep,
|
|
1075
1095
|
candidates,
|
|
1076
1096
|
data,
|
|
@@ -1080,7 +1100,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1080
1100
|
context,
|
|
1081
1101
|
session,
|
|
1082
1102
|
stepConditionContext,
|
|
1083
|
-
|
|
1103
|
+
includeCompletion = false,
|
|
1084
1104
|
} = params;
|
|
1085
1105
|
const templateContext = createTemplateContext({ context, session, history });
|
|
1086
1106
|
const pc = new PromptComposer<TContext, TData>(templateContext, this.options?.promptSectionCache);
|
|
@@ -1090,9 +1110,9 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1090
1110
|
await pc.addAgentMeta(agentOptions);
|
|
1091
1111
|
}
|
|
1092
1112
|
|
|
1093
|
-
// Add
|
|
1113
|
+
// Add flow context
|
|
1094
1114
|
await pc.addInstruction(
|
|
1095
|
-
`Active
|
|
1115
|
+
`Active Flow: ${route.title}\nDescription: ${route.description || "N/A"}`
|
|
1096
1116
|
);
|
|
1097
1117
|
|
|
1098
1118
|
// Add current step context
|
|
@@ -1102,7 +1122,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1102
1122
|
}`
|
|
1103
1123
|
);
|
|
1104
1124
|
} else {
|
|
1105
|
-
await pc.addInstruction("Current Step: None (entering
|
|
1125
|
+
await pc.addInstruction("Current Step: None (entering flow)");
|
|
1106
1126
|
}
|
|
1107
1127
|
|
|
1108
1128
|
// Add collected data context
|
|
@@ -1181,10 +1201,10 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1181
1201
|
"- Steps with skipIf conditions that are met have already been filtered out",
|
|
1182
1202
|
];
|
|
1183
1203
|
|
|
1184
|
-
if (
|
|
1204
|
+
if (includeCompletion) {
|
|
1185
1205
|
decisionRules.push(
|
|
1186
1206
|
"",
|
|
1187
|
-
`- You can select '
|
|
1207
|
+
`- You can select '__COMPLETE__' to complete this flow if:`,
|
|
1188
1208
|
" * All required data has been collected",
|
|
1189
1209
|
" * The user's intent suggests they're done with this task",
|
|
1190
1210
|
" * No further steps are needed to fulfill the user's request"
|
|
@@ -1207,13 +1227,13 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1207
1227
|
*/
|
|
1208
1228
|
private buildStepSelectionSchema(
|
|
1209
1229
|
validSteps: Step<TContext, TData>[],
|
|
1210
|
-
|
|
1230
|
+
includeCompletion: boolean = false
|
|
1211
1231
|
): StructuredSchema {
|
|
1212
1232
|
const stepIds = validSteps.map((s) => s.id);
|
|
1213
1233
|
|
|
1214
|
-
// Add
|
|
1215
|
-
if (
|
|
1216
|
-
stepIds.push(
|
|
1234
|
+
// Add completion option if requested (when required fields are complete)
|
|
1235
|
+
if (includeCompletion) {
|
|
1236
|
+
stepIds.push('__COMPLETE__');
|
|
1217
1237
|
}
|
|
1218
1238
|
|
|
1219
1239
|
return {
|
|
@@ -1229,8 +1249,8 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1229
1249
|
selectedStepId: {
|
|
1230
1250
|
type: "string",
|
|
1231
1251
|
nullable: false,
|
|
1232
|
-
description:
|
|
1233
|
-
?
|
|
1252
|
+
description: includeCompletion
|
|
1253
|
+
? "The ID of the selected step to transition to, or '__COMPLETE__' to complete the flow"
|
|
1234
1254
|
: "The ID of the selected step to transition to",
|
|
1235
1255
|
enum: stepIds,
|
|
1236
1256
|
},
|
|
@@ -1246,10 +1266,10 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1246
1266
|
};
|
|
1247
1267
|
}
|
|
1248
1268
|
|
|
1249
|
-
|
|
1250
|
-
routes:
|
|
1269
|
+
buildDynamicFlowSchema(
|
|
1270
|
+
routes: Flow<TContext, TData>[],
|
|
1251
1271
|
extrasSchema?: StructuredSchema,
|
|
1252
|
-
|
|
1272
|
+
activeFlowSteps?: Step<TContext, TData>[]
|
|
1253
1273
|
): StructuredSchema {
|
|
1254
1274
|
const routeIds = routes.map((r) => r.id);
|
|
1255
1275
|
const routeProperties: Record<string, StructuredSchema> = {};
|
|
@@ -1257,7 +1277,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1257
1277
|
routeProperties[id] = {
|
|
1258
1278
|
type: "number",
|
|
1259
1279
|
nullable: false,
|
|
1260
|
-
description: `Score for
|
|
1280
|
+
description: `Score for flow ${id} based on direct evidence, context and semantic fit (0-100)`,
|
|
1261
1281
|
minimum: 0,
|
|
1262
1282
|
maximum: 100,
|
|
1263
1283
|
} as StructuredSchema;
|
|
@@ -1265,7 +1285,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1265
1285
|
|
|
1266
1286
|
const base: StructuredSchema = {
|
|
1267
1287
|
description:
|
|
1268
|
-
"Full intent analysis: score ALL available
|
|
1288
|
+
"Full intent analysis: score ALL available flows (0-100) using evidence and context",
|
|
1269
1289
|
type: "object",
|
|
1270
1290
|
properties: {
|
|
1271
1291
|
context: {
|
|
@@ -1273,12 +1293,12 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1273
1293
|
nullable: false,
|
|
1274
1294
|
description: "Brief summary of the user's intent/context",
|
|
1275
1295
|
},
|
|
1276
|
-
|
|
1296
|
+
flows: {
|
|
1277
1297
|
type: "object",
|
|
1278
1298
|
properties: routeProperties,
|
|
1279
1299
|
required: routeIds,
|
|
1280
1300
|
nullable: false,
|
|
1281
|
-
description: "Mapping of
|
|
1301
|
+
description: "Mapping of flowId to score (0-100)",
|
|
1282
1302
|
},
|
|
1283
1303
|
responseDirectives: {
|
|
1284
1304
|
type: "array",
|
|
@@ -1287,19 +1307,19 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1287
1307
|
"Optional bullet points the response should address (concise)",
|
|
1288
1308
|
},
|
|
1289
1309
|
},
|
|
1290
|
-
required: ["context", "
|
|
1310
|
+
required: ["context", "flows"],
|
|
1291
1311
|
additionalProperties: false,
|
|
1292
1312
|
};
|
|
1293
1313
|
|
|
1294
|
-
// Add step selection fields if there's an active
|
|
1295
|
-
if (
|
|
1314
|
+
// Add step selection fields if there's an active flow with steps
|
|
1315
|
+
if (activeFlowSteps && activeFlowSteps.length > 0) {
|
|
1296
1316
|
base.properties = base.properties || {};
|
|
1297
1317
|
base.properties.selectedStepId = {
|
|
1298
1318
|
type: "string",
|
|
1299
1319
|
nullable: false,
|
|
1300
1320
|
description:
|
|
1301
|
-
"The step ID to transition to within the active
|
|
1302
|
-
enum:
|
|
1321
|
+
"The step ID to transition to within the active flow (required if continuing in current flow)",
|
|
1322
|
+
enum: activeFlowSteps.map((s) => s.id),
|
|
1303
1323
|
};
|
|
1304
1324
|
base.properties.stepReasoning = {
|
|
1305
1325
|
type: "string",
|
|
@@ -1326,11 +1346,11 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1326
1346
|
): Promise<string> {
|
|
1327
1347
|
const {
|
|
1328
1348
|
history,
|
|
1329
|
-
routes,
|
|
1349
|
+
flows: routes,
|
|
1330
1350
|
lastMessage,
|
|
1331
1351
|
agentOptions,
|
|
1332
1352
|
session,
|
|
1333
|
-
|
|
1353
|
+
activeFlowSteps,
|
|
1334
1354
|
context,
|
|
1335
1355
|
} = params;
|
|
1336
1356
|
const templateContext = createTemplateContext({ context, session, history });
|
|
@@ -1343,10 +1363,10 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1343
1363
|
);
|
|
1344
1364
|
|
|
1345
1365
|
// Add session context if available
|
|
1346
|
-
if (session?.
|
|
1366
|
+
if (session?.currentFlow) {
|
|
1347
1367
|
const sessionInfo = [
|
|
1348
1368
|
"Current conversation context:",
|
|
1349
|
-
`- Active route: ${session.
|
|
1369
|
+
`- Active route: ${session.currentFlow.title} (${session.currentFlow.id})`,
|
|
1350
1370
|
];
|
|
1351
1371
|
if (session.currentStep) {
|
|
1352
1372
|
sessionInfo.push(`- Current step: ${session.currentStep.id}`);
|
|
@@ -1363,18 +1383,18 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1363
1383
|
await pc.addInstruction(sessionInfo.join("\n"));
|
|
1364
1384
|
|
|
1365
1385
|
// Add cross-route completion status
|
|
1366
|
-
const completionStatus = this.
|
|
1367
|
-
const
|
|
1386
|
+
const completionStatus = this.getFlowCompletionStatus(routes, session.data || {});
|
|
1387
|
+
const completedFlows = this.evaluateFlowCompletions(routes, session.data || {});
|
|
1368
1388
|
|
|
1369
1389
|
if (completionStatus.size > 0) {
|
|
1370
1390
|
const statusInfo = [
|
|
1371
1391
|
"",
|
|
1372
|
-
"
|
|
1392
|
+
"Flow completion status based on collected data:",
|
|
1373
1393
|
];
|
|
1374
1394
|
|
|
1375
1395
|
for (const route of routes) {
|
|
1376
1396
|
const progress = completionStatus.get(route.id) || 0;
|
|
1377
|
-
const isComplete =
|
|
1397
|
+
const isComplete = completedFlows.includes(route);
|
|
1378
1398
|
const progressPercent = Math.round(progress * 100);
|
|
1379
1399
|
|
|
1380
1400
|
statusInfo.push(
|
|
@@ -1398,15 +1418,15 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1398
1418
|
}
|
|
1399
1419
|
|
|
1400
1420
|
// Add available steps for the active route
|
|
1401
|
-
if (
|
|
1421
|
+
if (activeFlowSteps && activeFlowSteps.length > 0) {
|
|
1402
1422
|
const stepInfo = [
|
|
1403
1423
|
"",
|
|
1404
1424
|
"Available steps in active route (choose one to transition to):",
|
|
1405
1425
|
];
|
|
1406
1426
|
const activeStepConditionContext: string[] = [];
|
|
1407
1427
|
|
|
1408
|
-
for (const step of
|
|
1409
|
-
const idx =
|
|
1428
|
+
for (const step of activeFlowSteps) {
|
|
1429
|
+
const idx = activeFlowSteps.indexOf(step);
|
|
1410
1430
|
stepInfo.push(`${idx + 1}. Step: ${step.id}`);
|
|
1411
1431
|
if (step.description) {
|
|
1412
1432
|
stepInfo.push(` Description: ${step.description}`);
|
|
@@ -1457,7 +1477,7 @@ export class RoutingEngine<TContext = unknown, TData = unknown> {
|
|
|
1457
1477
|
|
|
1458
1478
|
await pc.addInteractionHistory(history);
|
|
1459
1479
|
await pc.addLastMessage(lastMessage);
|
|
1460
|
-
await pc.
|
|
1480
|
+
await pc.addFlowOverview(routes);
|
|
1461
1481
|
|
|
1462
1482
|
await pc.addInstruction(
|
|
1463
1483
|
[
|