@falai/agent 2.6.0 → 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.
Files changed (320) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/MemoryAdapter.js +29 -33
  3. package/dist/adapters/MemoryAdapter.js.map +1 -1
  4. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  5. package/dist/adapters/PostgreSQLAdapter.js +16 -11
  6. package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
  7. package/dist/adapters/RedisAdapter.d.ts +1 -0
  8. package/dist/adapters/RedisAdapter.d.ts.map +1 -1
  9. package/dist/adapters/RedisAdapter.js +75 -25
  10. package/dist/adapters/RedisAdapter.js.map +1 -1
  11. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
  12. package/dist/adapters/SQLiteAdapter.js +7 -29
  13. package/dist/adapters/SQLiteAdapter.js.map +1 -1
  14. package/dist/adapters/sessionRow.d.ts +22 -0
  15. package/dist/adapters/sessionRow.d.ts.map +1 -0
  16. package/dist/adapters/sessionRow.js +48 -0
  17. package/dist/adapters/sessionRow.js.map +1 -0
  18. package/dist/cjs/adapters/MemoryAdapter.js +29 -33
  19. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
  20. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  21. package/dist/cjs/adapters/PostgreSQLAdapter.js +16 -11
  22. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
  23. package/dist/cjs/adapters/RedisAdapter.d.ts +1 -0
  24. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
  25. package/dist/cjs/adapters/RedisAdapter.js +75 -25
  26. package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
  27. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
  28. package/dist/cjs/adapters/SQLiteAdapter.js +7 -29
  29. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
  30. package/dist/cjs/adapters/sessionRow.d.ts +22 -0
  31. package/dist/cjs/adapters/sessionRow.d.ts.map +1 -0
  32. package/dist/cjs/adapters/sessionRow.js +52 -0
  33. package/dist/cjs/adapters/sessionRow.js.map +1 -0
  34. package/dist/cjs/core/Agent.d.ts +8 -0
  35. package/dist/cjs/core/Agent.d.ts.map +1 -1
  36. package/dist/cjs/core/Agent.js +43 -3
  37. package/dist/cjs/core/Agent.js.map +1 -1
  38. package/dist/cjs/core/AutoChainExecutor.d.ts +8 -18
  39. package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
  40. package/dist/cjs/core/AutoChainExecutor.js +23 -26
  41. package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
  42. package/dist/cjs/core/CompactionEngine.d.ts +14 -1
  43. package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
  44. package/dist/cjs/core/CompactionEngine.js +30 -6
  45. package/dist/cjs/core/CompactionEngine.js.map +1 -1
  46. package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
  47. package/dist/cjs/core/FlowRouter.js +32 -10
  48. package/dist/cjs/core/FlowRouter.js.map +1 -1
  49. package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -1
  50. package/dist/cjs/core/ResponseGenerationError.js +3 -5
  51. package/dist/cjs/core/ResponseGenerationError.js.map +1 -1
  52. package/dist/cjs/core/ResponseModal.d.ts +29 -0
  53. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  54. package/dist/cjs/core/ResponseModal.js +141 -18
  55. package/dist/cjs/core/ResponseModal.js.map +1 -1
  56. package/dist/cjs/core/ResponsePipeline.d.ts +36 -6
  57. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  58. package/dist/cjs/core/ResponsePipeline.js +208 -73
  59. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  60. package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -1
  61. package/dist/cjs/core/SessionFinalizer.js +30 -3
  62. package/dist/cjs/core/SessionFinalizer.js.map +1 -1
  63. package/dist/cjs/core/SessionManager.d.ts +10 -1
  64. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  65. package/dist/cjs/core/SessionManager.js +43 -16
  66. package/dist/cjs/core/SessionManager.js.map +1 -1
  67. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
  68. package/dist/cjs/core/SignalProcessor.js +5 -77
  69. package/dist/cjs/core/SignalProcessor.js.map +1 -1
  70. package/dist/cjs/core/Step.d.ts.map +1 -1
  71. package/dist/cjs/core/Step.js +50 -2
  72. package/dist/cjs/core/Step.js.map +1 -1
  73. package/dist/cjs/core/StepLifecycle.d.ts +16 -6
  74. package/dist/cjs/core/StepLifecycle.d.ts.map +1 -1
  75. package/dist/cjs/core/StepLifecycle.js +97 -14
  76. package/dist/cjs/core/StepLifecycle.js.map +1 -1
  77. package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
  78. package/dist/cjs/core/StreamingToolExecutor.js +28 -4
  79. package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
  80. package/dist/cjs/core/ToolLoopExecutor.d.ts +5 -1
  81. package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -1
  82. package/dist/cjs/core/ToolLoopExecutor.js +156 -64
  83. package/dist/cjs/core/ToolLoopExecutor.js.map +1 -1
  84. package/dist/cjs/core/ToolManager.d.ts +1 -1
  85. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  86. package/dist/cjs/core/ToolManager.js +40 -17
  87. package/dist/cjs/core/ToolManager.js.map +1 -1
  88. package/dist/cjs/core/flow-namespace.d.ts +15 -0
  89. package/dist/cjs/core/flow-namespace.d.ts.map +1 -1
  90. package/dist/cjs/core/flow-namespace.js +22 -0
  91. package/dist/cjs/core/flow-namespace.js.map +1 -1
  92. package/dist/cjs/index.d.ts +4 -1
  93. package/dist/cjs/index.d.ts.map +1 -1
  94. package/dist/cjs/index.js +5 -2
  95. package/dist/cjs/index.js.map +1 -1
  96. package/dist/cjs/providers/AnthropicProvider.d.ts +10 -3
  97. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  98. package/dist/cjs/providers/AnthropicProvider.js +54 -73
  99. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  100. package/dist/cjs/providers/GeminiProvider.d.ts +9 -3
  101. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  102. package/dist/cjs/providers/GeminiProvider.js +24 -68
  103. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  104. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +9 -0
  105. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -1
  106. package/dist/cjs/providers/OpenAICompatibleProvider.js +101 -74
  107. package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -1
  108. package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
  109. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  110. package/dist/cjs/providers/OpenAIProvider.js +1 -1
  111. package/dist/cjs/providers/errorClassification.d.ts +7 -0
  112. package/dist/cjs/providers/errorClassification.d.ts.map +1 -1
  113. package/dist/cjs/providers/errorClassification.js +27 -0
  114. package/dist/cjs/providers/errorClassification.js.map +1 -1
  115. package/dist/cjs/types/agent.d.ts +35 -0
  116. package/dist/cjs/types/agent.d.ts.map +1 -1
  117. package/dist/cjs/types/flow.d.ts +49 -32
  118. package/dist/cjs/types/flow.d.ts.map +1 -1
  119. package/dist/cjs/types/index.d.ts +1 -1
  120. package/dist/cjs/types/index.d.ts.map +1 -1
  121. package/dist/cjs/types/index.js.map +1 -1
  122. package/dist/cjs/types/tool.d.ts +2 -0
  123. package/dist/cjs/types/tool.d.ts.map +1 -1
  124. package/dist/cjs/types/tool.js.map +1 -1
  125. package/dist/cjs/utils/index.d.ts +4 -4
  126. package/dist/cjs/utils/index.d.ts.map +1 -1
  127. package/dist/cjs/utils/index.js +10 -1
  128. package/dist/cjs/utils/index.js.map +1 -1
  129. package/dist/cjs/utils/retry.d.ts +99 -1
  130. package/dist/cjs/utils/retry.d.ts.map +1 -1
  131. package/dist/cjs/utils/retry.js +140 -3
  132. package/dist/cjs/utils/retry.js.map +1 -1
  133. package/dist/cjs/utils/serialize.d.ts +17 -0
  134. package/dist/cjs/utils/serialize.d.ts.map +1 -1
  135. package/dist/cjs/utils/serialize.js +33 -0
  136. package/dist/cjs/utils/serialize.js.map +1 -1
  137. package/dist/cjs/utils/session.d.ts +21 -1
  138. package/dist/cjs/utils/session.d.ts.map +1 -1
  139. package/dist/cjs/utils/session.js +36 -4
  140. package/dist/cjs/utils/session.js.map +1 -1
  141. package/dist/core/Agent.d.ts +8 -0
  142. package/dist/core/Agent.d.ts.map +1 -1
  143. package/dist/core/Agent.js +43 -3
  144. package/dist/core/Agent.js.map +1 -1
  145. package/dist/core/AutoChainExecutor.d.ts +8 -18
  146. package/dist/core/AutoChainExecutor.d.ts.map +1 -1
  147. package/dist/core/AutoChainExecutor.js +23 -26
  148. package/dist/core/AutoChainExecutor.js.map +1 -1
  149. package/dist/core/CompactionEngine.d.ts +14 -1
  150. package/dist/core/CompactionEngine.d.ts.map +1 -1
  151. package/dist/core/CompactionEngine.js +30 -6
  152. package/dist/core/CompactionEngine.js.map +1 -1
  153. package/dist/core/FlowRouter.d.ts.map +1 -1
  154. package/dist/core/FlowRouter.js +32 -10
  155. package/dist/core/FlowRouter.js.map +1 -1
  156. package/dist/core/ResponseGenerationError.d.ts.map +1 -1
  157. package/dist/core/ResponseGenerationError.js +3 -5
  158. package/dist/core/ResponseGenerationError.js.map +1 -1
  159. package/dist/core/ResponseModal.d.ts +29 -0
  160. package/dist/core/ResponseModal.d.ts.map +1 -1
  161. package/dist/core/ResponseModal.js +142 -19
  162. package/dist/core/ResponseModal.js.map +1 -1
  163. package/dist/core/ResponsePipeline.d.ts +36 -6
  164. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  165. package/dist/core/ResponsePipeline.js +208 -73
  166. package/dist/core/ResponsePipeline.js.map +1 -1
  167. package/dist/core/SessionFinalizer.d.ts.map +1 -1
  168. package/dist/core/SessionFinalizer.js +31 -4
  169. package/dist/core/SessionFinalizer.js.map +1 -1
  170. package/dist/core/SessionManager.d.ts +10 -1
  171. package/dist/core/SessionManager.d.ts.map +1 -1
  172. package/dist/core/SessionManager.js +44 -17
  173. package/dist/core/SessionManager.js.map +1 -1
  174. package/dist/core/SignalProcessor.d.ts.map +1 -1
  175. package/dist/core/SignalProcessor.js +5 -77
  176. package/dist/core/SignalProcessor.js.map +1 -1
  177. package/dist/core/Step.d.ts.map +1 -1
  178. package/dist/core/Step.js +50 -2
  179. package/dist/core/Step.js.map +1 -1
  180. package/dist/core/StepLifecycle.d.ts +16 -6
  181. package/dist/core/StepLifecycle.d.ts.map +1 -1
  182. package/dist/core/StepLifecycle.js +97 -14
  183. package/dist/core/StepLifecycle.js.map +1 -1
  184. package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
  185. package/dist/core/StreamingToolExecutor.js +28 -4
  186. package/dist/core/StreamingToolExecutor.js.map +1 -1
  187. package/dist/core/ToolLoopExecutor.d.ts +5 -1
  188. package/dist/core/ToolLoopExecutor.d.ts.map +1 -1
  189. package/dist/core/ToolLoopExecutor.js +156 -64
  190. package/dist/core/ToolLoopExecutor.js.map +1 -1
  191. package/dist/core/ToolManager.d.ts +1 -1
  192. package/dist/core/ToolManager.d.ts.map +1 -1
  193. package/dist/core/ToolManager.js +41 -18
  194. package/dist/core/ToolManager.js.map +1 -1
  195. package/dist/core/flow-namespace.d.ts +15 -0
  196. package/dist/core/flow-namespace.d.ts.map +1 -1
  197. package/dist/core/flow-namespace.js +22 -0
  198. package/dist/core/flow-namespace.js.map +1 -1
  199. package/dist/index.d.ts +4 -1
  200. package/dist/index.d.ts.map +1 -1
  201. package/dist/index.js +2 -1
  202. package/dist/index.js.map +1 -1
  203. package/dist/providers/AnthropicProvider.d.ts +10 -3
  204. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  205. package/dist/providers/AnthropicProvider.js +56 -75
  206. package/dist/providers/AnthropicProvider.js.map +1 -1
  207. package/dist/providers/GeminiProvider.d.ts +9 -3
  208. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  209. package/dist/providers/GeminiProvider.js +26 -70
  210. package/dist/providers/GeminiProvider.js.map +1 -1
  211. package/dist/providers/OpenAICompatibleProvider.d.ts +9 -0
  212. package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -1
  213. package/dist/providers/OpenAICompatibleProvider.js +103 -76
  214. package/dist/providers/OpenAICompatibleProvider.js.map +1 -1
  215. package/dist/providers/OpenAIProvider.d.ts +1 -1
  216. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  217. package/dist/providers/OpenAIProvider.js +1 -1
  218. package/dist/providers/errorClassification.d.ts +7 -0
  219. package/dist/providers/errorClassification.d.ts.map +1 -1
  220. package/dist/providers/errorClassification.js +26 -0
  221. package/dist/providers/errorClassification.js.map +1 -1
  222. package/dist/types/agent.d.ts +35 -0
  223. package/dist/types/agent.d.ts.map +1 -1
  224. package/dist/types/flow.d.ts +49 -32
  225. package/dist/types/flow.d.ts.map +1 -1
  226. package/dist/types/index.d.ts +1 -1
  227. package/dist/types/index.d.ts.map +1 -1
  228. package/dist/types/index.js.map +1 -1
  229. package/dist/types/tool.d.ts +2 -0
  230. package/dist/types/tool.d.ts.map +1 -1
  231. package/dist/types/tool.js.map +1 -1
  232. package/dist/utils/index.d.ts +4 -4
  233. package/dist/utils/index.d.ts.map +1 -1
  234. package/dist/utils/index.js +3 -3
  235. package/dist/utils/index.js.map +1 -1
  236. package/dist/utils/retry.d.ts +99 -1
  237. package/dist/utils/retry.d.ts.map +1 -1
  238. package/dist/utils/retry.js +137 -3
  239. package/dist/utils/retry.js.map +1 -1
  240. package/dist/utils/serialize.d.ts +17 -0
  241. package/dist/utils/serialize.d.ts.map +1 -1
  242. package/dist/utils/serialize.js +31 -0
  243. package/dist/utils/serialize.js.map +1 -1
  244. package/dist/utils/session.d.ts +21 -1
  245. package/dist/utils/session.d.ts.map +1 -1
  246. package/dist/utils/session.js +33 -4
  247. package/dist/utils/session.js.map +1 -1
  248. package/docs/concepts/architecture.md +3 -3
  249. package/docs/concepts/directives.md +1 -1
  250. package/docs/guides/error-handling.md +46 -45
  251. package/docs/guides/flow-control.md +8 -1
  252. package/docs/guides/instructions.md +15 -6
  253. package/docs/guides/persistence.md +12 -5
  254. package/docs/guides/streaming.md +10 -0
  255. package/docs/migration/README.md +4 -0
  256. package/docs/migration/v2-3-to-v2-4.md +4 -0
  257. package/docs/migration/v2-6-to-v2-7.md +246 -0
  258. package/docs/reference/adapters.md +15 -1
  259. package/docs/reference/create-agent.md +30 -0
  260. package/docs/reference/directive.md +1 -1
  261. package/docs/reference/errors.md +29 -31
  262. package/docs/reference/providers.md +23 -19
  263. package/docs/reference/step.md +28 -21
  264. package/docs/reference/tool.md +14 -5
  265. package/docs/start/02-first-agent.md +8 -4
  266. package/docs/start/03-collect-data.md +19 -10
  267. package/examples/01-quickstart.ts +1 -1
  268. package/examples/02-data-extraction.ts +1 -1
  269. package/examples/03-tools.ts +1 -1
  270. package/examples/04-instructions.ts +1 -1
  271. package/examples/05-branching.ts +1 -1
  272. package/examples/06-flow-control.ts +3 -3
  273. package/examples/07-streaming.ts +1 -1
  274. package/examples/08-persistence.ts +1 -1
  275. package/examples/09-signals.ts +1 -1
  276. package/package.json +2 -2
  277. package/src/adapters/MemoryAdapter.ts +29 -33
  278. package/src/adapters/PostgreSQLAdapter.ts +23 -18
  279. package/src/adapters/RedisAdapter.ts +81 -34
  280. package/src/adapters/SQLiteAdapter.ts +10 -31
  281. package/src/adapters/sessionRow.ts +57 -0
  282. package/src/core/Agent.ts +51 -3
  283. package/src/core/AutoChainExecutor.ts +37 -50
  284. package/src/core/CompactionEngine.ts +40 -6
  285. package/src/core/FlowRouter.ts +38 -12
  286. package/src/core/ResponseGenerationError.ts +3 -6
  287. package/src/core/ResponseModal.ts +185 -19
  288. package/src/core/ResponsePipeline.ts +259 -85
  289. package/src/core/SessionFinalizer.ts +35 -5
  290. package/src/core/SessionManager.ts +55 -21
  291. package/src/core/SignalProcessor.ts +5 -85
  292. package/src/core/Step.ts +79 -2
  293. package/src/core/StepLifecycle.ts +125 -22
  294. package/src/core/StreamingToolExecutor.ts +31 -6
  295. package/src/core/ToolLoopExecutor.ts +201 -75
  296. package/src/core/ToolManager.ts +45 -18
  297. package/src/core/flow-namespace.ts +29 -0
  298. package/src/index.ts +4 -1
  299. package/src/providers/AnthropicProvider.ts +83 -131
  300. package/src/providers/GeminiProvider.ts +42 -116
  301. package/src/providers/OpenAICompatibleProvider.ts +132 -133
  302. package/src/providers/OpenAIProvider.ts +2 -2
  303. package/src/providers/errorClassification.ts +34 -0
  304. package/src/types/agent.ts +34 -0
  305. package/src/types/flow.ts +37 -39
  306. package/src/types/index.ts +1 -0
  307. package/src/types/tool.ts +2 -0
  308. package/src/utils/index.ts +22 -3
  309. package/src/utils/retry.ts +251 -2
  310. package/src/utils/serialize.ts +38 -0
  311. package/src/utils/session.ts +41 -4
  312. package/dist/cjs/core/DirectiveBus.d.ts +0 -88
  313. package/dist/cjs/core/DirectiveBus.d.ts.map +0 -1
  314. package/dist/cjs/core/DirectiveBus.js +0 -196
  315. package/dist/cjs/core/DirectiveBus.js.map +0 -1
  316. package/dist/core/DirectiveBus.d.ts +0 -88
  317. package/dist/core/DirectiveBus.d.ts.map +0 -1
  318. package/dist/core/DirectiveBus.js +0 -192
  319. package/dist/core/DirectiveBus.js.map +0 -1
  320. package/src/core/DirectiveBus.ts +0 -248
@@ -21,14 +21,14 @@ import type {
21
21
  } from "../types";
22
22
  import type { ProviderCapabilities } from "../types/ai";
23
23
  import type { HistoryItem } from "../types/history";
24
- import { withTimeoutAndRetry, withStreamRetry, resolveRetryConfig, combineAbortSignals } from "../utils/retry";
24
+ import { withTimeoutAndRetry, withStreamRetry, withBackupFallback, streamWithBackupFallback, backupFallbackLogging, resolveRetryConfig, combineAbortSignals } from "../utils/retry";
25
25
  import { assertUsableCompletion } from "../utils/completion";
26
26
  import { tryParseJSONResponse } from "../utils/json";
27
27
  import { logger } from "../utils/logger";
28
28
  import {
29
29
  classifyProviderError,
30
- getErrorMessage,
31
30
  isBackupEligible,
31
+ isRetriableProviderError,
32
32
  toProviderError,
33
33
  type ErrorClassificationOptions,
34
34
  } from "./errorClassification";
@@ -38,8 +38,8 @@ import {
38
38
  * Uses types from @google/genai package
39
39
  */
40
40
  export interface GeminiProviderOptions {
41
- /** Gemini API key */
42
- apiKey: string;
41
+ /** Gemini API key. Optional when `client` is injected (tests). */
42
+ apiKey?: string;
43
43
  /** Model to use (required) - e.g., "gemini-3.1-pro-preview" */
44
44
  model: string;
45
45
  /** Backup models to try if primary fails (default: []) */
@@ -51,6 +51,12 @@ export interface GeminiProviderOptions {
51
51
  timeout?: number;
52
52
  retries?: number;
53
53
  };
54
+ /**
55
+ * Pre-configured SDK client. Overrides the internally-constructed one.
56
+ * Intended for tests injecting scripted transports; production callers
57
+ * should pass `apiKey` instead.
58
+ */
59
+ client?: GoogleGenAIType;
54
60
  }
55
61
 
56
62
  /**
@@ -88,9 +94,9 @@ export class GeminiProvider implements AiProvider {
88
94
  private retryConfig: { timeout: number; retries: number };
89
95
 
90
96
  constructor(options: GeminiProviderOptions) {
91
- const { apiKey, model, backupModels = [], config, retryConfig } = options;
97
+ const { apiKey, model, backupModels = [], config, retryConfig, client } = options;
92
98
 
93
- if (!apiKey) {
99
+ if (!client && !apiKey) {
94
100
  throw new Error("Gemini API key is required");
95
101
  }
96
102
 
@@ -98,7 +104,7 @@ export class GeminiProvider implements AiProvider {
98
104
  throw new Error("Model is required. Example: 'gemini-3.1-pro-preview'");
99
105
  }
100
106
 
101
- this.genAI = new GoogleGenAI({ apiKey });
107
+ this.genAI = client ?? new GoogleGenAI({ apiKey });
102
108
  this.primaryModel = model;
103
109
  this.backupModels = backupModels;
104
110
  this.config = config;
@@ -352,58 +358,19 @@ export class GeminiProvider implements AiProvider {
352
358
  >(
353
359
  input: GenerateMessageInput<TContext>
354
360
  ): Promise<GenerateMessageOutput<TStructured>> {
355
- // Try primary model first
356
- try {
357
- return await this.generateWithModel(this.primaryModel, input);
358
- } catch (primaryError: unknown) {
359
- const primaryErrMsg = getErrorMessage(primaryError);
360
- logger.warn(
361
- `[GEMINI] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
362
- );
363
-
364
- if (!shouldUseBackupModel(primaryError)) {
365
- throw toProviderError(primaryError, this.name, CLASSIFICATION_OPTIONS);
366
- }
367
-
368
- logger.debug(`[GEMINI] Trying backup models`);
369
-
370
- let lastBackupError: unknown = primaryError;
371
-
372
- for (let i = 0; i < this.backupModels.length; i++) {
373
- const backupModel = this.backupModels[i];
374
- logger.debug(
375
- `[GEMINI] Trying backup model ${i + 1}/${this.backupModels.length
376
- }: ${backupModel}`
377
- );
361
+ const observer = backupFallbackLogging("[GEMINI]", shouldUseBackupModel);
378
362
 
379
- try {
380
- const result = await this.generateWithModel(backupModel, input);
381
- logger.debug(`[GEMINI] Backup model ${backupModel} succeeded`);
382
- return result as GenerateMessageOutput<TStructured>;
383
- } catch (backupError: unknown) {
384
- const backupErrMsg = getErrorMessage(backupError);
385
- logger.warn(
386
- `[GEMINI] Backup model ${backupModel} failed: ${backupErrMsg}`
387
- );
388
- lastBackupError = backupError;
389
-
390
- if (
391
- !shouldUseBackupModel(backupError) &&
392
- i < this.backupModels.length - 1
393
- ) {
394
- logger.debug(
395
- `[GEMINI] Backup model error doesn't qualify for further attempts`
396
- );
397
- break;
398
- }
399
- }
400
- }
401
-
402
- const lastBackupErrMsg = getErrorMessage(lastBackupError);
403
- logger.error(
404
- `[GEMINI] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
405
- );
406
- throw toProviderError(lastBackupError, this.name, CLASSIFICATION_OPTIONS);
363
+ try {
364
+ return await withBackupFallback({
365
+ models: [this.primaryModel, ...this.backupModels],
366
+ attempt: (model) =>
367
+ this.generateWithModel<TContext, TStructured>(model, input),
368
+ shouldTryBackup: shouldUseBackupModel,
369
+ ...observer.callbacks,
370
+ });
371
+ } catch (error: unknown) {
372
+ observer.logExhausted(error);
373
+ throw toProviderError(error, this.name, CLASSIFICATION_OPTIONS);
407
374
  }
408
375
  }
409
376
 
@@ -555,7 +522,8 @@ export class GeminiProvider implements AiProvider {
555
522
  operation,
556
523
  this.retryConfig.timeout,
557
524
  this.retryConfig.retries,
558
- `Gemini ${model}`
525
+ `Gemini ${model}`,
526
+ (error) => isRetriableProviderError(error, CLASSIFICATION_OPTIONS)
559
527
  ) as Promise<GenerateMessageOutput<TStructured>>;
560
528
  }
561
529
 
@@ -565,64 +533,22 @@ export class GeminiProvider implements AiProvider {
565
533
  >(
566
534
  input: GenerateMessageInput<TContext>
567
535
  ): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
568
- // Try primary model first
569
- try {
570
- yield* withStreamRetry(
571
- (signal) => this.generateStreamWithModel(this.primaryModel, input, signal),
572
- { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `Gemini ${this.primaryModel} stream` }
573
- );
574
- } catch (primaryError: unknown) {
575
- const primaryErrMsg = getErrorMessage(primaryError);
576
- logger.warn(
577
- `[GEMINI] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
578
- );
579
-
580
- if (!shouldUseBackupModel(primaryError)) {
581
- throw toProviderError(primaryError, this.name, CLASSIFICATION_OPTIONS);
582
- }
583
-
584
- logger.debug(`[GEMINI] Trying backup models for streaming`);
585
-
586
- let lastBackupError: unknown = primaryError;
587
-
588
- for (let i = 0; i < this.backupModels.length; i++) {
589
- const backupModel = this.backupModels[i];
590
- logger.debug(
591
- `[GEMINI] Trying backup model ${i + 1}/${this.backupModels.length
592
- }: ${backupModel}`
593
- );
536
+ const observer = backupFallbackLogging("[GEMINI]", shouldUseBackupModel, { streaming: true });
594
537
 
595
- try {
596
- yield* withStreamRetry(
597
- (signal) => this.generateStreamWithModel(backupModel, input, signal),
598
- { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `Gemini ${backupModel} stream` }
599
- );
600
- logger.debug(`[GEMINI] Backup model ${backupModel} succeeded`);
601
- return;
602
- } catch (backupError: unknown) {
603
- const backupErrMsg = getErrorMessage(backupError);
604
- logger.warn(
605
- `[GEMINI] Backup model ${backupModel} failed: ${backupErrMsg}`
606
- );
607
- lastBackupError = backupError;
608
-
609
- if (
610
- !shouldUseBackupModel(backupError) &&
611
- i < this.backupModels.length - 1
612
- ) {
613
- logger.debug(
614
- `[GEMINI] Backup model error doesn't qualify for further attempts`
615
- );
616
- break;
617
- }
618
- }
619
- }
620
-
621
- const lastBackupErrMsg = getErrorMessage(lastBackupError);
622
- logger.error(
623
- `[GEMINI] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
624
- );
625
- throw toProviderError(lastBackupError, this.name, CLASSIFICATION_OPTIONS);
538
+ try {
539
+ yield* streamWithBackupFallback({
540
+ models: [this.primaryModel, ...this.backupModels],
541
+ attempt: (model) =>
542
+ withStreamRetry(
543
+ (signal) => this.generateStreamWithModel<TContext, TStructured>(model, input, signal),
544
+ { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `Gemini ${model} stream`, isRetriable: (error) => isRetriableProviderError(error, CLASSIFICATION_OPTIONS) }
545
+ ),
546
+ shouldTryBackup: shouldUseBackupModel,
547
+ ...observer.callbacks,
548
+ });
549
+ } catch (error: unknown) {
550
+ observer.logExhausted(error);
551
+ throw toProviderError(error, this.name, CLASSIFICATION_OPTIONS);
626
552
  }
627
553
  }
628
554
 
@@ -27,11 +27,13 @@ import type {
27
27
  } from "../types";
28
28
  import type { ProviderCapabilities } from "../types/ai";
29
29
  import type { HistoryItem } from "../types/history";
30
- import { withTimeoutAndRetry, withStreamRetry, resolveRetryConfig, logger, assertUsableCompletion, combineAbortSignals } from "../utils";
30
+ import type { ResponseInput } from "openai/resources/responses/responses";
31
+ import { withTimeoutAndRetry, withStreamRetry, withBackupFallback, streamWithBackupFallback, backupFallbackLogging, resolveRetryConfig, logger, assertUsableCompletion, combineAbortSignals } from "../utils";
31
32
  import {
32
33
  classifyProviderError,
33
34
  getErrorMessage,
34
35
  isBackupEligible,
36
+ isRetriableProviderError,
35
37
  toProviderError,
36
38
  type ErrorClassificationOptions,
37
39
  } from "./errorClassification";
@@ -142,6 +144,56 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
142
144
  // Provider-specific hooks
143
145
  // ---------------------------------------------------------------------
144
146
 
147
+ /**
148
+ * Map interaction history onto Responses-API input items so structured
149
+ * generations carry the same context as chat completions: plain messages
150
+ * pass through as easy-input messages, and tool exchanges map to
151
+ * function_call / function_call_output pairs keyed by the original ids.
152
+ * The prompt is appended as the final user turn, mirroring how the
153
+ * chat-completions path builds its messages array.
154
+ */
155
+ private buildResponsesInput(history: HistoryItem[], prompt: string): ResponseInput {
156
+ const items: ResponseInput = [];
157
+
158
+ for (const item of history) {
159
+ switch (item.role) {
160
+ case "system":
161
+ items.push({ role: "system", content: item.content });
162
+ break;
163
+ case "user":
164
+ items.push({ role: "user", content: item.content });
165
+ break;
166
+ case "assistant": {
167
+ if (item.content) {
168
+ items.push({ role: "assistant", content: item.content });
169
+ }
170
+ for (const toolCall of item.tool_calls ?? []) {
171
+ items.push({
172
+ type: "function_call",
173
+ call_id: toolCall.id,
174
+ name: toolCall.name,
175
+ arguments: JSON.stringify(toolCall.arguments),
176
+ });
177
+ }
178
+ break;
179
+ }
180
+ case "tool":
181
+ items.push({
182
+ type: "function_call_output",
183
+ call_id: item.tool_call_id,
184
+ output:
185
+ typeof item.content === "string"
186
+ ? item.content
187
+ : JSON.stringify(item.content),
188
+ });
189
+ break;
190
+ }
191
+ }
192
+
193
+ items.push({ role: "user", content: prompt });
194
+ return items;
195
+ }
196
+
145
197
  /**
146
198
  * Generate a structured (JSON schema) response. Default uses the
147
199
  * responses.parse API (OpenAI, OpenRouter). Subclasses whose API lacks
@@ -162,8 +214,10 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
162
214
  const response = await this.client.responses.parse(
163
215
  {
164
216
  model,
165
- instructions: input.prompt,
166
- input: "",
217
+ // Full conversation context: mapped history plus the prompt as the
218
+ // final user turn. This used to send only `instructions` with an empty
219
+ // `input`, leaving every structured turn without conversation memory.
220
+ input: this.buildResponsesInput(input.history, input.prompt),
167
221
  reasoning: {
168
222
  effort: input.parameters?.reasoning?.effort || "low",
169
223
  },
@@ -368,65 +422,22 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
368
422
  >(
369
423
  input: GenerateMessageInput<TContext>
370
424
  ): Promise<GenerateMessageOutput<TStructured>> {
371
- // Try primary model first
372
- try {
373
- return await this.generateWithModel<TContext, TStructured>(
374
- this.primaryModel,
375
- input
376
- );
377
- } catch (primaryError: unknown) {
378
- const primaryErrMsg = getErrorMessage(primaryError);
379
- logger.warn(
380
- `[${this.logLabel}] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
381
- );
382
-
383
- if (!this.shouldUseBackupModel(primaryError)) {
384
- throw this.wrapTerminalError(primaryError);
385
- }
386
-
387
- logger.debug(`[${this.logLabel}] Trying backup models`);
388
-
389
- let lastBackupError: unknown = primaryError;
390
-
391
- for (let i = 0; i < this.backupModels.length; i++) {
392
- const backupModel = this.backupModels[i];
393
- logger.debug(
394
- `[${this.logLabel}] Trying backup model ${i + 1}/${
395
- this.backupModels.length
396
- }: ${backupModel}`
397
- );
398
-
399
- try {
400
- const result = await this.generateWithModel<TContext, TStructured>(
401
- backupModel,
402
- input
403
- );
404
- logger.debug(`[${this.logLabel}] Backup model ${backupModel} succeeded`);
405
- return result;
406
- } catch (backupError: unknown) {
407
- const backupErrMsg = getErrorMessage(backupError);
408
- logger.warn(
409
- `[${this.logLabel}] Backup model ${backupModel} failed: ${backupErrMsg}`
410
- );
411
- lastBackupError = backupError;
412
-
413
- if (
414
- !this.shouldUseBackupModel(backupError) &&
415
- i < this.backupModels.length - 1
416
- ) {
417
- logger.debug(
418
- `[${this.logLabel}] Backup model error doesn't qualify for further attempts`
419
- );
420
- break;
421
- }
422
- }
423
- }
425
+ const observer = backupFallbackLogging(
426
+ `[${this.logLabel}]`,
427
+ (error) => this.shouldUseBackupModel(error)
428
+ );
424
429
 
425
- const lastBackupErrMsg = getErrorMessage(lastBackupError);
426
- logger.error(
427
- `[${this.logLabel}] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
428
- );
429
- throw this.wrapTerminalError(lastBackupError);
430
+ try {
431
+ return await withBackupFallback({
432
+ models: [this.primaryModel, ...this.backupModels],
433
+ attempt: (model) =>
434
+ this.generateWithModel<TContext, TStructured>(model, input),
435
+ shouldTryBackup: (error) => this.shouldUseBackupModel(error),
436
+ ...observer.callbacks,
437
+ });
438
+ } catch (error: unknown) {
439
+ observer.logExhausted(error);
440
+ throw this.wrapTerminalError(error);
430
441
  }
431
442
  }
432
443
 
@@ -436,65 +447,26 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
436
447
  >(
437
448
  input: GenerateMessageInput<TContext>
438
449
  ): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
439
- // Try primary model first
440
- try {
441
- yield* withStreamRetry(
442
- (signal) => this.generateStreamWithModel<TContext, TStructured>(this.primaryModel, input, signal),
443
- { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `${this.logLabel} ${this.primaryModel} stream` }
444
- );
445
- } catch (primaryError: unknown) {
446
- const primaryErrMsg = getErrorMessage(primaryError);
447
- logger.warn(
448
- `[${this.logLabel}] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
449
- );
450
-
451
- if (!this.shouldUseBackupModel(primaryError)) {
452
- throw this.wrapTerminalError(primaryError);
453
- }
454
-
455
- logger.debug(`[${this.logLabel}] Trying backup models for streaming`);
456
-
457
- let lastBackupError: unknown = primaryError;
458
-
459
- for (let i = 0; i < this.backupModels.length; i++) {
460
- const backupModel = this.backupModels[i];
461
- logger.debug(
462
- `[${this.logLabel}] Trying backup model ${i + 1}/${
463
- this.backupModels.length
464
- }: ${backupModel}`
465
- );
466
-
467
- try {
468
- yield* withStreamRetry(
469
- (signal) => this.generateStreamWithModel<TContext, TStructured>(backupModel, input, signal),
470
- { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `${this.logLabel} ${backupModel} stream` }
471
- );
472
- logger.debug(`[${this.logLabel}] Backup model ${backupModel} succeeded`);
473
- return;
474
- } catch (backupError: unknown) {
475
- const backupErrMsg = getErrorMessage(backupError);
476
- logger.warn(
477
- `[${this.logLabel}] Backup model ${backupModel} failed: ${backupErrMsg}`
478
- );
479
- lastBackupError = backupError;
480
-
481
- if (
482
- !this.shouldUseBackupModel(backupError) &&
483
- i < this.backupModels.length - 1
484
- ) {
485
- logger.debug(
486
- `[${this.logLabel}] Backup model error doesn't qualify for further attempts`
487
- );
488
- break;
489
- }
490
- }
491
- }
450
+ const observer = backupFallbackLogging(
451
+ `[${this.logLabel}]`,
452
+ (error) => this.shouldUseBackupModel(error),
453
+ { streaming: true }
454
+ );
492
455
 
493
- const lastBackupErrMsg = getErrorMessage(lastBackupError);
494
- logger.error(
495
- `[${this.logLabel}] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
496
- );
497
- throw this.wrapTerminalError(lastBackupError);
456
+ try {
457
+ yield* streamWithBackupFallback({
458
+ models: [this.primaryModel, ...this.backupModels],
459
+ attempt: (model) =>
460
+ withStreamRetry(
461
+ (signal) => this.generateStreamWithModel<TContext, TStructured>(model, input, signal),
462
+ { maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `${this.logLabel} ${model} stream`, isRetriable: (error) => isRetriableProviderError(error, this.classificationOptions) }
463
+ ),
464
+ shouldTryBackup: (error) => this.shouldUseBackupModel(error),
465
+ ...observer.callbacks,
466
+ });
467
+ } catch (error: unknown) {
468
+ observer.logExhausted(error);
469
+ throw this.wrapTerminalError(error);
498
470
  }
499
471
  }
500
472
 
@@ -529,7 +501,8 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
529
501
  operation,
530
502
  this.retryConfig.timeout,
531
503
  this.retryConfig.retries,
532
- `${this.displayName} ${model}`
504
+ `${this.displayName} ${model}`,
505
+ (error) => isRetriableProviderError(error, this.classificationOptions)
533
506
  ) as Promise<GenerateMessageOutput<TStructured>>;
534
507
  }
535
508
 
@@ -689,10 +662,15 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
689
662
  let promptTokens: number | undefined;
690
663
  let completionTokens: number | undefined;
691
664
  let totalTokens: number | undefined;
692
- const toolCalls: Array<{
693
- toolName: string;
694
- arguments: Record<string, unknown>;
695
- }> = [];
665
+ // Tool calls arrive across MANY deltas: the first carries the id/name,
666
+ // later ones carry argument-string fragments that must be concatenated.
667
+ // Accumulate per call (keyed by `index`) and parse once at stream end —
668
+ // parsing per fragment produced N broken entries for one real call.
669
+ const pendingToolCalls = new Map<
670
+ number,
671
+ { toolName: string; argumentsBuffer: string }
672
+ >();
673
+ let lastSlot = 0;
696
674
 
697
675
  for await (const chunk of stream) {
698
676
  currentModel = chunk.model;
@@ -707,18 +685,25 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
707
685
  const choice = chunk.choices?.[0];
708
686
  if (!choice) continue;
709
687
 
710
- // Extract tool calls from delta
688
+ // Accumulate streamed tool-call fragments by index
711
689
  if (choice.delta?.tool_calls) {
712
690
  for (const toolCall of choice.delta.tool_calls) {
713
- if (toolCall.function) {
714
- toolCalls.push({
715
- toolName: toolCall.function.name || "",
716
- arguments: this.parseToolCallArguments(
717
- toolCall.function.arguments,
718
- true
719
- ),
720
- });
691
+ // Spec-compliant chunks carry `index`; servers that omit it send
692
+ // one call's fragments sequentially, so keep appending to lastSlot.
693
+ const slot =
694
+ typeof toolCall.index === "number" ? toolCall.index : lastSlot;
695
+ lastSlot = slot;
696
+ const entry = pendingToolCalls.get(slot) ?? {
697
+ toolName: "",
698
+ argumentsBuffer: "",
699
+ };
700
+ if (toolCall.function?.name) {
701
+ entry.toolName = toolCall.function.name;
702
+ }
703
+ if (toolCall.function?.arguments) {
704
+ entry.argumentsBuffer += toolCall.function.arguments;
721
705
  }
706
+ pendingToolCalls.set(slot, entry);
722
707
  }
723
708
  }
724
709
 
@@ -753,6 +738,20 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
753
738
  }
754
739
  }
755
740
 
741
+ const toolCalls: Array<{
742
+ toolName: string;
743
+ arguments: Record<string, unknown>;
744
+ }> = [];
745
+ for (const [, entry] of [...pendingToolCalls.entries()].sort(
746
+ ([a], [b]) => a - b
747
+ )) {
748
+ if (!entry.toolName) continue;
749
+ toolCalls.push({
750
+ toolName: entry.toolName,
751
+ arguments: this.parseToolCallArguments(entry.argumentsBuffer, true),
752
+ });
753
+ }
754
+
756
755
  // Include tool calls in structured response (even without JSON schema)
757
756
  if (toolCalls.length > 0) {
758
757
  structured = {
@@ -19,7 +19,7 @@ export interface OpenAIProviderOptions {
19
19
  apiKey: string;
20
20
  /** Organization ID (optional) */
21
21
  organization?: string;
22
- /** Model to use (required) - e.g., "gpt-5.5", "gpt-5.4" */
22
+ /** Model to use (required) - e.g., "gpt-5.6", "gpt-5.4-mini" */
23
23
  model: string;
24
24
  /** Backup models to try if primary fails (default: []) */
25
25
  backupModels?: string[];
@@ -64,7 +64,7 @@ export class OpenAIProvider extends OpenAICompatibleProvider {
64
64
  }
65
65
 
66
66
  if (!model) {
67
- throw new Error("Model is required. Example: 'gpt-5.5' or 'gpt-5.4'");
67
+ throw new Error("Model is required. Example: 'gpt-5.6' or 'gpt-5.5'");
68
68
  }
69
69
 
70
70
  super({
@@ -150,6 +150,40 @@ export function isBackupEligible(code: ProviderErrorCode): boolean {
150
150
  return code === "rate_limited" || code === "overloaded";
151
151
  }
152
152
 
153
+ /** Errors whose name marks them as caller- or SDK-initiated aborts. */
154
+ function isAbortLikeError(error: unknown): boolean {
155
+ if (!(error instanceof Error)) return false;
156
+ // OpenAI SDK: APIUserAbortError. Generic fetch/DOM: AbortError / TimeoutError
157
+ // handled below via classification (a timeout IS retriable; a caller abort is not).
158
+ return (
159
+ error.name === "AbortError" ||
160
+ error.name === "APIUserAbortError" ||
161
+ /abort/i.test(error.message)
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Whether an error is worth retrying with backoff: transient availability,
167
+ * rate-limiting, timeouts and network faults only. Deterministic failures —
168
+ * auth (401/403), invalid request (400/404/422) and caller aborts — must fail
169
+ * fast instead of burning the retry budget on the same outcome.
170
+ */
171
+ export function isRetriableProviderError(
172
+ error: unknown,
173
+ options?: ErrorClassificationOptions
174
+ ): boolean {
175
+ if (isAbortLikeError(error)) {
176
+ return false;
177
+ }
178
+ const code = classifyProviderError(error, options);
179
+ return (
180
+ code === "rate_limited" ||
181
+ code === "overloaded" ||
182
+ code === "timeout" ||
183
+ code === "network"
184
+ );
185
+ }
186
+
153
187
  /**
154
188
  * Wrap a terminal failure (after retries/backup exhaustion) in a
155
189
  * normalized ProviderError, preserving the original error as `cause`.
@@ -126,6 +126,13 @@ export interface AgentOptions<TContext = unknown, TData = unknown> {
126
126
  * Rendered into the system prompt as "who you are and how you communicate."
127
127
  */
128
128
  persona?: Template<TContext>;
129
+ /**
130
+ * Hard bound on session.history length (default: 400). The oldest entries
131
+ * are trimmed — never splitting an assistant/tool pair — when the bound is
132
+ * exceeded and no compaction strategy shrank the history first. Set 0 to
133
+ * disable bounding entirely.
134
+ */
135
+ maxHistoryMessages?: number;
129
136
  /** Enable debug logging */
130
137
  debug?: boolean;
131
138
  /** Default context data available to the agent */
@@ -187,6 +194,15 @@ export interface AgentOptions<TContext = unknown, TData = unknown> {
187
194
  * @default 10
188
195
  */
189
196
  maxDirectiveChain?: number;
197
+ /**
198
+ * Maximum number of tool loop iterations allowed within a single response
199
+ * generation before the pipeline stops executing further tool calls.
200
+ * Guards against runaway recursive tool calling. An explicit `0` is honored
201
+ * (no tool loops). Applies to both `respond()` and streaming paths.
202
+ *
203
+ * @default 5
204
+ */
205
+ maxToolLoops?: number;
190
206
  /**
191
207
  * Optional compaction configuration for managing conversation history size.
192
208
  * When provided, the agent will validate the options and make them available
@@ -321,6 +337,13 @@ export interface AppliedInstruction {
321
337
  scopeRef?: string;
322
338
  }
323
339
 
340
+ /** A flow that left its active position during a turn. */
341
+ export interface EndedFlow {
342
+ flowId: string;
343
+ title?: string;
344
+ reason: StoppedReason;
345
+ }
346
+
324
347
  export interface AgentResponse<TData = Record<string, unknown>> {
325
348
  message: string;
326
349
  session?: SessionState<TData>;
@@ -341,6 +364,17 @@ export interface AgentResponse<TData = Record<string, unknown>> {
341
364
  * Mirrors the observability framing of `executedSteps` and `appliedInstructions`.
342
365
  */
343
366
  triggeredSignals?: SignalFiring<unknown, TData>[];
367
+ /**
368
+ * Provider-reported usage for this turn's primary generation (routing and
369
+ * extraction sub-calls are not included).
370
+ */
371
+ metadata?: { tokensUsed?: number };
372
+ /**
373
+ * Flows that LEFT their active position this turn — completions, redirects,
374
+ * resets. planTurn knows these internally; surfaced so consumers stop
375
+ * re-deriving them from executedSteps + session cursor inspection.
376
+ */
377
+ endedFlows?: EndedFlow[];
344
378
  }
345
379
 
346
380
  export interface AgentResponseStreamChunk<TData = Record<string, unknown>> {