@tanstack/ai 0.42.0 → 0.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +15 -1
  2. package/dist/esm/activities/chat/adapter.js +23 -16
  3. package/dist/esm/activities/chat/adapter.js.map +1 -1
  4. package/dist/esm/activities/chat/agent-loop-strategies.d.ts +5 -36
  5. package/dist/esm/activities/chat/agent-loop-strategies.js +75 -21
  6. package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
  7. package/dist/esm/activities/chat/cancel.d.ts +40 -0
  8. package/dist/esm/activities/chat/cancel.js +54 -0
  9. package/dist/esm/activities/chat/cancel.js.map +1 -0
  10. package/dist/esm/activities/chat/index.d.ts +28 -21
  11. package/dist/esm/activities/chat/index.js +2100 -1813
  12. package/dist/esm/activities/chat/index.js.map +1 -1
  13. package/dist/esm/activities/chat/mcp/manager.d.ts +2 -2
  14. package/dist/esm/activities/chat/mcp/manager.js +90 -77
  15. package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
  16. package/dist/esm/activities/chat/mcp/types.d.ts +2 -2
  17. package/dist/esm/activities/chat/messages.js +397 -346
  18. package/dist/esm/activities/chat/messages.js.map +1 -1
  19. package/dist/esm/activities/chat/middleware/builder.js +17 -15
  20. package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
  21. package/dist/esm/activities/chat/middleware/capabilities.js +78 -43
  22. package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -1
  23. package/dist/esm/activities/chat/middleware/compose.d.ts +94 -1
  24. package/dist/esm/activities/chat/middleware/compose.js +623 -531
  25. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  26. package/dist/esm/activities/chat/middleware/define.js +12 -5
  27. package/dist/esm/activities/chat/middleware/define.js.map +1 -1
  28. package/dist/esm/activities/chat/middleware/index.d.ts +5 -1
  29. package/dist/esm/activities/chat/middleware/locks.d.ts +50 -0
  30. package/dist/esm/activities/chat/middleware/locks.js +71 -0
  31. package/dist/esm/activities/chat/middleware/locks.js.map +1 -0
  32. package/dist/esm/activities/chat/middleware/pending-turn.d.ts +15 -0
  33. package/dist/esm/activities/chat/middleware/pending-turn.js +35 -0
  34. package/dist/esm/activities/chat/middleware/pending-turn.js.map +1 -0
  35. package/dist/esm/activities/chat/middleware/run-disconnect.d.ts +23 -0
  36. package/dist/esm/activities/chat/middleware/run-disconnect.js +42 -0
  37. package/dist/esm/activities/chat/middleware/run-disconnect.js.map +1 -0
  38. package/dist/esm/activities/chat/middleware/run-store.d.ts +283 -0
  39. package/dist/esm/activities/chat/middleware/run-store.js +176 -0
  40. package/dist/esm/activities/chat/middleware/run-store.js.map +1 -0
  41. package/dist/esm/activities/chat/middleware/sandbox-runtime.js +14 -8
  42. package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -1
  43. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +79 -70
  44. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -1
  45. package/dist/esm/activities/chat/middleware/types.d.ts +59 -2
  46. package/dist/esm/activities/chat/middleware/validate.js +23 -28
  47. package/dist/esm/activities/chat/middleware/validate.js.map +1 -1
  48. package/dist/esm/activities/chat/stream/json-parser.js +39 -25
  49. package/dist/esm/activities/chat/stream/json-parser.js.map +1 -1
  50. package/dist/esm/activities/chat/stream/message-updaters.js +275 -234
  51. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  52. package/dist/esm/activities/chat/stream/processor.d.ts +24 -4
  53. package/dist/esm/activities/chat/stream/processor.js +1341 -1542
  54. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  55. package/dist/esm/activities/chat/stream/strategies.js +69 -53
  56. package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
  57. package/dist/esm/activities/chat/tools/approval-schema.d.ts +19 -0
  58. package/dist/esm/activities/chat/tools/approval-schema.js +117 -0
  59. package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -0
  60. package/dist/esm/activities/chat/tools/lazy-tool-manager.js +164 -191
  61. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  62. package/dist/esm/activities/chat/tools/lazy-tools.js +24 -12
  63. package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -1
  64. package/dist/esm/activities/chat/tools/schema-converter.js +293 -146
  65. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  66. package/dist/esm/activities/chat/tools/tool-calls.d.ts +18 -2
  67. package/dist/esm/activities/chat/tools/tool-calls.js +522 -531
  68. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  69. package/dist/esm/activities/chat/tools/tool-definition.d.ts +75 -16
  70. package/dist/esm/activities/chat/tools/tool-definition.js +95 -23
  71. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  72. package/dist/esm/activities/error-payload.js +85 -47
  73. package/dist/esm/activities/error-payload.js.map +1 -1
  74. package/dist/esm/activities/generateAudio/adapter.js +22 -15
  75. package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
  76. package/dist/esm/activities/generateAudio/index.d.ts +4 -0
  77. package/dist/esm/activities/generateAudio/index.js +141 -105
  78. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  79. package/dist/esm/activities/generateImage/adapter.js +22 -15
  80. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  81. package/dist/esm/activities/generateImage/index.d.ts +4 -0
  82. package/dist/esm/activities/generateImage/index.js +155 -111
  83. package/dist/esm/activities/generateImage/index.js.map +1 -1
  84. package/dist/esm/activities/generateSpeech/adapter.js +22 -15
  85. package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
  86. package/dist/esm/activities/generateSpeech/index.d.ts +4 -0
  87. package/dist/esm/activities/generateSpeech/index.js +159 -110
  88. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  89. package/dist/esm/activities/generateTranscription/adapter.js +22 -15
  90. package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
  91. package/dist/esm/activities/generateTranscription/index.d.ts +4 -0
  92. package/dist/esm/activities/generateTranscription/index.js +159 -100
  93. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  94. package/dist/esm/activities/generateVideo/adapter.js +36 -29
  95. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  96. package/dist/esm/activities/generateVideo/index.d.ts +143 -19
  97. package/dist/esm/activities/generateVideo/index.js +456 -279
  98. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  99. package/dist/esm/activities/generateVideo/snap.js +60 -48
  100. package/dist/esm/activities/generateVideo/snap.js.map +1 -1
  101. package/dist/esm/activities/index.js +8 -34
  102. package/dist/esm/activities/middleware/index.d.ts +1 -1
  103. package/dist/esm/activities/middleware/run.d.ts +10 -0
  104. package/dist/esm/activities/middleware/run.js +53 -29
  105. package/dist/esm/activities/middleware/run.js.map +1 -1
  106. package/dist/esm/activities/middleware/types.d.ts +44 -6
  107. package/dist/esm/activities/stream-generation-result.d.ts +4 -1
  108. package/dist/esm/activities/stream-generation-result.js +79 -44
  109. package/dist/esm/activities/stream-generation-result.js.map +1 -1
  110. package/dist/esm/activities/summarize/adapter.js +22 -15
  111. package/dist/esm/activities/summarize/adapter.js.map +1 -1
  112. package/dist/esm/activities/summarize/chat-stream-summarize.js +252 -202
  113. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  114. package/dist/esm/activities/summarize/index.d.ts +27 -0
  115. package/dist/esm/activities/summarize/index.js +268 -102
  116. package/dist/esm/activities/summarize/index.js.map +1 -1
  117. package/dist/esm/adapter-internals.d.ts +2 -1
  118. package/dist/esm/adapter-internals.js +4 -11
  119. package/dist/esm/client.d.ts +25 -3
  120. package/dist/esm/client.js +131 -64
  121. package/dist/esm/client.js.map +1 -1
  122. package/dist/esm/custom-events.d.ts +76 -0
  123. package/dist/esm/custom-events.js +37 -0
  124. package/dist/esm/custom-events.js.map +1 -0
  125. package/dist/esm/delivery-detach.d.ts +50 -0
  126. package/dist/esm/delivery-detach.js +71 -0
  127. package/dist/esm/delivery-detach.js.map +1 -0
  128. package/dist/esm/delivery-disconnect.d.ts +62 -0
  129. package/dist/esm/delivery-disconnect.js +81 -0
  130. package/dist/esm/delivery-disconnect.js.map +1 -0
  131. package/dist/esm/extend-adapter.js +19 -17
  132. package/dist/esm/extend-adapter.js.map +1 -1
  133. package/dist/esm/index.d.ts +24 -6
  134. package/dist/esm/index.js +30 -98
  135. package/dist/esm/interrupt-resume.d.ts +71 -0
  136. package/dist/esm/interrupt-resume.js +438 -0
  137. package/dist/esm/interrupt-resume.js.map +1 -0
  138. package/dist/esm/interrupt-serialization.d.ts +12 -0
  139. package/dist/esm/interrupt-serialization.js +178 -0
  140. package/dist/esm/interrupt-serialization.js.map +1 -0
  141. package/dist/esm/interrupts.d.ts +84 -0
  142. package/dist/esm/interrupts.js +31 -0
  143. package/dist/esm/interrupts.js.map +1 -0
  144. package/dist/esm/locks.d.ts +10 -0
  145. package/dist/esm/locks.js +2 -0
  146. package/dist/esm/logger/console-logger.js +101 -78
  147. package/dist/esm/logger/console-logger.js.map +1 -1
  148. package/dist/esm/logger/internal-logger.js +104 -89
  149. package/dist/esm/logger/internal-logger.js.map +1 -1
  150. package/dist/esm/logger/resolve.js +54 -49
  151. package/dist/esm/logger/resolve.js.map +1 -1
  152. package/dist/esm/logger/types.d.ts +1 -1
  153. package/dist/esm/middlewares/content-guard.js +142 -148
  154. package/dist/esm/middlewares/content-guard.js.map +1 -1
  155. package/dist/esm/middlewares/index.js +2 -6
  156. package/dist/esm/middlewares/otel.d.ts +3 -1
  157. package/dist/esm/middlewares/otel.js +599 -732
  158. package/dist/esm/middlewares/otel.js.map +1 -1
  159. package/dist/esm/middlewares/usage-attributes.js +47 -40
  160. package/dist/esm/middlewares/usage-attributes.js.map +1 -1
  161. package/dist/esm/realtime/event-emitter.js +24 -25
  162. package/dist/esm/realtime/event-emitter.js.map +1 -1
  163. package/dist/esm/realtime/index.d.ts +5 -9
  164. package/dist/esm/realtime/index.js +29 -6
  165. package/dist/esm/realtime/index.js.map +1 -1
  166. package/dist/esm/scope.d.ts +47 -0
  167. package/dist/esm/stream-durability.d.ts +171 -0
  168. package/dist/esm/stream-durability.js +295 -0
  169. package/dist/esm/stream-durability.js.map +1 -0
  170. package/dist/esm/stream-to-response.d.ts +178 -13
  171. package/dist/esm/stream-to-response.js +663 -115
  172. package/dist/esm/stream-to-response.js.map +1 -1
  173. package/dist/esm/strip-to-spec-middleware.js +30 -16
  174. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  175. package/dist/esm/system-prompts.js +27 -21
  176. package/dist/esm/system-prompts.js.map +1 -1
  177. package/dist/esm/tool-registry.js +72 -45
  178. package/dist/esm/tool-registry.js.map +1 -1
  179. package/dist/esm/tools/provider-tool.js +14 -5
  180. package/dist/esm/tools/provider-tool.js.map +1 -1
  181. package/dist/esm/types.d.ts +321 -42
  182. package/dist/esm/types.js +2 -0
  183. package/dist/esm/utilities/ag-ui-wire.js +79 -93
  184. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  185. package/dist/esm/utilities/chat-params.d.ts +26 -4
  186. package/dist/esm/utilities/chat-params.js +218 -92
  187. package/dist/esm/utilities/chat-params.js.map +1 -1
  188. package/dist/esm/utilities/errors.js +28 -18
  189. package/dist/esm/utilities/errors.js.map +1 -1
  190. package/dist/esm/utilities/media-prompt.js +46 -41
  191. package/dist/esm/utilities/media-prompt.js.map +1 -1
  192. package/dist/esm/utilities/numbers.js +13 -10
  193. package/dist/esm/utilities/numbers.js.map +1 -1
  194. package/dist/esm/utilities/provider-executed.js +20 -11
  195. package/dist/esm/utilities/provider-executed.js.map +1 -1
  196. package/dist/esm/utilities/sampling-keys.js +31 -19
  197. package/dist/esm/utilities/sampling-keys.js.map +1 -1
  198. package/dist/esm/utilities/tool-result.js +42 -30
  199. package/dist/esm/utilities/tool-result.js.map +1 -1
  200. package/dist/esm/utilities/usage.js +27 -9
  201. package/dist/esm/utilities/usage.js.map +1 -1
  202. package/dist/esm/utils.js +26 -18
  203. package/dist/esm/utils.js.map +1 -1
  204. package/package.json +10 -6
  205. package/skills/ai-core/SKILL.md +69 -18
  206. package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
  207. package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
  208. package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
  209. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
  210. package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
  211. package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
  212. package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
  213. package/skills/ai-core/chat-experience/SKILL.md +98 -11
  214. package/skills/ai-core/client-persistence/SKILL.md +277 -0
  215. package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
  216. package/skills/ai-core/debug-logging/SKILL.md +1 -1
  217. package/skills/ai-core/locks/SKILL.md +143 -0
  218. package/skills/ai-core/media-generation/SKILL.md +144 -12
  219. package/skills/ai-core/middleware/SKILL.md +258 -33
  220. package/skills/ai-core/structured-outputs/SKILL.md +1 -1
  221. package/skills/ai-core/tool-calling/SKILL.md +54 -61
  222. package/src/activities/chat/agent-loop-strategies.ts +5 -39
  223. package/src/activities/chat/cancel.ts +81 -0
  224. package/src/activities/chat/index.ts +1091 -200
  225. package/src/activities/chat/mcp/manager.ts +4 -4
  226. package/src/activities/chat/mcp/types.ts +2 -2
  227. package/src/activities/chat/messages.ts +5 -3
  228. package/src/activities/chat/middleware/builder.ts +1 -1
  229. package/src/activities/chat/middleware/compose.ts +186 -9
  230. package/src/activities/chat/middleware/index.ts +26 -0
  231. package/src/activities/chat/middleware/locks.ts +102 -0
  232. package/src/activities/chat/middleware/pending-turn.ts +47 -0
  233. package/src/activities/chat/middleware/run-disconnect.ts +62 -0
  234. package/src/activities/chat/middleware/run-store.ts +412 -0
  235. package/src/activities/chat/middleware/types.ts +62 -1
  236. package/src/activities/chat/stream/processor.ts +189 -5
  237. package/src/activities/chat/tools/approval-schema.ts +205 -0
  238. package/src/activities/chat/tools/tool-calls.ts +106 -13
  239. package/src/activities/chat/tools/tool-definition.ts +210 -39
  240. package/src/activities/generateAudio/index.ts +20 -3
  241. package/src/activities/generateImage/index.ts +20 -3
  242. package/src/activities/generateSpeech/index.ts +25 -3
  243. package/src/activities/generateTranscription/index.ts +26 -3
  244. package/src/activities/generateVideo/index.ts +345 -82
  245. package/src/activities/middleware/index.ts +2 -0
  246. package/src/activities/middleware/run.ts +31 -0
  247. package/src/activities/middleware/types.ts +49 -5
  248. package/src/activities/stream-generation-result.ts +30 -2
  249. package/src/activities/summarize/chat-stream-summarize.ts +5 -0
  250. package/src/activities/summarize/index.ts +200 -10
  251. package/src/adapter-internals.ts +10 -1
  252. package/src/client.ts +244 -0
  253. package/src/custom-events.ts +107 -0
  254. package/src/delivery-detach.ts +72 -0
  255. package/src/delivery-disconnect.ts +84 -0
  256. package/src/index.ts +138 -1
  257. package/src/interrupt-resume.ts +824 -0
  258. package/src/interrupt-serialization.ts +183 -0
  259. package/src/interrupts.ts +146 -0
  260. package/src/locks.ts +17 -0
  261. package/src/logger/types.ts +1 -1
  262. package/src/middlewares/otel.ts +23 -5
  263. package/src/realtime/index.ts +5 -9
  264. package/src/scope.ts +47 -0
  265. package/src/stream-durability.ts +598 -0
  266. package/src/stream-to-response.ts +1051 -95
  267. package/src/strip-to-spec-middleware.ts +3 -3
  268. package/src/types.ts +405 -45
  269. package/src/utilities/chat-params.ts +245 -55
  270. package/dist/esm/activities/index.js.map +0 -1
  271. package/dist/esm/adapter-internals.js.map +0 -1
  272. package/dist/esm/index.js.map +0 -1
  273. package/dist/esm/middlewares/index.js.map +0 -1
@@ -1,537 +1,629 @@
1
1
  import { aiEventClient } from "@tanstack/ai-event-client";
2
+ //#region src/activities/chat/middleware/compose.ts
3
+ /** Check if a middleware should be skipped for instrumentation events. */
2
4
  function shouldSkipInstrumentation(mw) {
3
- return mw.name === "devtools" || mw.name === "strip-to-spec";
5
+ return mw.name === "devtools" || mw.name === "strip-to-spec";
4
6
  }
7
+ /** Build the base context for middleware instrumentation events. */
5
8
  function instrumentCtx(ctx) {
6
- return {
7
- requestId: ctx.requestId,
8
- streamId: ctx.streamId,
9
- clientId: ctx.threadId,
10
- timestamp: Date.now()
11
- };
9
+ return {
10
+ requestId: ctx.requestId,
11
+ streamId: ctx.streamId,
12
+ clientId: ctx.threadId,
13
+ timestamp: Date.now()
14
+ };
12
15
  }
13
- class MiddlewareRunner {
14
- middlewares;
15
- logger;
16
- constructor(middlewares, logger) {
17
- this.middlewares = middlewares;
18
- this.logger = logger;
19
- }
20
- get hasMiddleware() {
21
- return this.middlewares.length > 0;
22
- }
23
- /**
24
- * Pipe config through all middleware onConfig hooks in order.
25
- * Each middleware receives the merged config from previous middleware.
26
- * Partial returns are shallow-merged with the current config.
27
- */
28
- async runOnConfig(ctx, config) {
29
- let current = config;
30
- for (const mw of this.middlewares) {
31
- if (mw.onConfig) {
32
- const skip = shouldSkipInstrumentation(mw);
33
- const start = Date.now();
34
- const result = await mw.onConfig(ctx, current);
35
- const hasTransform = result !== void 0 && result !== null;
36
- if (hasTransform) {
37
- current = { ...current, ...result };
38
- if (!skip) {
39
- this.logger.config(
40
- `middleware=${mw.name ?? "unnamed"} keys=${Object.keys(result).join(",")}`,
41
- {
42
- middleware: mw.name ?? "unnamed",
43
- changes: result
44
- }
45
- );
46
- }
47
- }
48
- if (!skip) {
49
- const base = instrumentCtx(ctx);
50
- aiEventClient.emit("middleware:hook:executed", {
51
- ...base,
52
- middlewareName: mw.name || "unnamed",
53
- hookName: "onConfig",
54
- iteration: ctx.iteration,
55
- duration: Date.now() - start,
56
- hasTransform
57
- });
58
- if (hasTransform) {
59
- aiEventClient.emit("middleware:config:transformed", {
60
- ...base,
61
- middlewareName: mw.name || "unnamed",
62
- iteration: ctx.iteration,
63
- changes: result
64
- });
65
- }
66
- }
67
- }
68
- }
69
- return current;
70
- }
71
- /**
72
- * Pipe config through all middleware onStructuredOutputConfig hooks in order.
73
- * Each middleware receives the merged config from previous middleware.
74
- * Partial returns are shallow-merged with the current config.
75
- *
76
- * Called once at the structured-output boundary, before runOnConfig at the
77
- * same boundary (which receives a ChatMiddlewareConfig view, no outputSchema).
78
- */
79
- async runOnStructuredOutputConfig(ctx, config) {
80
- let current = config;
81
- for (const mw of this.middlewares) {
82
- if (mw.onStructuredOutputConfig) {
83
- const skip = shouldSkipInstrumentation(mw);
84
- const start = Date.now();
85
- const result = await mw.onStructuredOutputConfig(ctx, current);
86
- const hasTransform = result !== void 0 && result !== null;
87
- if (hasTransform) {
88
- current = { ...current, ...result };
89
- if (!skip) {
90
- this.logger.config(
91
- `middleware=${mw.name ?? "unnamed"} keys=${Object.keys(result).join(",")}`,
92
- {
93
- middleware: mw.name ?? "unnamed",
94
- changes: result
95
- }
96
- );
97
- }
98
- }
99
- if (!skip) {
100
- const base = instrumentCtx(ctx);
101
- aiEventClient.emit("middleware:hook:executed", {
102
- ...base,
103
- middlewareName: mw.name || "unnamed",
104
- hookName: "onStructuredOutputConfig",
105
- iteration: ctx.iteration,
106
- duration: Date.now() - start,
107
- hasTransform
108
- });
109
- if (hasTransform) {
110
- aiEventClient.emit("middleware:config:transformed", {
111
- ...base,
112
- middlewareName: mw.name || "unnamed",
113
- iteration: ctx.iteration,
114
- // `result` is `Partial<StructuredOutputMiddlewareConfig>` —
115
- // Object.fromEntries(Object.entries(result)) yields the
116
- // structural `Record<string, unknown>` the event emitter wants
117
- // without an `as` cast.
118
- changes: Object.fromEntries(Object.entries(result))
119
- });
120
- }
121
- }
122
- }
123
- }
124
- return current;
125
- }
126
- /**
127
- * Run all `setup` hooks in array order, then assert every declared `provides`
128
- * capability was actually provided. Wires the last-wins duplicate-provide
129
- * warning into the registry. Runs before init `onConfig`.
130
- *
131
- * Takes the full `ChatMiddlewareContext` — the same stable context the engine
132
- * threads through every other hook — because it both forwards `ctx` to each
133
- * `setup` hook and emits instrumentation events from it.
134
- */
135
- async runSetup(ctx) {
136
- ctx.capabilities.setOnDuplicate((name) => {
137
- this.logger.warn(
138
- `capability "${name}" was provided more than once; last provider wins`,
139
- { capability: name }
140
- );
141
- });
142
- for (const mw of this.middlewares) {
143
- if (mw.setup) {
144
- const skip = shouldSkipInstrumentation(mw);
145
- const start = Date.now();
146
- await mw.setup(ctx);
147
- if (!skip) {
148
- this.logger.middleware(
149
- `hook=setup middleware=${mw.name ?? "unnamed"}`,
150
- { middleware: mw.name ?? "unnamed", hook: "setup" }
151
- );
152
- aiEventClient.emit("middleware:hook:executed", {
153
- ...instrumentCtx(ctx),
154
- middlewareName: mw.name || "unnamed",
155
- hookName: "setup",
156
- iteration: ctx.iteration,
157
- duration: Date.now() - start,
158
- hasTransform: false
159
- });
160
- }
161
- }
162
- }
163
- for (const mw of this.middlewares) {
164
- for (const handle of mw.provides ?? []) {
165
- if (!ctx.capabilities.has(handle)) {
166
- throw new Error(
167
- `Middleware "${mw.name ?? "unnamed"}" declares it provides "${handle.capabilityName}" but never called provide() in setup().`
168
- );
169
- }
170
- }
171
- }
172
- }
173
- /**
174
- * Call onStart on all middleware in order.
175
- */
176
- async runOnStart(ctx) {
177
- for (const mw of this.middlewares) {
178
- if (mw.onStart) {
179
- const skip = shouldSkipInstrumentation(mw);
180
- const start = Date.now();
181
- await mw.onStart(ctx);
182
- if (!skip) {
183
- this.logger.middleware(
184
- `hook=onStart middleware=${mw.name ?? "unnamed"}`,
185
- { middleware: mw.name ?? "unnamed", hook: "onStart" }
186
- );
187
- aiEventClient.emit("middleware:hook:executed", {
188
- ...instrumentCtx(ctx),
189
- middlewareName: mw.name || "unnamed",
190
- hookName: "onStart",
191
- iteration: ctx.iteration,
192
- duration: Date.now() - start,
193
- hasTransform: false
194
- });
195
- }
196
- }
197
- }
198
- }
199
- /**
200
- * Pipe a single chunk through all middleware onChunk hooks in order.
201
- * Returns the resulting chunks (0..N) to yield to the consumer.
202
- *
203
- * - void: pass through unchanged
204
- * - chunk: replace with this chunk
205
- * - chunk[]: expand to multiple chunks
206
- * - null: drop the chunk entirely
207
- */
208
- async runOnChunk(ctx, chunk) {
209
- let chunks = [chunk];
210
- for (const mw of this.middlewares) {
211
- if (!mw.onChunk) continue;
212
- const skip = shouldSkipInstrumentation(mw);
213
- const nextChunks = [];
214
- for (const c of chunks) {
215
- const chunkType = c.type;
216
- if (!skip) {
217
- this.logger.middleware(
218
- `hook=onChunk middleware=${mw.name ?? "unnamed"} in=${chunkType}`,
219
- { middleware: mw.name ?? "unnamed", hook: "onChunk", in: c }
220
- );
221
- }
222
- const result = await mw.onChunk(ctx, c);
223
- if (result === null) {
224
- if (!skip) {
225
- this.logger.middleware(
226
- `hook=onChunk middleware=${mw.name ?? "unnamed"} in=${chunkType} out=<dropped>`,
227
- {
228
- middleware: mw.name ?? "unnamed",
229
- hook: "onChunk",
230
- dropped: true
231
- }
232
- );
233
- aiEventClient.emit("middleware:chunk:transformed", {
234
- ...instrumentCtx(ctx),
235
- middlewareName: mw.name || "unnamed",
236
- originalChunkType: chunkType,
237
- resultCount: 0,
238
- wasDropped: true
239
- });
240
- }
241
- continue;
242
- } else if (result === void 0) {
243
- nextChunks.push(c);
244
- } else if (Array.isArray(result)) {
245
- nextChunks.push(...result);
246
- if (!skip) {
247
- this.logger.middleware(
248
- `hook=onChunk middleware=${mw.name ?? "unnamed"} in=${chunkType} out=[${result.map((r) => r.type).join(",")}]`,
249
- {
250
- middleware: mw.name ?? "unnamed",
251
- hook: "onChunk",
252
- in: c,
253
- out: result
254
- }
255
- );
256
- aiEventClient.emit("middleware:chunk:transformed", {
257
- ...instrumentCtx(ctx),
258
- middlewareName: mw.name || "unnamed",
259
- originalChunkType: chunkType,
260
- resultCount: result.length,
261
- wasDropped: false
262
- });
263
- }
264
- } else {
265
- nextChunks.push(result);
266
- if (!skip) {
267
- this.logger.middleware(
268
- `hook=onChunk middleware=${mw.name ?? "unnamed"} in=${chunkType} out=${result.type}`,
269
- {
270
- middleware: mw.name ?? "unnamed",
271
- hook: "onChunk",
272
- in: c,
273
- out: result
274
- }
275
- );
276
- aiEventClient.emit("middleware:chunk:transformed", {
277
- ...instrumentCtx(ctx),
278
- middlewareName: mw.name || "unnamed",
279
- originalChunkType: chunkType,
280
- resultCount: 1,
281
- wasDropped: false
282
- });
283
- }
284
- }
285
- }
286
- chunks = nextChunks;
287
- }
288
- return chunks;
289
- }
290
- /**
291
- * Dispatch a sandbox file event to every middleware's `sandbox` hooks, in
292
- * array order: the catch-all `onFile` then the type-specific hook. Errors are
293
- * logged and swallowed so one bad hook can't break the run.
294
- */
295
- async runSandboxFile(ctx, event) {
296
- const typed = {
297
- create: "onFileCreate",
298
- change: "onFileChange",
299
- delete: "onFileDelete"
300
- }[event.type];
301
- for (const mw of this.middlewares) {
302
- const hooks = mw.sandbox;
303
- if (!hooks) continue;
304
- for (const fn of [hooks.onFile, hooks[typed]]) {
305
- if (!fn) continue;
306
- try {
307
- await fn(ctx, event);
308
- } catch (error) {
309
- this.logger.sandbox(
310
- `hook=${typed} middleware=${mw.name ?? "unnamed"} threw`,
311
- { middleware: mw.name ?? "unnamed", error }
312
- );
313
- }
314
- }
315
- }
316
- }
317
- /**
318
- * Run onBeforeToolCall through middleware in order.
319
- * Returns the first non-void decision, or undefined to continue normally.
320
- */
321
- async runOnBeforeToolCall(ctx, hookCtx) {
322
- for (const mw of this.middlewares) {
323
- if (mw.onBeforeToolCall) {
324
- const skip = shouldSkipInstrumentation(mw);
325
- const start = Date.now();
326
- const decision = await mw.onBeforeToolCall(ctx, hookCtx);
327
- const hasTransform = decision !== void 0 && decision !== null;
328
- if (!skip) {
329
- this.logger.middleware(
330
- `hook=onBeforeToolCall middleware=${mw.name ?? "unnamed"}`,
331
- { middleware: mw.name ?? "unnamed", hook: "onBeforeToolCall" }
332
- );
333
- aiEventClient.emit("middleware:hook:executed", {
334
- ...instrumentCtx(ctx),
335
- middlewareName: mw.name || "unnamed",
336
- hookName: "onBeforeToolCall",
337
- iteration: ctx.iteration,
338
- duration: Date.now() - start,
339
- hasTransform
340
- });
341
- }
342
- if (hasTransform) {
343
- return decision;
344
- }
345
- }
346
- }
347
- return void 0;
348
- }
349
- /**
350
- * Run onAfterToolCall on all middleware in order.
351
- */
352
- async runOnAfterToolCall(ctx, info) {
353
- for (const mw of this.middlewares) {
354
- if (mw.onAfterToolCall) {
355
- const skip = shouldSkipInstrumentation(mw);
356
- const start = Date.now();
357
- await mw.onAfterToolCall(ctx, info);
358
- if (!skip) {
359
- this.logger.middleware(
360
- `hook=onAfterToolCall middleware=${mw.name ?? "unnamed"}`,
361
- { middleware: mw.name ?? "unnamed", hook: "onAfterToolCall" }
362
- );
363
- aiEventClient.emit("middleware:hook:executed", {
364
- ...instrumentCtx(ctx),
365
- middlewareName: mw.name || "unnamed",
366
- hookName: "onAfterToolCall",
367
- iteration: ctx.iteration,
368
- duration: Date.now() - start,
369
- hasTransform: false
370
- });
371
- }
372
- }
373
- }
374
- }
375
- /**
376
- * Run onUsage on all middleware in order.
377
- */
378
- async runOnUsage(ctx, usage) {
379
- for (const mw of this.middlewares) {
380
- if (mw.onUsage) {
381
- const skip = shouldSkipInstrumentation(mw);
382
- const start = Date.now();
383
- await mw.onUsage(ctx, usage);
384
- if (!skip) {
385
- this.logger.middleware(
386
- `hook=onUsage middleware=${mw.name ?? "unnamed"}`,
387
- { middleware: mw.name ?? "unnamed", hook: "onUsage" }
388
- );
389
- aiEventClient.emit("middleware:hook:executed", {
390
- ...instrumentCtx(ctx),
391
- middlewareName: mw.name || "unnamed",
392
- hookName: "onUsage",
393
- iteration: ctx.iteration,
394
- duration: Date.now() - start,
395
- hasTransform: false
396
- });
397
- }
398
- }
399
- }
400
- }
401
- /**
402
- * Run onFinish on all middleware in order.
403
- */
404
- async runOnFinish(ctx, info) {
405
- for (const mw of this.middlewares) {
406
- if (mw.onFinish) {
407
- const skip = shouldSkipInstrumentation(mw);
408
- const start = Date.now();
409
- await mw.onFinish(ctx, info);
410
- if (!skip) {
411
- this.logger.middleware(
412
- `hook=onFinish middleware=${mw.name ?? "unnamed"}`,
413
- { middleware: mw.name ?? "unnamed", hook: "onFinish" }
414
- );
415
- aiEventClient.emit("middleware:hook:executed", {
416
- ...instrumentCtx(ctx),
417
- middlewareName: mw.name || "unnamed",
418
- hookName: "onFinish",
419
- iteration: ctx.iteration,
420
- duration: Date.now() - start,
421
- hasTransform: false
422
- });
423
- }
424
- }
425
- }
426
- }
427
- /**
428
- * Run onAbort on all middleware in order.
429
- */
430
- async runOnAbort(ctx, info) {
431
- for (const mw of this.middlewares) {
432
- if (mw.onAbort) {
433
- const skip = shouldSkipInstrumentation(mw);
434
- const start = Date.now();
435
- await mw.onAbort(ctx, info);
436
- if (!skip) {
437
- this.logger.middleware(
438
- `hook=onAbort middleware=${mw.name ?? "unnamed"}`,
439
- { middleware: mw.name ?? "unnamed", hook: "onAbort" }
440
- );
441
- aiEventClient.emit("middleware:hook:executed", {
442
- ...instrumentCtx(ctx),
443
- middlewareName: mw.name || "unnamed",
444
- hookName: "onAbort",
445
- iteration: ctx.iteration,
446
- duration: Date.now() - start,
447
- hasTransform: false
448
- });
449
- }
450
- }
451
- }
452
- }
453
- /**
454
- * Run onError on all middleware in order.
455
- */
456
- async runOnError(ctx, info) {
457
- for (const mw of this.middlewares) {
458
- if (mw.onError) {
459
- const skip = shouldSkipInstrumentation(mw);
460
- const start = Date.now();
461
- await mw.onError(ctx, info);
462
- if (!skip) {
463
- this.logger.middleware(
464
- `hook=onError middleware=${mw.name ?? "unnamed"}`,
465
- { middleware: mw.name ?? "unnamed", hook: "onError" }
466
- );
467
- aiEventClient.emit("middleware:hook:executed", {
468
- ...instrumentCtx(ctx),
469
- middlewareName: mw.name || "unnamed",
470
- hookName: "onError",
471
- iteration: ctx.iteration,
472
- duration: Date.now() - start,
473
- hasTransform: false
474
- });
475
- }
476
- }
477
- }
478
- }
479
- /**
480
- * Run onIteration on all middleware in order.
481
- * Called at the start of each agent loop iteration.
482
- */
483
- async runOnIteration(ctx, info) {
484
- for (const mw of this.middlewares) {
485
- if (mw.onIteration) {
486
- const skip = shouldSkipInstrumentation(mw);
487
- const start = Date.now();
488
- await mw.onIteration(ctx, info);
489
- if (!skip) {
490
- this.logger.middleware(
491
- `hook=onIteration middleware=${mw.name ?? "unnamed"}`,
492
- { middleware: mw.name ?? "unnamed", hook: "onIteration" }
493
- );
494
- aiEventClient.emit("middleware:hook:executed", {
495
- ...instrumentCtx(ctx),
496
- middlewareName: mw.name || "unnamed",
497
- hookName: "onIteration",
498
- iteration: ctx.iteration,
499
- duration: Date.now() - start,
500
- hasTransform: false
501
- });
502
- }
503
- }
504
- }
505
- }
506
- /**
507
- * Run onToolPhaseComplete on all middleware in order.
508
- * Called after all tool calls in an iteration have been processed.
509
- */
510
- async runOnToolPhaseComplete(ctx, info) {
511
- for (const mw of this.middlewares) {
512
- if (mw.onToolPhaseComplete) {
513
- const skip = shouldSkipInstrumentation(mw);
514
- const start = Date.now();
515
- await mw.onToolPhaseComplete(ctx, info);
516
- if (!skip) {
517
- this.logger.middleware(
518
- `hook=onToolPhaseComplete middleware=${mw.name ?? "unnamed"}`,
519
- { middleware: mw.name ?? "unnamed", hook: "onToolPhaseComplete" }
520
- );
521
- aiEventClient.emit("middleware:hook:executed", {
522
- ...instrumentCtx(ctx),
523
- middlewareName: mw.name || "unnamed",
524
- hookName: "onToolPhaseComplete",
525
- iteration: ctx.iteration,
526
- duration: Date.now() - start,
527
- hasTransform: false
528
- });
529
- }
530
- }
531
- }
532
- }
533
- }
534
- export {
535
- MiddlewareRunner
16
+ /**
17
+ * Internal middleware runner that manages composed execution of middleware hooks.
18
+ * Created once per chat() invocation.
19
+ */
20
+ var MiddlewareRunner = class {
21
+ middlewares;
22
+ logger;
23
+ constructor(middlewares, logger) {
24
+ this.middlewares = middlewares;
25
+ this.logger = logger;
26
+ }
27
+ get hasMiddleware() {
28
+ return this.middlewares.length > 0;
29
+ }
30
+ /**
31
+ * Pipe config through all middleware onConfig hooks in order.
32
+ * Each middleware receives the merged config from previous middleware.
33
+ * Partial returns are shallow-merged with the current config.
34
+ */
35
+ async runOnConfig(ctx, config) {
36
+ let current = config;
37
+ for (const mw of this.middlewares) if (mw.onConfig) {
38
+ const skip = shouldSkipInstrumentation(mw);
39
+ const start = Date.now();
40
+ const result = await mw.onConfig(ctx, current);
41
+ const hasTransform = result !== void 0 && result !== null;
42
+ if (hasTransform) {
43
+ current = {
44
+ ...current,
45
+ ...result
46
+ };
47
+ if (!skip) this.logger.config(`middleware=${mw.name ?? "unnamed"} keys=${Object.keys(result).join(",")}`, {
48
+ middleware: mw.name ?? "unnamed",
49
+ changes: result
50
+ });
51
+ }
52
+ if (!skip) {
53
+ const base = instrumentCtx(ctx);
54
+ aiEventClient.emit("middleware:hook:executed", {
55
+ ...base,
56
+ middlewareName: mw.name || "unnamed",
57
+ hookName: "onConfig",
58
+ iteration: ctx.iteration,
59
+ duration: Date.now() - start,
60
+ hasTransform
61
+ });
62
+ if (hasTransform) aiEventClient.emit("middleware:config:transformed", {
63
+ ...base,
64
+ middlewareName: mw.name || "unnamed",
65
+ iteration: ctx.iteration,
66
+ changes: result
67
+ });
68
+ }
69
+ }
70
+ return current;
71
+ }
72
+ /**
73
+ * Pipe config through all middleware onStructuredOutputConfig hooks in order.
74
+ * Each middleware receives the merged config from previous middleware.
75
+ * Partial returns are shallow-merged with the current config.
76
+ *
77
+ * Called once at the structured-output boundary, before runOnConfig at the
78
+ * same boundary (which receives a ChatMiddlewareConfig view, no outputSchema).
79
+ */
80
+ async runOnStructuredOutputConfig(ctx, config) {
81
+ let current = config;
82
+ for (const mw of this.middlewares) if (mw.onStructuredOutputConfig) {
83
+ const skip = shouldSkipInstrumentation(mw);
84
+ const start = Date.now();
85
+ const result = await mw.onStructuredOutputConfig(ctx, current);
86
+ const hasTransform = result !== void 0 && result !== null;
87
+ if (hasTransform) {
88
+ current = {
89
+ ...current,
90
+ ...result
91
+ };
92
+ if (!skip) this.logger.config(`middleware=${mw.name ?? "unnamed"} keys=${Object.keys(result).join(",")}`, {
93
+ middleware: mw.name ?? "unnamed",
94
+ changes: result
95
+ });
96
+ }
97
+ if (!skip) {
98
+ const base = instrumentCtx(ctx);
99
+ aiEventClient.emit("middleware:hook:executed", {
100
+ ...base,
101
+ middlewareName: mw.name || "unnamed",
102
+ hookName: "onStructuredOutputConfig",
103
+ iteration: ctx.iteration,
104
+ duration: Date.now() - start,
105
+ hasTransform
106
+ });
107
+ if (hasTransform) aiEventClient.emit("middleware:config:transformed", {
108
+ ...base,
109
+ middlewareName: mw.name || "unnamed",
110
+ iteration: ctx.iteration,
111
+ changes: Object.fromEntries(Object.entries(result))
112
+ });
113
+ }
114
+ }
115
+ return current;
116
+ }
117
+ /**
118
+ * Run all `setup` hooks in array order, then assert every declared `provides`
119
+ * capability was actually provided. Wires the last-wins duplicate-provide
120
+ * warning into the registry. Runs before init `onConfig`.
121
+ *
122
+ * Takes the full `ChatMiddlewareContext` — the same stable context the engine
123
+ * threads through every other hook — because it both forwards `ctx` to each
124
+ * `setup` hook and emits instrumentation events from it.
125
+ */
126
+ async runSetup(ctx) {
127
+ ctx.capabilities.setOnDuplicate((name) => {
128
+ this.logger.warn(`capability "${name}" was provided more than once; last provider wins`, { capability: name });
129
+ });
130
+ for (const mw of this.middlewares) if (mw.setup) {
131
+ const skip = shouldSkipInstrumentation(mw);
132
+ const start = Date.now();
133
+ await mw.setup(ctx);
134
+ if (!skip) {
135
+ this.logger.middleware(`hook=setup middleware=${mw.name ?? "unnamed"}`, {
136
+ middleware: mw.name ?? "unnamed",
137
+ hook: "setup"
138
+ });
139
+ aiEventClient.emit("middleware:hook:executed", {
140
+ ...instrumentCtx(ctx),
141
+ middlewareName: mw.name || "unnamed",
142
+ hookName: "setup",
143
+ iteration: ctx.iteration,
144
+ duration: Date.now() - start,
145
+ hasTransform: false
146
+ });
147
+ }
148
+ }
149
+ for (const mw of this.middlewares) for (const handle of mw.provides ?? []) if (!ctx.capabilities.has(handle)) throw new Error(`Middleware "${mw.name ?? "unnamed"}" declares it provides "${handle.capabilityName}" but never called provide() in setup().`);
150
+ }
151
+ /**
152
+ * Call onStart on all middleware in order.
153
+ */
154
+ async runOnStart(ctx) {
155
+ for (const mw of this.middlewares) if (mw.onStart) {
156
+ const skip = shouldSkipInstrumentation(mw);
157
+ const start = Date.now();
158
+ await mw.onStart(ctx);
159
+ if (!skip) {
160
+ this.logger.middleware(`hook=onStart middleware=${mw.name ?? "unnamed"}`, {
161
+ middleware: mw.name ?? "unnamed",
162
+ hook: "onStart"
163
+ });
164
+ aiEventClient.emit("middleware:hook:executed", {
165
+ ...instrumentCtx(ctx),
166
+ middlewareName: mw.name || "unnamed",
167
+ hookName: "onStart",
168
+ iteration: ctx.iteration,
169
+ duration: Date.now() - start,
170
+ hasTransform: false
171
+ });
172
+ }
173
+ }
174
+ }
175
+ /**
176
+ * Pipe a single chunk through all middleware onChunk hooks in order.
177
+ * Returns the resulting chunks (0..N) to yield to the consumer.
178
+ *
179
+ * - void: pass through unchanged
180
+ * - chunk: replace with this chunk
181
+ * - chunk[]: expand to multiple chunks
182
+ * - null: drop the chunk entirely
183
+ */
184
+ async runOnChunk(ctx, chunk) {
185
+ let chunks = [chunk];
186
+ for (const mw of this.middlewares) {
187
+ if (!mw.onChunk) continue;
188
+ const skip = shouldSkipInstrumentation(mw);
189
+ const nextChunks = [];
190
+ for (const c of chunks) {
191
+ const chunkType = c.type;
192
+ if (!skip) this.logger.middleware(`hook=onChunk middleware=${mw.name ?? "unnamed"} in=${chunkType}`, {
193
+ middleware: mw.name ?? "unnamed",
194
+ hook: "onChunk",
195
+ in: c
196
+ });
197
+ const result = await mw.onChunk(ctx, c);
198
+ if (result === null) {
199
+ if (!skip) {
200
+ this.logger.middleware(`hook=onChunk middleware=${mw.name ?? "unnamed"} in=${chunkType} out=<dropped>`, {
201
+ middleware: mw.name ?? "unnamed",
202
+ hook: "onChunk",
203
+ dropped: true
204
+ });
205
+ aiEventClient.emit("middleware:chunk:transformed", {
206
+ ...instrumentCtx(ctx),
207
+ middlewareName: mw.name || "unnamed",
208
+ originalChunkType: chunkType,
209
+ resultCount: 0,
210
+ wasDropped: true
211
+ });
212
+ }
213
+ continue;
214
+ } else if (result === void 0) nextChunks.push(c);
215
+ else if (Array.isArray(result)) {
216
+ nextChunks.push(...result);
217
+ if (!skip) {
218
+ this.logger.middleware(`hook=onChunk middleware=${mw.name ?? "unnamed"} in=${chunkType} out=[${result.map((r) => r.type).join(",")}]`, {
219
+ middleware: mw.name ?? "unnamed",
220
+ hook: "onChunk",
221
+ in: c,
222
+ out: result
223
+ });
224
+ aiEventClient.emit("middleware:chunk:transformed", {
225
+ ...instrumentCtx(ctx),
226
+ middlewareName: mw.name || "unnamed",
227
+ originalChunkType: chunkType,
228
+ resultCount: result.length,
229
+ wasDropped: false
230
+ });
231
+ }
232
+ } else {
233
+ nextChunks.push(result);
234
+ if (!skip) {
235
+ this.logger.middleware(`hook=onChunk middleware=${mw.name ?? "unnamed"} in=${chunkType} out=${result.type}`, {
236
+ middleware: mw.name ?? "unnamed",
237
+ hook: "onChunk",
238
+ in: c,
239
+ out: result
240
+ });
241
+ aiEventClient.emit("middleware:chunk:transformed", {
242
+ ...instrumentCtx(ctx),
243
+ middlewareName: mw.name || "unnamed",
244
+ originalChunkType: chunkType,
245
+ resultCount: 1,
246
+ wasDropped: false
247
+ });
248
+ }
249
+ }
250
+ }
251
+ chunks = nextChunks;
252
+ }
253
+ return chunks;
254
+ }
255
+ /**
256
+ * Dispatch a sandbox file event to every middleware's `sandbox` hooks, in
257
+ * array order: the catch-all `onFile` then the type-specific hook. Errors are
258
+ * logged and swallowed so one bad hook can't break the run.
259
+ */
260
+ async runSandboxFile(ctx, event) {
261
+ const typed = {
262
+ create: "onFileCreate",
263
+ change: "onFileChange",
264
+ delete: "onFileDelete"
265
+ }[event.type];
266
+ for (const mw of this.middlewares) {
267
+ const hooks = mw.sandbox;
268
+ if (!hooks) continue;
269
+ for (const fn of [hooks.onFile, hooks[typed]]) {
270
+ if (!fn) continue;
271
+ try {
272
+ await fn(ctx, event);
273
+ } catch (error) {
274
+ this.logger.sandbox(`hook=${typed} middleware=${mw.name ?? "unnamed"} threw`, {
275
+ middleware: mw.name ?? "unnamed",
276
+ error
277
+ });
278
+ }
279
+ }
280
+ }
281
+ }
282
+ /**
283
+ * Run onBeforeToolCall through middleware in order.
284
+ * Returns the first non-void decision, or undefined to continue normally.
285
+ */
286
+ async runOnBeforeToolCall(ctx, hookCtx) {
287
+ for (const mw of this.middlewares) if (mw.onBeforeToolCall) {
288
+ const skip = shouldSkipInstrumentation(mw);
289
+ const start = Date.now();
290
+ const decision = await mw.onBeforeToolCall(ctx, hookCtx);
291
+ const hasTransform = decision !== void 0 && decision !== null;
292
+ if (!skip) {
293
+ this.logger.middleware(`hook=onBeforeToolCall middleware=${mw.name ?? "unnamed"}`, {
294
+ middleware: mw.name ?? "unnamed",
295
+ hook: "onBeforeToolCall"
296
+ });
297
+ aiEventClient.emit("middleware:hook:executed", {
298
+ ...instrumentCtx(ctx),
299
+ middlewareName: mw.name || "unnamed",
300
+ hookName: "onBeforeToolCall",
301
+ iteration: ctx.iteration,
302
+ duration: Date.now() - start,
303
+ hasTransform
304
+ });
305
+ }
306
+ if (hasTransform) return decision;
307
+ }
308
+ }
309
+ /**
310
+ * Run onAfterToolCall on all middleware in order.
311
+ */
312
+ async runOnAfterToolCall(ctx, info) {
313
+ for (const mw of this.middlewares) if (mw.onAfterToolCall) {
314
+ const skip = shouldSkipInstrumentation(mw);
315
+ const start = Date.now();
316
+ await mw.onAfterToolCall(ctx, info);
317
+ if (!skip) {
318
+ this.logger.middleware(`hook=onAfterToolCall middleware=${mw.name ?? "unnamed"}`, {
319
+ middleware: mw.name ?? "unnamed",
320
+ hook: "onAfterToolCall"
321
+ });
322
+ aiEventClient.emit("middleware:hook:executed", {
323
+ ...instrumentCtx(ctx),
324
+ middlewareName: mw.name || "unnamed",
325
+ hookName: "onAfterToolCall",
326
+ iteration: ctx.iteration,
327
+ duration: Date.now() - start,
328
+ hasTransform: false
329
+ });
330
+ }
331
+ }
332
+ }
333
+ /**
334
+ * Run onUsage on all middleware in order.
335
+ */
336
+ async runOnUsage(ctx, usage) {
337
+ for (const mw of this.middlewares) if (mw.onUsage) {
338
+ const skip = shouldSkipInstrumentation(mw);
339
+ const start = Date.now();
340
+ await mw.onUsage(ctx, usage);
341
+ if (!skip) {
342
+ this.logger.middleware(`hook=onUsage middleware=${mw.name ?? "unnamed"}`, {
343
+ middleware: mw.name ?? "unnamed",
344
+ hook: "onUsage"
345
+ });
346
+ aiEventClient.emit("middleware:hook:executed", {
347
+ ...instrumentCtx(ctx),
348
+ middlewareName: mw.name || "unnamed",
349
+ hookName: "onUsage",
350
+ iteration: ctx.iteration,
351
+ duration: Date.now() - start,
352
+ hasTransform: false
353
+ });
354
+ }
355
+ }
356
+ }
357
+ /**
358
+ * Await ONE terminal hook and RETURN its throw instead of letting it escape
359
+ * the caller's loop, logging it on the `errors` channel first so the failure
360
+ * is never invisible. `undefined` means the hook completed.
361
+ *
362
+ * Capturing (rather than swallowing at this level) is what lets isolation and
363
+ * reporting coexist: every caller gives every middleware its turn, and then
364
+ * each decides on its own whether the collected failures are worth telling the
365
+ * caller about. See {@link runOnFinish} vs {@link runOnAbort} /
366
+ * {@link runOnError}.
367
+ */
368
+ async captureTerminalHook(mw, hookName, invoke) {
369
+ try {
370
+ await invoke();
371
+ return;
372
+ } catch (error) {
373
+ this.logger.errors(`middleware ${hookName} hook failed`, {
374
+ middleware: mw.name ?? "unnamed",
375
+ hook: hookName,
376
+ error
377
+ });
378
+ return {
379
+ middleware: mw.name ?? "unnamed",
380
+ error
381
+ };
382
+ }
383
+ }
384
+ /**
385
+ * Run onFinish on all middleware in order.
386
+ *
387
+ * ISOLATED **and** REPORTED. `onFinish` is the only terminal fan-out on the
388
+ * SUCCESS path, and it is where `withPersistence.onFinish` writes the
389
+ * assistant turn through the store. So the two properties are needed together
390
+ * and neither may be traded for the other:
391
+ *
392
+ * - ISOLATION: every middleware's hook runs even if an earlier one threw, so a
393
+ * transient store error cannot skip a later middleware's own bookkeeping.
394
+ * Each failure is captured by {@link captureTerminalHook}, not propagated
395
+ * mid-loop.
396
+ * - REPORTING: after the loop, the failures are rethrown. `chat()`'s catch
397
+ * treats what we throw as a genuine error (it is not a
398
+ * `MiddlewareAbortError`, and `structuralInterruptFailure` does not match
399
+ * it) and rethrows it out of the generator.
400
+ *
401
+ * What that rethrow can and cannot achieve depends on the transport, because
402
+ * this fan-out is awaited AFTER the adapter's `RUN_FINISHED` has already been
403
+ * yielded (`chat()` yields terminal chunks while streaming, then awaits this
404
+ * hook on its way out). The success terminal is therefore already gone; the
405
+ * rethrow can only append to what the consumer saw, never retract it:
406
+ *
407
+ * - NON-DURABLE transport: the throw escapes the generator mid-response, and
408
+ * the SSE / HTTP-stream encoder turns it into a TRAILING `RUN_ERROR` on the
409
+ * wire carrying the store's own message and `code`. `ai-client` surfaces
410
+ * that as an error status, so the user is not told the turn was saved when
411
+ * it was not.
412
+ * - DURABLE transport: the throw reaches the durability sink instead. The
413
+ * terminal was already persisted AND forwarded, so the sink deliberately
414
+ * does NOT append a second, contradictory terminal, and `terminalForwarded`
415
+ * (see `stream-to-response.ts`) suppresses the rethrow to the live consumer.
416
+ * The `RUN_FINISHED` stands and the failure is RECORDED SERVER-SIDE on the
417
+ * sink's `errors` channel. That is the intended outcome, not a gap: the save
418
+ * failed, not the run — the consumer did receive the complete stream, so
419
+ * telling it the run errored would be the lie. What the rethrow buys here is
420
+ * that the sink sees the failure at all; while this loop swallowed, the only
421
+ * trace anywhere was {@link captureTerminalHook}'s log line.
422
+ *
423
+ * Either way, swallowing is the one option ruled out: a failed
424
+ * `messages.append` would otherwise leave a `completed` run record with the
425
+ * assistant turn missing from storage and nothing beyond a middleware log
426
+ * line, and the client would go on to send a history the server has no record
427
+ * of.
428
+ *
429
+ * A single failure is rethrown AS-IS so the store's own error — its message,
430
+ * `cause`, `code` and `instanceof` identity — is what reaches the caller and
431
+ * the wire; wrapping the common case would bury it. Two or more become an
432
+ * `AggregateError` (never a `MiddlewareAbortError`, so it cannot be mistaken
433
+ * for an abort) rather than picking a winner and dropping the rest.
434
+ */
435
+ async runOnFinish(ctx, info) {
436
+ const failures = [];
437
+ let firstFailure;
438
+ for (const mw of this.middlewares) {
439
+ const hook = mw.onFinish;
440
+ if (hook) {
441
+ const skip = shouldSkipInstrumentation(mw);
442
+ const start = Date.now();
443
+ const failure = await this.captureTerminalHook(mw, "onFinish", () => hook.call(mw, ctx, info));
444
+ if (failure !== void 0) {
445
+ firstFailure ??= failure;
446
+ failures.push(failure);
447
+ continue;
448
+ }
449
+ if (!skip) {
450
+ this.logger.middleware(`hook=onFinish middleware=${mw.name ?? "unnamed"}`, {
451
+ middleware: mw.name ?? "unnamed",
452
+ hook: "onFinish"
453
+ });
454
+ aiEventClient.emit("middleware:hook:executed", {
455
+ ...instrumentCtx(ctx),
456
+ middlewareName: mw.name || "unnamed",
457
+ hookName: "onFinish",
458
+ iteration: ctx.iteration,
459
+ duration: Date.now() - start,
460
+ hasTransform: false
461
+ });
462
+ }
463
+ }
464
+ }
465
+ if (firstFailure !== void 0) throw failures.length === 1 ? firstFailure.error : new AggregateError(failures.map((f) => f.error), `${failures.length} middleware onFinish hooks failed: ` + failures.map((f) => f.middleware).join(", "));
466
+ }
467
+ /**
468
+ * Run onAbort on all middleware in order.
469
+ *
470
+ * ISOLATED and DELIBERATELY SWALLOWED. `onAbort` is a pure teardown fan-out
471
+ * released from `chat()`'s `finally`, on a path where the outcome is already
472
+ * decided: the run stopped, and the caller is being told why. A throw here has
473
+ * nothing better to report than the abort reason it would DISPLACE — the
474
+ * `finally` would surface a flaky store's error in place of "client
475
+ * disconnected" — so failures are logged on the `errors` channel and go no
476
+ * further. That is not a silent failure; it is refusing to let teardown
477
+ * rewrite an outcome it did not produce.
478
+ *
479
+ * Isolation matters independently: these hooks release PER-MIDDLEWARE
480
+ * resources (`withSandbox.onAbort` detaches or destroys the sandbox and stamps
481
+ * `detachedSince`; `withPersistence.onAbort` records the run status through the
482
+ * store), so an unguarded loop turns one transient store error into a
483
+ * permanently leaked sandbox for every middleware ordered after it.
484
+ */
485
+ async runOnAbort(ctx, info) {
486
+ for (const mw of this.middlewares) {
487
+ const hook = mw.onAbort;
488
+ if (hook) {
489
+ const skip = shouldSkipInstrumentation(mw);
490
+ const start = Date.now();
491
+ if (await this.captureTerminalHook(mw, "onAbort", () => hook.call(mw, ctx, info)) === void 0 && !skip) {
492
+ this.logger.middleware(`hook=onAbort middleware=${mw.name ?? "unnamed"}`, {
493
+ middleware: mw.name ?? "unnamed",
494
+ hook: "onAbort"
495
+ });
496
+ aiEventClient.emit("middleware:hook:executed", {
497
+ ...instrumentCtx(ctx),
498
+ middlewareName: mw.name || "unnamed",
499
+ hookName: "onAbort",
500
+ iteration: ctx.iteration,
501
+ duration: Date.now() - start,
502
+ hasTransform: false
503
+ });
504
+ }
505
+ }
506
+ }
507
+ }
508
+ /**
509
+ * Run onError on all middleware in order.
510
+ *
511
+ * ISOLATED and DELIBERATELY SWALLOWED, for the same reason as
512
+ * {@link runOnAbort} and NOT merely because it is teardown: the run has
513
+ * already failed, `info.error` IS that failure, and `chat()` rethrows it to the
514
+ * caller the moment this fan-out returns. A propagated hook throw could only
515
+ * REPLACE the run's real error with a teardown artifact — strictly less
516
+ * information for the caller, who is already learning the run failed. Reporting
517
+ * would buy nothing and cost the diagnosis, so failures are logged on the
518
+ * `errors` channel and stop there.
519
+ *
520
+ * Contrast {@link runOnFinish}, where nothing else is telling the caller
521
+ * anything is wrong — which is why that one reports.
522
+ */
523
+ async runOnError(ctx, info) {
524
+ for (const mw of this.middlewares) {
525
+ const hook = mw.onError;
526
+ if (hook) {
527
+ const skip = shouldSkipInstrumentation(mw);
528
+ const start = Date.now();
529
+ if (await this.captureTerminalHook(mw, "onError", () => hook.call(mw, ctx, info)) === void 0 && !skip) {
530
+ this.logger.middleware(`hook=onError middleware=${mw.name ?? "unnamed"}`, {
531
+ middleware: mw.name ?? "unnamed",
532
+ hook: "onError"
533
+ });
534
+ aiEventClient.emit("middleware:hook:executed", {
535
+ ...instrumentCtx(ctx),
536
+ middlewareName: mw.name || "unnamed",
537
+ hookName: "onError",
538
+ iteration: ctx.iteration,
539
+ duration: Date.now() - start,
540
+ hasTransform: false
541
+ });
542
+ }
543
+ }
544
+ }
545
+ }
546
+ /**
547
+ * Run onIteration on all middleware in order.
548
+ * Called at the start of each agent loop iteration.
549
+ */
550
+ async runOnIteration(ctx, info) {
551
+ for (const mw of this.middlewares) if (mw.onIteration) {
552
+ const skip = shouldSkipInstrumentation(mw);
553
+ const start = Date.now();
554
+ await mw.onIteration(ctx, info);
555
+ if (!skip) {
556
+ this.logger.middleware(`hook=onIteration middleware=${mw.name ?? "unnamed"}`, {
557
+ middleware: mw.name ?? "unnamed",
558
+ hook: "onIteration"
559
+ });
560
+ aiEventClient.emit("middleware:hook:executed", {
561
+ ...instrumentCtx(ctx),
562
+ middlewareName: mw.name || "unnamed",
563
+ hookName: "onIteration",
564
+ iteration: ctx.iteration,
565
+ duration: Date.now() - start,
566
+ hasTransform: false
567
+ });
568
+ }
569
+ }
570
+ }
571
+ /**
572
+ * Run onShouldContinue through middleware in order (AND semantics).
573
+ * Any explicit `false` stops further iterations; `true` / void / undefined pass.
574
+ * Called after `agentLoopStrategy` has already approved continuation.
575
+ */
576
+ async runOnShouldContinue(ctx, state) {
577
+ for (const mw of this.middlewares) if (mw.onShouldContinue) {
578
+ const skip = shouldSkipInstrumentation(mw);
579
+ const start = Date.now();
580
+ const result = await mw.onShouldContinue(ctx, state);
581
+ if (!skip) {
582
+ this.logger.middleware(`hook=onShouldContinue middleware=${mw.name ?? "unnamed"}`, {
583
+ middleware: mw.name ?? "unnamed",
584
+ hook: "onShouldContinue",
585
+ result
586
+ });
587
+ aiEventClient.emit("middleware:hook:executed", {
588
+ ...instrumentCtx(ctx),
589
+ middlewareName: mw.name || "unnamed",
590
+ hookName: "onShouldContinue",
591
+ iteration: ctx.iteration,
592
+ duration: Date.now() - start,
593
+ hasTransform: result === false
594
+ });
595
+ }
596
+ if (result === false) return false;
597
+ }
598
+ return true;
599
+ }
600
+ /**
601
+ * Run onToolPhaseComplete on all middleware in order.
602
+ * Called after all tool calls in an iteration have been processed.
603
+ */
604
+ async runOnToolPhaseComplete(ctx, info) {
605
+ for (const mw of this.middlewares) if (mw.onToolPhaseComplete) {
606
+ const skip = shouldSkipInstrumentation(mw);
607
+ const start = Date.now();
608
+ await mw.onToolPhaseComplete(ctx, info);
609
+ if (!skip) {
610
+ this.logger.middleware(`hook=onToolPhaseComplete middleware=${mw.name ?? "unnamed"}`, {
611
+ middleware: mw.name ?? "unnamed",
612
+ hook: "onToolPhaseComplete"
613
+ });
614
+ aiEventClient.emit("middleware:hook:executed", {
615
+ ...instrumentCtx(ctx),
616
+ middlewareName: mw.name || "unnamed",
617
+ hookName: "onToolPhaseComplete",
618
+ iteration: ctx.iteration,
619
+ duration: Date.now() - start,
620
+ hasTransform: false
621
+ });
622
+ }
623
+ }
624
+ }
536
625
  };
537
- //# sourceMappingURL=compose.js.map
626
+ //#endregion
627
+ export { MiddlewareRunner };
628
+
629
+ //# sourceMappingURL=compose.js.map