@falai/agent 1.2.7 → 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 +1196 -1015
- 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 +524 -134
- 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/GeminiProvider.d.ts +3 -3
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +16 -14
- package/dist/cjs/providers/GeminiProvider.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 +1198 -1017
- 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 +524 -135
- 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/GeminiProvider.d.ts +3 -3
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +16 -14
- package/dist/providers/GeminiProvider.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 +1458 -1241
- package/src/core/ResponsePipeline.ts +675 -173
- 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/GeminiProvider.ts +17 -15
- 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
|
@@ -5,14 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.ResponseModal = exports.ResponseGenerationError = void 0;
|
|
8
|
-
const Step_1 = require("./Step");
|
|
9
8
|
const ResponseEngine_1 = require("./ResponseEngine");
|
|
10
9
|
const ResponsePipeline_1 = require("./ResponsePipeline");
|
|
11
|
-
const
|
|
12
|
-
const BatchPromptBuilder_1 = require("./BatchPromptBuilder");
|
|
10
|
+
const AutoChainExecutor_1 = require("./AutoChainExecutor");
|
|
13
11
|
const utils_1 = require("../utils");
|
|
14
12
|
const template_1 = require("../utils/template");
|
|
15
|
-
const constants_1 = require("../constants");
|
|
16
13
|
/**
|
|
17
14
|
* Error class for response generation failures
|
|
18
15
|
*/
|
|
@@ -31,7 +28,8 @@ class ResponseGenerationError extends Error {
|
|
|
31
28
|
*/
|
|
32
29
|
static fromError(error, phase, params, context) {
|
|
33
30
|
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
-
return new ResponseGenerationError(`Response generation failed in ${phase}: ${message}
|
|
31
|
+
return new ResponseGenerationError(`[ResponseGenerationError] Response generation failed in ${phase}: ${message}. ` +
|
|
32
|
+
`Check provider configuration and the ${phase} phase handler.`, { originalError: error, params, phase, context });
|
|
35
33
|
}
|
|
36
34
|
/**
|
|
37
35
|
* Check if an error is a ResponseGenerationError
|
|
@@ -52,12 +50,8 @@ class ResponseModal {
|
|
|
52
50
|
// Initialize response engine
|
|
53
51
|
this.responseEngine = new ResponseEngine_1.ResponseEngine(this.agent.promptSectionCache);
|
|
54
52
|
// Initialize response pipeline with agent dependencies
|
|
55
|
-
this.responsePipeline = new ResponsePipeline_1.ResponsePipeline(this.agent.getAgentOptions(), () => this.agent.
|
|
56
|
-
this.agent.getTools(), this.agent.
|
|
57
|
-
// Initialize batch executor for multi-step execution
|
|
58
|
-
this.batchExecutor = new BatchExecutor_1.BatchExecutor();
|
|
59
|
-
// Initialize batch prompt builder for combined prompts
|
|
60
|
-
this.batchPromptBuilder = new BatchPromptBuilder_1.BatchPromptBuilder(this.agent.promptSectionCache);
|
|
53
|
+
this.responsePipeline = new ResponsePipeline_1.ResponsePipeline(this.agent.getAgentOptions(), () => this.agent.getFlows(), // Pass a function to get flows dynamically
|
|
54
|
+
this.agent.getTools(), this.agent.getFlowRouter(), this.agent.updateContext.bind(this.agent), this.agent.getUpdateDataMethod(), this.agent.updateCollectedData.bind(this.agent), this.getToolManager(), this.agent.signalProcessor);
|
|
61
55
|
}
|
|
62
56
|
/**
|
|
63
57
|
* Generate a non-streaming response using unified logic
|
|
@@ -73,7 +67,8 @@ class ResponseModal {
|
|
|
73
67
|
return result;
|
|
74
68
|
}
|
|
75
69
|
catch (error) {
|
|
76
|
-
throw new ResponseGenerationError(`
|
|
70
|
+
throw new ResponseGenerationError(`[ResponseGenerationError] Response generation failed: ${error instanceof Error ? error.message : String(error)}. ` +
|
|
71
|
+
`Check provider configuration and network connectivity.`, { originalError: error, params, phase: 'response_generation' });
|
|
77
72
|
}
|
|
78
73
|
}
|
|
79
74
|
/**
|
|
@@ -226,6 +221,17 @@ class ResponseModal {
|
|
|
226
221
|
utils_1.logger.warn(`[ResponseModal] ToolManager not available on agent - tool execution will use fallback methods`);
|
|
227
222
|
return undefined;
|
|
228
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Collect scoped instructions from agent, flow, and step into a ScopedInstructions value.
|
|
226
|
+
* @private
|
|
227
|
+
*/
|
|
228
|
+
collectScopedInstructions(flow, step) {
|
|
229
|
+
return {
|
|
230
|
+
global: this.agent.instructions,
|
|
231
|
+
flow: flow ? { flowTitle: flow.title, items: flow.instructions } : undefined,
|
|
232
|
+
step: step ? { stepId: step.id, items: step.getInstructions() } : undefined,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
229
235
|
// UNIFIED RESPONSE LOGIC - Consolidates common logic between streaming and non-streaming
|
|
230
236
|
// ============================================================================
|
|
231
237
|
/**
|
|
@@ -238,7 +244,8 @@ class ResponseModal {
|
|
|
238
244
|
const { history: simpleHistory, contextOverride, signal } = params;
|
|
239
245
|
// Validate input parameters
|
|
240
246
|
if (!simpleHistory) {
|
|
241
|
-
throw new ResponseGenerationError('
|
|
247
|
+
throw new ResponseGenerationError('[ResponseGenerationError] Missing history: history is required for response generation. ' +
|
|
248
|
+
'Pass a valid history array to the respond/stream method.', { params, phase: 'validation' });
|
|
242
249
|
}
|
|
243
250
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
244
251
|
const historyEvents = (0, utils_1.historyToEvents)(simpleHistory);
|
|
@@ -250,7 +257,7 @@ class ResponseModal {
|
|
|
250
257
|
try {
|
|
251
258
|
// Set current context and session in pipeline for consistency
|
|
252
259
|
this.responsePipeline.setContext(await this.agent.getContext());
|
|
253
|
-
this.responsePipeline.setCurrentSession(this.agent.
|
|
260
|
+
this.responsePipeline.setCurrentSession(this.agent.currentSession);
|
|
254
261
|
responseContext = await this.responsePipeline.prepareResponseContext({
|
|
255
262
|
contextOverride,
|
|
256
263
|
session: params.session ? (0, utils_1.cloneDeep)(params.session) : undefined,
|
|
@@ -289,8 +296,8 @@ class ResponseModal {
|
|
|
289
296
|
catch (error) {
|
|
290
297
|
throw ResponseGenerationError.fromError(error, 'step_preparation', params, { session, effectiveContext });
|
|
291
298
|
}
|
|
292
|
-
// PHASE 2: ROUTING + STEP SELECTION - Determine which
|
|
293
|
-
//
|
|
299
|
+
// PHASE 2: ROUTING + STEP SELECTION - Determine which flow and step to use
|
|
300
|
+
// Performs pre-extraction and step selection
|
|
294
301
|
let routingResult;
|
|
295
302
|
try {
|
|
296
303
|
routingResult = await this.handleUnifiedRoutingAndStepSelection({
|
|
@@ -307,14 +314,15 @@ class ResponseModal {
|
|
|
307
314
|
effectiveContext,
|
|
308
315
|
session: routingResult.session,
|
|
309
316
|
history,
|
|
310
|
-
|
|
317
|
+
selectedFlow: routingResult.selectedFlow,
|
|
311
318
|
selectedStep: routingResult.selectedStep,
|
|
312
319
|
responseDirectives: routingResult.responseDirectives,
|
|
313
|
-
|
|
314
|
-
batchSteps: routingResult.batchSteps,
|
|
315
|
-
batchStoppedReason: routingResult.batchStoppedReason,
|
|
316
|
-
batchStoppedAtStep: routingResult.batchStoppedAtStep,
|
|
320
|
+
isFlowComplete: routingResult.isFlowComplete,
|
|
317
321
|
signal,
|
|
322
|
+
signalFirings: routingResult.signalFirings,
|
|
323
|
+
signalPreDirective: routingResult.signalPreDirective,
|
|
324
|
+
signalHalted: routingResult.signalHalted,
|
|
325
|
+
signalHaltReply: routingResult.signalHaltReply,
|
|
318
326
|
};
|
|
319
327
|
}
|
|
320
328
|
catch (error) {
|
|
@@ -331,27 +339,141 @@ class ResponseModal {
|
|
|
331
339
|
*/
|
|
332
340
|
async handleUnifiedRoutingAndStepSelection(params) {
|
|
333
341
|
try {
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
//
|
|
342
|
+
// Create a fresh chain tracker for this turn (Requirement 22.1)
|
|
343
|
+
this.responsePipeline.createChainTracker();
|
|
344
|
+
// ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
|
|
345
|
+
// When the current step has collect fields AND pre-extraction populates at least
|
|
346
|
+
// one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
|
|
347
|
+
const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
|
|
348
|
+
if (routingSkipResult) {
|
|
349
|
+
// Even when routing is skipped, run pre-signal phase if processor is present
|
|
350
|
+
if (this.agent.signalProcessor) {
|
|
351
|
+
const signalResult = await this.responsePipeline.runPreSignalPhase(params.session, params.context, params.history);
|
|
352
|
+
// If signal halts, override the routing skip result
|
|
353
|
+
if (signalResult.mergedDirective?.halt) {
|
|
354
|
+
return {
|
|
355
|
+
...routingSkipResult,
|
|
356
|
+
session: signalResult.updatedSession,
|
|
357
|
+
signalFirings: signalResult.firings,
|
|
358
|
+
signalHalted: true,
|
|
359
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
// If signal has position fields, override routing skip result
|
|
363
|
+
if ((0, ResponsePipeline_1.hasDirectivePositionField)(signalResult.mergedDirective)) {
|
|
364
|
+
return this.applySignalPositionDirective(signalResult, params);
|
|
365
|
+
}
|
|
366
|
+
// Non-position directive: propagate for pre-LLM augmentation
|
|
367
|
+
return {
|
|
368
|
+
...routingSkipResult,
|
|
369
|
+
session: signalResult.updatedSession,
|
|
370
|
+
signalFirings: signalResult.firings,
|
|
371
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return routingSkipResult;
|
|
375
|
+
}
|
|
376
|
+
// ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
|
|
377
|
+
// When signalProcessor is present, run pre-signals in parallel with routing.
|
|
378
|
+
// When absent, call the router directly (zero overhead, preserve current behavior).
|
|
379
|
+
if (this.agent.signalProcessor) {
|
|
380
|
+
// Run pre-signal phase in parallel with routing (Requirement 8.1)
|
|
381
|
+
const [signalResult, routingResult] = await Promise.all([
|
|
382
|
+
this.responsePipeline.runPreSignalPhase(params.session, params.context, params.history),
|
|
383
|
+
this.responsePipeline.handleRoutingAndStepSelection({
|
|
384
|
+
session: params.session,
|
|
385
|
+
history: params.history,
|
|
386
|
+
context: params.context,
|
|
387
|
+
signal: params.signal,
|
|
388
|
+
}),
|
|
389
|
+
]);
|
|
390
|
+
// ── Requirement 8.2: halt → discard routing, skip LLM ────────────
|
|
391
|
+
if (signalResult.mergedDirective?.halt) {
|
|
392
|
+
return {
|
|
393
|
+
selectedFlow: undefined,
|
|
394
|
+
selectedStep: undefined,
|
|
395
|
+
session: signalResult.updatedSession,
|
|
396
|
+
isFlowComplete: false,
|
|
397
|
+
signalFirings: signalResult.firings,
|
|
398
|
+
signalHalted: true,
|
|
399
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
// ── Requirement 8.3: position directive → discard routing, apply signal position ──
|
|
403
|
+
if ((0, ResponsePipeline_1.hasDirectivePositionField)(signalResult.mergedDirective)) {
|
|
404
|
+
return this.applySignalPositionDirective(signalResult, params);
|
|
405
|
+
}
|
|
406
|
+
// ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
|
|
407
|
+
// ── Requirement 8.5: no directive → use routing as-is ─────────────
|
|
408
|
+
let updatedSession = signalResult.updatedSession;
|
|
409
|
+
// Apply data/context updates from signal to the routed session
|
|
410
|
+
if (signalResult.mergedDirective?.dataUpdate) {
|
|
411
|
+
updatedSession = (0, utils_1.mergeCollected)(updatedSession, signalResult.mergedDirective.dataUpdate);
|
|
412
|
+
}
|
|
413
|
+
// Use routing result for flow/step, but carry signal session state
|
|
414
|
+
// Merge routing session changes on top of signal session
|
|
415
|
+
const routingSession = routingResult.session;
|
|
416
|
+
updatedSession = {
|
|
417
|
+
...updatedSession,
|
|
418
|
+
currentFlow: routingSession.currentFlow,
|
|
419
|
+
currentStep: routingSession.currentStep,
|
|
420
|
+
flowHistory: routingSession.flowHistory,
|
|
421
|
+
pendingDirective: routingSession.pendingDirective,
|
|
422
|
+
};
|
|
423
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
424
|
+
// PRE-EXTRACTION: same logic as below — extract data from user message
|
|
425
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
426
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
427
|
+
utils_1.logger.debug(`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
|
|
428
|
+
const extractedData = await this.preExtractFlowData({
|
|
429
|
+
route: routingResult.selectedFlow,
|
|
430
|
+
history: params.history,
|
|
431
|
+
context: params.context,
|
|
432
|
+
session: updatedSession,
|
|
433
|
+
signal: params.signal,
|
|
434
|
+
});
|
|
435
|
+
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
436
|
+
utils_1.logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
|
|
437
|
+
updatedSession = (0, utils_1.mergeCollected)(updatedSession, extractedData);
|
|
438
|
+
await this.agent.updateCollectedData(extractedData);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// Determine next step
|
|
443
|
+
const stepResult = await this.responsePipeline.determineNextStep({
|
|
444
|
+
selectedFlow: routingResult.selectedFlow,
|
|
445
|
+
selectedStep: routingResult.selectedStep,
|
|
446
|
+
session: updatedSession,
|
|
447
|
+
isFlowComplete,
|
|
448
|
+
});
|
|
449
|
+
return {
|
|
450
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
451
|
+
selectedStep: stepResult.nextStep,
|
|
452
|
+
responseDirectives: routingResult.responseDirectives,
|
|
453
|
+
session: stepResult.session,
|
|
454
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
455
|
+
signalFirings: signalResult.firings,
|
|
456
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
// ── No signal processor: existing behavior (zero overhead) ────────────
|
|
337
460
|
const routingResult = await this.responsePipeline.handleRoutingAndStepSelection({
|
|
338
461
|
session: params.session,
|
|
339
|
-
history: params.history,
|
|
462
|
+
history: params.history,
|
|
340
463
|
context: params.context,
|
|
341
464
|
signal: params.signal,
|
|
342
465
|
});
|
|
343
466
|
let updatedSession = routingResult.session;
|
|
344
|
-
const
|
|
345
|
-
// PRE-EXTRACTION: If entering a
|
|
467
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
468
|
+
// PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
|
|
346
469
|
// This allows us to skip steps whose data is already provided
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
route: routingResult.selectedRoute,
|
|
470
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
471
|
+
// Always pre-extract when flow collects data (not just on new flow entry)
|
|
472
|
+
// This ensures step selection has the most up-to-date data
|
|
473
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
474
|
+
utils_1.logger.debug(`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
|
|
475
|
+
const extractedData = await this.preExtractFlowData({
|
|
476
|
+
route: routingResult.selectedFlow,
|
|
355
477
|
history: params.history,
|
|
356
478
|
context: params.context,
|
|
357
479
|
session: updatedSession,
|
|
@@ -359,51 +481,27 @@ class ResponseModal {
|
|
|
359
481
|
});
|
|
360
482
|
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
361
483
|
utils_1.logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
|
|
362
|
-
//
|
|
484
|
+
// Merge pre-extracted data into session before step selection
|
|
363
485
|
updatedSession = (0, utils_1.mergeCollected)(updatedSession, extractedData);
|
|
364
486
|
// Also update agent's collected data
|
|
365
487
|
await this.agent.updateCollectedData(extractedData);
|
|
366
488
|
}
|
|
367
489
|
}
|
|
368
490
|
}
|
|
369
|
-
// BATCH DETERMINATION: Use BatchExecutor to determine which steps can execute together
|
|
370
|
-
// Requirement 3.4: Pre-extraction results affect batch determination
|
|
371
|
-
let batchSteps;
|
|
372
|
-
let batchStoppedReason;
|
|
373
|
-
let batchStoppedAtStep;
|
|
374
|
-
if (routingResult.selectedRoute && !isRouteComplete) {
|
|
375
|
-
// Determine current step position for batch determination
|
|
376
|
-
const currentStep = routingResult.selectedStep ||
|
|
377
|
-
(updatedSession.currentStep ? routingResult.selectedRoute.getStep(updatedSession.currentStep.id) : undefined);
|
|
378
|
-
utils_1.logger.debug(`[ResponseModal] Determining batch starting from step: ${currentStep?.id || 'initial'}`);
|
|
379
|
-
const batchResult = await this.batchExecutor.determineBatch({
|
|
380
|
-
route: routingResult.selectedRoute,
|
|
381
|
-
currentStep,
|
|
382
|
-
sessionData: updatedSession.data || {},
|
|
383
|
-
context: params.context,
|
|
384
|
-
maxSteps: this.agent.getAgentOptions().maxStepsPerBatch,
|
|
385
|
-
});
|
|
386
|
-
batchSteps = batchResult.steps;
|
|
387
|
-
batchStoppedReason = batchResult.stoppedReason;
|
|
388
|
-
batchStoppedAtStep = batchResult.stoppedAtStep;
|
|
389
|
-
utils_1.logger.debug(`[ResponseModal] Batch determined: ${batchSteps.length} steps, stopped reason: ${batchStoppedReason}`);
|
|
390
|
-
}
|
|
391
491
|
// Determine next step using pipeline method for consistency
|
|
392
492
|
const stepResult = await this.responsePipeline.determineNextStep({
|
|
393
|
-
|
|
493
|
+
selectedFlow: routingResult.selectedFlow,
|
|
394
494
|
selectedStep: routingResult.selectedStep,
|
|
395
495
|
session: updatedSession, // Use updated session with pre-extracted data
|
|
396
|
-
|
|
496
|
+
isFlowComplete, // Use updated completion status
|
|
397
497
|
});
|
|
398
498
|
return {
|
|
399
|
-
|
|
499
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
400
500
|
selectedStep: stepResult.nextStep, // Use the determined next step
|
|
401
501
|
responseDirectives: routingResult.responseDirectives,
|
|
402
502
|
session: stepResult.session,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
batchStoppedReason,
|
|
406
|
-
batchStoppedAtStep,
|
|
503
|
+
// If a branch changed the flow, the original isFlowComplete no longer applies
|
|
504
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
407
505
|
};
|
|
408
506
|
}
|
|
409
507
|
catch (error) {
|
|
@@ -411,31 +509,219 @@ class ResponseModal {
|
|
|
411
509
|
}
|
|
412
510
|
}
|
|
413
511
|
/**
|
|
414
|
-
*
|
|
512
|
+
* Apply a signal's position directive (goTo, goToStep, complete, abort, reset).
|
|
513
|
+
* Discards routing result and uses the signal's position decision.
|
|
415
514
|
* @private
|
|
515
|
+
* @requirements 8.3
|
|
416
516
|
*/
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
517
|
+
applySignalPositionDirective(signalResult, _params) {
|
|
518
|
+
const directive = signalResult.mergedDirective;
|
|
519
|
+
let session = signalResult.updatedSession;
|
|
520
|
+
const flows = this.agent.getFlows();
|
|
521
|
+
let selectedFlow;
|
|
522
|
+
let selectedStep;
|
|
523
|
+
let isFlowComplete = false;
|
|
524
|
+
// Apply data updates if present alongside position
|
|
525
|
+
if (directive.dataUpdate) {
|
|
526
|
+
session = (0, utils_1.mergeCollected)(session, directive.dataUpdate);
|
|
527
|
+
}
|
|
528
|
+
if (directive.goTo) {
|
|
529
|
+
const flowTarget = typeof directive.goTo === 'string'
|
|
530
|
+
? directive.goTo
|
|
531
|
+
: directive.goTo.flow ?? directive.goTo.step;
|
|
532
|
+
if (flowTarget) {
|
|
533
|
+
const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
|
|
534
|
+
if (targetFlow) {
|
|
535
|
+
session = (0, utils_1.enterFlow)(session, targetFlow.id, targetFlow.title);
|
|
536
|
+
selectedFlow = targetFlow;
|
|
537
|
+
if (typeof directive.goTo === 'object' && directive.goTo.step) {
|
|
538
|
+
const targetStep = targetFlow.getStep(directive.goTo.step);
|
|
539
|
+
if (targetStep) {
|
|
540
|
+
session = (0, utils_1.enterStep)(session, targetStep.id, targetStep.description);
|
|
541
|
+
selectedStep = targetStep;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
utils_1.logger.warn(`[Signals] Pre-phase goTo target not found: "${flowTarget}". Falling back to no flow.`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
else if (directive.goToStep) {
|
|
551
|
+
const stepTarget = typeof directive.goToStep === 'string'
|
|
552
|
+
? directive.goToStep
|
|
553
|
+
: directive.goToStep.step;
|
|
554
|
+
const flowTarget = typeof directive.goToStep === 'object'
|
|
555
|
+
? directive.goToStep.flow
|
|
556
|
+
: undefined;
|
|
557
|
+
if (flowTarget) {
|
|
558
|
+
const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
|
|
559
|
+
if (targetFlow) {
|
|
560
|
+
session = (0, utils_1.enterFlow)(session, targetFlow.id, targetFlow.title);
|
|
561
|
+
selectedFlow = targetFlow;
|
|
562
|
+
const targetStep = targetFlow.getStep(stepTarget);
|
|
563
|
+
if (targetStep) {
|
|
564
|
+
session = (0, utils_1.enterStep)(session, targetStep.id, targetStep.description);
|
|
565
|
+
selectedStep = targetStep;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else if (session.currentFlow) {
|
|
570
|
+
const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
|
|
571
|
+
if (currentFlow) {
|
|
572
|
+
selectedFlow = currentFlow;
|
|
573
|
+
const targetStep = currentFlow.getStep(stepTarget);
|
|
574
|
+
if (targetStep) {
|
|
575
|
+
session = (0, utils_1.enterStep)(session, targetStep.id, targetStep.description);
|
|
576
|
+
selectedStep = targetStep;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else if (directive.complete) {
|
|
582
|
+
isFlowComplete = true;
|
|
583
|
+
}
|
|
584
|
+
else if (directive.abort) {
|
|
585
|
+
// Abort — no flow, session cleared or marked
|
|
586
|
+
isFlowComplete = true;
|
|
587
|
+
}
|
|
588
|
+
else if (directive.reset) {
|
|
589
|
+
if (session.currentFlow) {
|
|
590
|
+
const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
|
|
591
|
+
if (currentFlow) {
|
|
592
|
+
selectedFlow = currentFlow;
|
|
593
|
+
const resetStep = typeof directive.reset === 'object' && directive.reset.step
|
|
594
|
+
? directive.reset.step
|
|
595
|
+
: undefined;
|
|
596
|
+
if (resetStep) {
|
|
597
|
+
const targetStep = currentFlow.getStep(resetStep);
|
|
598
|
+
if (targetStep) {
|
|
599
|
+
session = (0, utils_1.enterStep)(session, targetStep.id, targetStep.description);
|
|
600
|
+
selectedStep = targetStep;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
const initialStep = currentFlow.initialStep;
|
|
605
|
+
session = (0, utils_1.enterStep)(session, initialStep.id, initialStep.description);
|
|
606
|
+
selectedStep = initialStep;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
selectedFlow,
|
|
613
|
+
selectedStep,
|
|
614
|
+
session,
|
|
615
|
+
isFlowComplete,
|
|
616
|
+
signalFirings: signalResult.firings,
|
|
617
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Routing skip optimization (Requirements 20.1, 20.2, 20.3):
|
|
622
|
+
* When the current step declares `collect` fields AND pre-extraction populates
|
|
623
|
+
* at least one of those fields from the user's message, skip routing for this turn.
|
|
624
|
+
*
|
|
625
|
+
* Returns the routing result if the skip applies, or undefined to fall through
|
|
626
|
+
* to normal routing.
|
|
627
|
+
* @private
|
|
628
|
+
*/
|
|
629
|
+
async attemptRoutingSkipForCollect(params) {
|
|
630
|
+
const { session } = params;
|
|
631
|
+
// Only applies when we already have a current flow and step
|
|
632
|
+
if (!session.currentFlow || !session.currentStep) {
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
635
|
+
// Also skip this optimization if there's a pending directive (it takes priority)
|
|
636
|
+
if (session.pendingDirective) {
|
|
637
|
+
return undefined;
|
|
638
|
+
}
|
|
639
|
+
// Look up the actual Flow and Step objects to access `collect`
|
|
640
|
+
const currentFlow = this.agent.getFlows().find((f) => f.id === session.currentFlow?.id);
|
|
641
|
+
if (!currentFlow) {
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
645
|
+
if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
648
|
+
// We have a step with collect fields. Run pre-extraction to see if the
|
|
649
|
+
// user's message populates any of them.
|
|
650
|
+
const collectFields = currentStep.collect;
|
|
651
|
+
// Snapshot current data for comparison
|
|
652
|
+
const dataBefore = { ...session.data };
|
|
653
|
+
// Run pre-extraction against the current flow
|
|
654
|
+
const extractedData = await this.preExtractFlowData({
|
|
655
|
+
route: currentFlow,
|
|
656
|
+
history: params.history,
|
|
657
|
+
context: params.context,
|
|
658
|
+
session,
|
|
659
|
+
signal: params.signal,
|
|
660
|
+
});
|
|
661
|
+
if (!extractedData || Object.keys(extractedData).length === 0) {
|
|
662
|
+
return undefined;
|
|
663
|
+
}
|
|
664
|
+
// Determine which collect fields were newly populated by pre-extraction
|
|
665
|
+
const populatedCollectFields = [];
|
|
666
|
+
for (const field of collectFields) {
|
|
667
|
+
const key = field;
|
|
668
|
+
const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
|
|
669
|
+
const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
|
|
670
|
+
if (hasNewValue && !hadValue) {
|
|
671
|
+
populatedCollectFields.push(key);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (populatedCollectFields.length === 0) {
|
|
675
|
+
// Pre-extraction didn't populate any declared collect field — no skip
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
678
|
+
// ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
|
|
679
|
+
utils_1.logger.debug(`[ResponseModal] Routing skip: pre-extraction populated collect fields [${populatedCollectFields.join(', ')}] for step "${currentStep.id}" — skipping FlowRouter`);
|
|
680
|
+
// Merge extracted data into session
|
|
681
|
+
const updatedSession = (0, utils_1.mergeCollected)(session, extractedData);
|
|
682
|
+
await this.agent.updateCollectedData(extractedData);
|
|
683
|
+
// Determine next step using pipeline method for consistency
|
|
684
|
+
// Pass the current flow/step as the routing result (retained)
|
|
685
|
+
const stepResult = await this.responsePipeline.determineNextStep({
|
|
686
|
+
selectedFlow: currentFlow,
|
|
687
|
+
selectedStep: currentStep,
|
|
688
|
+
session: updatedSession,
|
|
689
|
+
isFlowComplete: false,
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
selectedFlow: stepResult.flowChanged || currentFlow,
|
|
693
|
+
selectedStep: stepResult.nextStep,
|
|
694
|
+
responseDirectives: undefined,
|
|
695
|
+
session: stepResult.session,
|
|
696
|
+
isFlowComplete: stepResult.flowChanged ? false : false,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Check if a flow should pre-extract data before determining the initial step
|
|
701
|
+
* @private
|
|
702
|
+
*/
|
|
703
|
+
shouldPreExtractData(flow) {
|
|
704
|
+
// Pre-extract if flow has declared required or optional fields
|
|
705
|
+
if (flow.requiredFields && flow.requiredFields.length > 0) {
|
|
420
706
|
return true;
|
|
421
707
|
}
|
|
422
|
-
if (
|
|
708
|
+
if (flow.optionalFields && flow.optionalFields.length > 0) {
|
|
423
709
|
return true;
|
|
424
710
|
}
|
|
425
|
-
// Pre-extract if any step in the
|
|
426
|
-
const steps =
|
|
711
|
+
// Pre-extract if any step in the flow collects data
|
|
712
|
+
const steps = flow.getAllSteps();
|
|
427
713
|
const hasDataCollectionSteps = steps.some(step => step.collect && step.collect.length > 0);
|
|
428
714
|
return hasDataCollectionSteps;
|
|
429
715
|
}
|
|
430
716
|
/**
|
|
431
|
-
* Pre-extract data from user message when entering a
|
|
717
|
+
* Pre-extract data from user message when entering a flow
|
|
432
718
|
* This allows skipping steps whose data is already provided
|
|
433
719
|
* @private
|
|
434
720
|
*/
|
|
435
|
-
async
|
|
436
|
-
const { route, history, signal } = params;
|
|
437
|
-
// Build a schema for data extraction based on
|
|
438
|
-
const extractionSchema = this.agent.
|
|
721
|
+
async preExtractFlowData(params) {
|
|
722
|
+
const { route: flow, history, signal } = params;
|
|
723
|
+
// Build a schema for data extraction based on flow's fields
|
|
724
|
+
const extractionSchema = this.agent.schema;
|
|
439
725
|
if (!extractionSchema) {
|
|
440
726
|
utils_1.logger.warn(`[ResponseModal] No schema available for pre-extraction`);
|
|
441
727
|
return {};
|
|
@@ -452,11 +738,11 @@ class ResponseModal {
|
|
|
452
738
|
`Extract data for these fields if present:`,
|
|
453
739
|
];
|
|
454
740
|
// Add field descriptions
|
|
455
|
-
if (
|
|
456
|
-
extractionPrompt.push(`Required fields: ${
|
|
741
|
+
if (flow.requiredFields) {
|
|
742
|
+
extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
|
|
457
743
|
}
|
|
458
|
-
if (
|
|
459
|
-
extractionPrompt.push(`Optional fields: ${
|
|
744
|
+
if (flow.optionalFields) {
|
|
745
|
+
extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
|
|
460
746
|
}
|
|
461
747
|
extractionPrompt.push(``, `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`);
|
|
462
748
|
// Convert Event[] to HistoryItem[] for provider call
|
|
@@ -489,98 +775,176 @@ class ResponseModal {
|
|
|
489
775
|
* @private
|
|
490
776
|
*/
|
|
491
777
|
async generateUnifiedResponse(responseContext) {
|
|
492
|
-
const { effectiveContext, session: initialSession, history,
|
|
778
|
+
const { effectiveContext, session: initialSession, history, selectedFlow, selectedStep, responseDirectives, isFlowComplete, signal, signalFirings: preSignalFirings, signalPreDirective, signalHalted, signalHaltReply, } = responseContext;
|
|
493
779
|
let session = initialSession;
|
|
494
|
-
//
|
|
780
|
+
// Accumulator for signal firings across both phases (fire order)
|
|
781
|
+
const signalFirings = [...(preSignalFirings || [])];
|
|
782
|
+
// Get last user message (needed for both flow and completion handling)
|
|
495
783
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
496
784
|
const historyEvents = (0, utils_1.historyToEvents)(history);
|
|
785
|
+
// ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
|
|
786
|
+
// Pre-signal phase emitted halt → skip LLM call entirely.
|
|
787
|
+
if (signalHalted) {
|
|
788
|
+
const haltMessage = signalHaltReply || '';
|
|
789
|
+
// Run post-signal phase even on halt (post-phase sees complete turn context)
|
|
790
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
|
|
791
|
+
session = postResult.updatedSession;
|
|
792
|
+
signalFirings.push(...postResult.firings);
|
|
793
|
+
// Apply post-phase position directive as pendingDirective (Requirement 9.3)
|
|
794
|
+
if (postResult.mergedDirective && (0, ResponsePipeline_1.hasDirectivePositionField)(postResult.mergedDirective)) {
|
|
795
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
796
|
+
}
|
|
797
|
+
await this.finalizeSession(session, effectiveContext);
|
|
798
|
+
return {
|
|
799
|
+
message: haltMessage,
|
|
800
|
+
session,
|
|
801
|
+
toolCalls: undefined,
|
|
802
|
+
isFlowComplete: false,
|
|
803
|
+
executedSteps: [],
|
|
804
|
+
stoppedReason: signalHaltReply ? 'reply' : 'halt',
|
|
805
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
497
808
|
let message;
|
|
498
809
|
let toolCalls = undefined;
|
|
499
810
|
let executedSteps;
|
|
500
811
|
let stoppedReason;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
historyEvents,
|
|
812
|
+
let appliedInstructions;
|
|
813
|
+
if (selectedFlow && !isFlowComplete) {
|
|
814
|
+
// AUTO-CHAIN: Walk consecutive auto-steps before any LLM work.
|
|
815
|
+
// If the current step is auto, the executor advances through it (and any
|
|
816
|
+
// subsequent auto-steps) until an interactive step or terminal condition.
|
|
817
|
+
let resolvedStep = selectedStep;
|
|
818
|
+
const currentStepInstance = session.currentStep
|
|
819
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
820
|
+
: selectedStep;
|
|
821
|
+
if (currentStepInstance?.auto) {
|
|
822
|
+
const autoChainExecutor = new AutoChainExecutor_1.AutoChainExecutor({
|
|
823
|
+
maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
|
|
514
824
|
});
|
|
515
|
-
|
|
516
|
-
toolCalls = batchResult.toolCalls;
|
|
517
|
-
session = batchResult.session;
|
|
518
|
-
executedSteps = batchResult.executedSteps;
|
|
519
|
-
stoppedReason = batchStoppedReason;
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
// SINGLE STEP EXECUTION: Fall back to single-step processing
|
|
523
|
-
// This happens when batch determination returns empty (first step needs input)
|
|
524
|
-
const result = await this.processRouteResponse({
|
|
525
|
-
selectedRoute,
|
|
526
|
-
selectedStep,
|
|
527
|
-
responseDirectives,
|
|
825
|
+
const autoResult = await autoChainExecutor.run({
|
|
528
826
|
session,
|
|
529
|
-
history,
|
|
530
827
|
context: effectiveContext,
|
|
531
|
-
|
|
532
|
-
signal,
|
|
828
|
+
flow: selectedFlow,
|
|
533
829
|
});
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
executedSteps = [
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
830
|
+
session = autoResult.session;
|
|
831
|
+
// Handle halt: emit verbatim reply, persist, return — no LLM call.
|
|
832
|
+
if (autoResult.stoppedReason === 'halt') {
|
|
833
|
+
message = autoResult.mergedDirective?.reply || '';
|
|
834
|
+
stoppedReason = 'halt';
|
|
835
|
+
executedSteps = [];
|
|
836
|
+
await this.finalizeSession(session, effectiveContext);
|
|
837
|
+
return {
|
|
838
|
+
message,
|
|
839
|
+
session,
|
|
840
|
+
toolCalls: undefined,
|
|
841
|
+
isFlowComplete: false,
|
|
842
|
+
executedSteps,
|
|
843
|
+
stoppedReason,
|
|
844
|
+
};
|
|
543
845
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
846
|
+
// Handle flow completion or cross-flow redirect from auto-chain.
|
|
847
|
+
// The auto-chain ended without resolving to an interactive step.
|
|
848
|
+
// Possible reasons: last_step (no successor), completed (explicit
|
|
849
|
+
// complete directive), or goto (cross-flow redirect).
|
|
850
|
+
if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
|
|
851
|
+
utils_1.logger.debug(`[ResponseModal] Auto-chain ended with ${autoResult.stoppedReason}`);
|
|
852
|
+
session = await this.applyFlowCompletion({
|
|
853
|
+
selectedFlow,
|
|
854
|
+
session,
|
|
855
|
+
context: effectiveContext,
|
|
856
|
+
history,
|
|
857
|
+
});
|
|
858
|
+
await this.finalizeSession(session, effectiveContext);
|
|
859
|
+
return {
|
|
860
|
+
message: '',
|
|
861
|
+
session,
|
|
862
|
+
toolCalls: undefined,
|
|
863
|
+
isFlowComplete: true,
|
|
864
|
+
executedSteps: [],
|
|
865
|
+
stoppedReason: autoResult.stoppedReason,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
// Normal case: auto-chain resolved to an interactive step.
|
|
869
|
+
resolvedStep = autoResult.resolvedStep;
|
|
563
870
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
871
|
+
// SINGLE STEP EXECUTION: Process the resolved interactive step.
|
|
872
|
+
// The auto-chain (if it ran) already walked auto-steps. Only the
|
|
873
|
+
// interactive step remains for the LLM call.
|
|
874
|
+
const result = await this.processFlowResponse({
|
|
875
|
+
selectedFlow,
|
|
876
|
+
selectedStep: resolvedStep,
|
|
877
|
+
responseDirectives,
|
|
878
|
+
session,
|
|
879
|
+
history,
|
|
880
|
+
context: effectiveContext,
|
|
881
|
+
historyEvents,
|
|
882
|
+
signal,
|
|
883
|
+
// Propagate signal pre-directive's appendPrompt for this turn's LLM call (Requirement 8.4)
|
|
884
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
885
|
+
// Merge signal pre-directive (halt/reply/injectTools) into the pre-LLM bus
|
|
886
|
+
mergedPreDirective: signalPreDirective,
|
|
887
|
+
});
|
|
888
|
+
message = result.message;
|
|
889
|
+
toolCalls = result.toolCalls;
|
|
890
|
+
session = result.session;
|
|
891
|
+
appliedInstructions = result.appliedInstructions;
|
|
892
|
+
// Track executed step for single-step execution
|
|
893
|
+
if (resolvedStep) {
|
|
894
|
+
executedSteps = [{
|
|
895
|
+
id: resolvedStep.id,
|
|
896
|
+
flowId: selectedFlow.id,
|
|
897
|
+
}];
|
|
570
898
|
}
|
|
899
|
+
// Use stoppedReason from processFlowResponse if set (halt/reply),
|
|
900
|
+
// otherwise default to 'needs_input' for normal LLM responses.
|
|
901
|
+
stoppedReason = result.stoppedReason || 'needs_input';
|
|
902
|
+
}
|
|
903
|
+
else if (isFlowComplete && selectedFlow) {
|
|
904
|
+
// Flow completion path: pure state transition, no LLM call.
|
|
905
|
+
// The framework emits no message of its own.
|
|
906
|
+
// stoppedReason is 'last_step' because this completion was detected by
|
|
907
|
+
// implicit terminus (no successor or all successors skipped), not by an
|
|
908
|
+
// explicit `complete` directive.
|
|
909
|
+
utils_1.logger.debug(`[ResponseModal] Releasing session to idle for completed flow: ${selectedFlow.title}`);
|
|
910
|
+
session = await this.applyFlowCompletion({
|
|
911
|
+
selectedFlow,
|
|
912
|
+
session,
|
|
913
|
+
context: effectiveContext,
|
|
914
|
+
history,
|
|
915
|
+
});
|
|
916
|
+
message = '';
|
|
917
|
+
stoppedReason = 'last_step';
|
|
918
|
+
executedSteps = [];
|
|
571
919
|
}
|
|
572
920
|
else {
|
|
573
|
-
// Fallback: No
|
|
574
|
-
|
|
921
|
+
// Fallback: No flows defined, generate a simple response
|
|
922
|
+
const fallbackResult = await this.generateFallbackResponse({
|
|
575
923
|
history,
|
|
576
924
|
context: effectiveContext,
|
|
577
925
|
session,
|
|
578
926
|
});
|
|
927
|
+
message = fallbackResult.message;
|
|
928
|
+
appliedInstructions = fallbackResult.appliedInstructions;
|
|
579
929
|
// For fallback responses, set empty executedSteps and no stoppedReason
|
|
580
|
-
// since there's no
|
|
930
|
+
// since there's no flow/step execution happening
|
|
581
931
|
executedSteps = [];
|
|
582
932
|
stoppedReason = undefined;
|
|
583
933
|
}
|
|
934
|
+
// POST-SIGNAL PHASE (Requirement 9.1, 9.2, 9.3, 9.4)
|
|
935
|
+
// Runs after finalize/onComplete and before session persistence.
|
|
936
|
+
// Post-phase signals see the complete turn result: assistant message in
|
|
937
|
+
// history, collected data, tool results.
|
|
938
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
|
|
939
|
+
session = postResult.updatedSession;
|
|
940
|
+
// Append post-phase firings to the accumulator (preserves fire order)
|
|
941
|
+
signalFirings.push(...postResult.firings);
|
|
942
|
+
// Requirement 9.3: Post-phase position directive sets session.pendingDirective
|
|
943
|
+
// (no mid-turn re-entry per D6 decision). Pre-LLM-only fields are already
|
|
944
|
+
// dropped inside runPostSignalPhase per Phase 4.5.
|
|
945
|
+
if (postResult.mergedDirective && (0, ResponsePipeline_1.hasDirectivePositionField)(postResult.mergedDirective)) {
|
|
946
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
947
|
+
}
|
|
584
948
|
// Ensure response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
585
949
|
// - executedSteps: array of steps executed (empty array if none)
|
|
586
950
|
// - stoppedReason: why execution stopped (undefined for fallback)
|
|
@@ -589,400 +953,30 @@ class ResponseModal {
|
|
|
589
953
|
message,
|
|
590
954
|
session,
|
|
591
955
|
toolCalls,
|
|
592
|
-
|
|
593
|
-
executedSteps: executedSteps || [],
|
|
594
|
-
stoppedReason,
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
* Execute a batch of steps with a single LLM call
|
|
599
|
-
*
|
|
600
|
-
* This method:
|
|
601
|
-
* 1. Executes all prepare hooks for steps in the batch (in order)
|
|
602
|
-
* 2. Builds a combined prompt using BatchPromptBuilder
|
|
603
|
-
* 3. Makes a single LLM call
|
|
604
|
-
* 4. Collects data from the response for all steps
|
|
605
|
-
* 5. Executes all finalize hooks for steps in the batch (in order)
|
|
606
|
-
*
|
|
607
|
-
* @private
|
|
608
|
-
* **Validates: Requirements 1.1, 4.4, 5.1, 5.2**
|
|
609
|
-
*/
|
|
610
|
-
async executeBatchResponse(params) {
|
|
611
|
-
const { selectedRoute, batchSteps, history, context, historyEvents, signal } = params;
|
|
612
|
-
let session = params.session;
|
|
613
|
-
utils_1.logger.debug(`[ResponseModal] Starting batch execution for ${batchSteps.length} steps`);
|
|
614
|
-
// Create hook executor function
|
|
615
|
-
const executeHook = async (hook, hookContext, data, step) => {
|
|
616
|
-
// Find the route for this step
|
|
617
|
-
const route = selectedRoute;
|
|
618
|
-
// Convert StepOptions to Step if needed for executePrepareFinalize
|
|
619
|
-
const stepInstance = step?.id ? route.getStep(step.id) : undefined;
|
|
620
|
-
await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
|
|
621
|
-
};
|
|
622
|
-
// PHASE 1: Execute all prepare hooks (Requirement 5.1)
|
|
623
|
-
utils_1.logger.debug(`[ResponseModal] Executing prepare hooks for batch`);
|
|
624
|
-
const prepareResult = await this.batchExecutor.executePrepareHooks({
|
|
625
|
-
steps: batchSteps,
|
|
626
|
-
context,
|
|
627
|
-
data: session.data,
|
|
628
|
-
executeHook,
|
|
629
|
-
});
|
|
630
|
-
if (!prepareResult.success) {
|
|
631
|
-
// Prepare hook failed - return error response
|
|
632
|
-
utils_1.logger.error(`[ResponseModal] Prepare hook failed:`, prepareResult.error);
|
|
633
|
-
throw new ResponseGenerationError(`Prepare hook failed: ${prepareResult.error?.message}`, {
|
|
634
|
-
phase: 'prepare_hooks',
|
|
635
|
-
context: {
|
|
636
|
-
stepId: prepareResult.error?.stepId,
|
|
637
|
-
executedSteps: prepareResult.executedSteps,
|
|
638
|
-
}
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
// PHASE 2: Build combined prompt using BatchPromptBuilder (Requirement 4.4)
|
|
642
|
-
utils_1.logger.debug(`[ResponseModal] Building batch prompt`);
|
|
643
|
-
const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
|
|
644
|
-
steps: batchSteps,
|
|
645
|
-
route: selectedRoute,
|
|
646
|
-
history: historyEvents,
|
|
647
|
-
context,
|
|
648
|
-
session,
|
|
649
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
650
|
-
});
|
|
651
|
-
utils_1.logger.debug(`[ResponseModal] Batch prompt built with ${batchPromptResult.stepCount} steps, collecting: ${batchPromptResult.collectFields.join(', ')}`);
|
|
652
|
-
// Build response schema for batch (includes all collect fields)
|
|
653
|
-
const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
|
|
654
|
-
// Collect available tools for AI (from all steps in batch)
|
|
655
|
-
const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
|
|
656
|
-
// PHASE 3: Make single LLM call (Requirement 4.4)
|
|
657
|
-
utils_1.logger.debug(`[ResponseModal] Making LLM call for batch`);
|
|
658
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
659
|
-
const result = await agentOptions.provider.generateMessage({
|
|
660
|
-
prompt: batchPromptResult.prompt,
|
|
661
|
-
history, // Use HistoryItem[] for AI provider
|
|
662
|
-
context,
|
|
663
|
-
tools: availableTools,
|
|
664
|
-
signal,
|
|
665
|
-
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_response" } : undefined,
|
|
666
|
-
});
|
|
667
|
-
let message = result.structured?.message || result.message;
|
|
668
|
-
let toolCalls = result.structured?.toolCalls;
|
|
669
|
-
utils_1.logger.debug(`[ResponseModal] LLM response received for batch`);
|
|
670
|
-
// Execute tools if any
|
|
671
|
-
if (toolCalls && toolCalls.length > 0) {
|
|
672
|
-
const toolResult = await this.executeUnifiedToolLoop({
|
|
673
|
-
toolCalls,
|
|
674
|
-
context,
|
|
675
|
-
session,
|
|
676
|
-
history,
|
|
677
|
-
selectedRoute,
|
|
678
|
-
responsePrompt: batchPromptResult.prompt,
|
|
679
|
-
availableTools,
|
|
680
|
-
responseSchema,
|
|
681
|
-
signal,
|
|
682
|
-
});
|
|
683
|
-
session = toolResult.session;
|
|
684
|
-
toolCalls = toolResult.finalToolCalls;
|
|
685
|
-
if (toolResult.finalMessage) {
|
|
686
|
-
message = toolResult.finalMessage;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
// PHASE 4: Collect data from response for all steps (Requirement 6.1, 6.2, 6.3)
|
|
690
|
-
utils_1.logger.debug(`[ResponseModal] Collecting batch data`);
|
|
691
|
-
const collectResult = this.batchExecutor.collectBatchData({
|
|
692
|
-
steps: batchSteps,
|
|
693
|
-
llmResponse: result.structured || {},
|
|
694
|
-
session,
|
|
695
|
-
schema: this.agent.getSchema(),
|
|
696
|
-
});
|
|
697
|
-
session = collectResult.session;
|
|
698
|
-
if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
|
|
699
|
-
// Update agent's collected data
|
|
700
|
-
await this.agent.updateCollectedData(collectResult.collectedData);
|
|
701
|
-
utils_1.logger.debug(`[ResponseModal] Batch collected data:`, collectResult.collectedData);
|
|
702
|
-
}
|
|
703
|
-
if (collectResult.validationErrors && collectResult.validationErrors.length > 0) {
|
|
704
|
-
utils_1.logger.warn(`[ResponseModal] Batch data validation errors:`, collectResult.validationErrors);
|
|
705
|
-
}
|
|
706
|
-
// Update session to final step position
|
|
707
|
-
const lastStep = batchSteps[batchSteps.length - 1];
|
|
708
|
-
if (lastStep?.id) {
|
|
709
|
-
session = (0, utils_1.enterStep)(session, lastStep.id, lastStep.description);
|
|
710
|
-
utils_1.logger.debug(`[ResponseModal] Updated session to final batch step: ${lastStep.id}`);
|
|
711
|
-
}
|
|
712
|
-
// PHASE 5: Execute all finalize hooks (Requirement 5.2)
|
|
713
|
-
utils_1.logger.debug(`[ResponseModal] Executing finalize hooks for batch`);
|
|
714
|
-
const finalizeResult = await this.batchExecutor.executeFinalizeHooks({
|
|
715
|
-
steps: batchSteps,
|
|
716
|
-
context,
|
|
717
|
-
data: session.data,
|
|
718
|
-
executeHook,
|
|
719
|
-
});
|
|
720
|
-
if (finalizeResult.errors && finalizeResult.errors.length > 0) {
|
|
721
|
-
// Log finalize errors but don't fail (Requirement 5.5)
|
|
722
|
-
utils_1.logger.warn(`[ResponseModal] Some finalize hooks failed:`, finalizeResult.errors);
|
|
723
|
-
}
|
|
724
|
-
// Build executed steps list
|
|
725
|
-
const executedSteps = batchSteps
|
|
726
|
-
.filter(step => step.id)
|
|
727
|
-
.map(step => ({
|
|
728
|
-
id: step.id,
|
|
729
|
-
routeId: selectedRoute.id,
|
|
730
|
-
}));
|
|
731
|
-
utils_1.logger.debug(`[ResponseModal] Batch execution complete. Executed ${executedSteps.length} steps`);
|
|
732
|
-
return {
|
|
733
|
-
message,
|
|
734
|
-
toolCalls,
|
|
735
|
-
session,
|
|
736
|
-
executedSteps,
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
/**
|
|
740
|
-
* Build response schema for batch execution
|
|
741
|
-
* @private
|
|
742
|
-
*/
|
|
743
|
-
buildBatchResponseSchema(collectFields) {
|
|
744
|
-
const properties = {
|
|
745
|
-
message: {
|
|
746
|
-
type: "string",
|
|
747
|
-
description: "Natural, conversational response directed at the user. Must NOT contain field names, raw data, or internal information.",
|
|
748
|
-
},
|
|
749
|
-
};
|
|
750
|
-
const agentSchema = this.agent.getSchema();
|
|
751
|
-
// Add collect fields to schema, using agent schema definitions when available
|
|
752
|
-
for (const field of collectFields) {
|
|
753
|
-
if (agentSchema?.properties && agentSchema.properties[field]) {
|
|
754
|
-
properties[field] = agentSchema.properties[field];
|
|
755
|
-
}
|
|
756
|
-
else {
|
|
757
|
-
// Dynamic fallback when no agent schema is defined
|
|
758
|
-
properties[field] = {
|
|
759
|
-
type: "string",
|
|
760
|
-
description: `Collected value for ${field}`,
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
return {
|
|
765
|
-
type: "object",
|
|
766
|
-
properties,
|
|
767
|
-
required: ["message"],
|
|
768
|
-
additionalProperties: true,
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Collect available tools from all steps in the batch
|
|
773
|
-
* @private
|
|
774
|
-
*/
|
|
775
|
-
collectBatchAvailableTools(route, batchSteps) {
|
|
776
|
-
const availableTools = new Map();
|
|
777
|
-
// Add agent-level tools
|
|
778
|
-
this.agent.getTools().forEach((tool) => {
|
|
779
|
-
availableTools.set(tool.id, tool);
|
|
780
|
-
});
|
|
781
|
-
// Add route-level tools
|
|
782
|
-
route.getTools().forEach((tool) => {
|
|
783
|
-
availableTools.set(tool.id, tool);
|
|
784
|
-
});
|
|
785
|
-
// Add step-level tools from all batch steps
|
|
786
|
-
for (const step of batchSteps) {
|
|
787
|
-
if (step.tools) {
|
|
788
|
-
for (const toolRef of step.tools) {
|
|
789
|
-
if (typeof toolRef === "string") {
|
|
790
|
-
// Reference to registered tool - already in availableTools
|
|
791
|
-
}
|
|
792
|
-
else if (typeof toolRef === 'object' && 'id' in toolRef && toolRef.id) {
|
|
793
|
-
// Inline tool definition
|
|
794
|
-
availableTools.set(toolRef.id, toolRef);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
// Convert to the format expected by AI providers
|
|
800
|
-
return Array.from(availableTools.values()).map((tool) => ({
|
|
801
|
-
id: tool.id,
|
|
802
|
-
name: tool.name || tool.id,
|
|
803
|
-
description: tool.description,
|
|
804
|
-
parameters: tool.parameters,
|
|
805
|
-
}));
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* Unified streaming response generation
|
|
809
|
-
* @private
|
|
810
|
-
*/
|
|
811
|
-
async *generateUnifiedStreamingResponse(responseContext) {
|
|
812
|
-
const { effectiveContext, session: initialSession, history, selectedRoute, selectedStep, responseDirectives, isRouteComplete, batchSteps, batchStoppedReason, } = responseContext;
|
|
813
|
-
const session = initialSession;
|
|
814
|
-
// Get last user message (needed for both route and completion handling)
|
|
815
|
-
// Convert HistoryItem[] to Event[] for internal processing
|
|
816
|
-
const historyEvents = (0, utils_1.historyToEvents)(history);
|
|
817
|
-
if (selectedRoute && !isRouteComplete) {
|
|
818
|
-
// Check if we have batch steps to execute
|
|
819
|
-
if (batchSteps && batchSteps.length > 0) {
|
|
820
|
-
// BATCH EXECUTION: Execute multiple steps with streaming
|
|
821
|
-
// Note: For streaming, we still use batch execution but stream the response
|
|
822
|
-
utils_1.logger.debug(`[ResponseModal] Streaming batch execution for ${batchSteps.length} steps`);
|
|
823
|
-
yield* this.streamBatchResponse({
|
|
824
|
-
selectedRoute,
|
|
825
|
-
batchSteps,
|
|
826
|
-
responseDirectives,
|
|
827
|
-
session,
|
|
828
|
-
history,
|
|
829
|
-
context: effectiveContext,
|
|
830
|
-
historyEvents,
|
|
831
|
-
batchStoppedReason,
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
else {
|
|
835
|
-
// SINGLE STEP EXECUTION: Fall back to single-step streaming
|
|
836
|
-
yield* this.processRouteStreamingResponse({
|
|
837
|
-
selectedRoute,
|
|
838
|
-
selectedStep,
|
|
839
|
-
responseDirectives,
|
|
840
|
-
session,
|
|
841
|
-
history,
|
|
842
|
-
context: effectiveContext,
|
|
843
|
-
historyEvents,
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
else if (isRouteComplete && selectedRoute) {
|
|
848
|
-
// Handle route completion streaming
|
|
849
|
-
yield* this.streamRouteCompletion({
|
|
850
|
-
selectedRoute,
|
|
851
|
-
session,
|
|
852
|
-
context: effectiveContext,
|
|
853
|
-
history,
|
|
854
|
-
historyEvents,
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
else {
|
|
858
|
-
// Fallback: No routes defined, stream a simple response
|
|
859
|
-
yield* this.streamFallbackResponse({
|
|
860
|
-
history,
|
|
861
|
-
context: effectiveContext,
|
|
862
|
-
session,
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* Stream a batch response with multiple steps
|
|
868
|
-
*
|
|
869
|
-
* Similar to executeBatchResponse but streams the LLM response.
|
|
870
|
-
*
|
|
871
|
-
* @private
|
|
872
|
-
*/
|
|
873
|
-
async *streamBatchResponse(params) {
|
|
874
|
-
const { selectedRoute, batchSteps, history, context, historyEvents, batchStoppedReason, signal } = params;
|
|
875
|
-
let session = params.session;
|
|
876
|
-
// Create hook executor function
|
|
877
|
-
const executeHook = async (hook, hookContext, data, step) => {
|
|
878
|
-
const route = selectedRoute;
|
|
879
|
-
const stepInstance = step?.id ? route.getStep(step.id) : undefined;
|
|
880
|
-
await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
|
|
881
|
-
};
|
|
882
|
-
// PHASE 1: Execute all prepare hooks
|
|
883
|
-
const prepareResult = await this.batchExecutor.executePrepareHooks({
|
|
884
|
-
steps: batchSteps,
|
|
885
|
-
context,
|
|
886
|
-
data: session.data,
|
|
887
|
-
executeHook,
|
|
888
|
-
});
|
|
889
|
-
if (!prepareResult.success) {
|
|
890
|
-
// Yield error chunk
|
|
891
|
-
yield {
|
|
892
|
-
delta: "",
|
|
893
|
-
accumulated: "",
|
|
894
|
-
done: true,
|
|
895
|
-
session,
|
|
896
|
-
error: new ResponseGenerationError(`Prepare hook failed: ${prepareResult.error?.message}`, { phase: 'prepare_hooks' }),
|
|
897
|
-
};
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
// PHASE 2: Build combined prompt
|
|
901
|
-
const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
|
|
902
|
-
steps: batchSteps,
|
|
903
|
-
route: selectedRoute,
|
|
904
|
-
history: historyEvents,
|
|
905
|
-
context,
|
|
906
|
-
session,
|
|
907
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
908
|
-
});
|
|
909
|
-
const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
|
|
910
|
-
const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
|
|
911
|
-
// PHASE 3: Stream LLM response
|
|
912
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
913
|
-
const stream = agentOptions.provider.generateMessageStream({
|
|
914
|
-
prompt: batchPromptResult.prompt,
|
|
915
|
-
history, // Use HistoryItem[] for AI provider
|
|
916
|
-
context,
|
|
917
|
-
tools: availableTools,
|
|
918
|
-
signal,
|
|
919
|
-
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_stream_response" } : undefined,
|
|
920
|
-
});
|
|
921
|
-
// Build executed steps list
|
|
922
|
-
const executedSteps = batchSteps
|
|
923
|
-
.filter(step => step.id)
|
|
924
|
-
.map(step => ({
|
|
925
|
-
id: step.id,
|
|
926
|
-
routeId: selectedRoute.id,
|
|
927
|
-
}));
|
|
928
|
-
// Stream chunks
|
|
929
|
-
for await (const chunk of stream) {
|
|
930
|
-
// On final chunk, collect data and execute finalize hooks
|
|
931
|
-
if (chunk.done) {
|
|
932
|
-
// Collect data from response
|
|
933
|
-
if (chunk.structured) {
|
|
934
|
-
const collectResult = this.batchExecutor.collectBatchData({
|
|
935
|
-
steps: batchSteps,
|
|
936
|
-
llmResponse: chunk.structured,
|
|
937
|
-
session,
|
|
938
|
-
schema: this.agent.getSchema(),
|
|
939
|
-
});
|
|
940
|
-
session = collectResult.session;
|
|
941
|
-
if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
|
|
942
|
-
await this.agent.updateCollectedData(collectResult.collectedData);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
// Update session to final step position
|
|
946
|
-
const lastStep = batchSteps[batchSteps.length - 1];
|
|
947
|
-
if (lastStep?.id) {
|
|
948
|
-
session = (0, utils_1.enterStep)(session, lastStep.id, lastStep.description);
|
|
949
|
-
}
|
|
950
|
-
// Execute finalize hooks
|
|
951
|
-
await this.batchExecutor.executeFinalizeHooks({
|
|
952
|
-
steps: batchSteps,
|
|
953
|
-
context,
|
|
954
|
-
data: session.data,
|
|
955
|
-
executeHook,
|
|
956
|
-
});
|
|
957
|
-
// Finalize session
|
|
958
|
-
await this.finalizeSession(session, context);
|
|
959
|
-
}
|
|
960
|
-
yield {
|
|
961
|
-
delta: chunk.delta,
|
|
962
|
-
accumulated: chunk.accumulated,
|
|
963
|
-
done: chunk.done,
|
|
964
|
-
session,
|
|
965
|
-
toolCalls: chunk.structured?.toolCalls,
|
|
966
|
-
isRouteComplete: false,
|
|
967
|
-
executedSteps: chunk.done ? executedSteps : undefined,
|
|
968
|
-
stoppedReason: chunk.done ? batchStoppedReason : undefined,
|
|
969
|
-
metadata: chunk.metadata,
|
|
970
|
-
structured: chunk.structured,
|
|
971
|
-
};
|
|
972
|
-
}
|
|
956
|
+
isFlowComplete: isFlowComplete,
|
|
957
|
+
executedSteps: executedSteps || [],
|
|
958
|
+
stoppedReason,
|
|
959
|
+
appliedInstructions,
|
|
960
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
961
|
+
};
|
|
973
962
|
}
|
|
974
963
|
/**
|
|
975
964
|
* Execute prepare function for current step if available
|
|
976
965
|
* @private
|
|
977
966
|
*/
|
|
978
967
|
async executeStepPrepare(session, context) {
|
|
979
|
-
if (session.
|
|
980
|
-
const
|
|
981
|
-
if (
|
|
982
|
-
const currentStep =
|
|
968
|
+
if (session.currentFlow && session.currentStep) {
|
|
969
|
+
const currentFlow = this.agent.getFlows().find((r) => r.id === session.currentFlow?.id);
|
|
970
|
+
if (currentFlow) {
|
|
971
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
972
|
+
// Skip auto-steps — their prepare is handled by AutoChainExecutor
|
|
973
|
+
if (currentStep?.auto) {
|
|
974
|
+
utils_1.logger.debug(`[ResponseModal] Skipping pre-routing prepare for auto-step: ${currentStep.id}`);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
983
977
|
if (currentStep?.prepare) {
|
|
984
978
|
utils_1.logger.debug(`[ResponseModal] Executing prepare for step: ${currentStep.id}`);
|
|
985
|
-
await this.executePrepareFinalize(currentStep.prepare, context, session.data,
|
|
979
|
+
await this.executePrepareFinalize(currentStep.prepare, context, session.data, currentFlow, currentStep);
|
|
986
980
|
}
|
|
987
981
|
}
|
|
988
982
|
}
|
|
@@ -992,23 +986,23 @@ class ResponseModal {
|
|
|
992
986
|
* @private
|
|
993
987
|
*/
|
|
994
988
|
async executeStepFinalize(session, context) {
|
|
995
|
-
if (session.
|
|
996
|
-
const
|
|
997
|
-
if (
|
|
998
|
-
const currentStep =
|
|
989
|
+
if (session.currentFlow && session.currentStep) {
|
|
990
|
+
const currentFlow = this.agent.getFlows().find((r) => r.id === session.currentFlow?.id);
|
|
991
|
+
if (currentFlow) {
|
|
992
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
999
993
|
if (currentStep?.finalize) {
|
|
1000
994
|
utils_1.logger.debug(`[ResponseModal] Executing finalize for step: ${currentStep.id}`);
|
|
1001
|
-
await this.executePrepareFinalize(currentStep.finalize, context, session.data,
|
|
995
|
+
await this.executePrepareFinalize(currentStep.finalize, context, session.data, currentFlow, currentStep);
|
|
1002
996
|
}
|
|
1003
997
|
}
|
|
1004
998
|
}
|
|
1005
999
|
}
|
|
1006
1000
|
/**
|
|
1007
|
-
* Process
|
|
1001
|
+
* Process flow response with unified tool execution and data collection
|
|
1008
1002
|
* @private
|
|
1009
1003
|
*/
|
|
1010
|
-
async
|
|
1011
|
-
const {
|
|
1004
|
+
async processFlowResponse(params) {
|
|
1005
|
+
const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
|
|
1012
1006
|
let session = params.session;
|
|
1013
1007
|
// Determine next step
|
|
1014
1008
|
let nextStep;
|
|
@@ -1016,25 +1010,42 @@ class ResponseModal {
|
|
|
1016
1010
|
nextStep = selectedStep;
|
|
1017
1011
|
}
|
|
1018
1012
|
else {
|
|
1019
|
-
// Determine current step from session if we're already in this
|
|
1020
|
-
const
|
|
1021
|
-
const currentStep =
|
|
1022
|
-
?
|
|
1013
|
+
// Determine current step from session if we're already in this flow
|
|
1014
|
+
const isInSameFlow = session.currentFlow?.id === selectedFlow.id;
|
|
1015
|
+
const currentStep = isInSameFlow && session.currentStep
|
|
1016
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1023
1017
|
: undefined;
|
|
1024
|
-
utils_1.logger.debug(`[ResponseModal] Step determination:
|
|
1025
|
-
//
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1018
|
+
utils_1.logger.debug(`[ResponseModal] Step determination: flow match=${isInSameFlow}, currentFlow=${session.currentFlow?.id}, selectedFlow=${selectedFlow.id}, currentStep=${currentStep?.id || 'none'}`);
|
|
1019
|
+
// STEP 1 (Algorithm 1): branches win over linear chain
|
|
1020
|
+
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
1021
|
+
const branchResult = await this.responsePipeline.evaluateStepBranches(currentStep, selectedFlow, session, context);
|
|
1022
|
+
if (branchResult) {
|
|
1023
|
+
if (branchResult.nextStep) {
|
|
1024
|
+
nextStep = branchResult.nextStep;
|
|
1025
|
+
session = branchResult.session;
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
// Flow transition or completion — no local step to render
|
|
1029
|
+
// Return empty message with updated session; caller handles flow transition
|
|
1030
|
+
return { message: '', session: branchResult.session };
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
1033
|
}
|
|
1034
|
-
|
|
1035
|
-
//
|
|
1036
|
-
|
|
1037
|
-
|
|
1034
|
+
if (!nextStep) {
|
|
1035
|
+
// Get candidate steps based on current position in the flow
|
|
1036
|
+
const flowRouter = this.agent.getFlowRouter();
|
|
1037
|
+
const candidates = await flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
|
|
1038
|
+
(0, template_1.createTemplateContext)({ data: session.data, session, context }));
|
|
1039
|
+
utils_1.logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new flow entry)'}`);
|
|
1040
|
+
if (candidates.length > 0) {
|
|
1041
|
+
nextStep = candidates[0].step;
|
|
1042
|
+
utils_1.logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
// Fallback to initial step even if it should be skipped
|
|
1046
|
+
nextStep = selectedFlow.initialStep;
|
|
1047
|
+
utils_1.logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
|
|
1048
|
+
}
|
|
1038
1049
|
}
|
|
1039
1050
|
}
|
|
1040
1051
|
// Update session with next step
|
|
@@ -1043,14 +1054,14 @@ class ResponseModal {
|
|
|
1043
1054
|
const sessionData = session.data || {};
|
|
1044
1055
|
const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
|
|
1045
1056
|
if (missingRequires.length > 0) {
|
|
1046
|
-
const warning = `[
|
|
1047
|
-
`missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
|
|
1057
|
+
const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
|
|
1058
|
+
`missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
|
|
1048
1059
|
utils_1.logger.warn(warning);
|
|
1049
1060
|
console.warn(warning);
|
|
1050
1061
|
// Stay at the current step - don't enter the next one
|
|
1051
1062
|
const currentStepId = session.currentStep?.id;
|
|
1052
1063
|
if (currentStepId) {
|
|
1053
|
-
const currentStepInstance =
|
|
1064
|
+
const currentStepInstance = selectedFlow.getStep(currentStepId);
|
|
1054
1065
|
if (currentStepInstance) {
|
|
1055
1066
|
nextStep = currentStepInstance;
|
|
1056
1067
|
utils_1.logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
@@ -1066,76 +1077,271 @@ class ResponseModal {
|
|
|
1066
1077
|
session = (0, utils_1.enterStep)(session, nextStep.id, nextStep.description);
|
|
1067
1078
|
utils_1.logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
|
|
1068
1079
|
}
|
|
1069
|
-
// Build response schema for this
|
|
1070
|
-
const responseSchema = this.responseEngine.
|
|
1071
|
-
//
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
}
|
|
1086
|
-
//
|
|
1087
|
-
|
|
1088
|
-
//
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
context,
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1080
|
+
// Build response schema for this flow (with collect fields from step)
|
|
1081
|
+
const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
|
|
1082
|
+
// ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
|
|
1083
|
+
// After pre-LLM emissions are merged, if `halt: true` then skip the LLM
|
|
1084
|
+
// call entirely. The behavior depends on whether `reply` is also set.
|
|
1085
|
+
if (mergedPreDirective?.halt) {
|
|
1086
|
+
if (mergedPreDirective.reply) {
|
|
1087
|
+
// halt + reply: emit the reply as the assistant message
|
|
1088
|
+
utils_1.logger.debug(`[ResponseModal] Halt with reply — skipping LLM call for step ${nextStep.id}`);
|
|
1089
|
+
return { message: mergedPreDirective.reply, session, stoppedReason: 'reply' };
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
// halt without reply: emit empty assistant content
|
|
1093
|
+
utils_1.logger.debug(`[ResponseModal] Halt without reply — skipping LLM call for step ${nextStep.id}`);
|
|
1094
|
+
return { message: '', session, stoppedReason: 'halt' };
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
// ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
|
|
1098
|
+
// A step with `reply` set emits a verbatim template response without LLM.
|
|
1099
|
+
// onEnter and prepare have already fired normally at this point.
|
|
1100
|
+
// If prepare returned a PreDirective with `reply`, that overrides
|
|
1101
|
+
// the step-declared reply (last-emission-wins per Algorithm 4).
|
|
1102
|
+
if (nextStep.reply != null) {
|
|
1103
|
+
// Determine the effective reply: prepare-emitted reply wins over step-declared
|
|
1104
|
+
const effectiveReply = mergedPreDirective?.reply ?? await (0, utils_1.render)(nextStep.reply, (0, template_1.createTemplateContext)({ data: session.data || {}, context, session }));
|
|
1105
|
+
utils_1.logger.debug(`[ResponseModal] Step.reply — skipping LLM call for step ${nextStep.id}`);
|
|
1106
|
+
return { message: effectiveReply, session, stoppedReason: 'reply' };
|
|
1107
|
+
}
|
|
1108
|
+
// Transient appendage: per-turn slot from PreDirective.appendPrompt.
|
|
1109
|
+
// Fresh each turn, never cached, never persisted.
|
|
1110
|
+
// Wrapped in try/finally to ensure cleanup even on abnormal termination.
|
|
1111
|
+
let turnTransientAppendage = transientAppendage;
|
|
1112
|
+
try {
|
|
1113
|
+
// Build response prompt
|
|
1114
|
+
const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
|
|
1115
|
+
flow: selectedFlow,
|
|
1116
|
+
currentStep: nextStep,
|
|
1117
|
+
rules: [],
|
|
1118
|
+
prohibitions: [],
|
|
1119
|
+
directives: responseDirectives,
|
|
1120
|
+
history: historyEvents,
|
|
1121
|
+
agentOptions: this.agent.getAgentOptions(),
|
|
1122
|
+
instructions: this.collectScopedInstructions(selectedFlow, nextStep),
|
|
1123
|
+
combinedTerms: this.agent.getTerms(),
|
|
1124
|
+
context,
|
|
1125
|
+
session,
|
|
1126
|
+
agentSchema: this.agent.schema,
|
|
1127
|
+
transientAppendage: turnTransientAppendage,
|
|
1128
|
+
});
|
|
1129
|
+
// Collect available tools for AI
|
|
1130
|
+
const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
|
|
1131
|
+
// Generate message using AI provider
|
|
1132
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
1133
|
+
const result = await agentOptions.provider.generateMessage({
|
|
1134
|
+
prompt: responsePrompt,
|
|
1135
|
+
history, // Use HistoryItem[] for AI provider
|
|
1136
|
+
context,
|
|
1137
|
+
tools: availableTools,
|
|
1138
|
+
signal,
|
|
1139
|
+
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "response_output" } : undefined,
|
|
1140
|
+
});
|
|
1141
|
+
let message = result.structured?.message || result.message;
|
|
1142
|
+
let toolCalls = result.structured?.toolCalls;
|
|
1143
|
+
// Debug: Log initial AI response
|
|
1144
|
+
utils_1.logger.debug(`[ResponseModal] Initial AI response:`, {
|
|
1145
|
+
hasMessage: !!message,
|
|
1146
|
+
messageLength: message?.length || 0,
|
|
1147
|
+
hasToolCalls: !!toolCalls,
|
|
1148
|
+
toolCallsCount: toolCalls?.length || 0,
|
|
1149
|
+
toolNames: toolCalls?.map(tc => tc.toolName) || [],
|
|
1150
|
+
});
|
|
1151
|
+
// Execute tools with unified loop handling
|
|
1152
|
+
const toolResult = await this.executeUnifiedToolLoop({
|
|
1153
|
+
toolCalls,
|
|
1154
|
+
context,
|
|
1155
|
+
session,
|
|
1156
|
+
history,
|
|
1157
|
+
selectedFlow,
|
|
1158
|
+
responsePrompt,
|
|
1159
|
+
availableTools,
|
|
1160
|
+
responseSchema,
|
|
1161
|
+
signal,
|
|
1162
|
+
});
|
|
1163
|
+
session = toolResult.session;
|
|
1164
|
+
toolCalls = toolResult.finalToolCalls;
|
|
1165
|
+
if (toolResult.finalMessage) {
|
|
1166
|
+
message = toolResult.finalMessage;
|
|
1167
|
+
}
|
|
1168
|
+
// Collect data from response
|
|
1169
|
+
// Use follow-up structured data from tool loop when available, fall back to original result
|
|
1170
|
+
const dataSource = toolResult.structured
|
|
1171
|
+
? { structured: toolResult.structured }
|
|
1172
|
+
: result;
|
|
1173
|
+
session = await this.collectDataFromResponse({ result: dataSource, selectedFlow, nextStep, session });
|
|
1174
|
+
return { message, toolCalls, session, appliedInstructions };
|
|
1175
|
+
}
|
|
1176
|
+
finally {
|
|
1177
|
+
// Drain the transient appendage at end of turn.
|
|
1178
|
+
// This ensures PreDirective.appendPrompt does not leak to subsequent
|
|
1179
|
+
// turns even when the turn terminates abnormally (error, abort, reject).
|
|
1180
|
+
turnTransientAppendage = undefined;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Unified streaming response generation
|
|
1185
|
+
* @private
|
|
1186
|
+
*/
|
|
1187
|
+
async *generateUnifiedStreamingResponse(responseContext) {
|
|
1188
|
+
const { effectiveContext, session: initialSession, history, selectedFlow, selectedStep, responseDirectives, isFlowComplete, signal, signalFirings: preSignalFirings, signalPreDirective, signalHalted, signalHaltReply, } = responseContext;
|
|
1189
|
+
let session = initialSession;
|
|
1190
|
+
// Accumulator for signal firings across both phases (fire order)
|
|
1191
|
+
const signalFirings = [...(preSignalFirings || [])];
|
|
1192
|
+
// Convert HistoryItem[] to Event[] for internal processing
|
|
1193
|
+
const historyEvents = (0, utils_1.historyToEvents)(history);
|
|
1194
|
+
// ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
|
|
1195
|
+
if (signalHalted) {
|
|
1196
|
+
const haltMessage = signalHaltReply || '';
|
|
1197
|
+
// Run post-signal phase even on halt
|
|
1198
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
|
|
1199
|
+
session = postResult.updatedSession;
|
|
1200
|
+
signalFirings.push(...postResult.firings);
|
|
1201
|
+
if (postResult.mergedDirective && (0, ResponsePipeline_1.hasDirectivePositionField)(postResult.mergedDirective)) {
|
|
1202
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
1203
|
+
}
|
|
1204
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1205
|
+
yield {
|
|
1206
|
+
delta: haltMessage,
|
|
1207
|
+
accumulated: haltMessage,
|
|
1208
|
+
done: true,
|
|
1209
|
+
session,
|
|
1210
|
+
stoppedReason: haltMessage ? 'reply' : 'halt',
|
|
1211
|
+
executedSteps: [],
|
|
1212
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
1213
|
+
};
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
// ── Determine the inner stream generator based on flow state ────────
|
|
1217
|
+
let innerStream;
|
|
1218
|
+
if (selectedFlow && !isFlowComplete) {
|
|
1219
|
+
// AUTO-CHAIN: Walk consecutive auto-steps before any LLM work (streaming path).
|
|
1220
|
+
let resolvedStep = selectedStep;
|
|
1221
|
+
const currentStepInstance = session.currentStep
|
|
1222
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1223
|
+
: selectedStep;
|
|
1224
|
+
if (currentStepInstance?.auto) {
|
|
1225
|
+
const autoChainExecutor = new AutoChainExecutor_1.AutoChainExecutor({
|
|
1226
|
+
maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
|
|
1227
|
+
});
|
|
1228
|
+
const autoResult = await autoChainExecutor.run({
|
|
1229
|
+
session,
|
|
1230
|
+
context: effectiveContext,
|
|
1231
|
+
flow: selectedFlow,
|
|
1232
|
+
});
|
|
1233
|
+
session = autoResult.session;
|
|
1234
|
+
// Handle halt: emit verbatim reply as a single chunk, done.
|
|
1235
|
+
if (autoResult.stoppedReason === 'halt') {
|
|
1236
|
+
const reply = autoResult.mergedDirective?.reply || '';
|
|
1237
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1238
|
+
yield {
|
|
1239
|
+
delta: reply,
|
|
1240
|
+
accumulated: reply,
|
|
1241
|
+
done: true,
|
|
1242
|
+
session,
|
|
1243
|
+
stoppedReason: 'halt',
|
|
1244
|
+
executedSteps: [],
|
|
1245
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
1246
|
+
};
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
// Handle flow completion or cross-flow redirect from auto-chain.
|
|
1250
|
+
if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
|
|
1251
|
+
innerStream = this.streamFlowCompletion({
|
|
1252
|
+
selectedFlow,
|
|
1253
|
+
session,
|
|
1254
|
+
context: effectiveContext,
|
|
1255
|
+
history,
|
|
1256
|
+
historyEvents,
|
|
1257
|
+
stoppedReason: autoResult.stoppedReason,
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
else {
|
|
1261
|
+
// Normal case: resolved to an interactive step.
|
|
1262
|
+
resolvedStep = autoResult.resolvedStep;
|
|
1263
|
+
innerStream = this.processFlowStreamingResponse({
|
|
1264
|
+
selectedFlow,
|
|
1265
|
+
selectedStep: resolvedStep,
|
|
1266
|
+
responseDirectives,
|
|
1267
|
+
session,
|
|
1268
|
+
history,
|
|
1269
|
+
context: effectiveContext,
|
|
1270
|
+
historyEvents,
|
|
1271
|
+
signal,
|
|
1272
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
1273
|
+
mergedPreDirective: signalPreDirective,
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
// No auto-step: directly stream the interactive step.
|
|
1279
|
+
innerStream = this.processFlowStreamingResponse({
|
|
1280
|
+
selectedFlow,
|
|
1281
|
+
selectedStep: resolvedStep,
|
|
1282
|
+
responseDirectives,
|
|
1283
|
+
session,
|
|
1284
|
+
history,
|
|
1285
|
+
context: effectiveContext,
|
|
1286
|
+
historyEvents,
|
|
1287
|
+
signal,
|
|
1288
|
+
// Propagate signal pre-directive's appendPrompt for this turn's LLM call
|
|
1289
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
1290
|
+
mergedPreDirective: signalPreDirective,
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
else if (isFlowComplete && selectedFlow) {
|
|
1295
|
+
// Handle flow completion streaming — implicit terminus (no successor
|
|
1296
|
+
// or all successors skipped), so the reason is 'last_step'.
|
|
1297
|
+
innerStream = this.streamFlowCompletion({
|
|
1298
|
+
selectedFlow,
|
|
1299
|
+
session,
|
|
1300
|
+
context: effectiveContext,
|
|
1301
|
+
history,
|
|
1302
|
+
historyEvents,
|
|
1303
|
+
stoppedReason: 'last_step',
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
// Fallback: No flows defined, stream a simple response
|
|
1308
|
+
innerStream = this.streamFallbackResponse({
|
|
1309
|
+
history,
|
|
1310
|
+
context: effectiveContext,
|
|
1311
|
+
session,
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
// ── Intercept the inner stream to run post-signal phase on the final chunk ──
|
|
1315
|
+
// This mirrors the non-streaming path: post-phase runs after finalize/onComplete
|
|
1316
|
+
// and before session persistence, attaching triggeredSignals to the final chunk
|
|
1317
|
+
// (Requirement 11.2).
|
|
1318
|
+
for await (const chunk of innerStream) {
|
|
1319
|
+
if (chunk.done) {
|
|
1320
|
+
// Run post-signal phase on final chunk (Requirement 9.1, 9.2)
|
|
1321
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(chunk.session || session, effectiveContext, historyEvents);
|
|
1322
|
+
let finalSession = postResult.updatedSession;
|
|
1323
|
+
signalFirings.push(...postResult.firings);
|
|
1324
|
+
// Requirement 9.3: Post-phase position directive sets session.pendingDirective
|
|
1325
|
+
if (postResult.mergedDirective && (0, ResponsePipeline_1.hasDirectivePositionField)(postResult.mergedDirective)) {
|
|
1326
|
+
finalSession = { ...finalSession, pendingDirective: postResult.mergedDirective };
|
|
1327
|
+
}
|
|
1328
|
+
yield {
|
|
1329
|
+
...chunk,
|
|
1330
|
+
session: finalSession,
|
|
1331
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
else {
|
|
1335
|
+
yield chunk;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1132
1338
|
}
|
|
1133
1339
|
/**
|
|
1134
|
-
* Process
|
|
1340
|
+
* Process flow streaming response with unified tool execution and data collection
|
|
1135
1341
|
* @private
|
|
1136
1342
|
*/
|
|
1137
|
-
async *
|
|
1138
|
-
const {
|
|
1343
|
+
async *processFlowStreamingResponse(params) {
|
|
1344
|
+
const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
|
|
1139
1345
|
let session = params.session;
|
|
1140
1346
|
// Determine next step (same logic as non-streaming)
|
|
1141
1347
|
let nextStep;
|
|
@@ -1143,21 +1349,44 @@ class ResponseModal {
|
|
|
1143
1349
|
nextStep = selectedStep;
|
|
1144
1350
|
}
|
|
1145
1351
|
else {
|
|
1146
|
-
// Determine current step from session if we're already in this
|
|
1147
|
-
const currentStep = session.
|
|
1148
|
-
?
|
|
1352
|
+
// Determine current step from session if we're already in this flow
|
|
1353
|
+
const currentStep = session.currentFlow?.id === selectedFlow.id && session.currentStep
|
|
1354
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1149
1355
|
: undefined;
|
|
1150
|
-
//
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1356
|
+
// STEP 1 (Algorithm 1): branches win over linear chain
|
|
1357
|
+
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
1358
|
+
const branchResult = await this.responsePipeline.evaluateStepBranches(currentStep, selectedFlow, session, context);
|
|
1359
|
+
if (branchResult) {
|
|
1360
|
+
// Branch resolved — yield a final chunk with the updated session and return
|
|
1361
|
+
if (branchResult.nextStep) {
|
|
1362
|
+
session = branchResult.session;
|
|
1363
|
+
nextStep = branchResult.nextStep;
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
// Flow transition or completion — no step to render
|
|
1367
|
+
yield {
|
|
1368
|
+
delta: '',
|
|
1369
|
+
accumulated: '',
|
|
1370
|
+
done: true,
|
|
1371
|
+
session: branchResult.session,
|
|
1372
|
+
};
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1157
1376
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1377
|
+
if (!nextStep) {
|
|
1378
|
+
// Get candidate steps based on current position in the flow
|
|
1379
|
+
const flowRouter = this.agent.getFlowRouter();
|
|
1380
|
+
const candidates = await flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
|
|
1381
|
+
(0, template_1.createTemplateContext)({ data: session.data, session, context }));
|
|
1382
|
+
if (candidates.length > 0) {
|
|
1383
|
+
nextStep = candidates[0].step;
|
|
1384
|
+
utils_1.logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
1385
|
+
}
|
|
1386
|
+
else {
|
|
1387
|
+
nextStep = selectedFlow.initialStep;
|
|
1388
|
+
utils_1.logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
|
|
1389
|
+
}
|
|
1161
1390
|
}
|
|
1162
1391
|
}
|
|
1163
1392
|
// Update session with next step
|
|
@@ -1166,13 +1395,13 @@ class ResponseModal {
|
|
|
1166
1395
|
const sessionData = session.data || {};
|
|
1167
1396
|
const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
|
|
1168
1397
|
if (missingRequires.length > 0) {
|
|
1169
|
-
const warning = `[
|
|
1170
|
-
`missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
|
|
1398
|
+
const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
|
|
1399
|
+
`missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
|
|
1171
1400
|
utils_1.logger.warn(warning);
|
|
1172
1401
|
console.warn(warning);
|
|
1173
1402
|
const currentStepId = session.currentStep?.id;
|
|
1174
1403
|
if (currentStepId) {
|
|
1175
|
-
const currentStepInstance =
|
|
1404
|
+
const currentStepInstance = selectedFlow.getStep(currentStepId);
|
|
1176
1405
|
if (currentStepInstance) {
|
|
1177
1406
|
nextStep = currentStepInstance;
|
|
1178
1407
|
utils_1.logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
@@ -1189,142 +1418,192 @@ class ResponseModal {
|
|
|
1189
1418
|
utils_1.logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
|
|
1190
1419
|
}
|
|
1191
1420
|
// Build response schema and prompt (same as non-streaming)
|
|
1192
|
-
const responseSchema = this.responseEngine.
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1421
|
+
const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
|
|
1422
|
+
// ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
|
|
1423
|
+
// After pre-LLM emissions are merged, if `halt: true` then skip the LLM
|
|
1424
|
+
// call entirely. Emit a single done chunk with the appropriate content.
|
|
1425
|
+
if (mergedPreDirective?.halt) {
|
|
1426
|
+
const reply = mergedPreDirective.reply || '';
|
|
1427
|
+
const reason = mergedPreDirective.reply ? 'reply' : 'halt';
|
|
1428
|
+
utils_1.logger.debug(`[ResponseModal] Halt (streaming) — skipping LLM call for step ${nextStep.id}, stoppedReason: ${reason}`);
|
|
1429
|
+
await this.finalizeSession(session, context);
|
|
1430
|
+
yield {
|
|
1431
|
+
delta: reply,
|
|
1432
|
+
accumulated: reply,
|
|
1433
|
+
done: true,
|
|
1434
|
+
session,
|
|
1435
|
+
stoppedReason: reason,
|
|
1436
|
+
executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
|
|
1437
|
+
};
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
// ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
|
|
1441
|
+
// A step with `reply` set emits a verbatim template response without LLM.
|
|
1442
|
+
// onEnter and prepare have already fired normally. If prepare returned
|
|
1443
|
+
// a PreDirective with `reply`, that overrides the step-declared reply.
|
|
1444
|
+
if (nextStep.reply != null) {
|
|
1445
|
+
const effectiveReply = mergedPreDirective?.reply ?? await (0, utils_1.render)(nextStep.reply, (0, template_1.createTemplateContext)({ data: session.data || {}, context, session }));
|
|
1446
|
+
utils_1.logger.debug(`[ResponseModal] Step.reply (streaming) — skipping LLM call for step ${nextStep.id}`);
|
|
1447
|
+
await this.finalizeSession(session, context);
|
|
1448
|
+
yield {
|
|
1449
|
+
delta: effectiveReply,
|
|
1450
|
+
accumulated: effectiveReply,
|
|
1451
|
+
done: true,
|
|
1452
|
+
session,
|
|
1453
|
+
stoppedReason: 'reply',
|
|
1454
|
+
executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
|
|
1455
|
+
};
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
// Transient appendage: per-turn slot from PreDirective.appendPrompt.
|
|
1459
|
+
// Fresh each turn, never cached, never persisted.
|
|
1460
|
+
// Wrapped in try/finally to ensure cleanup even on abnormal termination.
|
|
1461
|
+
let turnTransientAppendage = transientAppendage;
|
|
1462
|
+
try {
|
|
1463
|
+
const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
|
|
1464
|
+
flow: selectedFlow,
|
|
1465
|
+
currentStep: nextStep,
|
|
1466
|
+
rules: [],
|
|
1467
|
+
prohibitions: [],
|
|
1468
|
+
directives: responseDirectives,
|
|
1469
|
+
history: historyEvents,
|
|
1470
|
+
agentOptions: this.agent.getAgentOptions(),
|
|
1471
|
+
instructions: this.collectScopedInstructions(selectedFlow, nextStep),
|
|
1472
|
+
combinedTerms: this.agent.getTerms(),
|
|
1473
|
+
context,
|
|
1474
|
+
session,
|
|
1475
|
+
agentSchema: this.agent.schema,
|
|
1476
|
+
transientAppendage: turnTransientAppendage,
|
|
1477
|
+
});
|
|
1478
|
+
// Collect available tools for AI
|
|
1479
|
+
const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
|
|
1480
|
+
// Generate message stream using AI provider
|
|
1481
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
1482
|
+
const stream = agentOptions.provider.generateMessageStream({
|
|
1483
|
+
prompt: responsePrompt,
|
|
1484
|
+
history, // Use HistoryItem[] for AI provider
|
|
1485
|
+
context,
|
|
1486
|
+
tools: availableTools,
|
|
1487
|
+
signal,
|
|
1488
|
+
parameters: { jsonSchema: responseSchema, schemaName: "response_stream_output" },
|
|
1489
|
+
});
|
|
1490
|
+
// Stream chunks with unified tool handling
|
|
1491
|
+
for await (const chunk of stream) {
|
|
1492
|
+
let toolCalls = undefined;
|
|
1493
|
+
// Extract tool calls from AI response on final chunk
|
|
1494
|
+
if (chunk.done && chunk.structured?.toolCalls) {
|
|
1495
|
+
toolCalls = chunk.structured.toolCalls;
|
|
1496
|
+
const toolManager = this.getToolManager();
|
|
1497
|
+
// Use concurrent execution for the initial batch of tool calls
|
|
1498
|
+
if (toolManager && typeof toolManager.executeWithConcurrency === 'function') {
|
|
1499
|
+
const toolCallRequests = toolCalls.map((tc, i) => ({
|
|
1500
|
+
id: `${tc.toolName}-${i}-${Date.now()}`,
|
|
1501
|
+
toolName: tc.toolName,
|
|
1502
|
+
arguments: tc.arguments,
|
|
1503
|
+
}));
|
|
1504
|
+
const historyEvents = (0, utils_1.historyToEvents)(history);
|
|
1505
|
+
try {
|
|
1506
|
+
for await (const update of toolManager.executeWithConcurrency({
|
|
1507
|
+
toolCalls: toolCallRequests,
|
|
1508
|
+
context,
|
|
1509
|
+
data: session.data,
|
|
1510
|
+
history: historyEvents,
|
|
1511
|
+
signal,
|
|
1512
|
+
flow: selectedFlow,
|
|
1513
|
+
step: nextStep,
|
|
1514
|
+
})) {
|
|
1515
|
+
// Apply context updates
|
|
1516
|
+
if (update.contextUpdate) {
|
|
1517
|
+
try {
|
|
1518
|
+
await this.agent.updateContext(update.contextUpdate);
|
|
1519
|
+
}
|
|
1520
|
+
catch (error) {
|
|
1521
|
+
utils_1.logger.error(`[ResponseModal] Failed to update context from concurrent tool:`, error);
|
|
1522
|
+
}
|
|
1251
1523
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1524
|
+
// Apply data updates
|
|
1525
|
+
if (update.dataUpdate) {
|
|
1526
|
+
try {
|
|
1527
|
+
const updateDataMethod = this.agent.getUpdateDataMethod();
|
|
1528
|
+
session = await updateDataMethod(session, update.dataUpdate);
|
|
1529
|
+
}
|
|
1530
|
+
catch (error) {
|
|
1531
|
+
utils_1.logger.error(`[ResponseModal] Failed to update data from concurrent tool:`, error);
|
|
1532
|
+
}
|
|
1258
1533
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1534
|
+
// Yield progress updates immediately
|
|
1535
|
+
if (update.progress) {
|
|
1536
|
+
yield {
|
|
1537
|
+
delta: '',
|
|
1538
|
+
accumulated: chunk.accumulated,
|
|
1539
|
+
done: false,
|
|
1540
|
+
session,
|
|
1541
|
+
toolCalls: undefined,
|
|
1542
|
+
isFlowComplete: false,
|
|
1543
|
+
metadata: { toolProgress: update.progress, toolCallId: update.toolCallId },
|
|
1544
|
+
};
|
|
1261
1545
|
}
|
|
1262
1546
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
}
|
|
1547
|
+
utils_1.logger.debug(`[ResponseModal] Concurrent tool execution completed for ${toolCallRequests.length} tools`);
|
|
1548
|
+
}
|
|
1549
|
+
catch (error) {
|
|
1550
|
+
utils_1.logger.error(`[ResponseModal] Concurrent tool execution failed, falling back to sequential:`, error);
|
|
1551
|
+
// Fall back to the unified tool loop on failure
|
|
1552
|
+
const toolResult = await this.executeUnifiedToolLoop({
|
|
1553
|
+
toolCalls, context, session, history, selectedFlow,
|
|
1554
|
+
responsePrompt, availableTools, responseSchema, signal,
|
|
1555
|
+
});
|
|
1556
|
+
session = toolResult.session;
|
|
1557
|
+
toolCalls = toolResult.finalToolCalls;
|
|
1275
1558
|
}
|
|
1276
|
-
utils_1.logger.debug(`[ResponseModal] Concurrent tool execution completed for ${toolCallRequests.length} tools`);
|
|
1277
1559
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
// Fall back to the unified tool loop on failure
|
|
1560
|
+
else {
|
|
1561
|
+
// Fallback: no ToolManager or no executeWithConcurrency, use unified tool loop
|
|
1281
1562
|
const toolResult = await this.executeUnifiedToolLoop({
|
|
1282
|
-
toolCalls, context, session, history,
|
|
1563
|
+
toolCalls, context, session, history, selectedFlow,
|
|
1283
1564
|
responsePrompt, availableTools, responseSchema, signal,
|
|
1284
1565
|
});
|
|
1285
1566
|
session = toolResult.session;
|
|
1286
1567
|
toolCalls = toolResult.finalToolCalls;
|
|
1287
1568
|
}
|
|
1288
1569
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1570
|
+
// Extract collected data on final chunk
|
|
1571
|
+
if (chunk.done && chunk.structured && nextStep.collect) {
|
|
1572
|
+
session = await this.collectDataFromResponse({
|
|
1573
|
+
result: { structured: chunk.structured },
|
|
1574
|
+
selectedFlow,
|
|
1575
|
+
nextStep,
|
|
1576
|
+
session,
|
|
1294
1577
|
});
|
|
1295
|
-
session = toolResult.session;
|
|
1296
|
-
toolCalls = toolResult.finalToolCalls;
|
|
1297
1578
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1579
|
+
// Handle session finalization on final chunk
|
|
1580
|
+
if (chunk.done) {
|
|
1581
|
+
await this.finalizeSession(session, context);
|
|
1582
|
+
}
|
|
1583
|
+
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
1584
|
+
// - executedSteps: single step executed in this response
|
|
1585
|
+
// - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
|
|
1586
|
+
// - session.currentStep: reflects the executed step
|
|
1587
|
+
yield {
|
|
1588
|
+
delta: chunk.delta,
|
|
1589
|
+
accumulated: chunk.accumulated,
|
|
1590
|
+
done: chunk.done,
|
|
1305
1591
|
session,
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1592
|
+
toolCalls,
|
|
1593
|
+
isFlowComplete: false,
|
|
1594
|
+
executedSteps: chunk.done ? [{ id: nextStep.id, flowId: selectedFlow.id }] : undefined,
|
|
1595
|
+
stoppedReason: chunk.done ? 'needs_input' : undefined,
|
|
1596
|
+
metadata: chunk.metadata,
|
|
1597
|
+
structured: chunk.structured,
|
|
1598
|
+
appliedInstructions: chunk.done ? appliedInstructions : undefined,
|
|
1599
|
+
};
|
|
1311
1600
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
//
|
|
1315
|
-
//
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
accumulated: chunk.accumulated,
|
|
1319
|
-
done: chunk.done,
|
|
1320
|
-
session,
|
|
1321
|
-
toolCalls,
|
|
1322
|
-
isRouteComplete: false,
|
|
1323
|
-
executedSteps: chunk.done ? [{ id: nextStep.id, routeId: selectedRoute.id }] : undefined,
|
|
1324
|
-
stoppedReason: chunk.done ? 'needs_input' : undefined,
|
|
1325
|
-
metadata: chunk.metadata,
|
|
1326
|
-
structured: chunk.structured,
|
|
1327
|
-
};
|
|
1601
|
+
}
|
|
1602
|
+
finally {
|
|
1603
|
+
// Drain the transient appendage at end of turn.
|
|
1604
|
+
// This ensures PreDirective.appendPrompt does not leak to subsequent
|
|
1605
|
+
// turns even when the turn terminates abnormally (error, abort, reject).
|
|
1606
|
+
turnTransientAppendage = undefined;
|
|
1328
1607
|
}
|
|
1329
1608
|
}
|
|
1330
1609
|
/**
|
|
@@ -1334,19 +1613,21 @@ class ResponseModal {
|
|
|
1334
1613
|
*/
|
|
1335
1614
|
async executeUnifiedToolLoop(params) {
|
|
1336
1615
|
try {
|
|
1337
|
-
const { context, history,
|
|
1616
|
+
const { context, history, selectedFlow, responsePrompt, availableTools, responseSchema, signal } = params;
|
|
1338
1617
|
let { toolCalls, session } = params;
|
|
1339
1618
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
1340
1619
|
const historyEvents = (0, utils_1.historyToEvents)(history);
|
|
1341
1620
|
// Map to store tool execution results for history
|
|
1342
1621
|
const toolResultsMap = new Map();
|
|
1622
|
+
// Map to store tool call arguments for history reconstruction
|
|
1623
|
+
const toolArgsMap = new Map();
|
|
1343
1624
|
// Execute initial dynamic tool calls
|
|
1344
1625
|
if (toolCalls && toolCalls.length > 0) {
|
|
1345
1626
|
utils_1.logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls:`, toolCalls.map(tc => tc.toolName));
|
|
1346
1627
|
for (const toolCall of toolCalls) {
|
|
1347
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
1628
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1348
1629
|
if (!tool) {
|
|
1349
|
-
utils_1.logger.warn(`[
|
|
1630
|
+
utils_1.logger.warn(`[ToolExecutionError] Tool not found: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
|
|
1350
1631
|
continue;
|
|
1351
1632
|
}
|
|
1352
1633
|
try {
|
|
@@ -1370,6 +1651,7 @@ class ResponseModal {
|
|
|
1370
1651
|
}
|
|
1371
1652
|
// Store the actual tool result data for history
|
|
1372
1653
|
toolResultsMap.set(toolCall.toolName, (0, utils_1.serializeToolResult)(toolResult));
|
|
1654
|
+
toolArgsMap.set(toolCall.toolName, toolCall.arguments);
|
|
1373
1655
|
// Check if tool execution was successful
|
|
1374
1656
|
if (!toolResult.success) {
|
|
1375
1657
|
utils_1.logger.error(`[ResponseModal] Tool execution failed: ${toolCall.toolName} - ${toolResult.error}`);
|
|
@@ -1419,7 +1701,7 @@ class ResponseModal {
|
|
|
1419
1701
|
// Create tool result history items
|
|
1420
1702
|
const toolResultHistoryItems = [];
|
|
1421
1703
|
for (const toolCall of toolCalls || []) {
|
|
1422
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
1704
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1423
1705
|
if (tool) {
|
|
1424
1706
|
// Create HistoryItem format for tool results
|
|
1425
1707
|
// Add assistant message with tool_calls
|
|
@@ -1477,9 +1759,9 @@ class ResponseModal {
|
|
|
1477
1759
|
utils_1.logger.debug(`[ResponseModal] Follow-up call produced ${followUpToolCalls.length} additional tool calls`);
|
|
1478
1760
|
// Execute the follow-up tool calls
|
|
1479
1761
|
for (const toolCall of followUpToolCalls) {
|
|
1480
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
1762
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1481
1763
|
if (!tool) {
|
|
1482
|
-
utils_1.logger.warn(`[
|
|
1764
|
+
utils_1.logger.warn(`[ToolExecutionError] Tool not found in follow-up: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
|
|
1483
1765
|
continue;
|
|
1484
1766
|
}
|
|
1485
1767
|
try {
|
|
@@ -1527,6 +1809,7 @@ class ResponseModal {
|
|
|
1527
1809
|
}
|
|
1528
1810
|
// Store the follow-up tool result for potential next loop iteration
|
|
1529
1811
|
toolResultsMap.set(toolCall.toolName, (0, utils_1.serializeToolResult)(toolResult));
|
|
1812
|
+
toolArgsMap.set(toolCall.toolName, toolCall.arguments);
|
|
1530
1813
|
utils_1.logger.debug(`[ResponseModal] Executed follow-up tool: ${toolCall.toolName} (success: ${toolResult.success})`);
|
|
1531
1814
|
}
|
|
1532
1815
|
catch (error) {
|
|
@@ -1547,7 +1830,7 @@ class ResponseModal {
|
|
|
1547
1830
|
}
|
|
1548
1831
|
}
|
|
1549
1832
|
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
1550
|
-
utils_1.logger.warn(`[
|
|
1833
|
+
utils_1.logger.warn(`[ResponseGenerationError] Tool loop limit reached: ${toolLoopCount} iterations hit the cap (${MAX_TOOL_LOOPS}). Stopping tool execution. Increase MAX_TOOL_LOOPS or reduce recursive tool calls.`);
|
|
1551
1834
|
}
|
|
1552
1835
|
// If tools were executed but no final text message was produced,
|
|
1553
1836
|
// make one more LLM call to generate a proper text response from tool results.
|
|
@@ -1567,7 +1850,7 @@ class ResponseModal {
|
|
|
1567
1850
|
tool_calls: [{
|
|
1568
1851
|
id: toolName,
|
|
1569
1852
|
name: toolName,
|
|
1570
|
-
arguments: {},
|
|
1853
|
+
arguments: toolArgsMap.get(toolName) || {},
|
|
1571
1854
|
}],
|
|
1572
1855
|
});
|
|
1573
1856
|
finalToolResultHistoryItems.push({
|
|
@@ -1630,30 +1913,30 @@ class ResponseModal {
|
|
|
1630
1913
|
*/
|
|
1631
1914
|
async collectDataFromResponse(params) {
|
|
1632
1915
|
try {
|
|
1633
|
-
const { result,
|
|
1916
|
+
const { result, selectedFlow, nextStep, session } = params;
|
|
1634
1917
|
let updatedSession = session;
|
|
1635
|
-
// Extract collected data from final response (only for
|
|
1636
|
-
if (
|
|
1918
|
+
// Extract collected data from final response (only for flow-based interactions)
|
|
1919
|
+
if (selectedFlow && result.structured) {
|
|
1637
1920
|
try {
|
|
1638
1921
|
const collectedData = {};
|
|
1639
1922
|
// AgentStructuredResponse extends Record<string, unknown>, so we can safely access properties
|
|
1640
1923
|
const structuredData = result.structured;
|
|
1641
|
-
// Collect ALL
|
|
1642
|
-
const
|
|
1643
|
-
// Add
|
|
1644
|
-
if (
|
|
1645
|
-
|
|
1924
|
+
// Collect ALL flow fields (required + optional) from structured response
|
|
1925
|
+
const allFlowFields = new Set();
|
|
1926
|
+
// Add flow required fields
|
|
1927
|
+
if (selectedFlow.requiredFields) {
|
|
1928
|
+
selectedFlow.requiredFields.forEach(field => allFlowFields.add(String(field)));
|
|
1646
1929
|
}
|
|
1647
|
-
// Add
|
|
1648
|
-
if (
|
|
1649
|
-
|
|
1930
|
+
// Add flow optional fields
|
|
1931
|
+
if (selectedFlow.optionalFields) {
|
|
1932
|
+
selectedFlow.optionalFields.forEach(field => allFlowFields.add(String(field)));
|
|
1650
1933
|
}
|
|
1651
|
-
// Also include current step's collect fields (in case they're not in
|
|
1934
|
+
// Also include current step's collect fields (in case they're not in flow fields)
|
|
1652
1935
|
if (nextStep?.collect) {
|
|
1653
|
-
nextStep.collect.forEach(field =>
|
|
1936
|
+
nextStep.collect.forEach(field => allFlowFields.add(String(field)));
|
|
1654
1937
|
}
|
|
1655
1938
|
// Extract all available fields from structured response
|
|
1656
|
-
for (const field of
|
|
1939
|
+
for (const field of allFlowFields) {
|
|
1657
1940
|
const fieldKey = String(field);
|
|
1658
1941
|
if (fieldKey in structuredData && structuredData[fieldKey] !== undefined && structuredData[fieldKey] !== null) {
|
|
1659
1942
|
collectedData[fieldKey] = structuredData[fieldKey];
|
|
@@ -1703,207 +1986,122 @@ class ResponseModal {
|
|
|
1703
1986
|
}
|
|
1704
1987
|
}
|
|
1705
1988
|
/**
|
|
1706
|
-
*
|
|
1989
|
+
* Apply flow completion: release the session to idle state.
|
|
1990
|
+
*
|
|
1991
|
+
* This is a pure state transition. The framework emits **no message of
|
|
1992
|
+
* its own** at the completion boundary — every word delivered to the
|
|
1993
|
+
* user comes from a developer-defined step prompt. If the dev wants a
|
|
1994
|
+
* closing turn, they add a final interactive step with their own
|
|
1995
|
+
* `prompt`; the framework respects that step's natural LLM output.
|
|
1996
|
+
*
|
|
1997
|
+
* Behavior:
|
|
1998
|
+
* - Marks the active `flowHistory` entry as `completed: true` and
|
|
1999
|
+
* stamps `exitedAt`.
|
|
2000
|
+
* - Evaluates `flow.onComplete` for an explicit follow-up transition.
|
|
2001
|
+
* When set, populates `session.pendingDirective` (the next turn's
|
|
2002
|
+
* pipeline applies it). When absent, the session is fully idle.
|
|
2003
|
+
* - Clears `currentFlow` and `currentStep` to `undefined`.
|
|
2004
|
+
* - Clears owned fields when the flow is `reentrant` so subsequent
|
|
2005
|
+
* re-selections start from a clean state.
|
|
2006
|
+
*
|
|
2007
|
+
* Returns the updated session. Callers compose any reply text from
|
|
2008
|
+
* their own sources (an upstream LLM turn, a directive's `reply`, or
|
|
2009
|
+
* an empty string for silent completion).
|
|
2010
|
+
*
|
|
1707
2011
|
* @private
|
|
1708
2012
|
*/
|
|
1709
|
-
async
|
|
1710
|
-
const {
|
|
1711
|
-
//
|
|
1712
|
-
const
|
|
1713
|
-
//
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
2013
|
+
async applyFlowCompletion(params) {
|
|
2014
|
+
const { selectedFlow, session, context } = params;
|
|
2015
|
+
// 1) Evaluate onComplete first — needs the still-active session shape.
|
|
2016
|
+
const transitionConfig = await selectedFlow.evaluateOnComplete({ data: session.data }, context);
|
|
2017
|
+
// 2) Release to idle. If the flow is reentrant, scrub its owned
|
|
2018
|
+
// fields so re-selection on a future turn starts clean. When
|
|
2019
|
+
// onComplete fires we still go idle here — the next turn's
|
|
2020
|
+
// pipeline applies the pendingDirective before any routing.
|
|
2021
|
+
const ownedFields = selectedFlow.reentrant
|
|
2022
|
+
? [
|
|
2023
|
+
...(selectedFlow.requiredFields ?? []),
|
|
2024
|
+
...(selectedFlow.optionalFields ?? []),
|
|
2025
|
+
]
|
|
2026
|
+
: undefined;
|
|
2027
|
+
let nextSession = (0, utils_1.completeCurrentFlow)(session, {
|
|
2028
|
+
clearOwnedFields: ownedFields,
|
|
1720
2029
|
});
|
|
1721
|
-
//
|
|
1722
|
-
const completionSchema = {
|
|
1723
|
-
type: "object",
|
|
1724
|
-
properties: {
|
|
1725
|
-
message: {
|
|
1726
|
-
type: "string",
|
|
1727
|
-
description: "A natural, warm farewell message for the user. Must NOT contain task names, field names, collected data, or any internal/technical information.",
|
|
1728
|
-
},
|
|
1729
|
-
},
|
|
1730
|
-
required: ["message"],
|
|
1731
|
-
additionalProperties: false,
|
|
1732
|
-
};
|
|
1733
|
-
const templateContext = (0, template_1.createTemplateContext)({ context, session, history: historyEvents });
|
|
1734
|
-
// Build completion response prompt using ResponseEngine
|
|
1735
|
-
// Filter out conditional guidelines - only include always-active ones
|
|
1736
|
-
const alwaysActiveGuidelines = [
|
|
1737
|
-
...this.agent.getGuidelines().filter(g => !g.condition),
|
|
1738
|
-
...selectedRoute.getGuidelines().filter(g => !g.condition),
|
|
1739
|
-
];
|
|
1740
|
-
let completitionPrompt = "Send a brief, natural farewell message. Do NOT mention internal data or task details.";
|
|
1741
|
-
if (endStepSpec.prompt) {
|
|
1742
|
-
completitionPrompt = await (0, utils_1.render)(endStepSpec.prompt, templateContext);
|
|
1743
|
-
}
|
|
1744
|
-
const completionPrompt = await this.responseEngine.buildResponsePrompt({
|
|
1745
|
-
route: selectedRoute,
|
|
1746
|
-
currentStep: completionStep,
|
|
1747
|
-
rules: selectedRoute.getRules(),
|
|
1748
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1749
|
-
directives: [
|
|
1750
|
-
"The conversation task has been completed successfully",
|
|
1751
|
-
"Generate a natural, friendly farewell message for the user",
|
|
1752
|
-
"Do NOT mention task names, route names, collected data, field names, or any internal/technical information",
|
|
1753
|
-
"Do NOT list or summarize the data you collected - the user already knows what they told you",
|
|
1754
|
-
"Do NOT use words like 'tarefa', 'dados coletados', 'prospecção', 'concluída' or similar internal terms",
|
|
1755
|
-
"Keep it brief, warm, and conversational - as if ending a natural conversation",
|
|
1756
|
-
"Do NOT ask for more information - the conversation is ending",
|
|
1757
|
-
completitionPrompt,
|
|
1758
|
-
],
|
|
1759
|
-
history: historyEvents,
|
|
1760
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
1761
|
-
combinedGuidelines: alwaysActiveGuidelines, // Only non-conditional guidelines
|
|
1762
|
-
combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
|
|
1763
|
-
context,
|
|
1764
|
-
session,
|
|
1765
|
-
agentSchema: undefined, // No data collection schema for completion
|
|
1766
|
-
});
|
|
1767
|
-
// Generate completion message using AI provider
|
|
1768
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
1769
|
-
utils_1.logger.debug(`[ResponseModal] Calling AI provider for completion message...`);
|
|
1770
|
-
const completionResult = await agentOptions.provider.generateMessage({
|
|
1771
|
-
prompt: completionPrompt,
|
|
1772
|
-
history, // Use HistoryItem[] for AI provider
|
|
1773
|
-
context,
|
|
1774
|
-
signal,
|
|
1775
|
-
parameters: { jsonSchema: completionSchema, schemaName: "completion_message" },
|
|
1776
|
-
});
|
|
1777
|
-
utils_1.logger.debug(`[ResponseModal] AI provider returned completion result`);
|
|
1778
|
-
const message = completionResult.structured?.message || completionResult.message;
|
|
1779
|
-
utils_1.logger.debug(`[ResponseModal] Generated completion message for route: ${selectedRoute.title}`);
|
|
1780
|
-
// Check for onComplete transition
|
|
1781
|
-
const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
|
|
2030
|
+
// 3) Wire pendingDirective when onComplete returned a target.
|
|
1782
2031
|
if (transitionConfig) {
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2032
|
+
const goToTarget = typeof transitionConfig.goTo === 'string'
|
|
2033
|
+
? transitionConfig.goTo
|
|
2034
|
+
: transitionConfig.goTo?.flow;
|
|
2035
|
+
const targetFlow = goToTarget ? this.agent.getFlows().find((r) => r.id === goToTarget ||
|
|
2036
|
+
r.title === goToTarget) : undefined;
|
|
2037
|
+
if (targetFlow) {
|
|
2038
|
+
nextSession = {
|
|
2039
|
+
...nextSession,
|
|
2040
|
+
pendingDirective: {
|
|
2041
|
+
goTo: targetFlow.id,
|
|
2042
|
+
},
|
|
1792
2043
|
};
|
|
1793
|
-
utils_1.logger.debug(`[ResponseModal]
|
|
2044
|
+
utils_1.logger.debug(`[ResponseModal] Flow ${selectedFlow.title} completed with pending directive to: ${targetFlow.title}`);
|
|
1794
2045
|
}
|
|
1795
|
-
else {
|
|
1796
|
-
utils_1.logger.warn(`[
|
|
2046
|
+
else if (goToTarget) {
|
|
2047
|
+
utils_1.logger.warn(`[FlowConfigurationError] onComplete target not found: flow "${selectedFlow.title}" completed but onComplete target "${goToTarget}" does not match any flow. ` +
|
|
2048
|
+
`Fix the onComplete value to reference an existing flow id/title, or remove onComplete to release the session to idle.`);
|
|
1797
2049
|
}
|
|
1798
2050
|
}
|
|
1799
|
-
|
|
2051
|
+
else {
|
|
2052
|
+
utils_1.logger.debug(`[ResponseModal] Flow ${selectedFlow.title} completed; session released to idle.`);
|
|
2053
|
+
}
|
|
2054
|
+
return nextSession;
|
|
1800
2055
|
}
|
|
1801
2056
|
/**
|
|
1802
|
-
* Stream
|
|
2057
|
+
* Stream flow completion response
|
|
1803
2058
|
* @private
|
|
1804
2059
|
*/
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
const
|
|
1820
|
-
const
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
route: selectedRoute,
|
|
1824
|
-
currentStep: completionStep,
|
|
1825
|
-
rules: selectedRoute.getRules(),
|
|
1826
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1827
|
-
directives: undefined, // No directives for completion
|
|
1828
|
-
history: historyEvents,
|
|
1829
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
1830
|
-
combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
|
|
1831
|
-
combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
|
|
1832
|
-
context,
|
|
1833
|
-
session,
|
|
1834
|
-
agentSchema: this.agent.getSchema(),
|
|
1835
|
-
});
|
|
1836
|
-
// Stream completion message using AI provider
|
|
1837
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
1838
|
-
const stream = agentOptions.provider.generateMessageStream({
|
|
1839
|
-
prompt: completionPrompt,
|
|
1840
|
-
history, // Use HistoryItem[] for AI provider
|
|
2060
|
+
/**
|
|
2061
|
+
* Stream a flow completion as a single terminal chunk.
|
|
2062
|
+
*
|
|
2063
|
+
* No LLM call is made. The framework no longer authors a farewell — the
|
|
2064
|
+
* completion path is a pure state transition. The chunk emits an empty
|
|
2065
|
+
* `delta` and a `done: true` flag with the idle session attached so
|
|
2066
|
+
* downstream consumers can finalize cleanly.
|
|
2067
|
+
*
|
|
2068
|
+
* If the developer wants closing copy in a streaming response, they
|
|
2069
|
+
* should add a final interactive step whose own LLM turn delivers it.
|
|
2070
|
+
*
|
|
2071
|
+
* @private
|
|
2072
|
+
*/
|
|
2073
|
+
async *streamFlowCompletion(params) {
|
|
2074
|
+
const { selectedFlow, context, history } = params;
|
|
2075
|
+
const session = await this.applyFlowCompletion({
|
|
2076
|
+
selectedFlow,
|
|
2077
|
+
session: params.session,
|
|
1841
2078
|
context,
|
|
1842
|
-
|
|
1843
|
-
parameters: { jsonSchema: responseSchema, schemaName: "completion_message_stream" },
|
|
2079
|
+
history,
|
|
1844
2080
|
});
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
pendingTransition: {
|
|
1857
|
-
targetRouteId: targetRoute.id,
|
|
1858
|
-
condition: renderedCondition,
|
|
1859
|
-
reason: "route_complete",
|
|
1860
|
-
},
|
|
1861
|
-
};
|
|
1862
|
-
utils_1.logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
|
|
1863
|
-
}
|
|
1864
|
-
else {
|
|
1865
|
-
utils_1.logger.warn(`[ResponseModal] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
// Set step to END_ROUTE marker
|
|
1869
|
-
session = (0, utils_1.enterStep)(session, constants_1.END_ROUTE_ID, "Route completed");
|
|
1870
|
-
utils_1.logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
|
|
1871
|
-
// Stream completion chunks
|
|
1872
|
-
for await (const chunk of stream) {
|
|
1873
|
-
// Update current session if we have one
|
|
1874
|
-
if (chunk.done) {
|
|
1875
|
-
await this.finalizeSession(session, context);
|
|
1876
|
-
}
|
|
1877
|
-
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
1878
|
-
// - executedSteps: empty for route completion (no new steps executed)
|
|
1879
|
-
// - stoppedReason: 'route_complete' for completed routes
|
|
1880
|
-
// - session.currentStep: set to END_ROUTE
|
|
1881
|
-
yield {
|
|
1882
|
-
delta: chunk.delta,
|
|
1883
|
-
accumulated: chunk.accumulated,
|
|
1884
|
-
done: chunk.done,
|
|
1885
|
-
session,
|
|
1886
|
-
toolCalls: undefined,
|
|
1887
|
-
isRouteComplete: true,
|
|
1888
|
-
executedSteps: chunk.done ? [] : undefined,
|
|
1889
|
-
stoppedReason: chunk.done ? 'route_complete' : undefined,
|
|
1890
|
-
metadata: chunk.metadata,
|
|
1891
|
-
structured: chunk.structured,
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
2081
|
+
await this.finalizeSession(session, context);
|
|
2082
|
+
yield {
|
|
2083
|
+
delta: '',
|
|
2084
|
+
accumulated: '',
|
|
2085
|
+
done: true,
|
|
2086
|
+
session,
|
|
2087
|
+
toolCalls: undefined,
|
|
2088
|
+
isFlowComplete: true,
|
|
2089
|
+
executedSteps: [],
|
|
2090
|
+
stoppedReason: params.stoppedReason ?? 'completed',
|
|
2091
|
+
};
|
|
1894
2092
|
}
|
|
1895
2093
|
/**
|
|
1896
|
-
* Generate fallback response when no
|
|
2094
|
+
* Generate fallback response when no flows are available
|
|
1897
2095
|
* @private
|
|
1898
2096
|
*/
|
|
1899
2097
|
async generateFallbackResponse(params) {
|
|
1900
2098
|
const { history, context, session, signal } = params;
|
|
1901
|
-
utils_1.logger.debug(`[ResponseModal] No
|
|
1902
|
-
// Build basic response prompt without
|
|
1903
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
2099
|
+
utils_1.logger.debug(`[ResponseModal] No flow selected, generating basic response`);
|
|
2100
|
+
// Build basic response prompt without flow context
|
|
2101
|
+
const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
|
|
1904
2102
|
agentOptions: this.agent.getAgentOptions(),
|
|
1905
2103
|
terms: this.agent.getTerms(),
|
|
1906
|
-
|
|
2104
|
+
instructions: this.collectScopedInstructions(),
|
|
1907
2105
|
context,
|
|
1908
2106
|
session,
|
|
1909
2107
|
});
|
|
@@ -1923,18 +2121,18 @@ class ResponseModal {
|
|
|
1923
2121
|
schemaName: "fallback_response",
|
|
1924
2122
|
},
|
|
1925
2123
|
});
|
|
1926
|
-
return result.structured?.message || result.message;
|
|
2124
|
+
return { message: result.structured?.message || result.message, appliedInstructions };
|
|
1927
2125
|
}
|
|
1928
2126
|
/**
|
|
1929
|
-
* Stream fallback response when no
|
|
2127
|
+
* Stream fallback response when no flows are available
|
|
1930
2128
|
* @private
|
|
1931
2129
|
*/
|
|
1932
2130
|
async *streamFallbackResponse(params) {
|
|
1933
2131
|
const { history, context, session, signal } = params;
|
|
1934
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
2132
|
+
const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
|
|
1935
2133
|
agentOptions: this.agent.getAgentOptions(),
|
|
1936
2134
|
terms: this.agent.getTerms(),
|
|
1937
|
-
|
|
2135
|
+
instructions: this.collectScopedInstructions(),
|
|
1938
2136
|
context,
|
|
1939
2137
|
session,
|
|
1940
2138
|
});
|
|
@@ -1960,8 +2158,8 @@ class ResponseModal {
|
|
|
1960
2158
|
await this.finalizeSession(session, context);
|
|
1961
2159
|
}
|
|
1962
2160
|
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
1963
|
-
// - executedSteps: empty for fallback (no
|
|
1964
|
-
// - stoppedReason: undefined for fallback (no
|
|
2161
|
+
// - executedSteps: empty for fallback (no flow/step execution)
|
|
2162
|
+
// - stoppedReason: undefined for fallback (no flow context)
|
|
1965
2163
|
// - session.currentStep: unchanged (no step progression)
|
|
1966
2164
|
yield {
|
|
1967
2165
|
delta: chunk.delta,
|
|
@@ -1969,11 +2167,12 @@ class ResponseModal {
|
|
|
1969
2167
|
done: chunk.done,
|
|
1970
2168
|
session,
|
|
1971
2169
|
toolCalls: undefined,
|
|
1972
|
-
|
|
2170
|
+
isFlowComplete: false,
|
|
1973
2171
|
executedSteps: chunk.done ? [] : undefined,
|
|
1974
2172
|
stoppedReason: undefined,
|
|
1975
2173
|
metadata: chunk.metadata,
|
|
1976
2174
|
structured: chunk.structured,
|
|
2175
|
+
appliedInstructions: chunk.done ? appliedInstructions : undefined,
|
|
1977
2176
|
};
|
|
1978
2177
|
}
|
|
1979
2178
|
}
|
|
@@ -1994,52 +2193,52 @@ class ResponseModal {
|
|
|
1994
2193
|
// Execute finalize function
|
|
1995
2194
|
await this.executeStepFinalize(session, context);
|
|
1996
2195
|
// Update current session if we have one
|
|
1997
|
-
const currentSession = this.agent.
|
|
2196
|
+
const currentSession = this.agent.currentSession;
|
|
1998
2197
|
if (currentSession) {
|
|
1999
|
-
this.agent.
|
|
2198
|
+
this.agent.currentSession = session;
|
|
2000
2199
|
}
|
|
2001
2200
|
}
|
|
2002
2201
|
// ============================================================================
|
|
2003
2202
|
// UTILITY METHODS - Helper methods for tool management and other utilities
|
|
2004
2203
|
// ============================================================================
|
|
2005
2204
|
/**
|
|
2006
|
-
* Find an available tool by name for the given
|
|
2205
|
+
* Find an available tool by name for the given flow using ToolManager
|
|
2007
2206
|
* Delegates to ToolManager for unified tool resolution
|
|
2008
2207
|
* @private
|
|
2009
2208
|
*/
|
|
2010
|
-
findAvailableTool(toolName,
|
|
2209
|
+
findAvailableTool(toolName, flow) {
|
|
2011
2210
|
// Use ToolManager for unified tool resolution
|
|
2012
2211
|
const toolManager = this.getToolManager();
|
|
2013
2212
|
if (toolManager) {
|
|
2014
|
-
return toolManager.find(toolName, undefined, undefined,
|
|
2213
|
+
return toolManager.find(toolName, undefined, undefined, flow);
|
|
2015
2214
|
}
|
|
2016
2215
|
// Fallback to legacy resolution if ToolManager not available
|
|
2017
2216
|
utils_1.logger.warn(`[ResponseModal] ToolManager not available, using legacy tool resolution for: ${toolName}`);
|
|
2018
|
-
// Check
|
|
2019
|
-
if (
|
|
2020
|
-
const
|
|
2217
|
+
// Check flow-level tools first (if flow provided)
|
|
2218
|
+
if (flow) {
|
|
2219
|
+
const flowTool = flow
|
|
2021
2220
|
.getTools()
|
|
2022
|
-
.find((tool) => tool.id === toolName || tool.
|
|
2023
|
-
if (
|
|
2024
|
-
return
|
|
2221
|
+
.find((tool) => tool.id === toolName || tool.id === toolName);
|
|
2222
|
+
if (flowTool)
|
|
2223
|
+
return flowTool;
|
|
2025
2224
|
}
|
|
2026
2225
|
// Fall back to agent-level tools
|
|
2027
2226
|
const agentTools = this.agent.getTools();
|
|
2028
|
-
return agentTools.find((tool) => tool.id === toolName || tool.
|
|
2227
|
+
return agentTools.find((tool) => tool.id === toolName || tool.id === toolName);
|
|
2029
2228
|
}
|
|
2030
2229
|
/**
|
|
2031
|
-
* Collect all available tools for the given
|
|
2230
|
+
* Collect all available tools for the given flow and step context using ToolManager
|
|
2032
2231
|
* Delegates to ToolManager for unified tool resolution and deduplication
|
|
2033
2232
|
* @private
|
|
2034
2233
|
*/
|
|
2035
|
-
collectAvailableTools(
|
|
2234
|
+
collectAvailableTools(flow, step) {
|
|
2036
2235
|
// Use ToolManager for unified tool collection if available
|
|
2037
2236
|
const toolManager = this.getToolManager();
|
|
2038
2237
|
if (toolManager) {
|
|
2039
|
-
const availableTools = toolManager.getAvailable(undefined, step,
|
|
2238
|
+
const availableTools = toolManager.getAvailable(undefined, step, flow);
|
|
2040
2239
|
return availableTools.map((tool) => ({
|
|
2041
2240
|
id: tool.id,
|
|
2042
|
-
name: tool.
|
|
2241
|
+
name: tool.id || tool.id,
|
|
2043
2242
|
description: tool.description,
|
|
2044
2243
|
parameters: tool.parameters,
|
|
2045
2244
|
}));
|
|
@@ -2051,9 +2250,9 @@ class ResponseModal {
|
|
|
2051
2250
|
this.agent.getTools().forEach((tool) => {
|
|
2052
2251
|
availableTools.set(tool.id, tool);
|
|
2053
2252
|
});
|
|
2054
|
-
// Add
|
|
2055
|
-
if (
|
|
2056
|
-
|
|
2253
|
+
// Add flow-level tools (these take precedence)
|
|
2254
|
+
if (flow) {
|
|
2255
|
+
flow.getTools().forEach((tool) => {
|
|
2057
2256
|
availableTools.set(tool.id, tool);
|
|
2058
2257
|
});
|
|
2059
2258
|
}
|
|
@@ -2096,7 +2295,7 @@ class ResponseModal {
|
|
|
2096
2295
|
// Convert to the format expected by AI providers
|
|
2097
2296
|
return Array.from(availableTools.values()).map((tool) => ({
|
|
2098
2297
|
id: tool.id,
|
|
2099
|
-
name: tool.
|
|
2298
|
+
name: tool.id || tool.id,
|
|
2100
2299
|
description: tool.description,
|
|
2101
2300
|
parameters: tool.parameters,
|
|
2102
2301
|
}));
|
|
@@ -2105,7 +2304,7 @@ class ResponseModal {
|
|
|
2105
2304
|
* Execute a prepare or finalize function/tool
|
|
2106
2305
|
* @private
|
|
2107
2306
|
*/
|
|
2108
|
-
async executePrepareFinalize(prepareOrFinalize, context, data,
|
|
2307
|
+
async executePrepareFinalize(prepareOrFinalize, context, data, flow, step) {
|
|
2109
2308
|
if (!prepareOrFinalize)
|
|
2110
2309
|
return;
|
|
2111
2310
|
if (typeof prepareOrFinalize === "function") {
|
|
@@ -2119,7 +2318,7 @@ class ResponseModal {
|
|
|
2119
2318
|
// Tool ID - use ToolManager for unified resolution
|
|
2120
2319
|
const toolManager = this.getToolManager();
|
|
2121
2320
|
if (toolManager) {
|
|
2122
|
-
tool = toolManager.find(prepareOrFinalize, undefined, step,
|
|
2321
|
+
tool = toolManager.find(prepareOrFinalize, undefined, step, flow);
|
|
2123
2322
|
}
|
|
2124
2323
|
else {
|
|
2125
2324
|
// Fallback to legacy resolution if ToolManager not available
|
|
@@ -2129,9 +2328,9 @@ class ResponseModal {
|
|
|
2129
2328
|
this.agent.getTools().forEach((t) => {
|
|
2130
2329
|
availableTools.set(t.id, t);
|
|
2131
2330
|
});
|
|
2132
|
-
// Add
|
|
2133
|
-
if (
|
|
2134
|
-
|
|
2331
|
+
// Add flow-level tools
|
|
2332
|
+
if (flow) {
|
|
2333
|
+
flow.getTools().forEach((t) => {
|
|
2135
2334
|
availableTools.set(t.id, t);
|
|
2136
2335
|
});
|
|
2137
2336
|
}
|
|
@@ -2183,24 +2382,6 @@ class ResponseModal {
|
|
|
2183
2382
|
}
|
|
2184
2383
|
}
|
|
2185
2384
|
}
|
|
2186
|
-
/**
|
|
2187
|
-
* Merge terms with route-specific taking precedence on conflicts
|
|
2188
|
-
* @private
|
|
2189
|
-
*/
|
|
2190
|
-
mergeTerms(agentTerms, routeTerms) {
|
|
2191
|
-
const merged = new Map();
|
|
2192
|
-
// Add agent terms first
|
|
2193
|
-
agentTerms.forEach((term) => {
|
|
2194
|
-
const name = typeof term.name === "string" ? term.name : term.name.toString();
|
|
2195
|
-
merged.set(name, term);
|
|
2196
|
-
});
|
|
2197
|
-
// Add route terms (these take precedence)
|
|
2198
|
-
routeTerms.forEach((term) => {
|
|
2199
|
-
const name = typeof term.name === "string" ? term.name : term.name.toString();
|
|
2200
|
-
merged.set(name, term);
|
|
2201
|
-
});
|
|
2202
|
-
return Array.from(merged.values());
|
|
2203
|
-
}
|
|
2204
2385
|
}
|
|
2205
2386
|
exports.ResponseModal = ResponseModal;
|
|
2206
2387
|
//# sourceMappingURL=ResponseModal.js.map
|