@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
@@ -4,12 +4,12 @@ description: >
4
4
  Isomorphic tool system: toolDefinition() with Zod schemas,
5
5
  .server() and .client() implementations, passing tools to both
6
6
  chat() on server and useChat/clientTools on client, tool approval
7
- flows with needsApproval and addToolApprovalResponse(), lazy tool
7
+ flows with needsApproval and bound interrupts (resolveInterrupt), lazy tool
8
8
  discovery with lazy:true, rendering ToolCallPart and ToolResultPart
9
9
  in UI.
10
10
  type: sub-skill
11
11
  library: tanstack-ai
12
- library_version: '0.10.0'
12
+ library_version: '0.42.0'
13
13
  sources:
14
14
  - 'TanStack/ai:docs/tools/tools.md'
15
15
  - 'TanStack/ai:docs/tools/server-tools.md'
@@ -108,14 +108,14 @@ function ChatPage() {
108
108
  connection: fetchServerSentEvents("/api/chat"),
109
109
  tools,
110
110
  });
111
- type Messages = InferChatMessages<typeof chatOptions>;
112
-
113
111
  const { messages, sendMessage } = useChat(chatOptions);
112
+ // InferChatMessages ties part types to the configured tools when needed:
113
+ // type Messages = InferChatMessages<typeof chatOptions>
114
114
 
115
115
  return (
116
116
  <div>
117
117
  <span>Cart: {cartCount}</span>
118
- {(messages as Messages).map((msg) => (
118
+ {messages.map((msg) => (
119
119
  <div key={msg.id}>
120
120
  {msg.parts.map((part) => {
121
121
  if (part.type === "text") return <p>{part.content}</p>;
@@ -239,9 +239,11 @@ function ChatPage() {
239
239
 
240
240
  ### Pattern 3: Tool with Approval Flow
241
241
 
242
- Set `needsApproval: true` in the definition. Execution pauses until the client
243
- calls `addToolApprovalResponse()`. The part has `state: "approval-requested"`
244
- and an `approval` object with an `id`.
242
+ Set `needsApproval: true` in the definition. Execution pauses with
243
+ `RUN_FINISHED.outcome.type === 'interrupt'`. The primary client API is bound
244
+ `interrupts` + `resolveInterrupt` / `resolveInterrupts` / `cancel`.
245
+ `addToolApprovalResponse` and `pendingInterrupts` remain as deprecated
246
+ compatibility shims during migration.
245
247
 
246
248
  ```typescript
247
249
  import { toolDefinition } from '@tanstack/ai'
@@ -265,59 +267,40 @@ export const sendEmail = sendEmailDef.server(async ({ to, subject, body }) => {
265
267
  })
266
268
  ```
267
269
 
268
- Client -- render approval UI and respond:
270
+ Server route must forward `resume` / `parentRunId` (via `chatParamsFromRequest`
271
+ or equivalent). Client -- render bound interrupts:
269
272
 
270
273
  ```typescript
271
274
  import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
272
275
 
273
276
  function ChatPage() {
274
- const { messages, addToolApprovalResponse } = useChat({
277
+ const { messages, interrupts, sendMessage } = useChat({
275
278
  connection: fetchServerSentEvents("/api/chat"),
276
279
  });
277
280
 
278
281
  return (
279
282
  <div>
283
+ {interrupts.map((interrupt) => {
284
+ if (interrupt.kind !== "tool-approval") return null;
285
+ return (
286
+ <div key={interrupt.id}>
287
+ <p>Approve "{interrupt.toolName}"?</p>
288
+ <pre>{JSON.stringify(interrupt.originalArgs, null, 2)}</pre>
289
+ <button onClick={() => interrupt.resolveInterrupt(true)}>
290
+ Approve
291
+ </button>
292
+ <button onClick={() => interrupt.resolveInterrupt(false)}>
293
+ Deny
294
+ </button>
295
+ <button onClick={() => interrupt.cancel()}>Cancel</button>
296
+ </div>
297
+ );
298
+ })}
280
299
  {messages.map((msg) => (
281
300
  <div key={msg.id}>
282
- {msg.parts.map((part) => {
283
- if (part.type === "text") return <p>{part.content}</p>;
284
- if (
285
- part.type === "tool-call" &&
286
- part.state === "approval-requested" &&
287
- part.approval
288
- ) {
289
- return (
290
- <div key={part.id}>
291
- <p>Approve "{part.name}"?</p>
292
- {/* `part.input` is the parsed, typed object (populated once
293
- the arguments are complete, as they are at approval
294
- time); `part.arguments` remains the raw JSON string. */}
295
- <pre>{JSON.stringify(part.input, null, 2)}</pre>
296
- <button
297
- onClick={() =>
298
- addToolApprovalResponse({
299
- id: part.approval!.id,
300
- approved: true,
301
- })
302
- }
303
- >
304
- Approve
305
- </button>
306
- <button
307
- onClick={() =>
308
- addToolApprovalResponse({
309
- id: part.approval!.id,
310
- approved: false,
311
- })
312
- }
313
- >
314
- Deny
315
- </button>
316
- </div>
317
- );
318
- }
319
- return null;
320
- })}
301
+ {msg.parts.map((part) =>
302
+ part.type === "text" ? <p key={part.content}>{part.content}</p> : null
303
+ )}
321
304
  </div>
322
305
  ))}
323
306
  </div>
@@ -325,14 +308,24 @@ function ChatPage() {
325
308
  }
326
309
  ```
327
310
 
328
- > **Type-safe approval:** With typed `tools`, `part.approval` exists **only**
329
- > on parts for tools defined with `needsApproval: true`. Tools without approval
330
- > have no `approval` field (reading it is a compile error). For a
331
- > tool-agnostic handler over a typed union, narrow with `'approval' in part`
332
- > (`if (part.type === 'tool-call' && 'approval' in part && part.approval)`),
333
- > or type a shared component against the base `ToolCallPart`. An untyped
334
- > `useChat()` keeps `approval` on every tool-call part, which is why the
335
- > snippet above (no `tools` generic) reads it directly.
311
+ Batch all pending approvals with `resolveInterrupts` (void — submission is
312
+ async; watch `resuming` / `interruptErrors`):
313
+
314
+ ```typescript
315
+ // Payloadless tool-approvals only
316
+ resolveInterrupts(true)
317
+
318
+ // Or per-item:
319
+ resolveInterrupts((interrupt) => {
320
+ if (interrupt.kind === 'tool-approval') {
321
+ interrupt.resolveInterrupt(true)
322
+ }
323
+ })
324
+ ```
325
+
326
+ Migration: `pendingInterrupts` aliases `interrupts`; `addToolApprovalResponse`
327
+ forwards to the matching bound approval when present. Prefer the bound methods
328
+ above for new code. See `docs/interrupts/`.
336
329
 
337
330
  ### Pattern 4: Lazy Tool Discovery
338
331
 
@@ -375,8 +368,8 @@ export async function POST(request: Request) {
375
368
  adapter: openaiText('gpt-5.5'),
376
369
  messages,
377
370
  tools: [getProducts, compareProducts],
378
- // maxIterations bounds model turns, not tool calls. Prefer maxToolCalls
379
- // (and maxToolCallsPerTurn) when you need a tool-call budget.
371
+ // maxIterations bounds model turns, not tool calls. For tool budgets,
372
+ // use middleware onBeforeToolCall + onShouldContinue (see agentic-cycle docs).
380
373
  agentLoopStrategy: maxIterations(20),
381
374
  })
382
375
  return toServerSentEventsResponse(stream)
@@ -649,7 +642,7 @@ import { anthropicText } from '@tanstack/ai-anthropic'
649
642
  export async function POST(request: Request) {
650
643
  const { messages } = await request.json()
651
644
  const stream = chat({
652
- adapter: anthropicText('claude-sonnet-4-5'),
645
+ adapter: anthropicText('claude-sonnet-4-6'),
653
646
  messages,
654
647
  tools: [
655
648
  codeExecutionTool(
@@ -685,7 +678,7 @@ import { openaiText } from '@tanstack/ai-openai'
685
678
  export async function POST(request: Request) {
686
679
  const { messages } = await request.json()
687
680
  const stream = chat({
688
- adapter: openaiText('gpt-5.2'),
681
+ adapter: openaiText('gpt-5.5'),
689
682
  messages,
690
683
  tools: [
691
684
  shellTool({
@@ -4,9 +4,10 @@ import type { AgentLoopStrategy } from '../../types'
4
4
  * Creates a strategy that continues for a maximum number of **model turns**
5
5
  * (iterations), not tool calls.
6
6
  *
7
- * One iteration can still emit many parallel tool calls. Prefer
8
- * {@link maxToolCalls} (and optionally `maxToolCallsPerTurn` on `chat()`)
9
- * when you need a tool-call budget.
7
+ * One iteration can still emit many parallel tool calls. For a tool-call
8
+ * budget, use middleware with `onBeforeToolCall` (per-turn cap) and
9
+ * `onShouldContinue` (cumulative run budget) — see the docs recipe under
10
+ * Agentic Cycle.
10
11
  *
11
12
  * @param max - Maximum number of model turns to allow
12
13
  * @returns AgentLoopStrategy that stops after max iterations
@@ -26,40 +27,6 @@ export function maxIterations(max: number): AgentLoopStrategy {
26
27
  return ({ iterationCount }) => iterationCount < max
27
28
  }
28
29
 
29
- /**
30
- * Creates a strategy that continues while `toolCallCount < max`.
31
- *
32
- * Unlike {@link maxIterations} (which counts model turns), this bounds
33
- * **emitted** tool calls counted during the run (including ones skipped by
34
- * `maxToolCallsPerTurn`). Strategies only run between turns, so the turn that
35
- * crosses `max` is not truncated — the final count (and executions, unless
36
- * `maxToolCallsPerTurn` is set) may exceed `max`. Pair with
37
- * `chat({ maxToolCallsPerTurn })` to also cap parallel fan-out inside a single
38
- * turn.
39
- *
40
- * @param max - Maximum cumulative emitted tool calls before stopping further turns
41
- * @returns AgentLoopStrategy that returns true while `toolCallCount < max`
42
- *
43
- * @example
44
- * ```typescript
45
- * import { chat, combineStrategies, maxIterations, maxToolCalls } from '@tanstack/ai'
46
- *
47
- * const stream = chat({
48
- * adapter: openaiText('gpt-4o'),
49
- * messages: [...],
50
- * tools: [weatherTool],
51
- * maxToolCallsPerTurn: 10,
52
- * agentLoopStrategy: combineStrategies([
53
- * maxIterations(20),
54
- * maxToolCalls(20),
55
- * ]),
56
- * })
57
- * ```
58
- */
59
- export function maxToolCalls(max: number): AgentLoopStrategy {
60
- return ({ toolCallCount }) => toolCallCount < max
61
- }
62
-
63
30
  /**
64
31
  * Creates a strategy that continues until a specific finish reason is encountered
65
32
  *
@@ -99,7 +66,7 @@ export function untilFinishReason(
99
66
  * All strategies must return true to continue
100
67
  *
101
68
  * @param strategies - Array of strategies to combine
102
- * @returns AgentLoopStrategy that continues only if all strategies return true
69
+ * @returns AgentLoopStrategy that continues only if all strategies agree
103
70
  *
104
71
  * @example
105
72
  * ```typescript
@@ -110,7 +77,6 @@ export function untilFinishReason(
110
77
  * tools: [weatherTool],
111
78
  * agentLoopStrategy: combineStrategies([
112
79
  * maxIterations(10),
113
- * maxToolCalls(20),
114
80
  * ({ messages }) => messages.length < 100,
115
81
  * ]),
116
82
  * });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Out-of-band run cancellation.
3
+ *
4
+ * `ChatClient.stop()` only aborts a local `AbortController`; it sends nothing to
5
+ * the server. A user pressing Stop and a user refreshing the page produce the
6
+ * IDENTICAL TCP close, so intent is **not inferable** from a disconnect. It has
7
+ * to arrive out of band, and there are exactly two bands:
8
+ *
9
+ * 1. **Durable** — {@link requestRunCancel} records the intent on the run's
10
+ * `RunRecord`. This is the only channel that works when the run is being
11
+ * driven by a DIFFERENT host than the one the cancel request reached, which
12
+ * is the normal case for a detached run.
13
+ * 2. **In-process** — abort the run's signal with {@link RUN_CANCEL_REASON}.
14
+ * Core reads the reason back when it builds `AbortInfo`, so
15
+ * `AbortInfo.cancelRequested` is `true` for that abort and `false` for a
16
+ * disconnect. This is the fast path when the cancel reaches the driving host.
17
+ *
18
+ * A cancel endpoint SHOULD do both: record it (so a remote driver observes it)
19
+ * and abort locally (so a co-located driver stops immediately).
20
+ */
21
+ import type { RunStore } from './middleware/run-store'
22
+
23
+ /**
24
+ * Abort reason that marks an abort as an explicit cancellation.
25
+ *
26
+ * Namespaced so an application's own reason string cannot collide with it by
27
+ * accident, and matched with `===` (never a substring test) so an arbitrary
28
+ * provider error message can never be read as a deliberate cancel.
29
+ */
30
+ export const RUN_CANCEL_REASON = 'tanstack-ai:cancel-requested'
31
+
32
+ /** Whether an abort reason means "the user explicitly cancelled this run". */
33
+ export function isCancelRequestedReason(reason: string | undefined): boolean {
34
+ return reason === RUN_CANCEL_REASON
35
+ }
36
+
37
+ /**
38
+ * Record an explicit cancel on the run record.
39
+ *
40
+ * Deliberately does NOT set a status. The driver is the only actor that knows
41
+ * when the agent has actually stopped and the sandbox has been torn down, so it
42
+ * owns the transition to `'aborted'`. Writing a terminal status here would tell
43
+ * every reader the run is over while the agent is still burning tokens.
44
+ *
45
+ * A no-op for an unknown `runId`, inheriting `RunStore.update`'s documented
46
+ * invariant.
47
+ */
48
+ export async function requestRunCancel(
49
+ runs: RunStore,
50
+ runId: string,
51
+ ): Promise<void> {
52
+ await runs.update(runId, { cancelRequested: true })
53
+ }
54
+
55
+ /**
56
+ * Whether an explicit cancel has been recorded for `runId`.
57
+ *
58
+ * Answers `false` rather than throwing when the store cannot be read. Callers
59
+ * are middleware abort hooks, which are already on a teardown path, and a
60
+ * store failure there must not replace the caller's own reason for tearing
61
+ * down with a store error. The cost of a false negative is that a cancel
62
+ * degrades into a detach — the run record gains `detachedSince`/`sandboxKey`
63
+ * instead of transitioning to `'aborted'`. `@tanstack/ai-sandbox`'s
64
+ * `reapDetachedRuns` recovers that run once the `detachedRunTtlMs` the
65
+ * application passes to that sweep has elapsed — nothing derives it from
66
+ * `withSandbox`, which has no TTL option — so the cost is a delayed teardown
67
+ * rather than a lost one, provided the application actually schedules the
68
+ * sweep, which is its job and not the framework's. Still strictly better than
69
+ * failing the teardown.
70
+ */
71
+ export async function wasCancelRequested(
72
+ runs: RunStore,
73
+ runId: string,
74
+ ): Promise<boolean> {
75
+ try {
76
+ const record = await runs.get(runId)
77
+ return record?.cancelRequested === true
78
+ } catch {
79
+ return false
80
+ }
81
+ }