@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
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: ai-core/locks
3
+ description: >
4
+ LockStore, InMemoryLockStore, LocksCapability and withLocks for
5
+ multi-instance coordination in TanStack AI. Ships in @tanstack/ai — NOT in
6
+ @tanstack/ai-persistence. Separate from AIPersistence state stores — not a
7
+ stores key, not composable. InMemoryLockStore vs a distributed (e.g.
8
+ Cloudflare Durable Object) lock, lease recovery, AbortSignal in critical
9
+ sections. Use when sandbox or other middleware needs cross-worker mutual
10
+ exclusion — NOT for storing messages/runs (use withPersistence).
11
+ type: sub-skill
12
+ library: tanstack-ai
13
+ library_version: '0.42.0'
14
+ sources:
15
+ - 'TanStack/ai:docs/advanced/locks.md'
16
+ - 'TanStack/ai:packages/ai/src/activities/chat/middleware/locks.ts'
17
+ ---
18
+
19
+ # Locks (coordination — not persistence)
20
+
21
+ > **Dependency note:** This skill builds on ai-core and ai-core/middleware.
22
+ > `withLocks` is a ChatMiddleware that provides a capability. Locks are **not**
23
+ > part of `AIPersistence.stores` and are **not** composed with
24
+ > `composePersistence` — they ship in `@tanstack/ai`, independent of
25
+ > `@tanstack/ai-persistence`.
26
+
27
+ ## Why separate?
28
+
29
+ State stores answer "what is durable chat data?"
30
+ Locks answer "who may run this critical section right now?"
31
+
32
+ `withPersistence` does **not** automatically lock a whole turn. Take a
33
+ per-thread (or other) lock yourself when multi-writer races matter.
34
+
35
+ ## Wire locks
36
+
37
+ ```ts
38
+ import { withLocks, InMemoryLockStore } from '@tanstack/ai/locks'
39
+
40
+ middleware: [
41
+ withLocks(new InMemoryLockStore()), // single process
42
+ ]
43
+ ```
44
+
45
+ Alongside persistence — optional, locks do not require it:
46
+
47
+ ```ts
48
+ import { withLocks, InMemoryLockStore } from '@tanstack/ai/locks'
49
+ import { withPersistence } from '@tanstack/ai-persistence'
50
+
51
+ middleware: [withPersistence(persistence), withLocks(new InMemoryLockStore())]
52
+ ```
53
+
54
+ `withLocks` provides `LocksCapability` for downstream middleware (e.g.
55
+ sandbox). Order: usually state first, locks alongside or after depending on
56
+ who consumes the capability.
57
+
58
+ ## The contract
59
+
60
+ ```ts
61
+ interface LockStore {
62
+ withLock<T>(key: string, fn: (signal: AbortSignal) => Promise<T>): Promise<T>
63
+ }
64
+ ```
65
+
66
+ `InMemoryLockStore` ships in **`@tanstack/ai/locks`**: a per-key promise chain,
67
+ correct **within a single process only**. Multi-instance deployments need a
68
+ distributed implementation — you write it. The Cloudflare Durable Object recipe
69
+ is in **ai-persistence/build-cloudflare-adapter** (`@tanstack/ai-persistence`).
70
+
71
+ Type your own store with `defineLock` (autocomplete, no `: LockStore`
72
+ annotation), then hand it to `withLocks`. Acquire the key, run `fn`, release when
73
+ `fn` settles:
74
+
75
+ ```ts
76
+ import { defineLock, withLocks } from '@tanstack/ai/locks'
77
+ import { acquire } from './my-lock-backend'
78
+
79
+ const locks = defineLock({
80
+ async withLock(key, fn) {
81
+ const { release, signal } = await acquire(key)
82
+ try {
83
+ return await fn(signal)
84
+ } finally {
85
+ release()
86
+ }
87
+ },
88
+ })
89
+
90
+ middleware: [withLocks(locks)]
91
+ ```
92
+
93
+ ## Lease semantics
94
+
95
+ A good `LockStore`:
96
+
97
+ - Serializes owners per key,
98
+ - Uses **leases** (or equivalent) so a crashed owner cannot block forever,
99
+ - Passes an `AbortSignal` into the critical section via `withLock`; when the
100
+ lease is lost, abort so work stops starting external mutations.
101
+
102
+ Callbacks must honor the signal and pass it to cancellable dependencies.
103
+ `InMemoryLockStore` never aborts its signal — within one process, ownership
104
+ cannot be lost.
105
+
106
+ ## Capability identity
107
+
108
+ The `'locks'` capability token lives in `@tanstack/ai/locks`. Capability identity
109
+ is by **object reference**, so one shared token means a `withLocks` in the chain
110
+ reaches `withSandbox` automatically.
111
+
112
+ ## Common mistakes
113
+
114
+ ### HIGH: Importing locks from `@tanstack/ai-persistence`
115
+
116
+ They are not exported there. Use `@tanstack/ai`.
117
+
118
+ ### HIGH: Putting `locks` on `AIPersistence.stores`
119
+
120
+ Not supported. `stores` accepts only `messages`, `runs`, `interrupts`,
121
+ `metadata` — never `locks`. Use `withLocks`.
122
+
123
+ ### HIGH: Passing `locks` to `composePersistence` overrides
124
+
125
+ Same rejection, at the override layer. Locks are not state.
126
+
127
+ ### HIGH: Passing `'locks'` to the conformance testkit's `skip`
128
+
129
+ `skip` accepts only chat state store keys. The suite does not cover locks
130
+ at all — test lease expiry and abort separately.
131
+
132
+ ### HIGH: `InMemoryLockStore` across multiple processes
133
+
134
+ No mutual exclusion between machines — use a distributed lock store.
135
+
136
+ ### MEDIUM: Ignoring lease abort
137
+
138
+ Continuing work after losing the lease races other owners.
139
+
140
+ ## Cross-references
141
+
142
+ - See also: **ai-core/middleware/SKILL.md** -- the middleware chain and capability plumbing
143
+ - See also: **`@tanstack/ai-persistence` skills** (`skills/ai-persistence/SKILL.md` in that package) -- `ai-persistence/server` (state middleware) and `ai-persistence/build-cloudflare-adapter` (Durable Object lock recipe)
@@ -2,16 +2,16 @@
2
2
  name: ai-core/media-generation
3
3
  description: >
4
4
  Image, audio, video, speech (TTS), and transcription generation using
5
- activity-specific adapters: generateImage() with openaiImage/geminiImage,
5
+ activity-specific adapters: generateImage() with openaiImage/geminiImage/byteplusImage,
6
6
  generateAudio() with geminiAudio/falAudio, generateVideo() with async
7
- polling (openaiVideo/geminiVideo/grokVideo/falVideo, per-model typed
8
- durations), generateSpeech() with openaiSpeech, generateTranscription()
9
- with openaiTranscription. React hooks: useGenerateImage, useGenerateAudio,
7
+ polling (openaiVideo/geminiVideo/grokVideo/falVideo/byteplusVideo, per-model typed
8
+ durations), generateSpeech() with openaiSpeech/byteplusSpeech, generateTranscription()
9
+ with openaiTranscription/byteplusTranscription. React hooks: useGenerateImage, useGenerateAudio,
10
10
  useGenerateSpeech, useTranscription, useGenerateVideo.
11
11
  TanStack Start server function integration with toServerSentEventsResponse.
12
12
  type: sub-skill
13
13
  library: tanstack-ai
14
- library_version: '0.10.0'
14
+ library_version: '0.42.0'
15
15
  sources:
16
16
  - 'TanStack/ai:docs/media/generations.md'
17
17
  - 'TanStack/ai:docs/media/generation-hooks.md'
@@ -150,8 +150,16 @@ function ImageGenerator() {
150
150
  ### 1. Image Generation
151
151
 
152
152
  Supported adapters: `openaiImage` (dall-e-2, dall-e-3, gpt-image-1,
153
- gpt-image-1-mini, gpt-image-2) and `geminiImage` (gemini-3.1-flash-image-preview,
154
- gemini-3.1-flash-lite-image, imagen-4.0-generate-001, etc.).
153
+ gpt-image-1-mini, gpt-image-2), `geminiImage` (gemini-3.1-flash-image-preview,
154
+ gemini-3.1-flash-lite-image, imagen-4.0-generate-001, etc.) and `byteplusImage`
155
+ (Seedream — `seedream-4-0-250828`, `seedream-4-5-251128`, the 5.0 family).
156
+
157
+ > **Seedream quirks:** `watermark` defaults to **`true`** (pass
158
+ > `modelOptions: { watermark: false }` for a clean image), `size` is a token
159
+ > (`'1K'` | `'2K'` | `'4K'`) **or** explicit `'2048x2048'` pixels but never a
160
+ > mix, and `numberOfImages` is an **upper bound** — Seedream has no `n`, so it
161
+ > maps onto group-image mode and the model may return fewer. Reads
162
+ > `ARK_API_KEY`.
155
163
 
156
164
  ```typescript
157
165
  import { generateImage } from '@tanstack/ai'
@@ -333,7 +341,19 @@ const { generate, result, isLoading } = useGenerateAudio({
333
341
 
334
342
  ### 3. Text-to-Speech
335
343
 
336
- Adapter: `openaiSpeech` (tts-1, tts-1-hd, gpt-4o-audio-preview).
344
+ Adapters: `openaiSpeech` (tts-1, tts-1-hd, gpt-4o-audio-preview) and
345
+ `byteplusSpeech` (`seed-audio-1.0`).
346
+
347
+ > **BytePlus Seed Speech is a separate product from ModelArk** — it reads
348
+ > **`BYTEPLUS_VOICE_API_KEY`**, not `ARK_API_KEY`, and an Ark key there fails
349
+ > with `45000010 Invalid X-Api-Key`. Output is capped at **120 seconds**.
350
+ > There is no top-level `speaker` field — `voice` is sent as
351
+ > `references: [{ speaker }]`, and `modelOptions.references` **replaces** that
352
+ > array rather than merging, so passing `references` for voice cloning silently
353
+ > drops `voice`. Voice ids ending `_uranus_bigtts` are TTS 2.0,
354
+ > `_mars_bigtts` / `_moon_bigtts` are TTS 1.0, and `*_emo_v2_*` are the 1.0
355
+ > voices that accept emotion tags. Formats: `wav`, `mp3`, `pcm`, `ogg_opus`;
356
+ > `watermark` is also available on `modelOptions`.
337
357
 
338
358
  ```typescript
339
359
  import { generateSpeech } from '@tanstack/ai'
@@ -367,8 +387,10 @@ const { generate, result, isLoading } = useGenerateSpeech({
367
387
 
368
388
  ### 4. Audio Transcription
369
389
 
370
- Adapter: `openaiTranscription` (whisper-1, gpt-4o-transcribe,
371
- gpt-4o-mini-transcribe, gpt-4o-transcribe-diarize).
390
+ Adapters: `openaiTranscription` (whisper-1, gpt-4o-transcribe,
391
+ gpt-4o-mini-transcribe, gpt-4o-transcribe-diarize) and `byteplusTranscription`
392
+ (`seed-asr` — synchronous, no polling; audio up to 2 hours / 100 MB; also reads
393
+ **`BYTEPLUS_VOICE_API_KEY`**).
372
394
 
373
395
  > **Capturing audio in the browser:** Use `useAudioRecorder` from `@tanstack/ai-react` to record directly in the browser, then pass the recording as the `audio` input to `generate()`, or use `recording.part` as a prompt part in chat/generation calls. No transcoding or extra dependencies required — the recorder returns the native browser format (`audio/webm` or `audio/mp4`). For transcription, wrap it as a `data:` URL so the provider gets the real content type; passing raw `recording.base64` makes the adapter assume `audio/mpeg` and mislabel the webm/mp4 bytes.
374
396
  >
@@ -517,7 +539,20 @@ durations 4/8/12s, single `input_reference` image prompt part), `grokVideo(...)`
517
539
  (`grok-imagine-video` does text-to-video + image-to-video; `grok-imagine-video-1.5` is
518
540
  image-to-video only — needs an `image` prompt part as the starting frame, text-only throws;
519
541
  aspect-ratio size template like `'16:9_720p'`, integer durations 1-15s, reports
520
- `usage.unitsBilled` seconds and exact `usage.cost`), and `falVideo(...)` (hosted models, see cost tracking below).
542
+ `usage.unitsBilled` seconds and exact `usage.cost`), `byteplusVideo(...)` (Seedance —
543
+ aspect-ratio size template like `'16:9_720p'`, durations 4-15s on the 2.0 family,
544
+ 4-12s on 1.5-pro, 2-12s on the 1.0-pro models; reads `ARK_API_KEY`), and
545
+ `falVideo(...)` (hosted models, see cost tracking below).
546
+
547
+ > **Seedance option applicability is per model and enforced server-side** —
548
+ > Ark returns a 400 for an inapplicable field rather than ignoring it.
549
+ > `service_tier` / `camera_fixed` are Seedance 1.x only, `frames` is
550
+ > 1-0-pro + 1-0-pro-fast only, `draft` is 1-5-pro only, `priority` is the 2.0
551
+ > family only, and `duration: -1` works on 2.0 + 1-5-pro. There is no 2K tier
552
+ > on any model and `4k` exists only on `dreamina-seedance-2-0-260128`.
553
+ > **Video URLs expire 24 hours after the task completes** (task record kept 7
554
+ > days). Seedance is also reachable via `falVideo` — `byteplusVideo` is the
555
+ > direct-to-BytePlus path.
521
556
 
522
557
  Client hook with job tracking:
523
558
 
@@ -563,6 +598,94 @@ if (result.usage?.unitsBilled != null) {
563
598
  For video, the units arrive with the completed result: `getVideoJobStatus()`
564
599
  returns `usage` and emits a `video:usage` devtools event when fal reports it.
565
600
 
601
+ ### 7. Durable persistence (job lifecycle + artifact bytes)
602
+
603
+ To make generations survive a server restart and be re-served later, add
604
+ `withGenerationPersistence` from `@tanstack/ai-persistence` as generation
605
+ middleware. It requires `stores.generationRuns` (a `GenerationRunStore`, keyed on
606
+ the run's own `runId`, with a required `threadId` naming the stable slot the run
607
+ fills — that is what a client hydrates by) and, when you also pass an `stores.artifacts` +
608
+ `stores.blobs` **pair** (both or neither), it persists the generated media bytes
609
+ at blob key `artifacts/<runId>/<artifactId>` with an `ArtifactRecord` per file.
610
+ `memoryPersistence()` ships all three for dev/tests.
611
+
612
+ ```typescript
613
+ import { generateImage, toServerSentEventsResponse } from '@tanstack/ai'
614
+ import { openaiImage } from '@tanstack/ai-openai'
615
+ import {
616
+ withGenerationPersistence,
617
+ memoryPersistence,
618
+ retrieveArtifact,
619
+ retrieveBlob,
620
+ reconstructGeneration,
621
+ } from '@tanstack/ai-persistence'
622
+
623
+ const persistence = memoryPersistence() // swap for your DB/object-store adapter
624
+
625
+ export async function POST(req: Request) {
626
+ const { prompt, threadId } = await req.json()
627
+ return toServerSentEventsResponse(
628
+ generateImage({
629
+ adapter: openaiImage('gpt-image-1'),
630
+ prompt,
631
+ threadId, // the slot recorded on the job + artifacts
632
+ stream: true,
633
+ middleware: [
634
+ withGenerationPersistence(persistence, {
635
+ // Stamp a durable app-origin serve URL (the GET route below) onto
636
+ // each persisted artifact ref, and rewrite the live result's media to
637
+ // it. Both live and restored results then render from your origin.
638
+ artifactUrl: (ref) => `/api/artifacts?id=${ref.artifactId}`,
639
+ }),
640
+ ],
641
+ }),
642
+ )
643
+ }
644
+
645
+ // Serve the stored bytes back (GET /api/artifacts?id=…):
646
+ export async function GET(req: Request) {
647
+ const id = new URL(req.url).searchParams.get('id') ?? ''
648
+ const record = await retrieveArtifact(persistence, id)
649
+ if (!record) return new Response('Not found', { status: 404 })
650
+ const blob = await retrieveBlob(persistence, record)
651
+ if (!blob?.body) return new Response('Not found', { status: 404 })
652
+ return new Response(blob.body, {
653
+ headers: { 'content-type': record.mimeType },
654
+ })
655
+ }
656
+ ```
657
+
658
+ That route is enough for images. **Video needs `Range`**: seeking a `<video>`
659
+ is built on `206` / `Content-Range`, and Safari refuses to play a source that
660
+ ignores `Range` at all. Resolve the header against `record.size` (`416` when it
661
+ does not fit), pass `retrieveBlob(persistence, record, { range })`, and answer
662
+ `206` from the returned `blob.range` plus `accept-ranges: bytes`. The full
663
+ route is in the persistence docs under **Serve video: honour `Range`**.
664
+
665
+ On the client, `persistence` is **boolean only**: `persistence: true` hydrates
666
+ the last generation for the thread on mount, via the connection's
667
+ `hydrateGeneration` handler backed by a `reconstructGeneration` GET route. There
668
+ is no storage-adapter mode, so nothing about a generation is cached in the
669
+ browser.
670
+
671
+ **`persistence: true` requires a stable `threadId`**, and it is a type error to
672
+ set one without the other. That is the generation's scope, the slot successive
673
+ runs fill (e.g. `video-9-start-frame`), not a link to a chat. `id` is the
674
+ devtools label only and never a persistence key.
675
+
676
+ The hooks are transparent (like `useChat`): a reload repaints `status` /
677
+ `result` / `error`, not a separate `resumeSnapshot`. Because the `artifactUrl`
678
+ above stamps a durable URL onto each ref (carried on `result.artifacts`), the
679
+ restored `result` rebuilds its media from those refs and serves from your own
680
+ origin. Without byte storage, a reload restores `status` / `error` and `result`
681
+ stays `null`.
682
+
683
+ - Building the R2/D1-backed byte stores for a Cloudflare Worker:
684
+ **ai-persistence/build-cloudflare-artifact-store**.
685
+ - Store contracts, `composePersistence`, and the wiring end-to-end:
686
+ `docs/persistence/generation-persistence.md` and the
687
+ `ai-core/client-persistence` sub-skill.
688
+
566
689
  ---
567
690
 
568
691
  ## Common Hook API
@@ -577,7 +700,16 @@ All generation hooks return the same shape:
577
700
  | `error` | `Error \| undefined` | Current error |
578
701
  | `status` | `GenerationClientState` | `'idle' \| 'generating' \| 'success' \| 'error'` |
579
702
  | `stop` | `() => void` | Abort current generation |
580
- | `reset` | `() => void` | Clear state, return to idle |
703
+ | `reset` | `() => void` | Clear state and the in-memory snapshot |
704
+ | `runId` | `string \| null` | Id of the job WHILE it runs; null when idle |
705
+
706
+ The hook is **transparent**, mirroring `useChat`: there is no `resumeSnapshot`,
707
+ `resumeState`, `pendingArtifacts`, or `resultArtifacts` field. Hooks also accept
708
+ `persistence: true` plus a stable `threadId`: on mount the client hydrates the
709
+ last run for that scope from the server and repaints the **normal** `status` /
710
+ `result` / `error` fields, so the last run survives a reload (metadata only,
711
+ never media bytes; `result`'s media returns only with server byte storage +
712
+ `artifactUrl`). See `ai-core/client-persistence` for details.
581
713
 
582
714
  Provide either `connection` (streaming SSE transport) or `fetcher`
583
715
  (direct async call / server function returning `Response`). Use `onResult`