better-mastra 0.4.0 → 0.5.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 +22 -9
  2. package/dist/client/actions.d.ts +1 -1
  3. package/dist/client/actions.d.ts.map +1 -1
  4. package/dist/client/actions.js +24 -43
  5. package/dist/client/actions.js.map +1 -1
  6. package/dist/client/cards.d.ts.map +1 -1
  7. package/dist/client/cards.js.map +1 -1
  8. package/dist/client/chat.d.ts.map +1 -1
  9. package/dist/client/chat.js +7 -29
  10. package/dist/client/chat.js.map +1 -1
  11. package/dist/client/errors.d.ts.map +1 -1
  12. package/dist/client/errors.js +8 -13
  13. package/dist/client/errors.js.map +1 -1
  14. package/dist/client/fetch.d.ts.map +1 -1
  15. package/dist/client/fetch.js +5 -28
  16. package/dist/client/fetch.js.map +1 -1
  17. package/dist/client/messages.d.ts +2 -0
  18. package/dist/client/messages.d.ts.map +1 -1
  19. package/dist/client/messages.js +9 -7
  20. package/dist/client/messages.js.map +1 -1
  21. package/dist/client/pending.d.ts.map +1 -1
  22. package/dist/client/pending.js +17 -21
  23. package/dist/client/pending.js.map +1 -1
  24. package/dist/client/ratings.d.ts.map +1 -1
  25. package/dist/client/ratings.js +4 -15
  26. package/dist/client/ratings.js.map +1 -1
  27. package/dist/client/screen-context.d.ts.map +1 -1
  28. package/dist/client/screen-context.js +4 -10
  29. package/dist/client/screen-context.js.map +1 -1
  30. package/dist/client/session.d.ts +6 -0
  31. package/dist/client/session.d.ts.map +1 -1
  32. package/dist/client/session.js +48 -57
  33. package/dist/client/session.js.map +1 -1
  34. package/dist/client/turn.d.ts.map +1 -1
  35. package/dist/client/turn.js +167 -208
  36. package/dist/client/turn.js.map +1 -1
  37. package/dist/dictation/adapters.d.ts.map +1 -1
  38. package/dist/dictation/adapters.js +8 -1
  39. package/dist/dictation/adapters.js.map +1 -1
  40. package/dist/dictation/protocol.d.ts.map +1 -1
  41. package/dist/dictation/protocol.js +6 -20
  42. package/dist/dictation/protocol.js.map +1 -1
  43. package/dist/dictation/react/index.d.ts.map +1 -1
  44. package/dist/dictation/react/index.js +4 -10
  45. package/dist/dictation/react/index.js.map +1 -1
  46. package/dist/dictation/server/index.d.ts.map +1 -1
  47. package/dist/dictation/server/index.js +8 -1
  48. package/dist/dictation/server/index.js.map +1 -1
  49. package/dist/dictation/session.d.ts.map +1 -1
  50. package/dist/dictation/session.js +2 -1
  51. package/dist/dictation/session.js.map +1 -1
  52. package/dist/dictation/vue/index.d.ts.map +1 -1
  53. package/dist/dictation/vue/index.js.map +1 -1
  54. package/dist/embeddings/embedder.d.ts.map +1 -1
  55. package/dist/embeddings/embedder.js +11 -22
  56. package/dist/embeddings/embedder.js.map +1 -1
  57. package/dist/evals/checks.d.ts.map +1 -1
  58. package/dist/evals/checks.js +6 -5
  59. package/dist/evals/checks.js.map +1 -1
  60. package/dist/evals/cli.js +2 -3
  61. package/dist/evals/cli.js.map +1 -1
  62. package/dist/evals/config.d.ts.map +1 -1
  63. package/dist/evals/config.js +2 -1
  64. package/dist/evals/config.js.map +1 -1
  65. package/dist/evals/judge.d.ts.map +1 -1
  66. package/dist/evals/judge.js +2 -1
  67. package/dist/evals/judge.js.map +1 -1
  68. package/dist/evals/mastra.d.ts +5 -0
  69. package/dist/evals/mastra.d.ts.map +1 -1
  70. package/dist/evals/mastra.js +23 -43
  71. package/dist/evals/mastra.js.map +1 -1
  72. package/dist/evals/report.d.ts +4 -6
  73. package/dist/evals/report.d.ts.map +1 -1
  74. package/dist/evals/report.js +31 -37
  75. package/dist/evals/report.js.map +1 -1
  76. package/dist/evals/run.d.ts.map +1 -1
  77. package/dist/evals/run.js +6 -19
  78. package/dist/evals/run.js.map +1 -1
  79. package/dist/hono/chatRoute.d.ts +1 -1
  80. package/dist/hono/chatRoute.d.ts.map +1 -1
  81. package/dist/hono/chatRoute.js.map +1 -1
  82. package/dist/hono/index.d.ts +1 -1
  83. package/dist/hono/index.d.ts.map +1 -1
  84. package/dist/hono/index.js.map +1 -1
  85. package/dist/hono/studio.js.map +1 -1
  86. package/dist/models/anthropic-safe-schema.d.ts.map +1 -1
  87. package/dist/models/anthropic-safe-schema.js +3 -2
  88. package/dist/models/anthropic-safe-schema.js.map +1 -1
  89. package/dist/models/openrouter-batch.js +2 -2
  90. package/dist/models/openrouter-batch.js.map +1 -1
  91. package/dist/models/openrouter-compat.js +3 -3
  92. package/dist/models/openrouter-compat.js.map +1 -1
  93. package/dist/models/openrouter.d.ts.map +1 -1
  94. package/dist/models/openrouter.js +1 -1
  95. package/dist/models/openrouter.js.map +1 -1
  96. package/dist/models/prompt-cache.js +1 -1
  97. package/dist/models/prompt-cache.js.map +1 -1
  98. package/dist/models/providers/inception.d.ts.map +1 -1
  99. package/dist/models/providers/inception.js +2 -1
  100. package/dist/models/providers/inception.js.map +1 -1
  101. package/dist/models/providers/openrouter.d.ts.map +1 -1
  102. package/dist/models/providers/openrouter.js +3 -7
  103. package/dist/models/providers/openrouter.js.map +1 -1
  104. package/dist/models/providers/requesty.d.ts.map +1 -1
  105. package/dist/models/providers/requesty.js +2 -1
  106. package/dist/models/providers/requesty.js.map +1 -1
  107. package/dist/models/registry.d.ts.map +1 -1
  108. package/dist/models/registry.js +2 -4
  109. package/dist/models/registry.js.map +1 -1
  110. package/dist/models/route.d.ts +0 -1
  111. package/dist/models/route.d.ts.map +1 -1
  112. package/dist/models/route.js +2 -4
  113. package/dist/models/route.js.map +1 -1
  114. package/dist/resumable/index.d.ts +19 -5
  115. package/dist/resumable/index.d.ts.map +1 -1
  116. package/dist/resumable/index.js +147 -77
  117. package/dist/resumable/index.js.map +1 -1
  118. package/dist/resumable/redis.d.ts +5 -1
  119. package/dist/resumable/redis.d.ts.map +1 -1
  120. package/dist/resumable/redis.js +16 -7
  121. package/dist/resumable/redis.js.map +1 -1
  122. package/dist/runtime/health.d.ts.map +1 -1
  123. package/dist/runtime/health.js +2 -1
  124. package/dist/runtime/health.js.map +1 -1
  125. package/dist/runtime/storage.d.ts.map +1 -1
  126. package/dist/runtime/storage.js +6 -11
  127. package/dist/runtime/storage.js.map +1 -1
  128. package/dist/tools/clientActions.d.ts.map +1 -1
  129. package/dist/tools/clientActions.js +9 -11
  130. package/dist/tools/clientActions.js.map +1 -1
  131. package/dist/tools/deferredTools.d.ts.map +1 -1
  132. package/dist/tools/deferredTools.js +4 -3
  133. package/dist/tools/deferredTools.js.map +1 -1
  134. package/dist/tools/defineTool.d.ts.map +1 -1
  135. package/dist/tools/defineTool.js +5 -3
  136. package/dist/tools/defineTool.js.map +1 -1
  137. package/dist/tools/dryRun.d.ts.map +1 -1
  138. package/dist/tools/dryRun.js +2 -3
  139. package/dist/tools/dryRun.js.map +1 -1
  140. package/dist/tools/manifest.d.ts.map +1 -1
  141. package/dist/tools/manifest.js +9 -12
  142. package/dist/tools/manifest.js.map +1 -1
  143. package/dist/tools/projection.d.ts.map +1 -1
  144. package/dist/tools/projection.js.map +1 -1
  145. package/dist/tools/toolInputSchema.d.ts.map +1 -1
  146. package/dist/tools/toolInputSchema.js +22 -43
  147. package/dist/tools/toolInputSchema.js.map +1 -1
  148. package/dist/turn/changes.d.ts.map +1 -1
  149. package/dist/turn/changes.js +12 -8
  150. package/dist/turn/changes.js.map +1 -1
  151. package/dist/turn/index.d.ts +1 -1
  152. package/dist/turn/index.d.ts.map +1 -1
  153. package/dist/turn/index.js +1 -1
  154. package/dist/turn/index.js.map +1 -1
  155. package/dist/turn/modelArms.d.ts.map +1 -1
  156. package/dist/turn/modelArms.js +2 -3
  157. package/dist/turn/modelArms.js.map +1 -1
  158. package/dist/turn/observer.d.ts.map +1 -1
  159. package/dist/turn/observer.js +33 -33
  160. package/dist/turn/observer.js.map +1 -1
  161. package/dist/turn/processors.d.ts.map +1 -1
  162. package/dist/turn/processors.js +2 -5
  163. package/dist/turn/processors.js.map +1 -1
  164. package/dist/turn/promptBudget.d.ts.map +1 -1
  165. package/dist/turn/promptBudget.js +2 -1
  166. package/dist/turn/promptBudget.js.map +1 -1
  167. package/dist/turn/promptCache.d.ts.map +1 -1
  168. package/dist/turn/promptCache.js +8 -11
  169. package/dist/turn/promptCache.js.map +1 -1
  170. package/dist/turn/replySuggestions.d.ts +5 -0
  171. package/dist/turn/replySuggestions.d.ts.map +1 -1
  172. package/dist/turn/replySuggestions.js +7 -4
  173. package/dist/turn/replySuggestions.js.map +1 -1
  174. package/dist/turn/runChatTurn.d.ts.map +1 -1
  175. package/dist/turn/runChatTurn.js +4 -6
  176. package/dist/turn/runChatTurn.js.map +1 -1
  177. package/dist/turn/threads.d.ts.map +1 -1
  178. package/dist/turn/threads.js +8 -22
  179. package/dist/turn/threads.js.map +1 -1
  180. package/dist/turn/title.d.ts.map +1 -1
  181. package/dist/turn/title.js +4 -5
  182. package/dist/turn/title.js.map +1 -1
  183. package/dist/turn/types.d.ts.map +1 -1
  184. package/dist/turn/types.js.map +1 -1
  185. package/dist/usage/breakdown.d.ts.map +1 -1
  186. package/dist/usage/breakdown.js +4 -6
  187. package/dist/usage/breakdown.js.map +1 -1
  188. package/dist/usage/budget.d.ts.map +1 -1
  189. package/dist/usage/budget.js +10 -18
  190. package/dist/usage/budget.js.map +1 -1
  191. package/dist/usage/generate.d.ts.map +1 -1
  192. package/dist/usage/generate.js +13 -12
  193. package/dist/usage/generate.js.map +1 -1
  194. package/dist/usage/userRequests.d.ts.map +1 -1
  195. package/dist/usage/userRequests.js +2 -6
  196. package/dist/usage/userRequests.js.map +1 -1
  197. package/dist/vue/index.d.ts +1 -0
  198. package/dist/vue/index.d.ts.map +1 -1
  199. package/dist/vue/index.js +1 -0
  200. package/dist/vue/index.js.map +1 -1
  201. package/package.json +6 -5
  202. package/src/client/actions.ts +69 -83
  203. package/src/client/cards.ts +1 -3
  204. package/src/client/chat.ts +8 -36
  205. package/src/client/errors.ts +9 -13
  206. package/src/client/fetch.ts +6 -22
  207. package/src/client/messages.ts +11 -9
  208. package/src/client/pending.ts +18 -23
  209. package/src/client/ratings.ts +4 -11
  210. package/src/client/screen-context.ts +5 -11
  211. package/src/client/session.ts +74 -64
  212. package/src/client/turn.ts +216 -204
  213. package/src/dictation/adapters.ts +4 -1
  214. package/src/dictation/protocol.ts +8 -15
  215. package/src/dictation/react/index.ts +9 -16
  216. package/src/dictation/server/index.ts +5 -5
  217. package/src/dictation/session.ts +3 -1
  218. package/src/dictation/vue/index.ts +5 -5
  219. package/src/embeddings/embedder.ts +17 -19
  220. package/src/evals/checks.ts +7 -5
  221. package/src/evals/cli.ts +2 -2
  222. package/src/evals/config.ts +3 -1
  223. package/src/evals/judge.ts +2 -1
  224. package/src/evals/mastra.ts +24 -51
  225. package/src/evals/report.ts +43 -68
  226. package/src/evals/run.ts +6 -19
  227. package/src/hono/chatRoute.ts +2 -3
  228. package/src/hono/index.ts +1 -1
  229. package/src/hono/studio.ts +1 -2
  230. package/src/models/anthropic-safe-schema.ts +3 -4
  231. package/src/models/openrouter-batch.ts +2 -2
  232. package/src/models/openrouter-compat.ts +3 -3
  233. package/src/models/openrouter.ts +1 -1
  234. package/src/models/prompt-cache.ts +1 -1
  235. package/src/models/providers/inception.ts +2 -1
  236. package/src/models/providers/openrouter.ts +3 -9
  237. package/src/models/providers/requesty.ts +2 -1
  238. package/src/models/registry.ts +8 -6
  239. package/src/models/route.ts +2 -5
  240. package/src/resumable/index.ts +204 -81
  241. package/src/resumable/redis.ts +25 -9
  242. package/src/runtime/health.ts +3 -2
  243. package/src/runtime/storage.ts +7 -9
  244. package/src/tools/clientActions.ts +11 -13
  245. package/src/tools/deferredTools.ts +4 -2
  246. package/src/tools/defineTool.ts +6 -11
  247. package/src/tools/dryRun.ts +2 -5
  248. package/src/tools/manifest.ts +10 -15
  249. package/src/tools/projection.ts +3 -3
  250. package/src/tools/toolInputSchema.ts +49 -57
  251. package/src/turn/changes.ts +21 -16
  252. package/src/turn/index.ts +1 -0
  253. package/src/turn/modelArms.ts +2 -3
  254. package/src/turn/observer.ts +47 -45
  255. package/src/turn/processors.ts +11 -14
  256. package/src/turn/promptBudget.ts +2 -1
  257. package/src/turn/promptCache.ts +13 -11
  258. package/src/turn/replySuggestions.ts +8 -4
  259. package/src/turn/runChatTurn.ts +10 -10
  260. package/src/turn/threads.ts +28 -29
  261. package/src/turn/title.ts +3 -4
  262. package/src/turn/types.ts +1 -2
  263. package/src/usage/breakdown.ts +7 -7
  264. package/src/usage/budget.ts +17 -18
  265. package/src/usage/generate.ts +13 -25
  266. package/src/usage/userRequests.ts +4 -7
  267. package/src/vue/index.ts +14 -13
  268. package/dist/hono/types.d.ts +0 -17
  269. package/dist/hono/types.d.ts.map +0 -1
  270. package/dist/hono/types.js +0 -2
  271. package/dist/hono/types.js.map +0 -1
  272. package/src/hono/types.ts +0 -20
package/README.md CHANGED
@@ -28,12 +28,26 @@ await storage.init() // once, in the process that migrates
28
28
 
29
29
  Opt-in. A dropped connection no longer aborts the turn: the client reconnects with AI SDK `reconnectToStream` (`useChat({ resume: true })`) and gets a full replay of the current assistant message plus the live tail (`resumeStreamIsReplay: true`, the `DefaultChatTransport` default).
30
30
 
31
+ With the chat route, pass the registry and an ownership check; the route then adds `GET {path}/:threadId/stream` and `POST {path}/:threadId/stop`, which the chat client in `better-mastra/client` already calls:
32
+
31
33
  ```ts
32
34
  import { RedisClient } from 'bun'
35
+ import { createChatRoute } from 'better-mastra/hono'
33
36
  import { bunRedis, createResumableStreams } from 'better-mastra/resumable'
34
37
 
35
38
  const resumable = createResumableStreams({ redis: bunRedis(new RedisClient(process.env.REDIS_URL)) })
36
39
 
40
+ app.route('/', createChatRoute({ path: '/chat', auth, parseBody, runTurn, resumable, ownsThread }))
41
+
42
+ // shutdown: waits for active turns (drainTimeoutMs)
43
+ await resumable.close()
44
+ ```
45
+
46
+ Leave `redis` out for one process (local development, a single instance): turns are then resumable and stoppable on that process only.
47
+
48
+ Without the route, wire it yourself:
49
+
50
+ ```ts
37
51
  // POST /chat/:threadId — do not tie the run to request.signal
38
52
  const abort = new AbortController()
39
53
  const stream = await resumable.start({ threadKey: threadId, stream: uiMessageStream, abort })
@@ -44,16 +58,15 @@ if (!resumed) return new Response(null, { status: 204 })
44
58
 
45
59
  // POST /chat/:threadId/stop — works from any replica
46
60
  await resumable.stop(threadId)
47
-
48
- // shutdown: waits for active turns (drainTimeoutMs)
49
- await resumable.close()
50
61
  ```
51
62
 
52
- Use `ioRedis(client)` for ioredis. Both adapters open extra connections with `duplicate()`: one subscriber per replica, and one per cross-replica reader (for `XREAD BLOCK`).
63
+ A new `start()` on a thread whose turn still runs aborts that turn, on this replica or another. A stopped turn gets `stopTimeoutMs` (5 s) to end its stream; after that it ends with an `abort` chunk, so a tool that ignores its abort signal cannot keep the turn open.
64
+
65
+ The adapters need Redis 6.2 or newer (`SET … GET`). Use `ioRedis(client)` for ioredis. Both adapters open extra connections with `duplicate()`: one subscriber per replica, and one per cross-replica reader (for `XREAD BLOCK`). Sticky sessions keep reconnects on the owner, where they cost nothing.
53
66
 
54
67
  Use `upstashRedis(client)` for `@upstash/redis`. Upstash has no blocking `XREAD`, so a cross-replica reader polls every `pollMs` (200 ms); pass `() => Redis.fromEnv()` to create the client on first use.
55
68
 
56
- An app that already holds a per-thread lock can keep the locator in the lock's value with `locator: { read, write, expire }`. The locator then lives exactly as long as the lock. `expire` renews it while the turn runs and shortens it to `graceMs` after; leave it out when the lock renews itself:
69
+ An app that already holds a per-thread lock can keep the locator in the lock's value with `locator: { read, write, expire }`. The locator then lives exactly as long as the lock. `expire` renews it while the turn runs and shortens it to `graceMs` after; leave it out when the lock renews itself. `write` may resolve with the locator it replaced, so a turn still running on another replica is aborted:
57
70
 
58
71
  ```ts
59
72
  createResumableStreams({
@@ -66,7 +79,7 @@ createResumableStreams({
66
79
  })
67
80
  ```
68
81
 
69
- How it works: chunks are buffered in memory (consecutive `text-delta`s merged) and fanned out to subscribers. A per-thread locator `{ streamId, owner }` in Redis says which replica owns the turn. Reconnecting to the owner is served from memory. Reconnecting to another replica publishes a spill request on the owner's control channel; the owner writes its buffer in one `XADD`, then mirrors new chunks in 50 ms batches (first one immediate) until a terminal marker, and the reader follows with `XREAD BLOCK`.
82
+ How it works: chunks are buffered in memory (consecutive text, reasoning and tool-input deltas merged) and fanned out to subscribers. A per-thread locator `{ streamId, owner }` in Redis says which replica owns the turn; the owner writes it once it listens on its control channel. Reconnecting to the owner is served from memory. Reconnecting to another replica publishes a spill request on the owner's control channel; on the first request the owner writes its buffer in one `XADD` to the turn's spill stream, then mirrors new chunks in 50 ms batches (first one immediate), one `XADD` at a time so entries stay ordered on HTTP clients too, until a terminal marker. Every reader of the turn, including one that reconnects again, follows that same stream with `XREAD` from the start.
70
83
 
71
84
  Redis ops for a ~1,500-chunk turn:
72
85
 
@@ -74,11 +87,11 @@ Redis ops for a ~1,500-chunk turn:
74
87
  |---|---|
75
88
  | Mastra durable agents (`CachingPubSub`: cache write + publish per chunk) | ~3,000–4,500 |
76
89
  | Coalesced SETEX + PUBLISH (hellu support-turn) | ~600 |
77
- | This, no reconnect | 2 (locator SET, final PEXPIRE) + 1 PEXPIRE per 10 s of turn |
90
+ | This, no reconnect | 2 (locator SET … GET, final PEXPIRE) + 1 PEXPIRE per 10 s of turn, +1 PUBLISH when the thread's last turn ran on another replica |
78
91
  | This, same-replica reconnect | +0 for a running turn, +1 GET for a finished one |
79
- | This, cross-replica reconnect | +1 PUBLISH, +1 XADD and EXPIRE per 50 ms batch, reader XREADs |
92
+ | This, cross-replica reconnect | +1 GET and PUBLISH per reconnect; once per turn +1 XADD per 50 ms batch, 2 EXPIREs and 1 EXPIRE per 30 s; reader XREADs |
80
93
 
81
- Failure behaviour: a buffer over `maxBufferBytes` (2 MB) keeps streaming but new reconnects get 204. If the owner crashes, its locator expires and the cross-replica reader ends with an `error` chunk after `idleTimeoutMs`; the client reloads the thread from Mastra memory. If Redis is down, turns still run and same-replica reconnects work; cross-replica reconnects return 204. Finished turns stay resumable for `graceMs` (30 s).
94
+ Failure behaviour: a buffer over `maxBufferBytes` (2 MB) keeps streaming but new reconnects get 204. If the owner crashes, a cross-replica reconnect gets 204 within 2 s, and a reader already following ends with an `error` chunk after `idleTimeoutMs`; the client reloads the thread from Mastra memory. A reader whose own Redis read fails ends with an `error` chunk too; a failed liveness check is taken as a blip and the reader keeps waiting. If Redis is down, turns still run and same-replica reconnects work; cross-replica reconnects return 204. Finished turns stay resumable for `graceMs` (30 s).
82
95
 
83
96
  ## Per-user request limits (`better-mastra/usage`)
84
97
 
@@ -85,7 +85,7 @@ export declare function defineClientAction<const TName extends string, TSchema e
85
85
  * the schema itself accepts it. Unions try each member and keep the first
86
86
  * reading that parses.
87
87
  */
88
- export declare function normalizeModelNulls(schema: z.ZodType, value: unknown): unknown;
88
+ export declare function normalizeModelNulls(schema: z.core.$ZodType, value: unknown): unknown;
89
89
  /** The definitions a request carries, for the server to offer the model as client tools. */
90
90
  export declare function toClientToolDefs(actions: readonly ClientAction[]): ClientToolDef[];
91
91
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AAQnC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAMvB,KAAK,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7C,yFAAyF;AACzF,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAA;IACvC,uGAAuG;IACvG,QAAQ,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,CAAA;CACrC;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC1D,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAA;IAC/B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IACnC,yHAAyH;IACzH,QAAQ,CAAC,GAAG,EAAE,CACb,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,mBAAmB,KACxB,OAAO,CAAC,OAAO,CAAC,CAAA;IACrB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CACnB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,mBAAmB,KACxB,OAAO,CAAC,OAAO,CAAC,CAAA;CACrB;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CACnC;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CACvB;AAED,2DAA2D;AAC3D,MAAM,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAA;IAC9B,QAAQ,CAAC,aAAa,EAAE,CACvB,OAAO,EACJ;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,GACrD;QACA,KAAK,EAAE,cAAc,CAAA;QACrB,IAAI,EAAE,MAAM,CAAA;QACZ,UAAU,EAAE,MAAM,CAAA;QAClB,SAAS,EAAE,MAAM,CAAA;KAChB,KACA,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;CAC7B,CAAA;AAID,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,uBAAuB,CAAA;AAExD,sEAAsE;AACtE,eAAO,MAAM,2BAA2B,8FACoD,CAAA;AAE5F,wFAAwF;AACxF,eAAO,MAAM,sBAAsB,mGAC8D,CAAA;AAIjG;;;;;GAKG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,CAAC,KAAK,SAAS,MAAM,EAC1B,OAAO,SAAS,CAAC,CAAC,OAAO,EACzB,OAAO,EACN,MAAM,EAAE;IACT,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE,CACjB,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EACxB,OAAO,EAAE,mBAAmB,KACxB,OAAO,CAAC,OAAO,CAAC,CAAA;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAA;CAC5C,GAAG,YAAY,CAAC,KAAK,CAAC,CAyBtB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EACvB,KAAK,EAAE,OAAO,GACZ,OAAO,CAyET;AAED,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,SAAS,YAAY,EAAE,GAC9B,aAAa,EAAE,CAYjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,QAAQ,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CA8CtB"}
@@ -5,6 +5,7 @@
5
5
  * it, without the server's input validation, so the app parses it here.
6
6
  */
7
7
  import { getToolName, isToolUIPart } from 'ai';
8
+ import { isRecord, stableStringify, typedSwitch, typedSwitchGroup } from 'hanzio';
8
9
  import { z } from 'zod';
9
10
  import { currentUserTurn } from './messages.js';
10
11
  // CONSTANTS
@@ -51,65 +52,48 @@ export function defineClientAction(action) {
51
52
  */
52
53
  export function normalizeModelNulls(schema, value) {
53
54
  const def = schema._zod.def;
54
- switch (def.type) {
55
- case 'optional':
56
- case 'nullable':
57
- case 'default':
58
- case 'prefault':
59
- case 'readonly':
60
- case 'catch':
61
- return value == null || def.innerType === undefined
62
- ? value
63
- : normalizeModelNulls(def.innerType, value);
64
- case 'pipe':
65
- return def.in === undefined ? value : normalizeModelNulls(def.in, value);
66
- case 'lazy':
67
- return def.getter === undefined
68
- ? value
69
- : normalizeModelNulls(def.getter(), value);
70
- case 'array': {
71
- const element = def.element;
72
- return Array.isArray(value) && element !== undefined
73
- ? value.map((item) => normalizeModelNulls(element, item))
74
- : value;
75
- }
76
- case 'record': {
77
- const valueType = def.valueType;
78
- if (!isPlainObject(value) || valueType === undefined) {
55
+ return typedSwitch(def, 'type', {
56
+ ...typedSwitchGroup(def, 'type', ['optional', 'nullable', 'default', 'prefault', 'readonly', 'catch'], (wrapper) => value == null ? value : normalizeModelNulls(wrapper.innerType, value)),
57
+ pipe: (pipe) => normalizeModelNulls(pipe.in, value),
58
+ lazy: (lazy) => normalizeModelNulls(lazy.getter(), value),
59
+ array: (array) => Array.isArray(value)
60
+ ? value.map((item) => normalizeModelNulls(array.element, item))
61
+ : value,
62
+ record: (record) => {
63
+ const { valueType } = record;
64
+ if (!isRecord(value)) {
79
65
  return value;
80
66
  }
81
- return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => item === null && !valueType.safeParse(null).success
67
+ return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => item === null && !z.safeParse(valueType, null).success
82
68
  ? []
83
69
  : [[key, normalizeModelNulls(valueType, item)]]));
84
- }
85
- case 'union': {
86
- for (const option of def.options ?? []) {
70
+ },
71
+ union: (union) => {
72
+ for (const option of union.options) {
87
73
  const normalized = normalizeModelNulls(option, value);
88
- if (option.safeParse(normalized).success) {
74
+ if (z.safeParse(option, normalized).success) {
89
75
  return normalized;
90
76
  }
91
77
  }
92
78
  return value;
93
- }
94
- case 'object': {
95
- if (!isPlainObject(value) || !(schema instanceof z.ZodObject)) {
79
+ },
80
+ object: (object) => {
81
+ if (!isRecord(value) || !(schema instanceof z.ZodObject)) {
96
82
  return value;
97
83
  }
98
- const shape = schema.shape;
84
+ const { shape } = object;
99
85
  return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => {
100
86
  const field = shape[key];
101
87
  if (field === undefined) {
102
88
  return [[key, item]];
103
89
  }
104
90
  const leaveOut = item === null &&
105
- field.safeParse(undefined).success &&
106
- !field.safeParse(null).success;
91
+ z.safeParse(field, undefined).success &&
92
+ !z.safeParse(field, null).success;
107
93
  return leaveOut ? [] : [[key, normalizeModelNulls(field, item)]];
108
94
  }));
109
95
  }
110
- default:
111
- return value;
112
- }
96
+ }, () => value);
113
97
  }
114
98
  /** The definitions a request carries, for the server to offer the model as client tools. */
115
99
  export function toClientToolDefs(actions) {
@@ -201,9 +185,6 @@ function stepOf(messages, toolCallId) {
201
185
  }
202
186
  /** The input with the model's nulls left out, so the same call matches however the model wrote it. */
203
187
  function callKey(action, input) {
204
- return JSON.stringify(normalizeModelNulls(action.inputSchema, input ?? {}));
205
- }
206
- function isPlainObject(value) {
207
- return typeof value === 'object' && value !== null && !Array.isArray(value);
188
+ return stableStringify(normalizeModelNulls(action.inputSchema, input ?? {}));
208
189
  }
209
190
  //# sourceMappingURL=actions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Client actions: tools the app carries out itself (open a screen, fill a\n * form), whose definitions go along with each request and whose results go\n * back as the turn's follow-up. A call reaches the app as the model wrote\n * it, without the server's input validation, so the app parses it here.\n */\n\nimport type { UIMessage } from 'ai'\nimport { getToolName, isToolUIPart } from 'ai'\nimport { z } from 'zod'\n\nimport { currentUserTurn } from './messages.js'\n\n// TYPES\n\ntype MessagePart = UIMessage['parts'][number]\n\n/** Where the call sits, for an action with its own policy on repeats or shared steps. */\nexport interface ClientActionContext {\n\treadonly toolCallId: string\n\t/** The chat's messages when the call arrived; the call is in the last one. */\n\treadonly messages: readonly UIMessage[]\n\t/** The parts of the model step that made the call, the call and any server tool beside it included. */\n\treadonly step: readonly MessagePart[]\n}\n\n/** A tool that runs in the client. The server never interprets its name. */\nexport interface ClientAction<TName extends string = string> {\n\treadonly name: TName\n\treadonly description: string\n\treadonly inputSchema: z.ZodType\n\treadonly requiresApproval?: boolean\n\t/** Runs the action on the model's raw input. Throw to report a failure: the model reads it as an error it can act on. */\n\treadonly run: (\n\t\tinput: unknown,\n\t\tcontext: ClientActionContext\n\t) => Promise<unknown>\n\t/**\n\t * A call the user turn already made with the same input. Its result is\n\t * posted as the call's output; throw to post an error. Default: an error\n\t * with `CLIENT_ACTION_REPEATED`.\n\t */\n\treadonly onRepeat?: (\n\t\tinput: unknown,\n\t\tcontext: ClientActionContext\n\t) => Promise<unknown>\n}\n\n/** A client action as a request carries it, for the server to offer the model. */\nexport interface ClientToolDef {\n\treadonly name: string\n\treadonly description: string\n\treadonly jsonSchema: unknown\n\treadonly requiresApproval?: boolean\n}\n\n/** A tool call the stream handed to the client. */\nexport interface ClientToolCall {\n\treadonly toolCallId: string\n\treadonly toolName: string\n\treadonly input: unknown\n}\n\n/** The part of the AI SDK `Chat` a client action needs. */\nexport type ClientActionChat = {\n\treadonly messages: UIMessage[]\n\treadonly addToolOutput: (\n\t\toptions:\n\t\t\t| { tool: string; toolCallId: string; output: unknown }\n\t\t\t| {\n\t\t\t\t\tstate: 'output-error'\n\t\t\t\t\ttool: string\n\t\t\t\t\ttoolCallId: string\n\t\t\t\t\terrorText: string\n\t\t\t }\n\t) => void | PromiseLike<void>\n}\n\ntype SchemaDef = {\n\treadonly type: string\n\treadonly innerType?: z.ZodType\n\treadonly element?: z.ZodType\n\treadonly valueType?: z.ZodType\n\treadonly in?: z.ZodType\n\treadonly options?: readonly z.ZodType[]\n\treadonly getter?: () => z.ZodType\n}\n\n// CONSTANTS\n\n/** What the model reads when an action throws something that is not an Error. */\nexport const CLIENT_ACTION_FAILED = 'The action failed.'\n\n/** What the model reads when the app cannot read the call's input. */\nexport const CLIENT_ACTION_INVALID_INPUT =\n\t'The app could not read this input. Call the action again with input that fits its schema.'\n\n/** What the model reads for a call it already made with the same input in this turn. */\nexport const CLIENT_ACTION_REPEATED =\n\t'Already done with these values in this turn. Do not call it again; tell the user what changed.'\n\n// FUNCTIONS\n\n/**\n * A client action whose handler gets its input parsed, with the model's\n * `null`s taken out where the schema means \"may be left out\". With\n * `resultSummary`, the output is `{ summary, data }`, for a result the data\n * alone would leave the model guessing about.\n */\nexport function defineClientAction<\n\tconst TName extends string,\n\tTSchema extends z.ZodType,\n\tTOutput\n>(action: {\n\treadonly name: TName\n\treadonly description: string\n\treadonly inputSchema: TSchema\n\treadonly requiresApproval?: boolean\n\treadonly resultSummary?: string\n\treadonly handler: (\n\t\tinput: z.output<TSchema>,\n\t\tcontext: ClientActionContext\n\t) => Promise<TOutput>\n\treadonly onRepeat?: ClientAction['onRepeat']\n}): ClientAction<TName> {\n\treturn {\n\t\tname: action.name,\n\t\tdescription: action.description,\n\t\tinputSchema: action.inputSchema,\n\t\t...(action.requiresApproval === undefined\n\t\t\t? {}\n\t\t\t: { requiresApproval: action.requiresApproval }),\n\t\t...(action.onRepeat === undefined ? {} : { onRepeat: action.onRepeat }),\n\t\trun: async (input, context) => {\n\t\t\tconst parsed = action.inputSchema.safeParse(\n\t\t\t\tnormalizeModelNulls(action.inputSchema, input ?? {})\n\t\t\t)\n\n\t\t\tif (!parsed.success) {\n\t\t\t\tthrow new Error(CLIENT_ACTION_INVALID_INPUT)\n\t\t\t}\n\n\t\t\tconst data = await action.handler(parsed.data, context)\n\n\t\t\treturn action.resultSummary === undefined\n\t\t\t\t? data\n\t\t\t\t: { summary: action.resultSummary, data }\n\t\t}\n\t}\n}\n\n/**\n * Model arguments with `null` taken out wherever the schema means \"may be\n * left out\", at any depth. An OpenAI model behind Mastra's schema layer\n * fills every field it leaves alone with `null`; a `null` stays only where\n * the schema itself accepts it. Unions try each member and keep the first\n * reading that parses.\n */\nexport function normalizeModelNulls(\n\tschema: z.ZodType,\n\tvalue: unknown\n): unknown {\n\tconst def = schema._zod.def as unknown as SchemaDef\n\n\tswitch (def.type) {\n\t\tcase 'optional':\n\t\tcase 'nullable':\n\t\tcase 'default':\n\t\tcase 'prefault':\n\t\tcase 'readonly':\n\t\tcase 'catch':\n\t\t\treturn value == null || def.innerType === undefined\n\t\t\t\t? value\n\t\t\t\t: normalizeModelNulls(def.innerType, value)\n\t\tcase 'pipe':\n\t\t\treturn def.in === undefined ? value : normalizeModelNulls(def.in, value)\n\t\tcase 'lazy':\n\t\t\treturn def.getter === undefined\n\t\t\t\t? value\n\t\t\t\t: normalizeModelNulls(def.getter(), value)\n\t\tcase 'array': {\n\t\t\tconst element = def.element\n\n\t\t\treturn Array.isArray(value) && element !== undefined\n\t\t\t\t? value.map((item) => normalizeModelNulls(element, item))\n\t\t\t\t: value\n\t\t}\n\t\tcase 'record': {\n\t\t\tconst valueType = def.valueType\n\n\t\t\tif (!isPlainObject(value) || valueType === undefined) {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\treturn Object.fromEntries(\n\t\t\t\tObject.entries(value).flatMap(([key, item]) =>\n\t\t\t\t\titem === null && !valueType.safeParse(null).success\n\t\t\t\t\t\t? []\n\t\t\t\t\t\t: [[key, normalizeModelNulls(valueType, item)]]\n\t\t\t\t)\n\t\t\t)\n\t\t}\n\t\tcase 'union': {\n\t\t\tfor (const option of def.options ?? []) {\n\t\t\t\tconst normalized = normalizeModelNulls(option, value)\n\n\t\t\t\tif (option.safeParse(normalized).success) {\n\t\t\t\t\treturn normalized\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn value\n\t\t}\n\t\tcase 'object': {\n\t\t\tif (!isPlainObject(value) || !(schema instanceof z.ZodObject)) {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\tconst shape = schema.shape as Record<string, z.ZodType>\n\n\t\t\treturn Object.fromEntries(\n\t\t\t\tObject.entries(value).flatMap(([key, item]) => {\n\t\t\t\t\tconst field = shape[key]\n\n\t\t\t\t\tif (field === undefined) {\n\t\t\t\t\t\treturn [[key, item]]\n\t\t\t\t\t}\n\n\t\t\t\t\tconst leaveOut =\n\t\t\t\t\t\titem === null &&\n\t\t\t\t\t\tfield.safeParse(undefined).success &&\n\t\t\t\t\t\t!field.safeParse(null).success\n\n\t\t\t\t\treturn leaveOut ? [] : [[key, normalizeModelNulls(field, item)]]\n\t\t\t\t})\n\t\t\t)\n\t\t}\n\t\tdefault:\n\t\t\treturn value\n\t}\n}\n\n/** The definitions a request carries, for the server to offer the model as client tools. */\nexport function toClientToolDefs(\n\tactions: readonly ClientAction[]\n): ClientToolDef[] {\n\treturn actions.map((action) => ({\n\t\tname: action.name,\n\t\tdescription: action.description,\n\t\tjsonSchema: z.toJSONSchema(action.inputSchema, {\n\t\t\tio: 'input',\n\t\t\tunrepresentable: 'any'\n\t\t}),\n\t\t...(action.requiresApproval === undefined\n\t\t\t? {}\n\t\t\t: { requiresApproval: action.requiresApproval })\n\t}))\n}\n\n/**\n * Carries out the client action a tool call names and posts its result on the\n * chat; null when no action has that name (a server tool: its result arrives\n * on the stream). A call the user turn already made with the same input\n * (`null`s aside) is refused instead of carried out again, unless the action\n * has its own `onRepeat`.\n *\n * Never await it inside the chat's `onToolCall`: the AI SDK runs that callback\n * on the queue `addToolOutput` waits on, so awaiting deadlocks the stream.\n */\nexport function runClientAction(\n\tchat: ClientActionChat,\n\tactions: readonly ClientAction[],\n\ttoolCall: ClientToolCall\n): Promise<void> | null {\n\tconst action = actions.find(\n\t\t(candidate) => candidate.name === toolCall.toolName\n\t)\n\n\tif (action === undefined) {\n\t\treturn null\n\t}\n\n\tconst fail = (errorText: string) =>\n\t\tchat.addToolOutput({\n\t\t\tstate: 'output-error',\n\t\t\ttool: toolCall.toolName,\n\t\t\ttoolCallId: toolCall.toolCallId,\n\t\t\terrorText\n\t\t})\n\n\tconst context: ClientActionContext = {\n\t\ttoolCallId: toolCall.toolCallId,\n\t\tmessages: chat.messages,\n\t\tstep: stepOf(chat.messages, toolCall.toolCallId)\n\t}\n\n\treturn (async () => {\n\t\tconst repeated = isRepeatInTurn(chat.messages, action, toolCall)\n\n\t\tif (repeated && action.onRepeat === undefined) {\n\t\t\tawait fail(CLIENT_ACTION_REPEATED)\n\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tconst output = await (repeated && action.onRepeat\n\t\t\t\t? action.onRepeat(toolCall.input, context)\n\t\t\t\t: action.run(toolCall.input, context))\n\n\t\t\tawait chat.addToolOutput({\n\t\t\t\ttool: toolCall.toolName,\n\t\t\t\ttoolCallId: toolCall.toolCallId,\n\t\t\t\toutput\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tawait fail(error instanceof Error ? error.message : CLIENT_ACTION_FAILED)\n\t\t}\n\t})()\n}\n\n/**\n * The same action with the same input already ran in this user turn. Running\n * it again changes nothing and only feeds a model that is circling.\n */\nfunction isRepeatInTurn(\n\tmessages: readonly UIMessage[],\n\taction: ClientAction,\n\ttoolCall: ClientToolCall\n): boolean {\n\tconst key = callKey(action, toolCall.input)\n\n\treturn currentUserTurn(messages).some((message) =>\n\t\tmessage.parts.some(\n\t\t\t(part) =>\n\t\t\t\tisToolUIPart(part) &&\n\t\t\t\tpart.toolCallId !== toolCall.toolCallId &&\n\t\t\t\tpart.state === 'output-available' &&\n\t\t\t\tgetToolName(part) === action.name &&\n\t\t\t\tcallKey(action, part.input) === key\n\t\t)\n\t)\n}\n\n/** The parts between the `step-start` before the call and the next one; none when the call is not in the last message. */\nfunction stepOf(\n\tmessages: readonly UIMessage[],\n\ttoolCallId: string\n): MessagePart[] {\n\tconst parts = messages.at(-1)?.parts ?? []\n\tconst at = parts.findIndex(\n\t\t(part) => isToolUIPart(part) && part.toolCallId === toolCallId\n\t)\n\n\tif (at === -1) {\n\t\treturn []\n\t}\n\n\t// The tsconfig lib predates `findLastIndex`.\n\tconst start = parts\n\t\t.slice(0, at)\n\t\t.map((part) => part.type)\n\t\t.lastIndexOf('step-start')\n\tconst end = parts.findIndex(\n\t\t(part, index) => index > at && part.type === 'step-start'\n\t)\n\n\treturn parts.slice(start + 1, end === -1 ? undefined : end)\n}\n\n/** The input with the model's nulls left out, so the same call matches however the model wrote it. */\nfunction callKey(action: ClientAction, input: unknown): string {\n\treturn JSON.stringify(normalizeModelNulls(action.inputSchema, input ?? {}))\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"]}
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Client actions: tools the app carries out itself (open a screen, fill a\n * form), whose definitions go along with each request and whose results go\n * back as the turn's follow-up. A call reaches the app as the model wrote\n * it, without the server's input validation, so the app parses it here.\n */\n\nimport type { UIMessage } from 'ai'\nimport { getToolName, isToolUIPart } from 'ai'\nimport {\n\tisRecord,\n\tstableStringify,\n\ttypedSwitch,\n\ttypedSwitchGroup\n} from 'hanzio'\nimport { z } from 'zod'\n\nimport { currentUserTurn } from './messages.js'\n\n// TYPES\n\ntype MessagePart = UIMessage['parts'][number]\n\n/** Where the call sits, for an action with its own policy on repeats or shared steps. */\nexport interface ClientActionContext {\n\treadonly toolCallId: string\n\t/** The chat's messages when the call arrived; the call is in the last one. */\n\treadonly messages: readonly UIMessage[]\n\t/** The parts of the model step that made the call, the call and any server tool beside it included. */\n\treadonly step: readonly MessagePart[]\n}\n\n/** A tool that runs in the client. The server never interprets its name. */\nexport interface ClientAction<TName extends string = string> {\n\treadonly name: TName\n\treadonly description: string\n\treadonly inputSchema: z.ZodType\n\treadonly requiresApproval?: boolean\n\t/** Runs the action on the model's raw input. Throw to report a failure: the model reads it as an error it can act on. */\n\treadonly run: (\n\t\tinput: unknown,\n\t\tcontext: ClientActionContext\n\t) => Promise<unknown>\n\t/**\n\t * A call the user turn already made with the same input. Its result is\n\t * posted as the call's output; throw to post an error. Default: an error\n\t * with `CLIENT_ACTION_REPEATED`.\n\t */\n\treadonly onRepeat?: (\n\t\tinput: unknown,\n\t\tcontext: ClientActionContext\n\t) => Promise<unknown>\n}\n\n/** A client action as a request carries it, for the server to offer the model. */\nexport interface ClientToolDef {\n\treadonly name: string\n\treadonly description: string\n\treadonly jsonSchema: unknown\n\treadonly requiresApproval?: boolean\n}\n\n/** A tool call the stream handed to the client. */\nexport interface ClientToolCall {\n\treadonly toolCallId: string\n\treadonly toolName: string\n\treadonly input: unknown\n}\n\n/** The part of the AI SDK `Chat` a client action needs. */\nexport type ClientActionChat = {\n\treadonly messages: UIMessage[]\n\treadonly addToolOutput: (\n\t\toptions:\n\t\t\t| { tool: string; toolCallId: string; output: unknown }\n\t\t\t| {\n\t\t\t\t\tstate: 'output-error'\n\t\t\t\t\ttool: string\n\t\t\t\t\ttoolCallId: string\n\t\t\t\t\terrorText: string\n\t\t\t }\n\t) => void | PromiseLike<void>\n}\n\n// CONSTANTS\n\n/** What the model reads when an action throws something that is not an Error. */\nexport const CLIENT_ACTION_FAILED = 'The action failed.'\n\n/** What the model reads when the app cannot read the call's input. */\nexport const CLIENT_ACTION_INVALID_INPUT =\n\t'The app could not read this input. Call the action again with input that fits its schema.'\n\n/** What the model reads for a call it already made with the same input in this turn. */\nexport const CLIENT_ACTION_REPEATED =\n\t'Already done with these values in this turn. Do not call it again; tell the user what changed.'\n\n// FUNCTIONS\n\n/**\n * A client action whose handler gets its input parsed, with the model's\n * `null`s taken out where the schema means \"may be left out\". With\n * `resultSummary`, the output is `{ summary, data }`, for a result the data\n * alone would leave the model guessing about.\n */\nexport function defineClientAction<\n\tconst TName extends string,\n\tTSchema extends z.ZodType,\n\tTOutput\n>(action: {\n\treadonly name: TName\n\treadonly description: string\n\treadonly inputSchema: TSchema\n\treadonly requiresApproval?: boolean\n\treadonly resultSummary?: string\n\treadonly handler: (\n\t\tinput: z.output<TSchema>,\n\t\tcontext: ClientActionContext\n\t) => Promise<TOutput>\n\treadonly onRepeat?: ClientAction['onRepeat']\n}): ClientAction<TName> {\n\treturn {\n\t\tname: action.name,\n\t\tdescription: action.description,\n\t\tinputSchema: action.inputSchema,\n\t\t...(action.requiresApproval === undefined\n\t\t\t? {}\n\t\t\t: { requiresApproval: action.requiresApproval }),\n\t\t...(action.onRepeat === undefined ? {} : { onRepeat: action.onRepeat }),\n\t\trun: async (input, context) => {\n\t\t\tconst parsed = action.inputSchema.safeParse(\n\t\t\t\tnormalizeModelNulls(action.inputSchema, input ?? {})\n\t\t\t)\n\n\t\t\tif (!parsed.success) {\n\t\t\t\tthrow new Error(CLIENT_ACTION_INVALID_INPUT)\n\t\t\t}\n\n\t\t\tconst data = await action.handler(parsed.data, context)\n\n\t\t\treturn action.resultSummary === undefined\n\t\t\t\t? data\n\t\t\t\t: { summary: action.resultSummary, data }\n\t\t}\n\t}\n}\n\n/**\n * Model arguments with `null` taken out wherever the schema means \"may be\n * left out\", at any depth. An OpenAI model behind Mastra's schema layer\n * fills every field it leaves alone with `null`; a `null` stays only where\n * the schema itself accepts it. Unions try each member and keep the first\n * reading that parses.\n */\nexport function normalizeModelNulls(\n\tschema: z.core.$ZodType,\n\tvalue: unknown\n): unknown {\n\tconst def = (schema as z.core.$ZodTypes)._zod.def\n\n\treturn typedSwitch(\n\t\tdef,\n\t\t'type',\n\t\t{\n\t\t\t...typedSwitchGroup(\n\t\t\t\tdef,\n\t\t\t\t'type',\n\t\t\t\t['optional', 'nullable', 'default', 'prefault', 'readonly', 'catch'],\n\t\t\t\t(wrapper) =>\n\t\t\t\t\tvalue == null ? value : normalizeModelNulls(wrapper.innerType, value)\n\t\t\t),\n\t\t\tpipe: (pipe) => normalizeModelNulls(pipe.in, value),\n\t\t\tlazy: (lazy) => normalizeModelNulls(lazy.getter(), value),\n\t\t\tarray: (array) =>\n\t\t\t\tArray.isArray(value)\n\t\t\t\t\t? value.map((item) => normalizeModelNulls(array.element, item))\n\t\t\t\t\t: value,\n\t\t\trecord: (record) => {\n\t\t\t\tconst { valueType } = record\n\n\t\t\t\tif (!isRecord(value)) {\n\t\t\t\t\treturn value\n\t\t\t\t}\n\n\t\t\t\treturn Object.fromEntries(\n\t\t\t\t\tObject.entries(value).flatMap(([key, item]) =>\n\t\t\t\t\t\titem === null && !z.safeParse(valueType, null).success\n\t\t\t\t\t\t\t? []\n\t\t\t\t\t\t\t: [[key, normalizeModelNulls(valueType, item)]]\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t},\n\t\t\tunion: (union) => {\n\t\t\t\tfor (const option of union.options) {\n\t\t\t\t\tconst normalized = normalizeModelNulls(option, value)\n\n\t\t\t\t\tif (z.safeParse(option, normalized).success) {\n\t\t\t\t\t\treturn normalized\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn value\n\t\t\t},\n\t\t\tobject: (object) => {\n\t\t\t\tif (!isRecord(value) || !(schema instanceof z.ZodObject)) {\n\t\t\t\t\treturn value\n\t\t\t\t}\n\n\t\t\t\tconst { shape } = object\n\n\t\t\t\treturn Object.fromEntries(\n\t\t\t\t\tObject.entries(value).flatMap(([key, item]) => {\n\t\t\t\t\t\tconst field = shape[key]\n\n\t\t\t\t\t\tif (field === undefined) {\n\t\t\t\t\t\t\treturn [[key, item]]\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst leaveOut =\n\t\t\t\t\t\t\titem === null &&\n\t\t\t\t\t\t\tz.safeParse(field, undefined).success &&\n\t\t\t\t\t\t\t!z.safeParse(field, null).success\n\n\t\t\t\t\t\treturn leaveOut ? [] : [[key, normalizeModelNulls(field, item)]]\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t\t() => value\n\t)\n}\n\n/** The definitions a request carries, for the server to offer the model as client tools. */\nexport function toClientToolDefs(\n\tactions: readonly ClientAction[]\n): ClientToolDef[] {\n\treturn actions.map((action) => ({\n\t\tname: action.name,\n\t\tdescription: action.description,\n\t\tjsonSchema: z.toJSONSchema(action.inputSchema, {\n\t\t\tio: 'input',\n\t\t\tunrepresentable: 'any'\n\t\t}),\n\t\t...(action.requiresApproval === undefined\n\t\t\t? {}\n\t\t\t: { requiresApproval: action.requiresApproval })\n\t}))\n}\n\n/**\n * Carries out the client action a tool call names and posts its result on the\n * chat; null when no action has that name (a server tool: its result arrives\n * on the stream). A call the user turn already made with the same input\n * (`null`s aside) is refused instead of carried out again, unless the action\n * has its own `onRepeat`.\n *\n * Never await it inside the chat's `onToolCall`: the AI SDK runs that callback\n * on the queue `addToolOutput` waits on, so awaiting deadlocks the stream.\n */\nexport function runClientAction(\n\tchat: ClientActionChat,\n\tactions: readonly ClientAction[],\n\ttoolCall: ClientToolCall\n): Promise<void> | null {\n\tconst action = actions.find(\n\t\t(candidate) => candidate.name === toolCall.toolName\n\t)\n\n\tif (action === undefined) {\n\t\treturn null\n\t}\n\n\tconst fail = (errorText: string) =>\n\t\tchat.addToolOutput({\n\t\t\tstate: 'output-error',\n\t\t\ttool: toolCall.toolName,\n\t\t\ttoolCallId: toolCall.toolCallId,\n\t\t\terrorText\n\t\t})\n\n\tconst context: ClientActionContext = {\n\t\ttoolCallId: toolCall.toolCallId,\n\t\tmessages: chat.messages,\n\t\tstep: stepOf(chat.messages, toolCall.toolCallId)\n\t}\n\n\treturn (async () => {\n\t\tconst repeated = isRepeatInTurn(chat.messages, action, toolCall)\n\n\t\tif (repeated && action.onRepeat === undefined) {\n\t\t\tawait fail(CLIENT_ACTION_REPEATED)\n\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tconst output = await (repeated && action.onRepeat\n\t\t\t\t? action.onRepeat(toolCall.input, context)\n\t\t\t\t: action.run(toolCall.input, context))\n\n\t\t\tawait chat.addToolOutput({\n\t\t\t\ttool: toolCall.toolName,\n\t\t\t\ttoolCallId: toolCall.toolCallId,\n\t\t\t\toutput\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tawait fail(error instanceof Error ? error.message : CLIENT_ACTION_FAILED)\n\t\t}\n\t})()\n}\n\n/**\n * The same action with the same input already ran in this user turn. Running\n * it again changes nothing and only feeds a model that is circling.\n */\nfunction isRepeatInTurn(\n\tmessages: readonly UIMessage[],\n\taction: ClientAction,\n\ttoolCall: ClientToolCall\n): boolean {\n\tconst key = callKey(action, toolCall.input)\n\n\treturn currentUserTurn(messages).some((message) =>\n\t\tmessage.parts.some(\n\t\t\t(part) =>\n\t\t\t\tisToolUIPart(part) &&\n\t\t\t\tpart.toolCallId !== toolCall.toolCallId &&\n\t\t\t\tpart.state === 'output-available' &&\n\t\t\t\tgetToolName(part) === action.name &&\n\t\t\t\tcallKey(action, part.input) === key\n\t\t)\n\t)\n}\n\n/** The parts between the `step-start` before the call and the next one; none when the call is not in the last message. */\nfunction stepOf(\n\tmessages: readonly UIMessage[],\n\ttoolCallId: string\n): MessagePart[] {\n\tconst parts = messages.at(-1)?.parts ?? []\n\tconst at = parts.findIndex(\n\t\t(part) => isToolUIPart(part) && part.toolCallId === toolCallId\n\t)\n\n\tif (at === -1) {\n\t\treturn []\n\t}\n\n\t// The tsconfig lib predates `findLastIndex`.\n\tconst start = parts\n\t\t.slice(0, at)\n\t\t.map((part) => part.type)\n\t\t.lastIndexOf('step-start')\n\tconst end = parts.findIndex(\n\t\t(part, index) => index > at && part.type === 'step-start'\n\t)\n\n\treturn parts.slice(start + 1, end === -1 ? undefined : end)\n}\n\n/** The input with the model's nulls left out, so the same call matches however the model wrote it. */\nfunction callKey(action: ClientAction, input: unknown): string {\n\treturn stableStringify(normalizeModelNulls(action.inputSchema, input ?? {}))\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../../src/client/cards.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../../src/client/cards.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAI7C,qDAAqD;AACrD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;AAE7D,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,WAAW,IAC3C,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAA;AAE1C,6CAA6C;AAC7C,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS,WAAW,IACnC;IACA,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAC5B;QACA,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;QAChB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAC7B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;KACtB;IACH,2EAA2E;OACzE;QACA,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;QAChB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;QACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;KACtB;CACH,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GACnB;IACA,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CACtB,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAA;AAEvD,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,WAAW;IACzD,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,2HAA2H;IAC3H,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;IAClE,oIAAoI;IACpI,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAA;IAChE,2GAA2G;IAC3G,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;CACxD;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,WAAW;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7C,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,CAAA;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,KAAK,WAAW,CAAC,CAAC,CAAC,CAAA;CAC7D;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,WAAW,IAAI,SAAS,CACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;CAAE,GACvE;IACA,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;CACjC,CACH,EAAE,CAAA;AAIH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,CAAC,SAAS,WAAW,EAC7D,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC7B,YAAY,CAAC,CAAC,CAAC,CAsEjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"cards.js","sourceRoot":"","sources":["../../src/client/cards.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The card registry: which card a tool part draws, with its output checked\n * against the card's schema when it is drawn, never trusted. A thread stored\n * before a schema changed still opens: its card falls back to the generic\n * row. Lookups the model makes on its way to an answer are steps, which a\n * client folds into one activity list.\n */\n\nimport { getToolName } from 'ai'\nimport type { z } from 'zod'\n\nimport type { ToolPart } from './messages.js'\n\n// TYPES\n\n/** The output schema of each typed card, by kind. */\nexport type CardSchemas = Readonly<Record<string, z.ZodType>>\n\n/** Which card a tool draws: a typed card, the generic row, or nothing. */\nexport type CardKindOf<S extends CardSchemas> =\n\t| (keyof S & string)\n\t| 'generic'\n\t| 'silent'\n\n/** What a client draws for one tool part. */\nexport type Card<S extends CardSchemas> =\n\t| {\n\t\t\treadonly [K in keyof S & string]:\n\t\t\t\t| {\n\t\t\t\t\t\treadonly kind: K\n\t\t\t\t\t\treadonly state: 'done'\n\t\t\t\t\t\treadonly data: z.output<S[K]>\n\t\t\t\t\t\treadonly part: ToolPart\n\t\t\t\t }\n\t\t\t\t/** The call has no result yet: a card that shows its input can draw it. */\n\t\t\t\t| {\n\t\t\t\t\t\treadonly kind: K\n\t\t\t\t\t\treadonly state: 'running'\n\t\t\t\t\t\treadonly part: ToolPart\n\t\t\t\t }\n\t }[keyof S & string]\n\t| {\n\t\t\treadonly kind: 'generic'\n\t\t\treadonly toolName: string\n\t\t\treadonly part: ToolPart\n\t }\n\t| { readonly kind: 'silent'; readonly part: ToolPart }\n\nexport interface CardRegistryOptions<S extends CardSchemas> {\n\t/** The output schema each typed card reads. */\n\treadonly schemas: S\n\t/** The card a tool draws, by its wire name; undefined for a tool the client does not know, which draws the generic row. */\n\treadonly toolCard: (toolName: string) => CardKindOf<S> | undefined\n\t/** The card-ready data inside a tool's output, such as the `projection` of a result envelope. The output as it is when left out. */\n\treadonly select?: (output: unknown, toolName: string) => unknown\n\t/** Kinds that are steps, folded into the turn's activity list; `generic` is a step unless listed false. */\n\treadonly steps?: Partial<Record<CardKindOf<S>, boolean>>\n}\n\nexport interface CardRegistry<S extends CardSchemas> {\n\treadonly resolve: (part: ToolPart) => Card<S>\n\treadonly isStep: (card: Card<S>) => boolean\n\t/**\n\t * Neighbouring tool parts in order, each run of adjacent steps folded into\n\t * one `steps` group, even a run of one, so a run keeps its key as it grows.\n\t * Silent tools draw nothing and never split a run.\n\t */\n\treadonly fold: (parts: readonly ToolPart[]) => FoldedCards<S>\n}\n\nexport type FoldedCards<S extends CardSchemas> = readonly (\n\t| { readonly kind: 'card'; readonly key: string; readonly card: Card<S> }\n\t| {\n\t\t\treadonly kind: 'steps'\n\t\t\treadonly key: string\n\t\t\treadonly cards: readonly Card<S>[]\n\t }\n)[]\n\n// FUNCTIONS\n\nexport function createCardRegistry<const S extends CardSchemas>(\n\toptions: CardRegistryOptions<S>\n): CardRegistry<S> {\n\tconst select = options.select ?? ((output: unknown) => output)\n\n\tconst resolve = (part: ToolPart): Card<S> => {\n\t\tconst toolName = getToolName(part)\n\t\tconst kind = options.toolCard(toolName)\n\n\t\tif (kind === 'silent') {\n\t\t\treturn { kind: 'silent', part }\n\t\t}\n\n\t\tconst schema =\n\t\t\tkind === undefined || kind === 'generic'\n\t\t\t\t? undefined\n\t\t\t\t: options.schemas[kind]\n\n\t\tif (kind === undefined || schema === undefined) {\n\t\t\treturn { kind: 'generic', toolName, part }\n\t\t}\n\n\t\tif (part.state === 'output-error' || part.state === 'output-denied') {\n\t\t\treturn { kind: 'generic', toolName, part }\n\t\t}\n\n\t\tif (part.state !== 'output-available') {\n\t\t\treturn { kind, state: 'running', part } as Card<S>\n\t\t}\n\n\t\tconst parsed = schema.safeParse(select(part.output, toolName))\n\n\t\t// The schema of `kind` parsed the data, so it is that card's data.\n\t\treturn parsed.success\n\t\t\t? ({ kind, state: 'done', data: parsed.data, part } as Card<S>)\n\t\t\t: { kind: 'generic', toolName, part }\n\t}\n\n\tconst isStep = (card: Card<S>): boolean =>\n\t\t(options.steps?.[card.kind] as boolean | undefined) ??\n\t\tcard.kind === 'generic'\n\n\treturn {\n\t\tresolve,\n\t\tisStep,\n\t\tfold: (parts) => {\n\t\t\tconst folded: (\n\t\t\t\t| { kind: 'card'; key: string; card: Card<S> }\n\t\t\t\t| { kind: 'steps'; key: string; cards: Card<S>[] }\n\t\t\t)[] = []\n\n\t\t\tfor (const part of parts) {\n\t\t\t\tconst card = resolve(part)\n\n\t\t\t\tif (card.kind === 'silent') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tconst previous = folded.at(-1)\n\n\t\t\t\tif (!isStep(card)) {\n\t\t\t\t\tfolded.push({ kind: 'card', key: part.toolCallId, card })\n\t\t\t\t} else if (previous?.kind === 'steps') {\n\t\t\t\t\tprevious.cards.push(card)\n\t\t\t\t} else {\n\t\t\t\t\tfolded.push({ kind: 'steps', key: part.toolCallId, cards: [card] })\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn folded\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"cards.js","sourceRoot":"","sources":["../../src/client/cards.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAqEhC,YAAY;AAEZ,MAAM,UAAU,kBAAkB,CACjC,OAA+B;IAE/B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,MAAe,EAAE,EAAE,CAAC,MAAM,CAAC,CAAA;IAE9D,MAAM,OAAO,GAAG,CAAC,IAAc,EAAW,EAAE;QAC3C,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;QAClC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAEvC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;QAChC,CAAC;QAED,MAAM,MAAM,GACX,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS;YACvC,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAEzB,IAAI,IAAI,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAChD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;QAC3C,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,KAAK,cAAc,IAAI,IAAI,CAAC,KAAK,KAAK,eAAe,EAAE,CAAC;YACrE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;QAC3C,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,KAAK,kBAAkB,EAAE,CAAC;YACvC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAa,CAAA;QACnD,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;QAE9D,mEAAmE;QACnE,OAAO,MAAM,CAAC,OAAO;YACpB,CAAC,CAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAc;YAC/D,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;IACvC,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,CAAC,IAAa,EAAW,EAAE,CACxC,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAyB;QACnD,IAAI,CAAC,IAAI,KAAK,SAAS,CAAA;IAExB,OAAO;QACN,OAAO;QACP,MAAM;QACN,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;YACf,MAAM,MAAM,GAGN,EAAE,CAAA;YAER,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;gBAE1B,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,SAAQ;gBACT,CAAC;gBAED,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;gBAE9B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;oBACnB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;gBAC1D,CAAC;qBAAM,IAAI,QAAQ,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;oBACvC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAC1B,CAAC;qBAAM,CAAC;oBACP,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBACpE,CAAC;YACF,CAAC;YAED,OAAO,MAAM,CAAA;QACd,CAAC;KACD,CAAA;AACF,CAAC","sourcesContent":["/**\n * The card registry: which card a tool part draws, with its output checked\n * against the card's schema when it is drawn, never trusted. A thread stored\n * before a schema changed still opens: its card falls back to the generic\n * row. Lookups the model makes on its way to an answer are steps, which a\n * client folds into one activity list.\n */\n\nimport { getToolName } from 'ai'\nimport type { z } from 'zod'\n\nimport type { ToolPart } from './messages.js'\n\n// TYPES\n\n/** The output schema of each typed card, by kind. */\nexport type CardSchemas = Readonly<Record<string, z.ZodType>>\n\n/** Which card a tool draws: a typed card, the generic row, or nothing. */\nexport type CardKindOf<S extends CardSchemas> =\n\t(keyof S & string) | 'generic' | 'silent'\n\n/** What a client draws for one tool part. */\nexport type Card<S extends CardSchemas> =\n\t| {\n\t\t\treadonly [K in keyof S & string]:\n\t\t\t\t| {\n\t\t\t\t\t\treadonly kind: K\n\t\t\t\t\t\treadonly state: 'done'\n\t\t\t\t\t\treadonly data: z.output<S[K]>\n\t\t\t\t\t\treadonly part: ToolPart\n\t\t\t\t }\n\t\t\t\t/** The call has no result yet: a card that shows its input can draw it. */\n\t\t\t\t| {\n\t\t\t\t\t\treadonly kind: K\n\t\t\t\t\t\treadonly state: 'running'\n\t\t\t\t\t\treadonly part: ToolPart\n\t\t\t\t }\n\t }[keyof S & string]\n\t| {\n\t\t\treadonly kind: 'generic'\n\t\t\treadonly toolName: string\n\t\t\treadonly part: ToolPart\n\t }\n\t| { readonly kind: 'silent'; readonly part: ToolPart }\n\nexport interface CardRegistryOptions<S extends CardSchemas> {\n\t/** The output schema each typed card reads. */\n\treadonly schemas: S\n\t/** The card a tool draws, by its wire name; undefined for a tool the client does not know, which draws the generic row. */\n\treadonly toolCard: (toolName: string) => CardKindOf<S> | undefined\n\t/** The card-ready data inside a tool's output, such as the `projection` of a result envelope. The output as it is when left out. */\n\treadonly select?: (output: unknown, toolName: string) => unknown\n\t/** Kinds that are steps, folded into the turn's activity list; `generic` is a step unless listed false. */\n\treadonly steps?: Partial<Record<CardKindOf<S>, boolean>>\n}\n\nexport interface CardRegistry<S extends CardSchemas> {\n\treadonly resolve: (part: ToolPart) => Card<S>\n\treadonly isStep: (card: Card<S>) => boolean\n\t/**\n\t * Neighbouring tool parts in order, each run of adjacent steps folded into\n\t * one `steps` group, even a run of one, so a run keeps its key as it grows.\n\t * Silent tools draw nothing and never split a run.\n\t */\n\treadonly fold: (parts: readonly ToolPart[]) => FoldedCards<S>\n}\n\nexport type FoldedCards<S extends CardSchemas> = readonly (\n\t| { readonly kind: 'card'; readonly key: string; readonly card: Card<S> }\n\t| {\n\t\t\treadonly kind: 'steps'\n\t\t\treadonly key: string\n\t\t\treadonly cards: readonly Card<S>[]\n\t }\n)[]\n\n// FUNCTIONS\n\nexport function createCardRegistry<const S extends CardSchemas>(\n\toptions: CardRegistryOptions<S>\n): CardRegistry<S> {\n\tconst select = options.select ?? ((output: unknown) => output)\n\n\tconst resolve = (part: ToolPart): Card<S> => {\n\t\tconst toolName = getToolName(part)\n\t\tconst kind = options.toolCard(toolName)\n\n\t\tif (kind === 'silent') {\n\t\t\treturn { kind: 'silent', part }\n\t\t}\n\n\t\tconst schema =\n\t\t\tkind === undefined || kind === 'generic'\n\t\t\t\t? undefined\n\t\t\t\t: options.schemas[kind]\n\n\t\tif (kind === undefined || schema === undefined) {\n\t\t\treturn { kind: 'generic', toolName, part }\n\t\t}\n\n\t\tif (part.state === 'output-error' || part.state === 'output-denied') {\n\t\t\treturn { kind: 'generic', toolName, part }\n\t\t}\n\n\t\tif (part.state !== 'output-available') {\n\t\t\treturn { kind, state: 'running', part } as Card<S>\n\t\t}\n\n\t\tconst parsed = schema.safeParse(select(part.output, toolName))\n\n\t\t// The schema of `kind` parsed the data, so it is that card's data.\n\t\treturn parsed.success\n\t\t\t? ({ kind, state: 'done', data: parsed.data, part } as Card<S>)\n\t\t\t: { kind: 'generic', toolName, part }\n\t}\n\n\tconst isStep = (card: Card<S>): boolean =>\n\t\t(options.steps?.[card.kind] as boolean | undefined) ??\n\t\tcard.kind === 'generic'\n\n\treturn {\n\t\tresolve,\n\t\tisStep,\n\t\tfold: (parts) => {\n\t\t\tconst folded: (\n\t\t\t\t| { kind: 'card'; key: string; card: Card<S> }\n\t\t\t\t| { kind: 'steps'; key: string; cards: Card<S>[] }\n\t\t\t)[] = []\n\n\t\t\tfor (const part of parts) {\n\t\t\t\tconst card = resolve(part)\n\n\t\t\t\tif (card.kind === 'silent') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tconst previous = folded.at(-1)\n\n\t\t\t\tif (!isStep(card)) {\n\t\t\t\t\tfolded.push({ kind: 'card', key: part.toolCallId, card })\n\t\t\t\t} else if (previous?.kind === 'steps') {\n\t\t\t\t\tprevious.cards.push(card)\n\t\t\t\t} else {\n\t\t\t\t\tfolded.push({ kind: 'steps', key: part.toolCallId, cards: [card] })\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn folded\n\t\t}\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/client/chat.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACN,YAAY,EACZ,KAAK,QAAQ,EAEb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,MAAM,IAAI,CAAA;AAIX,uFAAuF;AACvF,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;IAC5D,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAA;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAAA;CACjC;AAsGD,iDAAiD;AACjD,qBAAa,cAAc,CAC1B,CAAC,SAAS,SAAS,GAAG,SAAS,CAC9B,SAAQ,YAAY,CAAC,CAAC,CAAC;;gBAGZ,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IAO9C,yFAAyF;IACzF,QAAQ,CAAC,WAAW,QAAO,YAAY,CAAC,CAAC,CAAC,CAA6B;IAEvE,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CACzB;CAChC;AAID,mIAAmI;AACnI,wBAAgB,iBAAiB,CAChC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,EAC/C,EAAE,EAAE,MAAM,GAAG,SAAS,GACpB,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAmCtC"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/client/chat.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACN,YAAY,EACZ,KAAK,QAAQ,EAEb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,MAAM,IAAI,CAAA;AAKX,uFAAuF;AACvF,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;IAC5D,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAA;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAAA;CACjC;AA8FD,iDAAiD;AACjD,qBAAa,cAAc,CAC1B,CAAC,SAAS,SAAS,GAAG,SAAS,CAC9B,SAAQ,YAAY,CAAC,CAAC,CAAC;;gBAGZ,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IAO9C,yFAAyF;IACzF,QAAQ,CAAC,WAAW,QAAO,YAAY,CAAC,CAAC,CAAC,CAA6B;IAEvE,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CACzB;CAChC;AAID,mIAAmI;AACnI,wBAAgB,iBAAiB,CAChC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,EAC/C,EAAE,EAAE,MAAM,GAAG,SAAS,GACpB,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CActC"}
@@ -5,13 +5,14 @@
5
5
  * listener list instead of private callbacks.
6
6
  */
7
7
  import { AbstractChat } from 'ai';
8
+ import { createEmitter, throttle } from 'hanzio';
8
9
  // CLASSES
9
10
  class ObservableChatState {
10
11
  #messages;
11
12
  #status = 'ready';
12
13
  #error = undefined;
13
14
  #snapshot;
14
- #listeners = new Set();
15
+ #changes = createEmitter();
15
16
  constructor(messages = []) {
16
17
  this.#messages = messages;
17
18
  }
@@ -70,17 +71,10 @@ class ObservableChatState {
70
71
  };
71
72
  return this.#snapshot;
72
73
  };
73
- subscribe = (listener) => {
74
- this.#listeners.add(listener);
75
- return () => {
76
- this.#listeners.delete(listener);
77
- };
78
- };
74
+ subscribe = (listener) => this.#changes.on('change', listener);
79
75
  #notify() {
80
76
  this.#snapshot = undefined;
81
- for (const listener of this.#listeners) {
82
- listener();
83
- }
77
+ this.#changes.emit('change');
84
78
  }
85
79
  }
86
80
  /** The AI SDK chat with a subscribable state. */
@@ -103,26 +97,10 @@ export function throttleSubscribe(subscribe, ms) {
103
97
  return subscribe;
104
98
  }
105
99
  return (listener) => {
106
- let timer;
107
- let pending = false;
108
- const flush = () => {
109
- timer = undefined;
110
- if (pending) {
111
- pending = false;
112
- listener();
113
- timer = setTimeout(flush, ms);
114
- }
115
- };
116
- const unsubscribe = subscribe(() => {
117
- if (timer !== undefined) {
118
- pending = true;
119
- return;
120
- }
121
- listener();
122
- timer = setTimeout(flush, ms);
123
- });
100
+ const throttled = throttle(listener, ms);
101
+ const unsubscribe = subscribe(() => throttled());
124
102
  return () => {
125
- clearTimeout(timer);
103
+ throttled.cancel();
126
104
  unsubscribe();
127
105
  };
128
106
  };
@@ -1 +1 @@
1
- {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/client/chat.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The AI SDK `Chat` the session owns, with a state any framework can\n * subscribe to: React reads it with `useSyncExternalStore`, Vue mirrors it\n * into a ref. The same state the AI SDK's React chat keeps, with one\n * listener list instead of private callbacks.\n */\n\nimport {\n\tAbstractChat,\n\ttype ChatInit,\n\ttype ChatState,\n\ttype ChatStatus,\n\ttype UIMessage\n} from 'ai'\n\n// TYPES\n\n/** What a framework renders from the chat; a new object whenever any of it changes. */\nexport interface ChatSnapshot<M extends UIMessage = UIMessage> {\n\treadonly messages: readonly M[]\n\treadonly status: ChatStatus\n\treadonly error: Error | undefined\n}\n\n// CLASSES\n\nclass ObservableChatState<M extends UIMessage> implements ChatState<M> {\n\t#messages: M[]\n\t#status: ChatStatus = 'ready'\n\t#error: Error | undefined = undefined\n\t#snapshot: ChatSnapshot<M> | undefined\n\treadonly #listeners = new Set<() => void>()\n\n\tconstructor(messages: M[] = []) {\n\t\tthis.#messages = messages\n\t}\n\n\tget messages(): M[] {\n\t\treturn this.#messages\n\t}\n\n\tset messages(messages: M[]) {\n\t\tthis.#messages = [...messages]\n\t\tthis.#notify()\n\t}\n\n\tget status(): ChatStatus {\n\t\treturn this.#status\n\t}\n\n\tset status(status: ChatStatus) {\n\t\tthis.#status = status\n\t\tthis.#notify()\n\t}\n\n\tget error(): Error | undefined {\n\t\treturn this.#error\n\t}\n\n\tset error(error: Error | undefined) {\n\t\tthis.#error = error\n\t\tthis.#notify()\n\t}\n\n\tpushMessage = (message: M): void => {\n\t\tthis.#messages = this.#messages.concat(message)\n\t\tthis.#notify()\n\t}\n\n\tpopMessage = (): void => {\n\t\tthis.#messages = this.#messages.slice(0, -1)\n\t\tthis.#notify()\n\t}\n\n\treplaceMessage = (index: number, message: M): void => {\n\t\tthis.#messages = [\n\t\t\t...this.#messages.slice(0, index),\n\t\t\tthis.snapshot(message),\n\t\t\t...this.#messages.slice(index + 1)\n\t\t]\n\t\tthis.#notify()\n\t}\n\n\t/** A copy of a streaming message: the AI SDK keeps writing into the one it holds. */\n\tsnapshot = <T>(value: T): T => {\n\t\tif (\n\t\t\tvalue === null ||\n\t\t\ttypeof value !== 'object' ||\n\t\t\t!('parts' in value) ||\n\t\t\t!Array.isArray(value.parts)\n\t\t) {\n\t\t\treturn value\n\t\t}\n\n\t\treturn { ...value, parts: value.parts.map((part: object) => ({ ...part })) }\n\t}\n\n\tgetSnapshot = (): ChatSnapshot<M> => {\n\t\tthis.#snapshot ??= {\n\t\t\tmessages: this.#messages,\n\t\t\tstatus: this.#status,\n\t\t\terror: this.#error\n\t\t}\n\n\t\treturn this.#snapshot\n\t}\n\n\tsubscribe = (listener: () => void): (() => void) => {\n\t\tthis.#listeners.add(listener)\n\n\t\treturn () => {\n\t\t\tthis.#listeners.delete(listener)\n\t\t}\n\t}\n\n\t#notify(): void {\n\t\tthis.#snapshot = undefined\n\n\t\tfor (const listener of this.#listeners) {\n\t\t\tlistener()\n\t\t}\n\t}\n}\n\n/** The AI SDK chat with a subscribable state. */\nexport class ObservableChat<\n\tM extends UIMessage = UIMessage\n> extends AbstractChat<M> {\n\treadonly #state: ObservableChatState<M>\n\n\tconstructor({ messages, ...init }: ChatInit<M>) {\n\t\tconst state = new ObservableChatState(messages)\n\n\t\tsuper({ ...init, state })\n\t\tthis.#state = state\n\t}\n\n\t/** The messages, status and error as one value that changes identity on every change. */\n\treadonly getSnapshot = (): ChatSnapshot<M> => this.#state.getSnapshot()\n\n\t/** Hears every change of the messages, status or error. */\n\treadonly subscribe = (listener: () => void): (() => void) =>\n\t\tthis.#state.subscribe(listener)\n}\n\n// FUNCTIONS\n\n/** A subscribe whose listener hears at most one change per `ms`, the last one always: a render throttle for a streaming answer. */\nexport function throttleSubscribe(\n\tsubscribe: (listener: () => void) => () => void,\n\tms: number | undefined\n): (listener: () => void) => () => void {\n\tif (ms === undefined || ms <= 0) {\n\t\treturn subscribe\n\t}\n\n\treturn (listener) => {\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined\n\t\tlet pending = false\n\n\t\tconst flush = () => {\n\t\t\ttimer = undefined\n\n\t\t\tif (pending) {\n\t\t\t\tpending = false\n\t\t\t\tlistener()\n\t\t\t\ttimer = setTimeout(flush, ms)\n\t\t\t}\n\t\t}\n\n\t\tconst unsubscribe = subscribe(() => {\n\t\t\tif (timer !== undefined) {\n\t\t\t\tpending = true\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlistener()\n\t\t\ttimer = setTimeout(flush, ms)\n\t\t})\n\n\t\treturn () => {\n\t\t\tclearTimeout(timer)\n\t\t\tunsubscribe()\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/client/chat.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACN,YAAY,EAKZ,MAAM,IAAI,CAAA;AACX,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAWhD,UAAU;AAEV,MAAM,mBAAmB;IACxB,SAAS,CAAK;IACd,OAAO,GAAe,OAAO,CAAA;IAC7B,MAAM,GAAsB,SAAS,CAAA;IACrC,SAAS,CAA6B;IAC7B,QAAQ,GAAG,aAAa,EAAyB,CAAA;IAE1D,YAAY,WAAgB,EAAE;QAC7B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED,IAAI,QAAQ;QACX,OAAO,IAAI,CAAC,SAAS,CAAA;IACtB,CAAC;IAED,IAAI,QAAQ,CAAC,QAAa;QACzB,IAAI,CAAC,SAAS,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAA;QAC9B,IAAI,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,OAAO,CAAA;IACpB,CAAC;IAED,IAAI,MAAM,CAAC,MAAkB;QAC5B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,IAAI,KAAK;QACR,OAAO,IAAI,CAAC,MAAM,CAAA;IACnB,CAAC;IAED,IAAI,KAAK,CAAC,KAAwB;QACjC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,WAAW,GAAG,CAAC,OAAU,EAAQ,EAAE;QAClC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC/C,IAAI,CAAC,OAAO,EAAE,CAAA;IACf,CAAC,CAAA;IAED,UAAU,GAAG,GAAS,EAAE;QACvB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAA;IACf,CAAC,CAAA;IAED,cAAc,GAAG,CAAC,KAAa,EAAE,OAAU,EAAQ,EAAE;QACpD,IAAI,CAAC,SAAS,GAAG;YAChB,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;YACjC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YACtB,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;SAClC,CAAA;QACD,IAAI,CAAC,OAAO,EAAE,CAAA;IACf,CAAC,CAAA;IAED,qFAAqF;IACrF,QAAQ,GAAG,CAAI,KAAQ,EAAK,EAAE;QAC7B,IACC,KAAK,KAAK,IAAI;YACd,OAAO,KAAK,KAAK,QAAQ;YACzB,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC;YACnB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAC1B,CAAC;YACF,OAAO,KAAK,CAAA;QACb,CAAC;QAED,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,EAAE,CAAA;IAC7E,CAAC,CAAA;IAED,WAAW,GAAG,GAAoB,EAAE;QACnC,IAAI,CAAC,SAAS,KAAK;YAClB,QAAQ,EAAE,IAAI,CAAC,SAAS;YACxB,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,KAAK,EAAE,IAAI,CAAC,MAAM;SAClB,CAAA;QAED,OAAO,IAAI,CAAC,SAAS,CAAA;IACtB,CAAC,CAAA;IAED,SAAS,GAAG,CAAC,QAAoB,EAAgB,EAAE,CAClD,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAErC,OAAO;QACN,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC7B,CAAC;CACD;AAED,iDAAiD;AACjD,MAAM,OAAO,cAEX,SAAQ,YAAe;IACf,MAAM,CAAwB;IAEvC,YAAY,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAe;QAC7C,MAAM,KAAK,GAAG,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAA;QAE/C,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QACzB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;IACpB,CAAC;IAED,yFAAyF;IAChF,WAAW,GAAG,GAAoB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAA;IAEvE,2DAA2D;IAClD,SAAS,GAAG,CAAC,QAAoB,EAAgB,EAAE,CAC3D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;CAChC;AAED,YAAY;AAEZ,mIAAmI;AACnI,MAAM,UAAU,iBAAiB,CAChC,SAA+C,EAC/C,EAAsB;IAEtB,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QACjC,OAAO,SAAS,CAAA;IACjB,CAAC;IAED,OAAO,CAAC,QAAQ,EAAE,EAAE;QACnB,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;QACxC,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAA;QAEhD,OAAO,GAAG,EAAE;YACX,SAAS,CAAC,MAAM,EAAE,CAAA;YAClB,WAAW,EAAE,CAAA;QACd,CAAC,CAAA;IACF,CAAC,CAAA;AACF,CAAC","sourcesContent":["/**\n * The AI SDK `Chat` the session owns, with a state any framework can\n * subscribe to: React reads it with `useSyncExternalStore`, Vue mirrors it\n * into a ref. The same state the AI SDK's React chat keeps, with one\n * listener list instead of private callbacks.\n */\n\nimport {\n\tAbstractChat,\n\ttype ChatInit,\n\ttype ChatState,\n\ttype ChatStatus,\n\ttype UIMessage\n} from 'ai'\nimport { createEmitter, throttle } from 'hanzio'\n\n// TYPES\n\n/** What a framework renders from the chat; a new object whenever any of it changes. */\nexport interface ChatSnapshot<M extends UIMessage = UIMessage> {\n\treadonly messages: readonly M[]\n\treadonly status: ChatStatus\n\treadonly error: Error | undefined\n}\n\n// CLASSES\n\nclass ObservableChatState<M extends UIMessage> implements ChatState<M> {\n\t#messages: M[]\n\t#status: ChatStatus = 'ready'\n\t#error: Error | undefined = undefined\n\t#snapshot: ChatSnapshot<M> | undefined\n\treadonly #changes = createEmitter<{ change: undefined }>()\n\n\tconstructor(messages: M[] = []) {\n\t\tthis.#messages = messages\n\t}\n\n\tget messages(): M[] {\n\t\treturn this.#messages\n\t}\n\n\tset messages(messages: M[]) {\n\t\tthis.#messages = [...messages]\n\t\tthis.#notify()\n\t}\n\n\tget status(): ChatStatus {\n\t\treturn this.#status\n\t}\n\n\tset status(status: ChatStatus) {\n\t\tthis.#status = status\n\t\tthis.#notify()\n\t}\n\n\tget error(): Error | undefined {\n\t\treturn this.#error\n\t}\n\n\tset error(error: Error | undefined) {\n\t\tthis.#error = error\n\t\tthis.#notify()\n\t}\n\n\tpushMessage = (message: M): void => {\n\t\tthis.#messages = this.#messages.concat(message)\n\t\tthis.#notify()\n\t}\n\n\tpopMessage = (): void => {\n\t\tthis.#messages = this.#messages.slice(0, -1)\n\t\tthis.#notify()\n\t}\n\n\treplaceMessage = (index: number, message: M): void => {\n\t\tthis.#messages = [\n\t\t\t...this.#messages.slice(0, index),\n\t\t\tthis.snapshot(message),\n\t\t\t...this.#messages.slice(index + 1)\n\t\t]\n\t\tthis.#notify()\n\t}\n\n\t/** A copy of a streaming message: the AI SDK keeps writing into the one it holds. */\n\tsnapshot = <T>(value: T): T => {\n\t\tif (\n\t\t\tvalue === null ||\n\t\t\ttypeof value !== 'object' ||\n\t\t\t!('parts' in value) ||\n\t\t\t!Array.isArray(value.parts)\n\t\t) {\n\t\t\treturn value\n\t\t}\n\n\t\treturn { ...value, parts: value.parts.map((part: object) => ({ ...part })) }\n\t}\n\n\tgetSnapshot = (): ChatSnapshot<M> => {\n\t\tthis.#snapshot ??= {\n\t\t\tmessages: this.#messages,\n\t\t\tstatus: this.#status,\n\t\t\terror: this.#error\n\t\t}\n\n\t\treturn this.#snapshot\n\t}\n\n\tsubscribe = (listener: () => void): (() => void) =>\n\t\tthis.#changes.on('change', listener)\n\n\t#notify(): void {\n\t\tthis.#snapshot = undefined\n\t\tthis.#changes.emit('change')\n\t}\n}\n\n/** The AI SDK chat with a subscribable state. */\nexport class ObservableChat<\n\tM extends UIMessage = UIMessage\n> extends AbstractChat<M> {\n\treadonly #state: ObservableChatState<M>\n\n\tconstructor({ messages, ...init }: ChatInit<M>) {\n\t\tconst state = new ObservableChatState(messages)\n\n\t\tsuper({ ...init, state })\n\t\tthis.#state = state\n\t}\n\n\t/** The messages, status and error as one value that changes identity on every change. */\n\treadonly getSnapshot = (): ChatSnapshot<M> => this.#state.getSnapshot()\n\n\t/** Hears every change of the messages, status or error. */\n\treadonly subscribe = (listener: () => void): (() => void) =>\n\t\tthis.#state.subscribe(listener)\n}\n\n// FUNCTIONS\n\n/** A subscribe whose listener hears at most one change per `ms`, the last one always: a render throttle for a streaming answer. */\nexport function throttleSubscribe(\n\tsubscribe: (listener: () => void) => () => void,\n\tms: number | undefined\n): (listener: () => void) => () => void {\n\tif (ms === undefined || ms <= 0) {\n\t\treturn subscribe\n\t}\n\n\treturn (listener) => {\n\t\tconst throttled = throttle(listener, ms)\n\t\tconst unsubscribe = subscribe(() => throttled())\n\n\t\treturn () => {\n\t\t\tthrottled.cancel()\n\t\t\tunsubscribe()\n\t\t}\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,KAAK,iBAAiB,EAA2B,MAAM,YAAY,CAAA;AAC5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAI5C,0FAA0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,CAC/B,QAAQ,EAAE,iBAAiB,KACvB,WAAW,GAAG,SAAS,CAAA;AAiB5B;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,iBAe/B,CAAA;AAED,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,OAAO,GACZ,iBAAiB,GAAG,SAAS,CAiB/B;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,OAAO,EACd,GAAG,EAAE;IAAE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,GACjD,OAAO,CAET"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,KAAK,iBAAiB,EAA2B,MAAM,YAAY,CAAA;AAC5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAI5C,0FAA0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,CAC/B,QAAQ,EAAE,iBAAiB,KACvB,WAAW,GAAG,SAAS,CAAA;AAiB5B;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,iBAe/B,CAAA;AAED,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,OAAO,GACZ,iBAAiB,GAAG,SAAS,CAY/B;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,OAAO,EACd,GAAG,EAAE;IAAE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,GACjD,OAAO,CAET"}
@@ -4,15 +4,16 @@
4
4
  * `{ error: code }` bodies; an app with its own codes passes its own.
5
5
  */
6
6
  import { APICallError } from 'ai';
7
+ import { isOneOf, safeJsonParse } from 'hanzio';
7
8
  import { ChatConnectionLostError } from './fetch.js';
8
9
  // CONSTANTS
9
10
  /** Refusals worth trying again: the cause passes (a turn ends, a limit resets, consent is given). */
10
- const RETRYABLE_CODES = new Set([
11
+ const RETRYABLE_CODES = [
11
12
  'turn_in_progress',
12
13
  'temporarily_unavailable',
13
14
  'usage_limited',
14
15
  'consent_required'
15
- ]);
16
+ ];
16
17
  /** The refusal of a request that answers an approval or suspended run the server no longer has waiting. */
17
18
  const EXPIRED_CODE = 'change_expired';
18
19
  // FUNCTIONS
@@ -27,7 +28,7 @@ export const classifyChatError = ({ status, body }) => {
27
28
  }
28
29
  return {
29
30
  code,
30
- canTryAgain: RETRYABLE_CODES.has(code) ||
31
+ canTryAgain: isOneOf(RETRYABLE_CODES, code) ||
31
32
  (code !== EXPIRED_CODE &&
32
33
  (status === 409 || status === 429 || status >= 500)),
33
34
  expired: code === EXPIRED_CODE
@@ -38,16 +39,10 @@ export function readErrorResponse(error) {
38
39
  if (!APICallError.isInstance(error) || error.statusCode === undefined) {
39
40
  return undefined;
40
41
  }
41
- let body;
42
- try {
43
- body =
44
- error.responseBody === undefined
45
- ? undefined
46
- : JSON.parse(error.responseBody);
47
- }
48
- catch {
49
- body = undefined;
50
- }
42
+ const parsed = error.responseBody === undefined
43
+ ? undefined
44
+ : safeJsonParse(error.responseBody);
45
+ const body = parsed?.ok ? parsed.data : undefined;
51
46
  return { status: error.statusCode, body };
52
47
  }
53
48
  /** Whether a request failed on the network rather than the server. */
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AAEjC,OAAO,EAA0B,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAU5E,YAAY;AAEZ,qGAAqG;AACrG,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC/B,kBAAkB;IAClB,yBAAyB;IACzB,eAAe;IACf,kBAAkB;CAClB,CAAC,CAAA;AAEF,2GAA2G;AAC3G,MAAM,YAAY,GAAG,gBAAgB,CAAA;AAErC,YAAY;AAEZ;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;IACxE,MAAM,IAAI,GAAI,IAA+C,EAAE,KAAK,CAAA;IAEpE,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAA;IACjB,CAAC;IAED,OAAO;QACN,IAAI;QACJ,WAAW,EACV,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;YACzB,CAAC,IAAI,KAAK,YAAY;gBACrB,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC,CAAC;QACtD,OAAO,EAAE,IAAI,KAAK,YAAY;KAC9B,CAAA;AACF,CAAC,CAAA;AAED,6FAA6F;AAC7F,MAAM,UAAU,iBAAiB,CAChC,KAAc;IAEd,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACvE,OAAO,SAAS,CAAA;IACjB,CAAC;IAED,IAAI,IAAa,CAAA;IAEjB,IAAI,CAAC;QACJ,IAAI;YACH,KAAK,CAAC,YAAY,KAAK,SAAS;gBAC/B,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACnC,CAAC;IAAC,MAAM,CAAC;QACR,IAAI,GAAG,SAAS,CAAA;IACjB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,CAAA;AAC1C,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,gBAAgB,CAC/B,KAAc,EACd,GAAmD;IAEnD,OAAO,GAAG,EAAE,YAAY,KAAK,IAAI,IAAI,KAAK,YAAY,uBAAuB,CAAA;AAC9E,CAAC","sourcesContent":["/**\n * How a failed chat request reads: a refusal of the chat route, a server\n * error, or a lost connection. The default classifier reads the route's\n * `{ error: code }` bodies; an app with its own codes passes its own.\n */\n\nimport { APICallError } from 'ai'\n\nimport { type ChatErrorResponse, ChatConnectionLostError } from './fetch.js'\nimport type { ChatRefusal } from './turn.js'\n\n// TYPES\n\n/** Reads a response that is not OK; undefined when it is no refusal of the chat route. */\nexport type ClassifyChatError = (\n\tresponse: ChatErrorResponse\n) => ChatRefusal | undefined\n\n// CONSTANTS\n\n/** Refusals worth trying again: the cause passes (a turn ends, a limit resets, consent is given). */\nconst RETRYABLE_CODES = new Set([\n\t'turn_in_progress',\n\t'temporarily_unavailable',\n\t'usage_limited',\n\t'consent_required'\n])\n\n/** The refusal of a request that answers an approval or suspended run the server no longer has waiting. */\nconst EXPIRED_CODE = 'change_expired'\n\n// FUNCTIONS\n\n/**\n * A `{ error: code }` body is a refusal. Try again is offered for the codes\n * above, and for an unknown code on 409, 429 or 5xx.\n */\nexport const classifyChatError: ClassifyChatError = ({ status, body }) => {\n\tconst code = (body as { error?: unknown } | null | undefined)?.error\n\n\tif (typeof code !== 'string') {\n\t\treturn undefined\n\t}\n\n\treturn {\n\t\tcode,\n\t\tcanTryAgain:\n\t\t\tRETRYABLE_CODES.has(code) ||\n\t\t\t(code !== EXPIRED_CODE &&\n\t\t\t\t(status === 409 || status === 429 || status >= 500)),\n\t\texpired: code === EXPIRED_CODE\n\t}\n}\n\n/** The status and JSON body of a request the AI SDK failed on a response that was not OK. */\nexport function readErrorResponse(\n\terror: unknown\n): ChatErrorResponse | undefined {\n\tif (!APICallError.isInstance(error) || error.statusCode === undefined) {\n\t\treturn undefined\n\t}\n\n\tlet body: unknown\n\n\ttry {\n\t\tbody =\n\t\t\terror.responseBody === undefined\n\t\t\t\t? undefined\n\t\t\t\t: JSON.parse(error.responseBody)\n\t} catch {\n\t\tbody = undefined\n\t}\n\n\treturn { status: error.statusCode, body }\n}\n\n/** Whether a request failed on the network rather than the server. */\nexport function isConnectionLost(\n\terror: unknown,\n\tend: { readonly isDisconnect: boolean } | undefined\n): boolean {\n\treturn end?.isDisconnect === true || error instanceof ChatConnectionLostError\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AAE/C,OAAO,EAA0B,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAU5E,YAAY;AAEZ,qGAAqG;AACrG,MAAM,eAAe,GAAG;IACvB,kBAAkB;IAClB,yBAAyB;IACzB,eAAe;IACf,kBAAkB;CAClB,CAAA;AAED,2GAA2G;AAC3G,MAAM,YAAY,GAAG,gBAAgB,CAAA;AAErC,YAAY;AAEZ;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;IACxE,MAAM,IAAI,GAAI,IAA+C,EAAE,KAAK,CAAA;IAEpE,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAA;IACjB,CAAC;IAED,OAAO;QACN,IAAI;QACJ,WAAW,EACV,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC;YAC9B,CAAC,IAAI,KAAK,YAAY;gBACrB,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC,CAAC;QACtD,OAAO,EAAE,IAAI,KAAK,YAAY;KAC9B,CAAA;AACF,CAAC,CAAA;AAED,6FAA6F;AAC7F,MAAM,UAAU,iBAAiB,CAChC,KAAc;IAEd,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACvE,OAAO,SAAS,CAAA;IACjB,CAAC;IAED,MAAM,MAAM,GACX,KAAK,CAAC,YAAY,KAAK,SAAS;QAC/B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACrC,MAAM,IAAI,GAAG,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;IAEjD,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,CAAA;AAC1C,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,gBAAgB,CAC/B,KAAc,EACd,GAAmD;IAEnD,OAAO,GAAG,EAAE,YAAY,KAAK,IAAI,IAAI,KAAK,YAAY,uBAAuB,CAAA;AAC9E,CAAC","sourcesContent":["/**\n * How a failed chat request reads: a refusal of the chat route, a server\n * error, or a lost connection. The default classifier reads the route's\n * `{ error: code }` bodies; an app with its own codes passes its own.\n */\n\nimport { APICallError } from 'ai'\nimport { isOneOf, safeJsonParse } from 'hanzio'\n\nimport { type ChatErrorResponse, ChatConnectionLostError } from './fetch.js'\nimport type { ChatRefusal } from './turn.js'\n\n// TYPES\n\n/** Reads a response that is not OK; undefined when it is no refusal of the chat route. */\nexport type ClassifyChatError = (\n\tresponse: ChatErrorResponse\n) => ChatRefusal | undefined\n\n// CONSTANTS\n\n/** Refusals worth trying again: the cause passes (a turn ends, a limit resets, consent is given). */\nconst RETRYABLE_CODES = [\n\t'turn_in_progress',\n\t'temporarily_unavailable',\n\t'usage_limited',\n\t'consent_required'\n]\n\n/** The refusal of a request that answers an approval or suspended run the server no longer has waiting. */\nconst EXPIRED_CODE = 'change_expired'\n\n// FUNCTIONS\n\n/**\n * A `{ error: code }` body is a refusal. Try again is offered for the codes\n * above, and for an unknown code on 409, 429 or 5xx.\n */\nexport const classifyChatError: ClassifyChatError = ({ status, body }) => {\n\tconst code = (body as { error?: unknown } | null | undefined)?.error\n\n\tif (typeof code !== 'string') {\n\t\treturn undefined\n\t}\n\n\treturn {\n\t\tcode,\n\t\tcanTryAgain:\n\t\t\tisOneOf(RETRYABLE_CODES, code) ||\n\t\t\t(code !== EXPIRED_CODE &&\n\t\t\t\t(status === 409 || status === 429 || status >= 500)),\n\t\texpired: code === EXPIRED_CODE\n\t}\n}\n\n/** The status and JSON body of a request the AI SDK failed on a response that was not OK. */\nexport function readErrorResponse(\n\terror: unknown\n): ChatErrorResponse | undefined {\n\tif (!APICallError.isInstance(error) || error.statusCode === undefined) {\n\t\treturn undefined\n\t}\n\n\tconst parsed =\n\t\terror.responseBody === undefined\n\t\t\t? undefined\n\t\t\t: safeJsonParse(error.responseBody)\n\tconst body = parsed?.ok ? parsed.data : undefined\n\n\treturn { status: error.statusCode, body }\n}\n\n/** Whether a request failed on the network rather than the server. */\nexport function isConnectionLost(\n\terror: unknown,\n\tend: { readonly isDisconnect: boolean } | undefined\n): boolean {\n\treturn end?.isDisconnect === true || error instanceof ChatConnectionLostError\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/client/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,0FAA0F;AAC1F,MAAM,MAAM,SAAS,GAAG,CACvB,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAC7B,IAAI,CAAC,EAAE,WAAW,KACd,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEtB,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAChB,WAAW,GACX,SAAS,GACT,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAA;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAChB,8EAA8E;QAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;QACjC,mGAAmG;QACnG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;KAClC,CAAA;IACD,iEAAiE;IACjE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAChE,8CAA8C;IAC9C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IACpC,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C;AAKD,2CAA2C;AAC3C,MAAM,MAAM,WAAW,GAAG,qBAAqB,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AAIlE,oEAAoE;AACpE,eAAO,MAAM,2BAA2B,MAAM,CAAA;AAE9C,qGAAqG;AACrG,eAAO,MAAM,4BAA4B,OAAQ,CAAA;AAyBjD;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,SAAS;gBACzC,KAAK,EAAE,OAAO;CAI1B;AAID,gDAAgD;AAChD,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAoEzE;AAED,oHAAoH;AACpH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAY/D"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/client/fetch.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAIH,0FAA0F;AAC1F,MAAM,MAAM,SAAS,GAAG,CACvB,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAC7B,IAAI,CAAC,EAAE,WAAW,KACd,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEtB,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAClB,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAA;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE;QAChB,8EAA8E;QAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;QACjC,mGAAmG;QACnG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;KAClC,CAAA;IACD,iEAAiE;IACjE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAChE,8CAA8C;IAC9C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IACpC,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C;AAKD,2CAA2C;AAC3C,MAAM,MAAM,WAAW,GAAG,qBAAqB,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AAIlE,oEAAoE;AACpE,eAAO,MAAM,2BAA2B,MAAM,CAAA;AAE9C,qGAAqG;AACrG,eAAO,MAAM,4BAA4B,OAAQ,CAAA;AAyBjD;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,SAAS;gBACzC,KAAK,EAAE,OAAO;CAI1B;AAID,gDAAgD;AAChD,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAqEzE;AAED,oHAAoH;AACpH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAY/D"}