@falai/agent 2.6.1 → 2.7.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 +1 -1
- package/dist/adapters/MemoryAdapter.js +29 -33
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +16 -11
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.d.ts +1 -0
- package/dist/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/adapters/RedisAdapter.js +75 -25
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +7 -29
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/adapters/sessionRow.d.ts +22 -0
- package/dist/adapters/sessionRow.d.ts.map +1 -0
- package/dist/adapters/sessionRow.js +48 -0
- package/dist/adapters/sessionRow.js.map +1 -0
- package/dist/cjs/adapters/MemoryAdapter.js +29 -33
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +16 -11
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.d.ts +1 -0
- package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +75 -25
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +7 -29
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/sessionRow.d.ts +22 -0
- package/dist/cjs/adapters/sessionRow.d.ts.map +1 -0
- package/dist/cjs/adapters/sessionRow.js +52 -0
- package/dist/cjs/adapters/sessionRow.js.map +1 -0
- package/dist/cjs/core/Agent.d.ts +8 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +40 -2
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +8 -18
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.js +23 -26
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
- package/dist/cjs/core/CompactionEngine.d.ts +14 -1
- package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
- package/dist/cjs/core/CompactionEngine.js +30 -6
- package/dist/cjs/core/CompactionEngine.js.map +1 -1
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
- package/dist/cjs/core/FlowRouter.js +32 -10
- package/dist/cjs/core/FlowRouter.js.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.js +3 -5
- package/dist/cjs/core/ResponseGenerationError.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +29 -0
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +141 -18
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +36 -6
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +208 -73
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -1
- package/dist/cjs/core/SessionFinalizer.js +30 -3
- package/dist/cjs/core/SessionFinalizer.js.map +1 -1
- package/dist/cjs/core/SessionManager.d.ts +10 -1
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +43 -16
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
- package/dist/cjs/core/SignalProcessor.js +5 -77
- package/dist/cjs/core/SignalProcessor.js.map +1 -1
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +50 -2
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StepLifecycle.d.ts +16 -6
- package/dist/cjs/core/StepLifecycle.d.ts.map +1 -1
- package/dist/cjs/core/StepLifecycle.js +97 -14
- package/dist/cjs/core/StepLifecycle.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.js +28 -4
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts +5 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -1
- package/dist/cjs/core/ToolLoopExecutor.js +155 -63
- package/dist/cjs/core/ToolLoopExecutor.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +1 -1
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +40 -17
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/core/flow-namespace.d.ts +15 -0
- package/dist/cjs/core/flow-namespace.d.ts.map +1 -1
- package/dist/cjs/core/flow-namespace.js +22 -0
- package/dist/cjs/core/flow-namespace.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +10 -3
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +54 -73
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +9 -3
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +24 -68
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +9 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.js +101 -74
- package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +1 -1
- package/dist/cjs/providers/errorClassification.d.ts +7 -0
- package/dist/cjs/providers/errorClassification.d.ts.map +1 -1
- package/dist/cjs/providers/errorClassification.js +27 -0
- package/dist/cjs/providers/errorClassification.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +26 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/flow.d.ts +49 -32
- package/dist/cjs/types/flow.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -1
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/tool.d.ts +2 -0
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +4 -4
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +10 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/retry.d.ts +99 -1
- package/dist/cjs/utils/retry.d.ts.map +1 -1
- package/dist/cjs/utils/retry.js +140 -3
- package/dist/cjs/utils/retry.js.map +1 -1
- package/dist/cjs/utils/serialize.d.ts +17 -0
- package/dist/cjs/utils/serialize.d.ts.map +1 -1
- package/dist/cjs/utils/serialize.js +33 -0
- package/dist/cjs/utils/serialize.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +21 -1
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +36 -4
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/core/Agent.d.ts +8 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +40 -2
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts +8 -18
- package/dist/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/core/AutoChainExecutor.js +23 -26
- package/dist/core/AutoChainExecutor.js.map +1 -1
- package/dist/core/CompactionEngine.d.ts +14 -1
- package/dist/core/CompactionEngine.d.ts.map +1 -1
- package/dist/core/CompactionEngine.js +30 -6
- package/dist/core/CompactionEngine.js.map +1 -1
- package/dist/core/FlowRouter.d.ts.map +1 -1
- package/dist/core/FlowRouter.js +32 -10
- package/dist/core/FlowRouter.js.map +1 -1
- package/dist/core/ResponseGenerationError.d.ts.map +1 -1
- package/dist/core/ResponseGenerationError.js +3 -5
- package/dist/core/ResponseGenerationError.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +29 -0
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +142 -19
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +36 -6
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +208 -73
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SessionFinalizer.d.ts.map +1 -1
- package/dist/core/SessionFinalizer.js +31 -4
- package/dist/core/SessionFinalizer.js.map +1 -1
- package/dist/core/SessionManager.d.ts +10 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +44 -17
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SignalProcessor.d.ts.map +1 -1
- package/dist/core/SignalProcessor.js +5 -77
- package/dist/core/SignalProcessor.js.map +1 -1
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +50 -2
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StepLifecycle.d.ts +16 -6
- package/dist/core/StepLifecycle.d.ts.map +1 -1
- package/dist/core/StepLifecycle.js +97 -14
- package/dist/core/StepLifecycle.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/core/StreamingToolExecutor.js +28 -4
- package/dist/core/StreamingToolExecutor.js.map +1 -1
- package/dist/core/ToolLoopExecutor.d.ts +5 -1
- package/dist/core/ToolLoopExecutor.d.ts.map +1 -1
- package/dist/core/ToolLoopExecutor.js +155 -63
- package/dist/core/ToolLoopExecutor.js.map +1 -1
- package/dist/core/ToolManager.d.ts +1 -1
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +41 -18
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/core/flow-namespace.d.ts +15 -0
- package/dist/core/flow-namespace.d.ts.map +1 -1
- package/dist/core/flow-namespace.js +22 -0
- package/dist/core/flow-namespace.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +10 -3
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +56 -75
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +9 -3
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +26 -70
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.d.ts +9 -0
- package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.js +103 -76
- package/dist/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +1 -1
- package/dist/providers/errorClassification.d.ts +7 -0
- package/dist/providers/errorClassification.d.ts.map +1 -1
- package/dist/providers/errorClassification.js +26 -0
- package/dist/providers/errorClassification.js.map +1 -1
- package/dist/types/agent.d.ts +26 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/flow.d.ts +49 -32
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/tool.d.ts +2 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/index.d.ts +4 -4
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/retry.d.ts +99 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +137 -3
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/serialize.d.ts +17 -0
- package/dist/utils/serialize.d.ts.map +1 -1
- package/dist/utils/serialize.js +31 -0
- package/dist/utils/serialize.js.map +1 -1
- package/dist/utils/session.d.ts +21 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +33 -4
- package/dist/utils/session.js.map +1 -1
- package/docs/concepts/architecture.md +3 -3
- package/docs/concepts/directives.md +1 -1
- package/docs/guides/error-handling.md +46 -45
- package/docs/guides/flow-control.md +8 -1
- package/docs/guides/instructions.md +15 -6
- package/docs/guides/persistence.md +12 -5
- package/docs/guides/streaming.md +10 -0
- package/docs/migration/README.md +4 -0
- package/docs/migration/v2-3-to-v2-4.md +4 -0
- package/docs/migration/v2-6-to-v2-7.md +246 -0
- package/docs/reference/adapters.md +15 -1
- package/docs/reference/create-agent.md +28 -0
- package/docs/reference/directive.md +1 -1
- package/docs/reference/errors.md +29 -31
- package/docs/reference/providers.md +23 -19
- package/docs/reference/step.md +28 -21
- package/docs/reference/tool.md +14 -5
- package/docs/start/02-first-agent.md +8 -4
- package/docs/start/03-collect-data.md +19 -10
- package/examples/01-quickstart.ts +1 -1
- package/examples/02-data-extraction.ts +1 -1
- package/examples/03-tools.ts +1 -1
- package/examples/04-instructions.ts +1 -1
- package/examples/05-branching.ts +1 -1
- package/examples/06-flow-control.ts +3 -3
- package/examples/07-streaming.ts +1 -1
- package/examples/08-persistence.ts +1 -1
- package/examples/09-signals.ts +1 -1
- package/package.json +2 -2
- package/src/adapters/MemoryAdapter.ts +29 -33
- package/src/adapters/PostgreSQLAdapter.ts +23 -18
- package/src/adapters/RedisAdapter.ts +81 -34
- package/src/adapters/SQLiteAdapter.ts +10 -31
- package/src/adapters/sessionRow.ts +57 -0
- package/src/core/Agent.ts +48 -2
- package/src/core/AutoChainExecutor.ts +37 -50
- package/src/core/CompactionEngine.ts +40 -6
- package/src/core/FlowRouter.ts +38 -12
- package/src/core/ResponseGenerationError.ts +3 -6
- package/src/core/ResponseModal.ts +185 -19
- package/src/core/ResponsePipeline.ts +259 -85
- package/src/core/SessionFinalizer.ts +35 -5
- package/src/core/SessionManager.ts +55 -21
- package/src/core/SignalProcessor.ts +5 -85
- package/src/core/Step.ts +79 -2
- package/src/core/StepLifecycle.ts +125 -22
- package/src/core/StreamingToolExecutor.ts +31 -6
- package/src/core/ToolLoopExecutor.ts +200 -74
- package/src/core/ToolManager.ts +45 -18
- package/src/core/flow-namespace.ts +29 -0
- package/src/index.ts +4 -1
- package/src/providers/AnthropicProvider.ts +83 -131
- package/src/providers/GeminiProvider.ts +42 -116
- package/src/providers/OpenAICompatibleProvider.ts +132 -133
- package/src/providers/OpenAIProvider.ts +2 -2
- package/src/providers/errorClassification.ts +34 -0
- package/src/types/agent.ts +25 -0
- package/src/types/flow.ts +37 -39
- package/src/types/index.ts +1 -0
- package/src/types/tool.ts +2 -0
- package/src/utils/index.ts +22 -3
- package/src/utils/retry.ts +251 -2
- package/src/utils/serialize.ts +38 -0
- package/src/utils/session.ts +41 -4
- package/dist/cjs/core/DirectiveBus.d.ts +0 -88
- package/dist/cjs/core/DirectiveBus.d.ts.map +0 -1
- package/dist/cjs/core/DirectiveBus.js +0 -196
- package/dist/cjs/core/DirectiveBus.js.map +0 -1
- package/dist/core/DirectiveBus.d.ts +0 -88
- package/dist/core/DirectiveBus.d.ts.map +0 -1
- package/dist/core/DirectiveBus.js +0 -192
- package/dist/core/DirectiveBus.js.map +0 -1
- package/src/core/DirectiveBus.ts +0 -248
|
@@ -42,6 +42,7 @@ interface AgentOptions<TContext = unknown, TData = unknown> {
|
|
|
42
42
|
persistence?: PersistenceConfig<TData>;
|
|
43
43
|
knowledgeBase?: Record<string, unknown>;
|
|
44
44
|
flowSwitchMargin?: number;
|
|
45
|
+
maxHistoryMessages?: number;
|
|
45
46
|
maxAutoStepsPerTurn?: number;
|
|
46
47
|
maxDirectiveChain?: number;
|
|
47
48
|
maxToolLoops?: number;
|
|
@@ -75,6 +76,7 @@ interface AgentOptions<TContext = unknown, TData = unknown> {
|
|
|
75
76
|
| `persistence` | `PersistenceConfig<TData>` | no | in-memory | Session storage: `adapter`, `autoSave`, `userId`, plus `schemaVersion` / `migrateSession` for upgrading state written by older deployments. Omit for `MemoryAdapter`. |
|
|
76
77
|
| `knowledgeBase` | `Record<string, unknown>` | no | — | Arbitrary JSON inlined into the prompt as background knowledge. |
|
|
77
78
|
| `flowSwitchMargin` | `number` | no | `15` | Margin (0–100) the best alternative flow must exceed the current flow's score by before switching. Higher values make the agent stickier. |
|
|
79
|
+
| `maxHistoryMessages` | `number` | no | `400` | Hard bound on `session.history`. Applied at end-of-turn finalize (and on interim auto-saves): the oldest entries are trimmed — never splitting an assistant/tool pair — and a warning is logged. Use `compaction` for summarization instead of truncation; set `0` to disable bounding entirely. |
|
|
78
80
|
| `maxAutoStepsPerTurn` | `number` | no | `10` | Cap on consecutive `auto: true` steps per turn. Throws `FlowConfigurationError` when exceeded. |
|
|
79
81
|
| `maxDirectiveChain` | `number` | no | `10` | Cap on chained directives per turn (e.g., `goTo` → `onEnter` emits `goTo` → …). Throws `FlowConfigurationError` when exceeded. |
|
|
80
82
|
| `maxToolLoops` | `number` | no | `5` | Cap on tool-call follow-up rounds per turn, after the initial tool batch. Stops executing further tool calls when reached. Applies to both `respond()` and streaming. An explicit `0` disables tool loops. |
|
|
@@ -166,6 +168,32 @@ const agent = createAgent({
|
|
|
166
168
|
});
|
|
167
169
|
```
|
|
168
170
|
|
|
171
|
+
## Turn parameters and response surface
|
|
172
|
+
|
|
173
|
+
`respond()` / `respondStream()` share one params object (`RespondParams`). Beyond the required `history`, two per-turn knobs matter:
|
|
174
|
+
|
|
175
|
+
| Param | Type | Notes |
|
|
176
|
+
|-------|------|-------|
|
|
177
|
+
| `message` | `string` | The user's message for this turn. When set, the engine appends it to the history the model sees **and** to the returned session's history, then appends the assistant reply on top — callers who hold sessions no longer maintain history arrays by hand. Empty replies are not recorded. |
|
|
178
|
+
| `allowedFlows` | `string[]` | Restricts this turn's **routing** candidates to these flow ids/titles. Directive targets (`goTo` etc.) still resolve against the full registry. Use for entry-pin funnels instead of cloning/filtering agents. |
|
|
179
|
+
|
|
180
|
+
On the result side, `AgentResponse` carries:
|
|
181
|
+
|
|
182
|
+
- `endedFlows?: EndedFlow[]` — flows that left their active position this turn (completions, redirects, resets), each as `{ flowId, title?, reason }`. Lets consumers stop re-deriving exits from `executedSteps` + cursor inspection. Streaming chunks do not carry `endedFlows`.
|
|
183
|
+
- `metadata.tokensUsed?` — provider-reported usage for the turn's primary generation call (routing and extraction sub-calls are not included); present only when the provider reports usage. On streaming turns the same value rides on chunk `metadata`.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const response = await agent.respond({
|
|
187
|
+
history,
|
|
188
|
+
message: "I'd like the vegetarian menu",
|
|
189
|
+
allowedFlows: ["order"],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
for (const ended of response.endedFlows ?? []) {
|
|
193
|
+
console.log(`flow "${ended.title ?? ended.flowId}" ended (${ended.reason})`);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
169
197
|
## Errors
|
|
170
198
|
|
|
171
199
|
`createAgent` runs the same construction-time validation as `new Agent(...)`.
|
|
@@ -74,7 +74,7 @@ Exactly **zero or one** position field may be set per directive. Setting two or
|
|
|
74
74
|
| `abort` | `string` (reason) | `{ reason, clearSession? }` | End the conversation. The string form is sugar for `{ reason: <string> }`. When `clearSession: true`, the session is cleared at the next persistence write. `reply` cannot co-exist with `abort` — an aborted conversation cannot deliver a reply. |
|
|
75
75
|
| `reset` | `true` | `{ step?, clearData?, reason? }` | Restart the current flow. `step` re-enters at a specific step (default: initial). `clearData: true` clears every field declared in the flow's `requiredFields` and `optionalFields`. |
|
|
76
76
|
|
|
77
|
-
Each object form carries an optional `reason: string`. The reason is **observability-only** — it appears in the per-turn directive
|
|
77
|
+
Each object form carries an optional `reason: string`. The reason is **observability-only** — it appears in the per-turn directive-chain debug log so traces are self-explaining. It does not influence routing, merging, or any pipeline decision.
|
|
78
78
|
|
|
79
79
|
### State writes
|
|
80
80
|
|
package/docs/reference/errors.md
CHANGED
|
@@ -9,7 +9,7 @@ order: 12
|
|
|
9
9
|
|
|
10
10
|
> **Where this is introduced:** [Errors](../guides/error-handling.md)
|
|
11
11
|
|
|
12
|
-
`@falai/agent` throws typed `Error` subclasses for every failure mode the framework owns. Catch them by class to discriminate construction errors from runtime errors, and by `error.name` when
|
|
12
|
+
`@falai/agent` throws typed `Error` subclasses for every failure mode the framework owns. Catch them by class to discriminate construction errors from runtime errors, and by `error.name` when a class that is not exported (`DataValidationError`) needs to be matched.
|
|
13
13
|
|
|
14
14
|
Every thrown message follows the same format contract:
|
|
15
15
|
|
|
@@ -42,10 +42,8 @@ class SessionConflictError extends Error {
|
|
|
42
42
|
expectedVersion: number;
|
|
43
43
|
actualVersion: number | undefined;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
// Internal — match by `error.name` (not exported from the package barrel)
|
|
47
|
-
class DataValidationError extends Error { errors: ValidationError[] }
|
|
48
45
|
class ResponseGenerationError extends Error {
|
|
46
|
+
cause?: unknown; // native `cause` — the original error
|
|
49
47
|
details?: {
|
|
50
48
|
originalError?: unknown;
|
|
51
49
|
params?: Record<string, unknown>;
|
|
@@ -53,6 +51,9 @@ class ResponseGenerationError extends Error {
|
|
|
53
51
|
context?: Record<string, unknown>;
|
|
54
52
|
};
|
|
55
53
|
}
|
|
54
|
+
|
|
55
|
+
// Internal — match by `error.name` (not exported from the package barrel)
|
|
56
|
+
class DataValidationError extends Error { errors: ValidationError[] }
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
## Fields
|
|
@@ -63,8 +64,8 @@ class ResponseGenerationError extends Error {
|
|
|
63
64
|
| `ToolCreationError` | A `Tool` fails registration (invalid schema, duplicate id, builder threw). | `toolId`, `cause` | Repair the tool definition. Not user-facing. |
|
|
64
65
|
| `ToolExecutionError` | A handler throws, all retries fail, or `validateInput` cannot correct invalid args. | `toolId`, `executionContext`, `cause` | Surface a user-friendly message; optionally `agent.dispatch({ goTo: '<recovery-flow>' })`. |
|
|
65
66
|
| `DataValidationError` | `agent.respond(...)` collects values that violate the declared `schema`. | `errors: ValidationError[]` | Re-prompt for the offending fields, then retry. |
|
|
66
|
-
| `ResponseGenerationError` |
|
|
67
|
-
| `ProviderError` | A provider call fails terminally — retries and `backupModels` exhausted. Normalized across all vendors. | `code`, `provider`, `cause` (original SDK error) | Match on `code`: backoff for `rate_limited`/`overloaded`, fix credentials for `auth`, fail fast otherwise.
|
|
67
|
+
| `ResponseGenerationError` | A turn fails for any reason other than a typed error the framework rethrows bare (see `ProviderError` below) — provider call failure, malformed structured output, hook failure. Exported; also carries the original error on the native `.cause`. | `cause`, `details.phase`, `details.originalError` | Retry with backoff, fall back to a different provider, or surface a soft failure to the user. |
|
|
68
|
+
| `ProviderError` | A provider call fails terminally — retries and `backupModels` exhausted. Normalized across all vendors. Propagates **bare** out of `respond()` (`instanceof` survives — no unwrapping needed). Streaming turns surface it wrapped on the final chunk's `error`. | `code`, `provider`, `cause` (original SDK error) | Match on `code`: backoff for `rate_limited`/`overloaded`, fix credentials for `auth`, fail fast otherwise. |
|
|
68
69
|
| `SessionConflictError` | A session save carries a stale `version` — another writer persisted the session after this one loaded it (concurrent `respond()` calls, parallel webhooks, two tabs). | `sessionId`, `expectedVersion`, `actualVersion` | Reload the session and retry the operation, or surface the conflict. |
|
|
69
70
|
| `NotImplementedError` | A reserved option is set to a value this version does not support (e.g. `routerMode: 'embedding'` in v2.0). | `message` | Use a supported value. |
|
|
70
71
|
|
|
@@ -75,6 +76,7 @@ class ResponseGenerationError extends Error {
|
|
|
75
76
|
```typescript
|
|
76
77
|
import {
|
|
77
78
|
FlowConfigurationError,
|
|
79
|
+
ResponseGenerationError,
|
|
78
80
|
ToolExecutionError,
|
|
79
81
|
NotImplementedError,
|
|
80
82
|
} from "@falai/agent";
|
|
@@ -93,7 +95,9 @@ try {
|
|
|
93
95
|
if (err instanceof Error && err.name === "DataValidationError") {
|
|
94
96
|
return "I need you to clarify a few details — let's try that again.";
|
|
95
97
|
}
|
|
96
|
-
if (err instanceof
|
|
98
|
+
if (err instanceof ResponseGenerationError) {
|
|
99
|
+
// err.cause is the original error — e.g. a provider SDK failure.
|
|
100
|
+
log.warn({ cause: err.cause, phase: err.details?.phase }, err.message);
|
|
97
101
|
return "I'm having trouble reaching the model. Please retry.";
|
|
98
102
|
}
|
|
99
103
|
throw err;
|
|
@@ -102,38 +106,33 @@ try {
|
|
|
102
106
|
|
|
103
107
|
### 2. Matching provider failures by normalized code
|
|
104
108
|
|
|
105
|
-
|
|
109
|
+
`ProviderError` propagates **bare** out of `respond()` — catch it with `instanceof`, no unwrapping. The original SDK/HTTP error is on `.cause`.
|
|
106
110
|
|
|
107
111
|
```typescript
|
|
108
112
|
import { ProviderError } from "@falai/agent";
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return retryWithBackoff(); // transient — wait and retry
|
|
125
|
-
case "auth":
|
|
126
|
-
throw providerError; // config bug — crash loudly
|
|
127
|
-
default:
|
|
128
|
-
log.error({ cause: providerError.cause }, providerError.message);
|
|
129
|
-
return "I'm having trouble reaching the model. Please retry.";
|
|
114
|
+
try {
|
|
115
|
+
const response = await agent.respond({ history });
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err instanceof ProviderError) {
|
|
118
|
+
switch (err.code) {
|
|
119
|
+
case "rate_limited":
|
|
120
|
+
case "overloaded":
|
|
121
|
+
return retryWithBackoff(); // transient — wait and retry
|
|
122
|
+
case "auth":
|
|
123
|
+
throw err; // config bug — crash loudly
|
|
124
|
+
default:
|
|
125
|
+
log.error({ cause: err.cause }, err.message);
|
|
126
|
+
return "I'm having trouble reaching the model. Please retry.";
|
|
127
|
+
}
|
|
130
128
|
}
|
|
129
|
+
throw err;
|
|
131
130
|
}
|
|
132
131
|
```
|
|
133
132
|
|
|
134
133
|
### 3. Recovering from a session conflict
|
|
135
134
|
|
|
136
|
-
`SessionConflictError` means another writer persisted the session between your load and your save. Reload, then retry.
|
|
135
|
+
`SessionConflictError` means another writer persisted the session between your load and your save. Like `ProviderError`, it propagates bare out of `respond()`. Reload, then retry.
|
|
137
136
|
|
|
138
137
|
```typescript
|
|
139
138
|
import { SessionConflictError } from "@falai/agent";
|
|
@@ -141,8 +140,7 @@ import { SessionConflictError } from "@falai/agent";
|
|
|
141
140
|
try {
|
|
142
141
|
await agent.respond({ history, session });
|
|
143
142
|
} catch (err) {
|
|
144
|
-
|
|
145
|
-
if (err instanceof SessionConflictError || original instanceof SessionConflictError) {
|
|
143
|
+
if (err instanceof SessionConflictError) {
|
|
146
144
|
const fresh = await agent.session.getOrCreate(sessionId);
|
|
147
145
|
return agent.respond({ history, session: fresh });
|
|
148
146
|
}
|
|
@@ -63,11 +63,11 @@ import {
|
|
|
63
63
|
|
|
64
64
|
const provider =
|
|
65
65
|
process.env.PROVIDER === "openai"
|
|
66
|
-
? new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: "gpt-5.
|
|
66
|
+
? new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: "gpt-5.6" })
|
|
67
67
|
: process.env.PROVIDER === "anthropic"
|
|
68
|
-
? new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY!, model: "claude-sonnet-
|
|
68
|
+
? new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY!, model: "claude-sonnet-5" })
|
|
69
69
|
: process.env.PROVIDER === "openrouter"
|
|
70
|
-
? new OpenRouterProvider({ apiKey: process.env.OPENROUTER_API_KEY!, model: "anthropic/claude-sonnet-
|
|
70
|
+
? new OpenRouterProvider({ apiKey: process.env.OPENROUTER_API_KEY!, model: "anthropic/claude-sonnet-5" })
|
|
71
71
|
: process.env.PROVIDER === "deepseek"
|
|
72
72
|
? new DeepSeekProvider({ apiKey: process.env.DEEPSEEK_API_KEY!, model: "deepseek-chat" })
|
|
73
73
|
: new GeminiProvider({ apiKey: process.env.GEMINI_API_KEY!, model: "gemini-3.1-pro-preview" });
|
|
@@ -88,6 +88,7 @@ interface GeminiProviderOptions {
|
|
|
88
88
|
backupModels?: string[];
|
|
89
89
|
config?: Partial<GenerateContentConfig>; // from @google/genai
|
|
90
90
|
retryConfig?: { timeout?: number; retries?: number };
|
|
91
|
+
client?: GoogleGenAI; // pre-configured SDK client override
|
|
91
92
|
}
|
|
92
93
|
```
|
|
93
94
|
|
|
@@ -95,12 +96,13 @@ interface GeminiProviderOptions {
|
|
|
95
96
|
|
|
96
97
|
| Field | Type | Required | Default | Notes |
|
|
97
98
|
|-------|------|----------|---------|-------|
|
|
98
|
-
| `apiKey` | `string` | yes | — | Throws if empty. |
|
|
99
|
+
| `apiKey` | `string` | yes* | — | Throws if empty (unless `client` is set). |
|
|
99
100
|
| `model` | `string` | yes | — | Use the model id, e.g. `"gemini-3.1-pro-preview"`. |
|
|
100
|
-
| `backupModels` | `string[]` | no | `[]` | Tried in order on
|
|
101
|
+
| `backupModels` | `string[]` | no | `[]` | Tried in order on retriable failures (rate limits, overload, timeouts, network). |
|
|
101
102
|
| `config` | `Partial<GenerateContentConfig>` | no | — | Vendor-typed defaults (e.g. `temperature`, `systemInstruction`). |
|
|
102
|
-
| `retryConfig.timeout` | `number` | no | `60000` | Per-attempt timeout in ms. |
|
|
103
|
+
| `retryConfig.timeout` | `number` | no | `60000` | Per-attempt timeout in ms. On streams it also bounds time-to-first-token. |
|
|
103
104
|
| `retryConfig.retries` | `number` | no | `3` | Total attempts before giving up. |
|
|
105
|
+
| `client` | `GoogleGenAI` | no | — | Pre-configured SDK client; overrides the internally-constructed one. Intended for tests injecting scripted transports; production callers should pass `apiKey`. |
|
|
104
106
|
|
|
105
107
|
### Example
|
|
106
108
|
|
|
@@ -136,7 +138,7 @@ interface OpenAIProviderOptions {
|
|
|
136
138
|
|-------|------|----------|---------|-------|
|
|
137
139
|
| `apiKey` | `string` | yes | — | Throws if empty. |
|
|
138
140
|
| `organization` | `string` | no | — | Forwarded as `OpenAI-Organization`. |
|
|
139
|
-
| `model` | `string` | yes | — | e.g. `"gpt-5.
|
|
141
|
+
| `model` | `string` | yes | — | e.g. `"gpt-5.6"`, `"gpt-5.4-mini"`. |
|
|
140
142
|
| `backupModels` | `string[]` | no | `[]` | Tried in order on overload/rate-limit errors. |
|
|
141
143
|
| `config` | OpenAI params | no | — | Defaults for `temperature`, `top_p`, etc. |
|
|
142
144
|
| `retryConfig.timeout` | `number` | no | `60000` | Per-attempt timeout in ms. |
|
|
@@ -147,7 +149,7 @@ interface OpenAIProviderOptions {
|
|
|
147
149
|
```typescript
|
|
148
150
|
const openai = new OpenAIProvider({
|
|
149
151
|
apiKey: process.env.OPENAI_API_KEY!,
|
|
150
|
-
model: "gpt-5.
|
|
152
|
+
model: "gpt-5.6",
|
|
151
153
|
organization: "org_abc",
|
|
152
154
|
config: { temperature: 0.2 },
|
|
153
155
|
});
|
|
@@ -166,6 +168,7 @@ interface AnthropicProviderOptions {
|
|
|
166
168
|
backupModels?: string[];
|
|
167
169
|
config?: Partial<Omit<MessageCreateParamsNonStreaming, "model" | "messages">>;
|
|
168
170
|
retryConfig?: { timeout?: number; retries?: number };
|
|
171
|
+
client?: Anthropic; // pre-configured SDK client override
|
|
169
172
|
}
|
|
170
173
|
```
|
|
171
174
|
|
|
@@ -173,19 +176,20 @@ interface AnthropicProviderOptions {
|
|
|
173
176
|
|
|
174
177
|
| Field | Type | Required | Default | Notes |
|
|
175
178
|
|-------|------|----------|---------|-------|
|
|
176
|
-
| `apiKey` | `string` | yes | — | Throws if empty. |
|
|
177
|
-
| `model` | `string` | yes | — | e.g. `"claude-sonnet-
|
|
178
|
-
| `backupModels` | `string[]` | no | `[]` | Tried in order on
|
|
179
|
+
| `apiKey` | `string` | yes* | — | Throws if empty (unless `client` is set). |
|
|
180
|
+
| `model` | `string` | yes | — | e.g. `"claude-sonnet-5"`, `"claude-opus-5"`. |
|
|
181
|
+
| `backupModels` | `string[]` | no | `[]` | Tried in order on retriable failures (rate limits, overload incl. 529, timeouts, network). |
|
|
179
182
|
| `config` | Anthropic params | no | — | Defaults for `max_tokens`, `system`, etc. The provider sets `max_tokens=4096` if neither `config.max_tokens` nor `parameters.maxOutputTokens` is set. |
|
|
180
|
-
| `retryConfig.timeout` | `number` | no | `60000` | Per-attempt timeout in ms. |
|
|
183
|
+
| `retryConfig.timeout` | `number` | no | `60000` | Per-attempt timeout in ms. On streams it also bounds time-to-first-token. |
|
|
181
184
|
| `retryConfig.retries` | `number` | no | `3` | Total attempts. |
|
|
185
|
+
| `client` | `Anthropic` | no | — | Pre-configured SDK client; overrides the internally-constructed one. Intended for tests injecting scripted transports; production callers should pass `apiKey`. |
|
|
182
186
|
|
|
183
187
|
### Example
|
|
184
188
|
|
|
185
189
|
```typescript
|
|
186
190
|
const anthropic = new AnthropicProvider({
|
|
187
191
|
apiKey: process.env.ANTHROPIC_API_KEY!,
|
|
188
|
-
model: "claude-sonnet-
|
|
192
|
+
model: "claude-sonnet-5",
|
|
189
193
|
config: { max_tokens: 8192 },
|
|
190
194
|
});
|
|
191
195
|
```
|
|
@@ -215,7 +219,7 @@ interface OpenRouterProviderOptions {
|
|
|
215
219
|
| Field | Type | Required | Default | Notes |
|
|
216
220
|
|-------|------|----------|---------|-------|
|
|
217
221
|
| `apiKey` | `string` | yes | — | Throws if empty. |
|
|
218
|
-
| `model` | `string` | yes | — | OpenRouter model id, e.g. `"anthropic/claude-sonnet-
|
|
222
|
+
| `model` | `string` | yes | — | OpenRouter model id, e.g. `"anthropic/claude-sonnet-5"`. See [openrouter.ai/models](https://openrouter.ai/models). |
|
|
219
223
|
| `backupModels` | `string[]` | no | `[]` | Tried in order on overload/capacity errors. |
|
|
220
224
|
| `siteUrl` | `string` | no | `""` | Sent as `HTTP-Referer` for OpenRouter rankings. |
|
|
221
225
|
| `siteName` | `string` | no | `""` | Sent as `X-Title` for OpenRouter rankings. |
|
|
@@ -228,8 +232,8 @@ interface OpenRouterProviderOptions {
|
|
|
228
232
|
```typescript
|
|
229
233
|
const openrouter = new OpenRouterProvider({
|
|
230
234
|
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
231
|
-
model: "anthropic/claude-sonnet-
|
|
232
|
-
backupModels: ["openai/gpt-5.
|
|
235
|
+
model: "anthropic/claude-sonnet-5",
|
|
236
|
+
backupModels: ["openai/gpt-5.6", "google/gemini-3.1-pro-preview"],
|
|
233
237
|
siteName: "My App",
|
|
234
238
|
});
|
|
235
239
|
```
|
|
@@ -326,10 +330,10 @@ All five providers share the same construction-time guards and runtime failure m
|
|
|
326
330
|
| `apiKey` is empty or missing | `Error("<vendor> API key is required")` | Thrown from the constructor. |
|
|
327
331
|
| `model` is empty or missing | `Error("Model is required. ...")` | Thrown from the constructor. |
|
|
328
332
|
| Vendor returns no text and no tool calls | `Error("No response from <vendor>")` | Surfaces as a `ResponseGenerationError` once it bubbles through the agent. |
|
|
329
|
-
| Primary and every backup model fail | `ProviderError` with a normalized `code` | After exhausting retries and `backupModels`.
|
|
333
|
+
| Primary and every backup model fail | `ProviderError` with a normalized `code` | After exhausting retries and `backupModels`. Propagates bare out of `respond()`. |
|
|
330
334
|
| Anthropic streaming with `system: undefined` | Vendor 400 | Set `config.system` or rely on history-derived system messages. |
|
|
331
335
|
|
|
332
|
-
The retry/backup logic only kicks in for transient errors:
|
|
336
|
+
The retry/backup logic only kicks in for **transient** errors: rate limits (429), overload/availability (500/503, Anthropic's 529, `overloaded`-style codes and messages such as `overloaded`, `unavailable`, `internal error`, or OpenRouter's `capacity`), timeouts, and network faults. Deterministic failures — auth (401/403), invalid requests (400/404/422), and caller aborts — fail fast without burning the retry budget. On streaming calls, only failures *before the first chunk* are retried; `retryConfig.timeout` doubles as the time-to-first-token deadline so a stream that opens and stalls is treated as failed.
|
|
333
337
|
|
|
334
338
|
### `ProviderError`
|
|
335
339
|
|
|
@@ -358,7 +362,7 @@ try {
|
|
|
358
362
|
}
|
|
359
363
|
```
|
|
360
364
|
|
|
361
|
-
When the failure
|
|
365
|
+
When the failure surfaces through `agent.respond(...)`, the `ProviderError` propagates **bare** — catch it with `instanceof`, no unwrapping. (On streaming turns, errors arrive wrapped as `ResponseGenerationError` on the final chunk's `error` field, with the original on `.cause`.) See [Errors](./errors.md).
|
|
362
366
|
|
|
363
367
|
## Related
|
|
364
368
|
|
package/docs/reference/step.md
CHANGED
|
@@ -101,30 +101,33 @@ interface StepLifecycleHooks<TContext = unknown, TData = unknown> {
|
|
|
101
101
|
| `skip` | `(ctx) => boolean \| Promise<boolean>` or array | no | — | Code-evaluated skip predicates (OR semantics). When any predicate returns `true`, the step is bypassed. Only code predicates — no AI strings. |
|
|
102
102
|
| `tools` | `(string \| Tool<TContext, TData>)[]` | no | `[]` | Tools available during this step. Strings are resolved against the agent's tool registry; objects are inline tools. Stacked on top of agent and flow scopes. |
|
|
103
103
|
| `instructions` | `Instruction<TContext, TData>[]` | no | `[]` | Step-scoped behavioural statements (`kind: 'must' \| 'never' \| 'should'`). Active only while this step is current. See [Instruction](./instruction.md). |
|
|
104
|
-
| `prepare` | function, tool id, or `Tool` object | no | — |
|
|
105
|
-
| `finalize` | function, tool id, or `Tool` object | no | — |
|
|
106
|
-
| `hooks` | `StepLifecycleHooks<TContext, TData>` | no | — |
|
|
104
|
+
| `prepare` | function, tool id, or `Tool` object | no | — | Pre-LLM hook. Receives `(context, data?)` and returns `void \| PrepareResult` (a Directive may be returned too). `PrepareResult` includes the pre-LLM augmentation fields `appendPrompt: string[]` and `injectTools: Tool[]` — honored by auto steps; other step kinds drop them with a loud warning. |
|
|
105
|
+
| `finalize` | function, tool id, or `Tool` object | no | — | Post-generation hook, runs before persistence. Same shape as `prepare`. A returned `goTo`/`goToStep`/`reset`/`complete` redirects the **next** turn; state writes land immediately. |
|
|
106
|
+
| `hooks` | `StepLifecycleHooks<TContext, TData>` | no | — | Alternative spelling of `prepare`/`finalize` (`hooks.prepare`, `hooks.finalize`). Both spellings run when declared together — shorthand first, then the hook, returns merged via Algorithm 4. |
|
|
107
107
|
| `branches` | `BranchEntry<TContext, TData>[]` | no | — | Explicit source-local fork. Evaluated after `finalize`, before linear successor selection. The first entry whose `if`/`when` passes wins; its `then` resolves to a step id, a flow id, or a `Directive`. See [Branches](./branches.md). |
|
|
108
108
|
|
|
109
109
|
### Lifecycle hooks
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
`finalize`
|
|
113
|
-
|
|
111
|
+
Two hook positions exist per step: `prepare` (before generation) and
|
|
112
|
+
`finalize` (after generation, before persistence). Each is reachable
|
|
113
|
+
through a top-level field, a `hooks.*` entry, or both — declaring both
|
|
114
|
+
runs them in sequence (shorthand first) with their returns merged by
|
|
115
|
+
Algorithm 4 (`flow.merge`).
|
|
114
116
|
|
|
115
|
-
|
|
|
116
|
-
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `finalize` | After the LLM call and tool loop complete. | `void \| Directive` | `void \| PrepareResult` | Validate collected data, redirect with `goTo` / `goToStep`, complete the flow. |
|
|
120
|
-
| `onExit` | When the step is left (next step entered, flow completed, aborted). | `void` | n/a | Emit telemetry. Cannot influence flow control. |
|
|
117
|
+
| Position | When it fires | May return | Use it for |
|
|
118
|
+
|----------|---------------|------------|------------|
|
|
119
|
+
| `prepare` / `hooks.prepare` | Before routing on the step's turn (non-auto steps). | `void \| PrepareResult` | Write session/context state, redirect the current turn with `goToStep`/`goTo`/`reset` (queued as `pendingDirective` and applied by routing this turn). |
|
|
120
|
+
| `finalize` / `hooks.finalize` | After generation completes, BEFORE the session is persisted. | `void \| PrepareResult` | Validate collected data, write state, queue a redirect for the next turn (`pendingDirective`), e.g. `{ complete: { next: { goTo: 'Checkout' } } }`. |
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
Handler directives merge through the canonical algorithm and are
|
|
123
|
+
validated with `flow.validate`. State writes
|
|
124
|
+
(`dataUpdate`/`contextUpdate`) apply immediately to the turn session,
|
|
125
|
+
so auto-save captures them even if this is the conversation's last
|
|
126
|
+
turn.
|
|
127
|
+
|
|
128
|
+
Note for **auto steps**: their `prepare` runs inside the auto-chain,
|
|
129
|
+
where pre-LLM fields (`appendPrompt`, `injectTools`, `halt`, `reply`)
|
|
130
|
+
are honored directly.
|
|
128
131
|
|
|
129
132
|
### Resolution within a step
|
|
130
133
|
|
|
@@ -133,12 +136,12 @@ For one step, the engine walks this sequence per turn:
|
|
|
133
136
|
1. Evaluate `if` (code, AND) and `when` (AI: positive OR, `!` exclusions inhibit) — fails skip the step entirely.
|
|
134
137
|
2. Evaluate `skip` (code, OR) — true means bypass and fall through.
|
|
135
138
|
3. Check `requires` — refuse entry if any required field is missing.
|
|
136
|
-
4. Run `
|
|
139
|
+
4. Run `prepare` / `hooks.prepare`. Its merged directive applies state writes immediately; position fields queue as `pendingDirective` for this turn's routing.
|
|
137
140
|
5. **LLM step**: call the LLM with the step's prompt, tools, and instructions; tool loop runs until completion. **Auto step**: skip the LLM call. **Reply step**: render `reply` as the verbatim assistant message.
|
|
138
|
-
6. Run `finalize` / `hooks.finalize
|
|
141
|
+
6. Run `finalize` / `hooks.finalize` (before persistence). State writes land now; position fields queue for the next turn.
|
|
139
142
|
7. Evaluate `branches`. The first entry whose `if`/`when` passes wins; its `then` resolves to a step id, a flow id, or a full `Directive`. If no entry matches, fall through.
|
|
140
143
|
8. Linear successor / AI step selection.
|
|
141
|
-
9.
|
|
144
|
+
9. Persist the session (auto-save) after finalize has run.
|
|
142
145
|
|
|
143
146
|
## Examples
|
|
144
147
|
|
|
@@ -317,6 +320,10 @@ following are thrown synchronously when the agent is built:
|
|
|
317
320
|
`reset`).
|
|
318
321
|
- `FlowConfigurationError` — a `branches[i].then` Directive contains
|
|
319
322
|
an empty `goTo: {}`.
|
|
323
|
+
- `FlowConfigurationError` — a tool-based `prepare`/`finalize` (tool id
|
|
324
|
+
or `Tool` object) is combined with `hooks.prepare`/`hooks.finalize`.
|
|
325
|
+
A step can only have one handler per position — remove one or inline
|
|
326
|
+
the tool call inside a function.
|
|
320
327
|
|
|
321
328
|
Runtime errors that surface from a step's hook execution include
|
|
322
329
|
`DataValidationError` (invalid collected data), `ToolExecutionError`
|
package/docs/reference/tool.md
CHANGED
|
@@ -251,15 +251,24 @@ export const bookHotel: Tool<Ctx, Data, { id: string }> = {
|
|
|
251
251
|
};
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
+
## Directive wiring and turn semantics
|
|
255
|
+
|
|
256
|
+
Tool-emitted directives work end-to-end: both `ctx.dispatch(directive)` calls and `{ directive }` returns are collected during execution, merged via Algorithm 4, and delivered to the engine in the same turn.
|
|
257
|
+
|
|
258
|
+
- **State fields** (`dataUpdate`, `contextUpdate`) apply immediately.
|
|
259
|
+
- **A `reply` directive short-circuits the tool loop** — its verbatim text becomes the final assistant message with no follow-up LLM call.
|
|
260
|
+
- **Control-flow fields** (`goTo`, `goToStep`, `reset`, …) queue on `session.pendingDirective` and steer the *next* turn (same deferred semantics as `agent.dispatch()`).
|
|
261
|
+
|
|
262
|
+
A handler that **throws** (or a call to an unregistered tool) never crashes the turn: the executor reports a failure result *to the model* — a `role: "tool"` message shaped `{"success":false,"error":"…"}` — so it can react to the failed call instead of the framework fabricating a success.
|
|
263
|
+
|
|
254
264
|
## Errors
|
|
255
265
|
|
|
256
|
-
Misuse surfaces as typed errors from
|
|
266
|
+
Misuse surfaces as typed errors from registration-time validation; execution-time problems degrade to failed tool results rather than thrown errors:
|
|
257
267
|
|
|
258
|
-
- `
|
|
268
|
+
- `ToolCreationError` — invalid tool definition at registration (missing id/handler, duplicate id, bad schema).
|
|
259
269
|
- `FlowConfigurationError` — a returned `directive` is malformed (e.g., two position fields set, or `goTo` references an unknown flow/step).
|
|
260
|
-
- `
|
|
261
|
-
|
|
262
|
-
Permission denials (`checkPermissions` returning `allowed: false`) are surfaced as a structured tool result with `success: false` and `error: <reason>` rather than a thrown error — this keeps the AI's reasoning loop intact.
|
|
270
|
+
- Execution failures — a thrown handler, `success: false` return, permission denial, failed `validateInput`, timeout, or unknown tool name all become structured `success: false` tool results surfaced to the model, keeping the AI's reasoning loop intact.
|
|
271
|
+
- `DataValidationError` — `dataUpdate` violates the agent schema (logged; the call reports failure instead of applying the write).
|
|
263
272
|
|
|
264
273
|
## Related
|
|
265
274
|
|
|
@@ -32,7 +32,9 @@ const agent = createAgent({
|
|
|
32
32
|
}],
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
const response = await agent.respond(
|
|
35
|
+
const response = await agent.respond({
|
|
36
|
+
history: [{ role: "user", content: "Hi, I'm Alice" }],
|
|
37
|
+
});
|
|
36
38
|
console.log(response.message);
|
|
37
39
|
```
|
|
38
40
|
|
|
@@ -110,14 +112,16 @@ requiredFields: ["name"],
|
|
|
110
112
|
|
|
111
113
|
For this agent, completion happens on the first turn. For a longer flow, the gate would force more steps before the model wraps up.
|
|
112
114
|
|
|
113
|
-
### `agent.respond(
|
|
115
|
+
### `agent.respond(params)`
|
|
114
116
|
|
|
115
117
|
```typescript
|
|
116
|
-
const response = await agent.respond(
|
|
118
|
+
const response = await agent.respond({
|
|
119
|
+
history: [{ role: "user", content: "Hi, I'm Alice" }],
|
|
120
|
+
});
|
|
117
121
|
console.log(response.message);
|
|
118
122
|
```
|
|
119
123
|
|
|
120
|
-
`respond(
|
|
124
|
+
`respond(params)` runs one turn end to end: route to a flow, extract data, walk auto-step chains, call the LLM, deliver the assistant message, persist. `params` takes `history` (required — the conversation so far as `{ role, content }[]`) and optionally `session`, `contextOverride`, and `signal`. When no session is passed, the agent manages one internally; pass an explicit session (see below) for server-side, multi-conversation use. It returns an `AgentResponse` with the fields you usually want on hand:
|
|
121
125
|
|
|
122
126
|
| Field | Type | What it is |
|
|
123
127
|
|-------|------|------------|
|
|
@@ -147,16 +147,15 @@ The pattern is consistent: the schema describes the universe of possible data, e
|
|
|
147
147
|
Run the file with a single message that contains all three fields:
|
|
148
148
|
|
|
149
149
|
```typescript
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
);
|
|
150
|
+
const response = await agent.respond({
|
|
151
|
+
history: [
|
|
152
|
+
{ role: "user", content: "I want a hotel in Lisbon for two people next Friday." },
|
|
153
|
+
],
|
|
154
|
+
});
|
|
156
155
|
|
|
157
156
|
console.log(response.message);
|
|
158
|
-
console.log(response.data);
|
|
159
|
-
console.log(response.currentStep?.id);
|
|
157
|
+
console.log(response.session?.data);
|
|
158
|
+
console.log(response.session?.currentStep?.id);
|
|
160
159
|
```
|
|
161
160
|
|
|
162
161
|
The output is roughly:
|
|
@@ -176,7 +175,13 @@ Three things happened in one turn:
|
|
|
176
175
|
Try a message with one missing field:
|
|
177
176
|
|
|
178
177
|
```typescript
|
|
179
|
-
await agent.respond(
|
|
178
|
+
await agent.respond({
|
|
179
|
+
history: [
|
|
180
|
+
{ role: "user", content: "I want a hotel in Lisbon" },
|
|
181
|
+
{ role: "assistant", content: "Sure — when do you travel?" },
|
|
182
|
+
{ role: "user", content: "Book me a hotel in Lisbon next Friday." },
|
|
183
|
+
],
|
|
184
|
+
});
|
|
180
185
|
```
|
|
181
186
|
|
|
182
187
|
The extractor populates `city` and `checkIn`. `ask_city` and `ask_check_in` both skip — their `collect` keys are present. `ask_guests` does *not* skip — `guests` is undefined — so the engine enters it and the assistant asks how many people are travelling.
|
|
@@ -184,7 +189,11 @@ The extractor populates `city` and `checkIn`. `ask_city` and `ask_check_in` both
|
|
|
184
189
|
Try the inverse: a message with only one field.
|
|
185
190
|
|
|
186
191
|
```typescript
|
|
187
|
-
await agent.respond(
|
|
192
|
+
await agent.respond({
|
|
193
|
+
history: [
|
|
194
|
+
{ role: "user", content: "I'd like to go to Lisbon." },
|
|
195
|
+
],
|
|
196
|
+
});
|
|
188
197
|
```
|
|
189
198
|
|
|
190
199
|
The extractor populates `city` only. `ask_city` skips, `ask_check_in` enters next (its `requires: ["city"]` is satisfied), and the assistant asks for the check-in date. Three turns later, the same `confirm` step runs.
|
|
@@ -7,7 +7,7 @@ if (!process.env.GEMINI_API_KEY) throw new Error("Set GEMINI_API_KEY");
|
|
|
7
7
|
|
|
8
8
|
const agent = createAgent({
|
|
9
9
|
name: "Greeter",
|
|
10
|
-
provider: new GeminiProvider({ apiKey: process.env.GEMINI_API_KEY, model: "gemini-3.
|
|
10
|
+
provider: new GeminiProvider({ apiKey: process.env.GEMINI_API_KEY, model: "gemini-3.5-flash-lite" }),
|
|
11
11
|
schema: { type: "object", properties: { name: { type: "string" } } },
|
|
12
12
|
flows: [{
|
|
13
13
|
title: "Greet",
|
|
@@ -40,7 +40,7 @@ const agent = createAgent<Record<string, never>, BookingData>({
|
|
|
40
40
|
name: "Booking Agent",
|
|
41
41
|
provider: new GeminiProvider({
|
|
42
42
|
apiKey: process.env.GEMINI_API_KEY!,
|
|
43
|
-
model: "gemini-3.
|
|
43
|
+
model: "gemini-3.5-flash-lite",
|
|
44
44
|
}),
|
|
45
45
|
schema,
|
|
46
46
|
flows: [
|
package/examples/03-tools.ts
CHANGED
|
@@ -99,7 +99,7 @@ const agent = createAgent<AppContext, AppData>({
|
|
|
99
99
|
name: "BookingAgent",
|
|
100
100
|
provider: new GeminiProvider({
|
|
101
101
|
apiKey: process.env.GEMINI_API_KEY!,
|
|
102
|
-
model: "gemini-3.
|
|
102
|
+
model: "gemini-3.5-flash-lite",
|
|
103
103
|
}),
|
|
104
104
|
schema: {
|
|
105
105
|
type: "object",
|
package/examples/05-branching.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface Data { intent: string; query: string; answer: string }
|
|
|
13
13
|
|
|
14
14
|
const provider = new GeminiProvider({
|
|
15
15
|
apiKey: process.env.GEMINI_API_KEY!,
|
|
16
|
-
model: "gemini-3.
|
|
16
|
+
model: "gemini-3.5-flash-lite",
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
// ─── Steps with branches ─────────────────────────────────────────────────────
|
|
@@ -19,7 +19,7 @@ const agent = createAgent<{}, AppData>({
|
|
|
19
19
|
name: "FlowControlDemo",
|
|
20
20
|
provider: new GeminiProvider({
|
|
21
21
|
apiKey: process.env.GEMINI_API_KEY!,
|
|
22
|
-
model: "gemini-3.
|
|
22
|
+
model: "gemini-3.5-flash-lite",
|
|
23
23
|
}),
|
|
24
24
|
schema: {
|
|
25
25
|
type: "object",
|
|
@@ -45,8 +45,8 @@ const agent = createAgent<{}, AppData>({
|
|
|
45
45
|
// When the user confirms, complete with a chained directive
|
|
46
46
|
// that moves to the "Checkout" flow.
|
|
47
47
|
hooks: {
|
|
48
|
-
finalize: (
|
|
49
|
-
if (data
|
|
48
|
+
finalize: (_context, data) => {
|
|
49
|
+
if (data?.confirmed) {
|
|
50
50
|
const d: Directive<{}, AppData> = {
|
|
51
51
|
complete: { next: { goTo: "Checkout" }, reason: "User confirmed" },
|
|
52
52
|
};
|
package/examples/07-streaming.ts
CHANGED
package/examples/09-signals.ts
CHANGED
|
@@ -100,7 +100,7 @@ const agent = createAgent<AppContext, AppData>({
|
|
|
100
100
|
name: "SupportAgent",
|
|
101
101
|
provider: new GeminiProvider({
|
|
102
102
|
apiKey: process.env.GEMINI_API_KEY!,
|
|
103
|
-
model: "gemini-3.
|
|
103
|
+
model: "gemini-3.5-flash-lite",
|
|
104
104
|
}),
|
|
105
105
|
context: { supportTier: "free" },
|
|
106
106
|
schema: {
|