@tanstack/ai 0.41.0 → 0.43.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 (272) 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 +10 -4
  5. package/dist/esm/activities/chat/agent-loop-strategies.js +75 -17
  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 -16
  11. package/dist/esm/activities/chat/index.js +2100 -1744
  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 +23 -5
  134. package/dist/esm/index.js +30 -97
  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.js +598 -732
  157. package/dist/esm/middlewares/otel.js.map +1 -1
  158. package/dist/esm/middlewares/usage-attributes.js +47 -40
  159. package/dist/esm/middlewares/usage-attributes.js.map +1 -1
  160. package/dist/esm/realtime/event-emitter.js +24 -25
  161. package/dist/esm/realtime/event-emitter.js.map +1 -1
  162. package/dist/esm/realtime/index.d.ts +5 -9
  163. package/dist/esm/realtime/index.js +29 -6
  164. package/dist/esm/realtime/index.js.map +1 -1
  165. package/dist/esm/scope.d.ts +47 -0
  166. package/dist/esm/stream-durability.d.ts +171 -0
  167. package/dist/esm/stream-durability.js +295 -0
  168. package/dist/esm/stream-durability.js.map +1 -0
  169. package/dist/esm/stream-to-response.d.ts +178 -13
  170. package/dist/esm/stream-to-response.js +663 -115
  171. package/dist/esm/stream-to-response.js.map +1 -1
  172. package/dist/esm/strip-to-spec-middleware.js +30 -16
  173. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  174. package/dist/esm/system-prompts.js +27 -21
  175. package/dist/esm/system-prompts.js.map +1 -1
  176. package/dist/esm/tool-registry.js +72 -45
  177. package/dist/esm/tool-registry.js.map +1 -1
  178. package/dist/esm/tools/provider-tool.js +14 -5
  179. package/dist/esm/tools/provider-tool.js.map +1 -1
  180. package/dist/esm/types.d.ts +332 -21
  181. package/dist/esm/types.js +2 -0
  182. package/dist/esm/utilities/ag-ui-wire.js +79 -93
  183. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  184. package/dist/esm/utilities/chat-params.d.ts +26 -4
  185. package/dist/esm/utilities/chat-params.js +218 -92
  186. package/dist/esm/utilities/chat-params.js.map +1 -1
  187. package/dist/esm/utilities/errors.js +28 -18
  188. package/dist/esm/utilities/errors.js.map +1 -1
  189. package/dist/esm/utilities/media-prompt.js +46 -41
  190. package/dist/esm/utilities/media-prompt.js.map +1 -1
  191. package/dist/esm/utilities/numbers.js +13 -10
  192. package/dist/esm/utilities/numbers.js.map +1 -1
  193. package/dist/esm/utilities/provider-executed.js +20 -11
  194. package/dist/esm/utilities/provider-executed.js.map +1 -1
  195. package/dist/esm/utilities/sampling-keys.js +31 -19
  196. package/dist/esm/utilities/sampling-keys.js.map +1 -1
  197. package/dist/esm/utilities/tool-result.js +42 -30
  198. package/dist/esm/utilities/tool-result.js.map +1 -1
  199. package/dist/esm/utilities/usage.js +27 -9
  200. package/dist/esm/utilities/usage.js.map +1 -1
  201. package/dist/esm/utils.js +26 -18
  202. package/dist/esm/utils.js.map +1 -1
  203. package/package.json +10 -6
  204. package/skills/ai-core/SKILL.md +69 -18
  205. package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
  206. package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
  207. package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
  208. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
  209. package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
  210. package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
  211. package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
  212. package/skills/ai-core/chat-experience/SKILL.md +156 -11
  213. package/skills/ai-core/client-persistence/SKILL.md +277 -0
  214. package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
  215. package/skills/ai-core/debug-logging/SKILL.md +1 -1
  216. package/skills/ai-core/locks/SKILL.md +143 -0
  217. package/skills/ai-core/media-generation/SKILL.md +144 -12
  218. package/skills/ai-core/middleware/SKILL.md +258 -33
  219. package/skills/ai-core/structured-outputs/SKILL.md +1 -1
  220. package/skills/ai-core/tool-calling/SKILL.md +54 -59
  221. package/src/activities/chat/agent-loop-strategies.ts +10 -4
  222. package/src/activities/chat/cancel.ts +81 -0
  223. package/src/activities/chat/index.ts +1152 -153
  224. package/src/activities/chat/mcp/manager.ts +4 -4
  225. package/src/activities/chat/mcp/types.ts +2 -2
  226. package/src/activities/chat/messages.ts +5 -3
  227. package/src/activities/chat/middleware/builder.ts +1 -1
  228. package/src/activities/chat/middleware/compose.ts +186 -9
  229. package/src/activities/chat/middleware/index.ts +26 -0
  230. package/src/activities/chat/middleware/locks.ts +102 -0
  231. package/src/activities/chat/middleware/pending-turn.ts +47 -0
  232. package/src/activities/chat/middleware/run-disconnect.ts +62 -0
  233. package/src/activities/chat/middleware/run-store.ts +412 -0
  234. package/src/activities/chat/middleware/types.ts +62 -1
  235. package/src/activities/chat/stream/processor.ts +189 -5
  236. package/src/activities/chat/tools/approval-schema.ts +205 -0
  237. package/src/activities/chat/tools/tool-calls.ts +106 -13
  238. package/src/activities/chat/tools/tool-definition.ts +210 -39
  239. package/src/activities/generateAudio/index.ts +20 -3
  240. package/src/activities/generateImage/index.ts +20 -3
  241. package/src/activities/generateSpeech/index.ts +25 -3
  242. package/src/activities/generateTranscription/index.ts +26 -3
  243. package/src/activities/generateVideo/index.ts +345 -82
  244. package/src/activities/middleware/index.ts +2 -0
  245. package/src/activities/middleware/run.ts +31 -0
  246. package/src/activities/middleware/types.ts +49 -5
  247. package/src/activities/stream-generation-result.ts +30 -2
  248. package/src/activities/summarize/chat-stream-summarize.ts +5 -0
  249. package/src/activities/summarize/index.ts +200 -10
  250. package/src/adapter-internals.ts +10 -1
  251. package/src/client.ts +244 -0
  252. package/src/custom-events.ts +107 -0
  253. package/src/delivery-detach.ts +72 -0
  254. package/src/delivery-disconnect.ts +84 -0
  255. package/src/index.ts +138 -0
  256. package/src/interrupt-resume.ts +824 -0
  257. package/src/interrupt-serialization.ts +183 -0
  258. package/src/interrupts.ts +146 -0
  259. package/src/locks.ts +17 -0
  260. package/src/logger/types.ts +1 -1
  261. package/src/middlewares/otel.ts +1 -0
  262. package/src/realtime/index.ts +5 -9
  263. package/src/scope.ts +47 -0
  264. package/src/stream-durability.ts +598 -0
  265. package/src/stream-to-response.ts +1051 -95
  266. package/src/strip-to-spec-middleware.ts +3 -3
  267. package/src/types.ts +416 -24
  268. package/src/utilities/chat-params.ts +245 -55
  269. package/dist/esm/activities/index.js.map +0 -1
  270. package/dist/esm/adapter-internals.js.map +0 -1
  271. package/dist/esm/index.js.map +0 -1
  272. package/dist/esm/middlewares/index.js.map +0 -1
@@ -25,11 +25,14 @@ import type { TokenUsage } from '../../types'
25
25
  /**
26
26
  * The activity an observability event describes.
27
27
  *
28
- * Mirrors the public surface a caller reaches for: `'chat'` for `chat()`, and
29
- * the media kinds for the `generate*` activities. `'tts'` matches the speech
30
- * adapter's kind (the public discriminator avoids inventing a parallel
31
- * `'speech'`/`'text'` vocabulary). `otelMiddleware` maps each to its
32
- * `gen_ai.operation.name`.
28
+ * Mirrors the public surface a caller reaches for: `'chat'` for `chat()`,
29
+ * `'summarize'` for `summarize()`, and the media kinds for the `generate*`
30
+ * activities. `'tts'` matches the speech adapter's kind (the public
31
+ * discriminator avoids inventing a parallel `'speech'`/`'text'` vocabulary).
32
+ * `otelMiddleware` maps each to its `gen_ai.operation.name`.
33
+ *
34
+ * `'summarize'` produces text, not media, so it has no artifacts — a
35
+ * persistence middleware stores its run record and result and nothing else.
33
36
  */
34
37
  export type GenerationActivity =
35
38
  | 'chat'
@@ -38,6 +41,7 @@ export type GenerationActivity =
38
41
  | 'audio'
39
42
  | 'tts'
40
43
  | 'transcription'
44
+ | 'summarize'
41
45
 
42
46
  /**
43
47
  * Stable context passed to every {@link GenerationMiddleware} hook. Created
@@ -60,6 +64,10 @@ export interface GenerationMiddlewareContext<TContext = unknown> {
60
64
  provider: string
61
65
  /** Model id. Emitted as `gen_ai.request.model`. */
62
66
  model: string
67
+ /** Stable conversation/thread id, when supplied by the caller. */
68
+ threadId?: string
69
+ /** Stable run id, when supplied by the caller. */
70
+ runId?: string
63
71
  /**
64
72
  * Provider-specific options passed to the activity, if any. Typed `unknown`
65
73
  * because each activity's options are strongly typed per model; a supertype
@@ -72,8 +80,44 @@ export interface GenerationMiddlewareContext<TContext = unknown> {
72
80
  createId: (prefix: string) => string
73
81
  /** Runtime context provided by the activity options, if any. */
74
82
  context: TContext
83
+ /**
84
+ * Result transforms registered by middleware during this activity call.
85
+ * Transforms run after the raw adapter result exists and before the final
86
+ * result is returned or streamed. Push multiple transforms to run them in
87
+ * registration order.
88
+ *
89
+ * REQUIRED (always an array, empty when nothing registered): middleware
90
+ * registers by pushing onto it, so an optional array would let a host that
91
+ * builds its own context omit it and silently no-op every registration —
92
+ * generation persistence would then mark a run completed with neither its
93
+ * result nor its artifacts written, with nothing to observe but the missing
94
+ * data. Every context the library builds comes from
95
+ * `createGenerationContext`, which always sets `[]`.
96
+ */
97
+ resultTransforms: Array<GenerationResultTransform<any, TContext>>
98
+ /**
99
+ * Activity inputs captured for middleware that needs to transform or persist
100
+ * the result together with reconstructable request metadata.
101
+ */
102
+ artifactInputs?: unknown
75
103
  }
76
104
 
105
+ /** Stable context handed to each {@link GenerationResultTransform}. */
106
+ export interface GenerationResultTransformContext<TContext = unknown> {
107
+ /** The activity call being transformed. */
108
+ middleware: GenerationMiddlewareContext<TContext>
109
+ }
110
+
111
+ /**
112
+ * A transform middleware registers on `ctx.resultTransforms` to rewrite the raw
113
+ * adapter result before it is returned or streamed. Return a new result to
114
+ * replace it, or `undefined` to leave it unchanged.
115
+ */
116
+ export type GenerationResultTransform<TResult = unknown, TContext = unknown> = (
117
+ result: TResult,
118
+ ctx: GenerationResultTransformContext<TContext>,
119
+ ) => TResult | undefined | Promise<TResult | undefined>
120
+
77
121
  // ===========================
78
122
  // Hook payloads
79
123
  // ===========================
@@ -12,6 +12,19 @@ function createId(prefix: string): string {
12
12
  return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
13
13
  }
14
14
 
15
+ /**
16
+ * Persisted artifact refs a middleware may have attached to the result. Read
17
+ * defensively: the result shape is activity-specific and `artifacts` is only
18
+ * present when generation persistence is wired with an artifact + blob store.
19
+ */
20
+ function artifactsFromResult(result: unknown): Array<unknown> | undefined {
21
+ if (typeof result !== 'object' || result === null) return undefined
22
+ const artifacts = (result as { artifacts?: unknown }).artifacts
23
+ return Array.isArray(artifacts) && artifacts.length > 0
24
+ ? artifacts
25
+ : undefined
26
+ }
27
+
15
28
  /**
16
29
  * Wrap a one-shot generation result as a StreamChunk async iterable.
17
30
  *
@@ -23,7 +36,10 @@ function createId(prefix: string): string {
23
36
  * @returns An AsyncIterable of StreamChunks with RUN_STARTED, CUSTOM(generation:result), and RUN_FINISHED events on success, or RUN_STARTED and RUN_ERROR on failure
24
37
  */
25
38
  export async function* streamGenerationResult<TResult>(
26
- generator: () => Promise<TResult>,
39
+ generator: (resolved: {
40
+ runId: string
41
+ threadId: string
42
+ }) => Promise<TResult>,
27
43
  options?: { runId?: string; threadId?: string },
28
44
  ): AsyncIterable<StreamChunk> {
29
45
  const runId = options?.runId ?? createId('run')
@@ -37,7 +53,19 @@ export async function* streamGenerationResult<TResult>(
37
53
  }
38
54
 
39
55
  try {
40
- const result = await generator()
56
+ const result = await generator({ runId, threadId })
57
+
58
+ // Emit persisted artifact refs (if a middleware attached any) before the
59
+ // result, so the client records them as the run streams.
60
+ const artifacts = artifactsFromResult(result)
61
+ if (artifacts) {
62
+ yield {
63
+ type: EventType.CUSTOM,
64
+ name: 'generation:artifacts',
65
+ value: artifacts,
66
+ timestamp: Date.now(),
67
+ }
68
+ }
41
69
 
42
70
  yield {
43
71
  type: EventType.CUSTOM,
@@ -362,6 +362,11 @@ export class ChatStreamSummarizeAdapter<
362
362
  systemPrompts: [systemPrompt],
363
363
  modelOptions,
364
364
  logger: options.logger,
365
+ // Forward the run identity so the wrapped chat stamps it onto RUN_STARTED
366
+ // (chat uses `runId` as its `runIdOverride`). Conditional spreads keep the
367
+ // fields absent when unset, under `exactOptionalPropertyTypes`.
368
+ ...(options.runId !== undefined ? { runId: options.runId } : {}),
369
+ ...(options.threadId !== undefined ? { threadId: options.threadId } : {}),
365
370
  }
366
371
  }
367
372
 
@@ -8,8 +8,18 @@
8
8
  import { aiEventClient } from '@tanstack/ai-event-client'
9
9
  import { streamGenerationResult } from '../stream-generation-result.js'
10
10
  import { resolveDebugOption } from '../../logger/resolve'
11
+ import {
12
+ applyGenerationResultTransforms,
13
+ createGenerationContext,
14
+ runGenerationAbort,
15
+ runGenerationError,
16
+ runGenerationFinish,
17
+ runGenerationStart,
18
+ runGenerationUsage,
19
+ } from '../middleware/run'
11
20
  import type { InternalLogger } from '../../logger/internal-logger'
12
21
  import type { DebugOption } from '../../logger/types'
22
+ import type { GenerationMiddleware } from '../middleware/types'
13
23
  import type { SummarizeAdapter } from './adapter'
14
24
  import type { StreamChunk, SummarizationResult } from '../../types'
15
25
 
@@ -57,6 +67,32 @@ export interface SummarizeActivityOptions<
57
67
  focus?: Array<string>
58
68
  /** Provider-specific options */
59
69
  modelOptions?: SummarizeProviderOptions<TAdapter>
70
+ /**
71
+ * Optional run identity. When set on a streaming summarize, it is stamped
72
+ * onto the emitted `RUN_STARTED` so a delivery-durable route keys the run's
73
+ * log by the same id the client rejoins with — making a mid-run reload
74
+ * resumable. Filed under `threadId` when persistence is wired.
75
+ */
76
+ runId?: string
77
+ /**
78
+ * Stable conversation/thread id for correlating this run when persisted — the
79
+ * slot a reloading client hydrates the last summary by. Pass it whenever
80
+ * persistence is on; `withGenerationPersistence` refuses a run without one.
81
+ */
82
+ threadId?: string
83
+ /**
84
+ * Observe-only middleware notified on start, usage, success, and error. Pass
85
+ * `otelMiddleware()` for OpenTelemetry, `withGenerationPersistence()` to
86
+ * record the run (summaries are text, so the run record holds the result and
87
+ * there are no artifacts to store), or implement the `GenerationMiddleware`
88
+ * contract for a custom backend.
89
+ *
90
+ * Streaming and non-streaming behave the same way: one `onStart`, then a
91
+ * terminal `onFinish` / `onError`, with the result transforms applied to the
92
+ * `SummarizationResult` in between. A streaming consumer that disconnects
93
+ * mid-summary fires `onAbort`.
94
+ */
95
+ middleware?: Array<GenerationMiddleware>
60
96
  /**
61
97
  * Whether to stream the summarization result.
62
98
  * When true, returns an AsyncIterable<StreamChunk> for streaming output.
@@ -180,13 +216,27 @@ export function summarize<
180
216
  async function runSummarize(
181
217
  options: SummarizeActivityOptions<SummarizeAdapter<string, object>, false>,
182
218
  ): Promise<SummarizationResult> {
183
- const { adapter, text, maxLength, style, focus, modelOptions } = options
219
+ const { adapter, text, maxLength, style, focus, modelOptions, middleware } =
220
+ options
184
221
  const model = adapter.model
185
222
  const requestId = createId('summarize')
186
223
  const inputLength = text.length
187
224
  const startTime = Date.now()
188
225
  const logger: InternalLogger = resolveDebugOption(options.debug)
189
226
 
227
+ const mwCtx = createGenerationContext({
228
+ requestId,
229
+ activity: 'summarize',
230
+ provider: adapter.name,
231
+ model,
232
+ modelOptions,
233
+ threadId: options.threadId,
234
+ runId: options.runId,
235
+ createId,
236
+ })
237
+
238
+ await runGenerationStart(middleware, mwCtx)
239
+
190
240
  aiEventClient.emit('summarize:request:started', {
191
241
  requestId,
192
242
  provider: adapter.name,
@@ -212,7 +262,11 @@ async function runSummarize(
212
262
  }
213
263
 
214
264
  try {
215
- const result = await adapter.summarize(summarizeOptions)
265
+ const rawResult = await adapter.summarize(summarizeOptions)
266
+ // Transforms run before anything observes the result — the same order every
267
+ // media activity uses — so the run record and the returned value are the
268
+ // same object.
269
+ const result = await applyGenerationResultTransforms(mwCtx, rawResult)
216
270
 
217
271
  const duration = Date.now() - startTime
218
272
  const outputLength = result.summary.length
@@ -232,8 +286,18 @@ async function runSummarize(
232
286
  outputLength,
233
287
  })
234
288
 
289
+ if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage)
290
+ await runGenerationFinish(middleware, mwCtx, {
291
+ duration,
292
+ usage: result.usage,
293
+ })
294
+
235
295
  return result
236
296
  } catch (error) {
297
+ await runGenerationError(middleware, mwCtx, {
298
+ error,
299
+ duration: Date.now() - startTime,
300
+ })
237
301
  logger.errors('summarize activity failed', {
238
302
  error,
239
303
  source: 'summarize',
@@ -242,6 +306,15 @@ async function runSummarize(
242
306
  }
243
307
  }
244
308
 
309
+ /** Read a `usage` off a transformed result without asserting its shape. */
310
+ function usageOf(result: unknown): SummarizationResult['usage'] | undefined {
311
+ if (typeof result !== 'object' || result === null) return undefined
312
+ const usage = (result as { usage?: unknown }).usage
313
+ return typeof usage === 'object' && usage !== null
314
+ ? (usage as SummarizationResult['usage'])
315
+ : undefined
316
+ }
317
+
245
318
  /**
246
319
  * Run streaming summarization
247
320
  * Uses the adapter's native streaming if available, otherwise falls back
@@ -250,7 +323,16 @@ async function runSummarize(
250
323
  async function* runStreamingSummarize(
251
324
  options: SummarizeActivityOptions<SummarizeAdapter<string, object>, true>,
252
325
  ): AsyncIterable<StreamChunk> {
253
- const { adapter, text, maxLength, style, focus, modelOptions } = options
326
+ const {
327
+ adapter,
328
+ text,
329
+ maxLength,
330
+ style,
331
+ focus,
332
+ modelOptions,
333
+ runId,
334
+ threadId,
335
+ } = options
254
336
  const model = adapter.model
255
337
  const logger: InternalLogger = resolveDebugOption(options.debug)
256
338
 
@@ -260,6 +342,10 @@ async function* runStreamingSummarize(
260
342
  stream: true,
261
343
  })
262
344
 
345
+ // Thread the caller's run identity through so the emitted `RUN_STARTED`
346
+ // carries it — keeps a delivery-durable route's log keyed by the id the
347
+ // client rejoins with (mid-run reload resumability). Conditional spreads keep
348
+ // the fields off the object entirely under `exactOptionalPropertyTypes`.
263
349
  const summarizeOptions = {
264
350
  model,
265
351
  text,
@@ -268,23 +354,127 @@ async function* runStreamingSummarize(
268
354
  focus,
269
355
  modelOptions,
270
356
  logger,
357
+ ...(runId !== undefined ? { runId } : {}),
358
+ ...(threadId !== undefined ? { threadId } : {}),
359
+ }
360
+
361
+ // Use real streaming if the adapter supports it
362
+ if (adapter.summarizeStream) {
363
+ yield* runNativeSummarizeStream(
364
+ options,
365
+ adapter.summarizeStream(summarizeOptions),
366
+ logger,
367
+ )
368
+ return
271
369
  }
272
370
 
273
371
  try {
274
- // Use real streaming if the adapter supports it
275
- if (adapter.summarizeStream) {
276
- yield* adapter.summarizeStream(summarizeOptions)
277
- return
278
- }
372
+ // Fall back to non-streaming — wrap the result with streamGenerationResult,
373
+ // forwarding the run identity so its RUN_STARTED matches too. The generation
374
+ // itself goes through `runSummarize`, so middleware (and its result
375
+ // transforms) run exactly as they do for a non-streaming call. Only `runId`
376
+ // is taken from the resolved wire identity — `threadId` stays the CALLER's,
377
+ // since a minted one would file the run in a slot no client can hydrate.
378
+ yield* streamGenerationResult(
379
+ (resolved) =>
380
+ runSummarize({ ...options, stream: false, runId: resolved.runId }),
381
+ {
382
+ ...(runId !== undefined ? { runId } : {}),
383
+ ...(threadId !== undefined ? { threadId } : {}),
384
+ },
385
+ )
386
+ } catch (error) {
387
+ logger.errors('summarize activity failed', {
388
+ error,
389
+ source: 'summarize',
390
+ })
391
+ throw error
392
+ }
393
+ }
279
394
 
280
- // Fall back to non-streaming — wrap result with streamGenerationResult
281
- yield* streamGenerationResult(() => adapter.summarize(summarizeOptions))
395
+ /**
396
+ * Drive an adapter's native `summarizeStream`, wiring the generation middleware
397
+ * around it.
398
+ *
399
+ * The adapter emits a terminal `generation:result` CUSTOM chunk carrying the
400
+ * assembled {@link SummarizationResult}; that is the one point where a result
401
+ * exists, so the transforms run there and the REWRITTEN result is what gets
402
+ * yielded — the client and the persisted run record then hold the same object.
403
+ * An adapter whose stream never emits one still finishes the run, just with no
404
+ * result recorded.
405
+ */
406
+ async function* runNativeSummarizeStream(
407
+ options: SummarizeActivityOptions<SummarizeAdapter<string, object>, true>,
408
+ stream: AsyncIterable<StreamChunk>,
409
+ logger: InternalLogger,
410
+ ): AsyncIterable<StreamChunk> {
411
+ const { adapter, middleware, modelOptions } = options
412
+ const mwCtx = createGenerationContext({
413
+ requestId: createId('summarize'),
414
+ activity: 'summarize',
415
+ provider: adapter.name,
416
+ model: adapter.model,
417
+ modelOptions,
418
+ threadId: options.threadId,
419
+ runId: options.runId,
420
+ createId,
421
+ })
422
+
423
+ await runGenerationStart(middleware, mwCtx)
424
+
425
+ const startTime = Date.now()
426
+ // Tracks whether a terminal hook already fired, so the `finally` can report an
427
+ // abandoned stream without double-firing. Mirrors the streaming video path.
428
+ let settled = false
429
+ try {
430
+ for await (const chunk of stream) {
431
+ if (chunk.type === 'CUSTOM' && chunk.name === 'generation:result') {
432
+ const result = await applyGenerationResultTransforms<unknown>(
433
+ mwCtx,
434
+ chunk.value,
435
+ )
436
+ const usage = usageOf(result)
437
+ // Finish before yielding the terminal chunks: a consumer that stops
438
+ // reading once it has the result must not trip the abandonment path.
439
+ if (usage) await runGenerationUsage(middleware, mwCtx, usage)
440
+ await runGenerationFinish(middleware, mwCtx, {
441
+ duration: Date.now() - startTime,
442
+ usage,
443
+ })
444
+ settled = true
445
+ yield { ...chunk, value: result }
446
+ continue
447
+ }
448
+ yield chunk
449
+ }
450
+ if (!settled) {
451
+ await runGenerationFinish(middleware, mwCtx, {
452
+ duration: Date.now() - startTime,
453
+ })
454
+ settled = true
455
+ }
282
456
  } catch (error) {
457
+ settled = true
458
+ await runGenerationError(middleware, mwCtx, {
459
+ error,
460
+ duration: Date.now() - startTime,
461
+ })
283
462
  logger.errors('summarize activity failed', {
284
463
  error,
285
464
  source: 'summarize',
286
465
  })
287
466
  throw error
467
+ } finally {
468
+ if (!settled) {
469
+ // The consumer abandoned the stream mid-summary, so the generator is being
470
+ // unwound at a `yield`. Report a cancel, not an error, so an observer ends
471
+ // its span (and persistence marks the run interrupted) instead of leaving
472
+ // the run open forever.
473
+ await runGenerationAbort(middleware, mwCtx, {
474
+ reason: 'Summarize stream abandoned before completion',
475
+ duration: Date.now() - startTime,
476
+ })
477
+ }
288
478
  }
289
479
  }
290
480
 
@@ -15,4 +15,13 @@ export {
15
15
  provideSandboxRuntime,
16
16
  SandboxRuntimeCapability,
17
17
  } from './activities/chat/middleware/sandbox-runtime'
18
- export type { SandboxRuntime } from './activities/chat/middleware/sandbox-runtime'
18
+ export {
19
+ getRunDisconnect,
20
+ provideRunDisconnect,
21
+ RunDisconnectCapability,
22
+ } from './activities/chat/middleware/run-disconnect'
23
+ export {
24
+ getPendingTurn,
25
+ PendingTurnCapability,
26
+ providePendingTurn,
27
+ } from './activities/chat/middleware/pending-turn'