ai 6.0.33 → 6.0.34

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 (351) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.mjs +1 -1
  4. package/dist/internal/index.js +1 -1
  5. package/dist/internal/index.mjs +1 -1
  6. package/docs/02-foundations/03-prompts.mdx +2 -2
  7. package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +1 -1
  8. package/docs/07-reference/01-ai-sdk-core/28-output.mdx +1 -1
  9. package/package.json +6 -4
  10. package/src/agent/agent.ts +116 -0
  11. package/src/agent/create-agent-ui-stream-response.test.ts +258 -0
  12. package/src/agent/create-agent-ui-stream-response.ts +50 -0
  13. package/src/agent/create-agent-ui-stream.ts +73 -0
  14. package/src/agent/index.ts +33 -0
  15. package/src/agent/infer-agent-tools.ts +7 -0
  16. package/src/agent/infer-agent-ui-message.test-d.ts +54 -0
  17. package/src/agent/infer-agent-ui-message.ts +11 -0
  18. package/src/agent/pipe-agent-ui-stream-to-response.ts +52 -0
  19. package/src/agent/tool-loop-agent-on-finish-callback.ts +31 -0
  20. package/src/agent/tool-loop-agent-on-step-finish-callback.ts +11 -0
  21. package/src/agent/tool-loop-agent-settings.ts +182 -0
  22. package/src/agent/tool-loop-agent.test-d.ts +114 -0
  23. package/src/agent/tool-loop-agent.test.ts +442 -0
  24. package/src/agent/tool-loop-agent.ts +114 -0
  25. package/src/embed/__snapshots__/embed-many.test.ts.snap +191 -0
  26. package/src/embed/__snapshots__/embed.test.ts.snap +81 -0
  27. package/src/embed/embed-many-result.ts +53 -0
  28. package/src/embed/embed-many.test.ts +653 -0
  29. package/src/embed/embed-many.ts +378 -0
  30. package/src/embed/embed-result.ts +50 -0
  31. package/src/embed/embed.test.ts +298 -0
  32. package/src/embed/embed.ts +211 -0
  33. package/src/embed/index.ts +4 -0
  34. package/src/error/index.ts +34 -0
  35. package/src/error/invalid-argument-error.ts +34 -0
  36. package/src/error/invalid-stream-part-error.ts +28 -0
  37. package/src/error/invalid-tool-approval-error.ts +26 -0
  38. package/src/error/invalid-tool-input-error.ts +33 -0
  39. package/src/error/no-image-generated-error.ts +39 -0
  40. package/src/error/no-object-generated-error.ts +70 -0
  41. package/src/error/no-output-generated-error.ts +26 -0
  42. package/src/error/no-speech-generated-error.ts +18 -0
  43. package/src/error/no-such-tool-error.ts +35 -0
  44. package/src/error/no-transcript-generated-error.ts +20 -0
  45. package/src/error/tool-call-not-found-for-approval-error.ts +32 -0
  46. package/src/error/tool-call-repair-error.ts +30 -0
  47. package/src/error/unsupported-model-version-error.ts +23 -0
  48. package/src/error/verify-no-object-generated-error.ts +27 -0
  49. package/src/generate-image/generate-image-result.ts +42 -0
  50. package/src/generate-image/generate-image.test.ts +1420 -0
  51. package/src/generate-image/generate-image.ts +360 -0
  52. package/src/generate-image/index.ts +18 -0
  53. package/src/generate-object/__snapshots__/generate-object.test.ts.snap +133 -0
  54. package/src/generate-object/__snapshots__/stream-object.test.ts.snap +297 -0
  55. package/src/generate-object/generate-object-result.ts +67 -0
  56. package/src/generate-object/generate-object.test-d.ts +49 -0
  57. package/src/generate-object/generate-object.test.ts +1191 -0
  58. package/src/generate-object/generate-object.ts +518 -0
  59. package/src/generate-object/index.ts +9 -0
  60. package/src/generate-object/inject-json-instruction.test.ts +181 -0
  61. package/src/generate-object/inject-json-instruction.ts +30 -0
  62. package/src/generate-object/output-strategy.ts +415 -0
  63. package/src/generate-object/parse-and-validate-object-result.ts +111 -0
  64. package/src/generate-object/repair-text.ts +12 -0
  65. package/src/generate-object/stream-object-result.ts +120 -0
  66. package/src/generate-object/stream-object.test-d.ts +74 -0
  67. package/src/generate-object/stream-object.test.ts +1950 -0
  68. package/src/generate-object/stream-object.ts +986 -0
  69. package/src/generate-object/validate-object-generation-input.ts +144 -0
  70. package/src/generate-speech/generate-speech-result.ts +30 -0
  71. package/src/generate-speech/generate-speech.test.ts +300 -0
  72. package/src/generate-speech/generate-speech.ts +190 -0
  73. package/src/generate-speech/generated-audio-file.ts +65 -0
  74. package/src/generate-speech/index.ts +3 -0
  75. package/src/generate-text/__snapshots__/generate-text.test.ts.snap +1872 -0
  76. package/src/generate-text/__snapshots__/stream-text.test.ts.snap +1255 -0
  77. package/src/generate-text/collect-tool-approvals.test.ts +553 -0
  78. package/src/generate-text/collect-tool-approvals.ts +116 -0
  79. package/src/generate-text/content-part.ts +25 -0
  80. package/src/generate-text/execute-tool-call.ts +129 -0
  81. package/src/generate-text/extract-reasoning-content.ts +17 -0
  82. package/src/generate-text/extract-text-content.ts +15 -0
  83. package/src/generate-text/generate-text-result.ts +168 -0
  84. package/src/generate-text/generate-text.test-d.ts +68 -0
  85. package/src/generate-text/generate-text.test.ts +7011 -0
  86. package/src/generate-text/generate-text.ts +1223 -0
  87. package/src/generate-text/generated-file.ts +70 -0
  88. package/src/generate-text/index.ts +57 -0
  89. package/src/generate-text/is-approval-needed.ts +29 -0
  90. package/src/generate-text/output-utils.ts +23 -0
  91. package/src/generate-text/output.test.ts +698 -0
  92. package/src/generate-text/output.ts +590 -0
  93. package/src/generate-text/parse-tool-call.test.ts +570 -0
  94. package/src/generate-text/parse-tool-call.ts +188 -0
  95. package/src/generate-text/prepare-step.ts +103 -0
  96. package/src/generate-text/prune-messages.test.ts +720 -0
  97. package/src/generate-text/prune-messages.ts +167 -0
  98. package/src/generate-text/reasoning-output.ts +20 -0
  99. package/src/generate-text/reasoning.ts +8 -0
  100. package/src/generate-text/response-message.ts +10 -0
  101. package/src/generate-text/run-tools-transformation.test.ts +1143 -0
  102. package/src/generate-text/run-tools-transformation.ts +420 -0
  103. package/src/generate-text/smooth-stream.test.ts +2101 -0
  104. package/src/generate-text/smooth-stream.ts +162 -0
  105. package/src/generate-text/step-result.ts +238 -0
  106. package/src/generate-text/stop-condition.ts +29 -0
  107. package/src/generate-text/stream-text-result.ts +463 -0
  108. package/src/generate-text/stream-text.test-d.ts +200 -0
  109. package/src/generate-text/stream-text.test.ts +19979 -0
  110. package/src/generate-text/stream-text.ts +2505 -0
  111. package/src/generate-text/to-response-messages.test.ts +922 -0
  112. package/src/generate-text/to-response-messages.ts +163 -0
  113. package/src/generate-text/tool-approval-request-output.ts +21 -0
  114. package/src/generate-text/tool-call-repair-function.ts +27 -0
  115. package/src/generate-text/tool-call.ts +47 -0
  116. package/src/generate-text/tool-error.ts +34 -0
  117. package/src/generate-text/tool-output-denied.ts +21 -0
  118. package/src/generate-text/tool-output.ts +7 -0
  119. package/src/generate-text/tool-result.ts +36 -0
  120. package/src/generate-text/tool-set.ts +14 -0
  121. package/src/global.ts +24 -0
  122. package/src/index.ts +50 -0
  123. package/src/logger/index.ts +6 -0
  124. package/src/logger/log-warnings.test.ts +351 -0
  125. package/src/logger/log-warnings.ts +119 -0
  126. package/src/middleware/__snapshots__/simulate-streaming-middleware.test.ts.snap +64 -0
  127. package/src/middleware/add-tool-input-examples-middleware.test.ts +476 -0
  128. package/src/middleware/add-tool-input-examples-middleware.ts +90 -0
  129. package/src/middleware/default-embedding-settings-middleware.test.ts +126 -0
  130. package/src/middleware/default-embedding-settings-middleware.ts +22 -0
  131. package/src/middleware/default-settings-middleware.test.ts +388 -0
  132. package/src/middleware/default-settings-middleware.ts +33 -0
  133. package/src/middleware/extract-json-middleware.test.ts +827 -0
  134. package/src/middleware/extract-json-middleware.ts +197 -0
  135. package/src/middleware/extract-reasoning-middleware.test.ts +1028 -0
  136. package/src/middleware/extract-reasoning-middleware.ts +238 -0
  137. package/src/middleware/index.ts +10 -0
  138. package/src/middleware/simulate-streaming-middleware.test.ts +911 -0
  139. package/src/middleware/simulate-streaming-middleware.ts +79 -0
  140. package/src/middleware/wrap-embedding-model.test.ts +358 -0
  141. package/src/middleware/wrap-embedding-model.ts +86 -0
  142. package/src/middleware/wrap-image-model.test.ts +423 -0
  143. package/src/middleware/wrap-image-model.ts +85 -0
  144. package/src/middleware/wrap-language-model.test.ts +518 -0
  145. package/src/middleware/wrap-language-model.ts +104 -0
  146. package/src/middleware/wrap-provider.test.ts +120 -0
  147. package/src/middleware/wrap-provider.ts +51 -0
  148. package/src/model/as-embedding-model-v3.test.ts +319 -0
  149. package/src/model/as-embedding-model-v3.ts +24 -0
  150. package/src/model/as-image-model-v3.test.ts +409 -0
  151. package/src/model/as-image-model-v3.ts +24 -0
  152. package/src/model/as-language-model-v3.test.ts +508 -0
  153. package/src/model/as-language-model-v3.ts +103 -0
  154. package/src/model/as-provider-v3.ts +36 -0
  155. package/src/model/as-speech-model-v3.test.ts +356 -0
  156. package/src/model/as-speech-model-v3.ts +24 -0
  157. package/src/model/as-transcription-model-v3.test.ts +529 -0
  158. package/src/model/as-transcription-model-v3.ts +24 -0
  159. package/src/model/resolve-model.test.ts +244 -0
  160. package/src/model/resolve-model.ts +126 -0
  161. package/src/prompt/call-settings.ts +148 -0
  162. package/src/prompt/content-part.ts +209 -0
  163. package/src/prompt/convert-to-language-model-prompt.test.ts +2018 -0
  164. package/src/prompt/convert-to-language-model-prompt.ts +442 -0
  165. package/src/prompt/create-tool-model-output.test.ts +508 -0
  166. package/src/prompt/create-tool-model-output.ts +34 -0
  167. package/src/prompt/data-content.test.ts +15 -0
  168. package/src/prompt/data-content.ts +134 -0
  169. package/src/prompt/index.ts +27 -0
  170. package/src/prompt/invalid-data-content-error.ts +29 -0
  171. package/src/prompt/invalid-message-role-error.ts +27 -0
  172. package/src/prompt/message-conversion-error.ts +28 -0
  173. package/src/prompt/message.ts +68 -0
  174. package/src/prompt/prepare-call-settings.test.ts +159 -0
  175. package/src/prompt/prepare-call-settings.ts +108 -0
  176. package/src/prompt/prepare-tools-and-tool-choice.test.ts +461 -0
  177. package/src/prompt/prepare-tools-and-tool-choice.ts +86 -0
  178. package/src/prompt/prompt.ts +43 -0
  179. package/src/prompt/split-data-url.ts +17 -0
  180. package/src/prompt/standardize-prompt.test.ts +82 -0
  181. package/src/prompt/standardize-prompt.ts +99 -0
  182. package/src/prompt/wrap-gateway-error.ts +29 -0
  183. package/src/registry/custom-provider.test.ts +211 -0
  184. package/src/registry/custom-provider.ts +155 -0
  185. package/src/registry/index.ts +7 -0
  186. package/src/registry/no-such-provider-error.ts +41 -0
  187. package/src/registry/provider-registry.test.ts +691 -0
  188. package/src/registry/provider-registry.ts +328 -0
  189. package/src/rerank/index.ts +2 -0
  190. package/src/rerank/rerank-result.ts +70 -0
  191. package/src/rerank/rerank.test.ts +516 -0
  192. package/src/rerank/rerank.ts +237 -0
  193. package/src/telemetry/assemble-operation-name.ts +21 -0
  194. package/src/telemetry/get-base-telemetry-attributes.ts +53 -0
  195. package/src/telemetry/get-tracer.ts +20 -0
  196. package/src/telemetry/noop-tracer.ts +69 -0
  197. package/src/telemetry/record-span.ts +63 -0
  198. package/src/telemetry/select-telemetry-attributes.ts +78 -0
  199. package/src/telemetry/select-temetry-attributes.test.ts +114 -0
  200. package/src/telemetry/stringify-for-telemetry.test.ts +114 -0
  201. package/src/telemetry/stringify-for-telemetry.ts +33 -0
  202. package/src/telemetry/telemetry-settings.ts +44 -0
  203. package/src/test/mock-embedding-model-v2.ts +35 -0
  204. package/src/test/mock-embedding-model-v3.ts +48 -0
  205. package/src/test/mock-image-model-v2.ts +28 -0
  206. package/src/test/mock-image-model-v3.ts +28 -0
  207. package/src/test/mock-language-model-v2.ts +72 -0
  208. package/src/test/mock-language-model-v3.ts +77 -0
  209. package/src/test/mock-provider-v2.ts +68 -0
  210. package/src/test/mock-provider-v3.ts +80 -0
  211. package/src/test/mock-reranking-model-v3.ts +25 -0
  212. package/src/test/mock-server-response.ts +69 -0
  213. package/src/test/mock-speech-model-v2.ts +24 -0
  214. package/src/test/mock-speech-model-v3.ts +24 -0
  215. package/src/test/mock-tracer.ts +156 -0
  216. package/src/test/mock-transcription-model-v2.ts +24 -0
  217. package/src/test/mock-transcription-model-v3.ts +24 -0
  218. package/src/test/mock-values.ts +4 -0
  219. package/src/test/not-implemented.ts +3 -0
  220. package/src/text-stream/create-text-stream-response.test.ts +38 -0
  221. package/src/text-stream/create-text-stream-response.ts +18 -0
  222. package/src/text-stream/index.ts +2 -0
  223. package/src/text-stream/pipe-text-stream-to-response.test.ts +38 -0
  224. package/src/text-stream/pipe-text-stream-to-response.ts +26 -0
  225. package/src/transcribe/index.ts +2 -0
  226. package/src/transcribe/transcribe-result.ts +60 -0
  227. package/src/transcribe/transcribe.test.ts +313 -0
  228. package/src/transcribe/transcribe.ts +173 -0
  229. package/src/types/embedding-model-middleware.ts +3 -0
  230. package/src/types/embedding-model.ts +18 -0
  231. package/src/types/image-model-middleware.ts +3 -0
  232. package/src/types/image-model-response-metadata.ts +16 -0
  233. package/src/types/image-model.ts +19 -0
  234. package/src/types/index.ts +29 -0
  235. package/src/types/json-value.ts +15 -0
  236. package/src/types/language-model-middleware.ts +3 -0
  237. package/src/types/language-model-request-metadata.ts +6 -0
  238. package/src/types/language-model-response-metadata.ts +21 -0
  239. package/src/types/language-model.ts +104 -0
  240. package/src/types/provider-metadata.ts +16 -0
  241. package/src/types/provider.ts +55 -0
  242. package/src/types/reranking-model.ts +6 -0
  243. package/src/types/speech-model-response-metadata.ts +21 -0
  244. package/src/types/speech-model.ts +6 -0
  245. package/src/types/transcription-model-response-metadata.ts +16 -0
  246. package/src/types/transcription-model.ts +9 -0
  247. package/src/types/usage.ts +200 -0
  248. package/src/types/warning.ts +7 -0
  249. package/src/ui/__snapshots__/append-response-messages.test.ts.snap +416 -0
  250. package/src/ui/__snapshots__/convert-to-model-messages.test.ts.snap +419 -0
  251. package/src/ui/__snapshots__/process-chat-text-response.test.ts.snap +142 -0
  252. package/src/ui/call-completion-api.ts +157 -0
  253. package/src/ui/chat-transport.ts +83 -0
  254. package/src/ui/chat.test-d.ts +233 -0
  255. package/src/ui/chat.test.ts +2695 -0
  256. package/src/ui/chat.ts +716 -0
  257. package/src/ui/convert-file-list-to-file-ui-parts.ts +36 -0
  258. package/src/ui/convert-to-model-messages.test.ts +2775 -0
  259. package/src/ui/convert-to-model-messages.ts +373 -0
  260. package/src/ui/default-chat-transport.ts +36 -0
  261. package/src/ui/direct-chat-transport.test.ts +446 -0
  262. package/src/ui/direct-chat-transport.ts +118 -0
  263. package/src/ui/http-chat-transport.test.ts +185 -0
  264. package/src/ui/http-chat-transport.ts +292 -0
  265. package/src/ui/index.ts +71 -0
  266. package/src/ui/last-assistant-message-is-complete-with-approval-responses.ts +44 -0
  267. package/src/ui/last-assistant-message-is-complete-with-tool-calls.test.ts +371 -0
  268. package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +39 -0
  269. package/src/ui/process-text-stream.test.ts +38 -0
  270. package/src/ui/process-text-stream.ts +16 -0
  271. package/src/ui/process-ui-message-stream.test.ts +8052 -0
  272. package/src/ui/process-ui-message-stream.ts +713 -0
  273. package/src/ui/text-stream-chat-transport.ts +23 -0
  274. package/src/ui/transform-text-to-ui-message-stream.test.ts +124 -0
  275. package/src/ui/transform-text-to-ui-message-stream.ts +27 -0
  276. package/src/ui/ui-messages.test.ts +48 -0
  277. package/src/ui/ui-messages.ts +534 -0
  278. package/src/ui/use-completion.ts +84 -0
  279. package/src/ui/validate-ui-messages.test.ts +1428 -0
  280. package/src/ui/validate-ui-messages.ts +476 -0
  281. package/src/ui-message-stream/create-ui-message-stream-response.test.ts +266 -0
  282. package/src/ui-message-stream/create-ui-message-stream-response.ts +32 -0
  283. package/src/ui-message-stream/create-ui-message-stream.test.ts +639 -0
  284. package/src/ui-message-stream/create-ui-message-stream.ts +124 -0
  285. package/src/ui-message-stream/get-response-ui-message-id.test.ts +55 -0
  286. package/src/ui-message-stream/get-response-ui-message-id.ts +24 -0
  287. package/src/ui-message-stream/handle-ui-message-stream-finish.test.ts +429 -0
  288. package/src/ui-message-stream/handle-ui-message-stream-finish.ts +135 -0
  289. package/src/ui-message-stream/index.ts +13 -0
  290. package/src/ui-message-stream/json-to-sse-transform-stream.ts +12 -0
  291. package/src/ui-message-stream/pipe-ui-message-stream-to-response.test.ts +90 -0
  292. package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +40 -0
  293. package/src/ui-message-stream/read-ui-message-stream.test.ts +122 -0
  294. package/src/ui-message-stream/read-ui-message-stream.ts +87 -0
  295. package/src/ui-message-stream/ui-message-chunks.test-d.ts +18 -0
  296. package/src/ui-message-stream/ui-message-chunks.ts +344 -0
  297. package/src/ui-message-stream/ui-message-stream-headers.ts +7 -0
  298. package/src/ui-message-stream/ui-message-stream-on-finish-callback.ts +32 -0
  299. package/src/ui-message-stream/ui-message-stream-response-init.ts +5 -0
  300. package/src/ui-message-stream/ui-message-stream-writer.ts +24 -0
  301. package/src/util/as-array.ts +3 -0
  302. package/src/util/async-iterable-stream.test.ts +241 -0
  303. package/src/util/async-iterable-stream.ts +94 -0
  304. package/src/util/consume-stream.ts +29 -0
  305. package/src/util/cosine-similarity.test.ts +57 -0
  306. package/src/util/cosine-similarity.ts +47 -0
  307. package/src/util/create-resolvable-promise.ts +30 -0
  308. package/src/util/create-stitchable-stream.test.ts +239 -0
  309. package/src/util/create-stitchable-stream.ts +112 -0
  310. package/src/util/data-url.ts +17 -0
  311. package/src/util/deep-partial.ts +84 -0
  312. package/src/util/detect-media-type.test.ts +670 -0
  313. package/src/util/detect-media-type.ts +184 -0
  314. package/src/util/download/download-function.ts +45 -0
  315. package/src/util/download/download.test.ts +69 -0
  316. package/src/util/download/download.ts +46 -0
  317. package/src/util/error-handler.ts +1 -0
  318. package/src/util/fix-json.test.ts +279 -0
  319. package/src/util/fix-json.ts +401 -0
  320. package/src/util/get-potential-start-index.test.ts +34 -0
  321. package/src/util/get-potential-start-index.ts +30 -0
  322. package/src/util/index.ts +11 -0
  323. package/src/util/is-deep-equal-data.test.ts +119 -0
  324. package/src/util/is-deep-equal-data.ts +48 -0
  325. package/src/util/is-non-empty-object.ts +5 -0
  326. package/src/util/job.ts +1 -0
  327. package/src/util/log-v2-compatibility-warning.ts +21 -0
  328. package/src/util/merge-abort-signals.test.ts +155 -0
  329. package/src/util/merge-abort-signals.ts +43 -0
  330. package/src/util/merge-objects.test.ts +118 -0
  331. package/src/util/merge-objects.ts +79 -0
  332. package/src/util/now.ts +4 -0
  333. package/src/util/parse-partial-json.test.ts +80 -0
  334. package/src/util/parse-partial-json.ts +30 -0
  335. package/src/util/prepare-headers.test.ts +51 -0
  336. package/src/util/prepare-headers.ts +14 -0
  337. package/src/util/prepare-retries.test.ts +10 -0
  338. package/src/util/prepare-retries.ts +47 -0
  339. package/src/util/retry-error.ts +41 -0
  340. package/src/util/retry-with-exponential-backoff.test.ts +446 -0
  341. package/src/util/retry-with-exponential-backoff.ts +154 -0
  342. package/src/util/serial-job-executor.test.ts +162 -0
  343. package/src/util/serial-job-executor.ts +36 -0
  344. package/src/util/simulate-readable-stream.test.ts +98 -0
  345. package/src/util/simulate-readable-stream.ts +39 -0
  346. package/src/util/split-array.test.ts +60 -0
  347. package/src/util/split-array.ts +20 -0
  348. package/src/util/value-of.ts +65 -0
  349. package/src/util/write-to-server-response.test.ts +266 -0
  350. package/src/util/write-to-server-response.ts +49 -0
  351. package/src/version.ts +5 -0
@@ -0,0 +1,1428 @@
1
+ import { z } from 'zod/v4';
2
+ import { InferUITool, UIMessage } from './ui-messages';
3
+ import {
4
+ safeValidateUIMessages,
5
+ validateUIMessages,
6
+ } from './validate-ui-messages';
7
+ import { describe, it, expect, expectTypeOf } from 'vitest';
8
+
9
+ describe('validateUIMessages', () => {
10
+ describe('parameter validation', () => {
11
+ it('should throw InvalidArgumentError when messages parameter is null', async () => {
12
+ await expect(
13
+ validateUIMessages({
14
+ messages: null,
15
+ }),
16
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
17
+ [AI_InvalidArgumentError: Invalid argument for parameter messages: messages parameter must be provided]
18
+ `);
19
+ });
20
+
21
+ it('should throw InvalidArgumentError when messages parameter is undefined', async () => {
22
+ await expect(
23
+ validateUIMessages({
24
+ messages: undefined,
25
+ }),
26
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
27
+ [AI_InvalidArgumentError: Invalid argument for parameter messages: messages parameter must be provided]
28
+ `);
29
+ });
30
+
31
+ it('should throw TypeValidationError when messages array is empty', async () => {
32
+ await expect(
33
+ validateUIMessages({
34
+ messages: [],
35
+ }),
36
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
37
+ [AI_TypeValidationError: Type validation failed: Value: [].
38
+ Error message: [
39
+ {
40
+ "origin": "array",
41
+ "code": "too_small",
42
+ "minimum": 1,
43
+ "inclusive": true,
44
+ "path": [],
45
+ "message": "Messages array must not be empty"
46
+ }
47
+ ]]
48
+ `);
49
+ });
50
+
51
+ it('should throw TypeValidationError when message has empty parts array', async () => {
52
+ await expect(
53
+ validateUIMessages({
54
+ messages: [
55
+ {
56
+ id: '1',
57
+ role: 'user',
58
+ parts: [],
59
+ },
60
+ ],
61
+ }),
62
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
63
+ [AI_TypeValidationError: Type validation failed: Value: [{"id":"1","role":"user","parts":[]}].
64
+ Error message: [
65
+ {
66
+ "origin": "array",
67
+ "code": "too_small",
68
+ "minimum": 1,
69
+ "inclusive": true,
70
+ "path": [
71
+ 0,
72
+ "parts"
73
+ ],
74
+ "message": "Message must contain at least one part"
75
+ }
76
+ ]]
77
+ `);
78
+ });
79
+ });
80
+
81
+ describe('metadata', () => {
82
+ it('should validate a user message with metadata when no metadata schema is provided', async () => {
83
+ type TestMessage = UIMessage<{ foo: string }>;
84
+
85
+ const messages = await validateUIMessages<TestMessage>({
86
+ messages: [
87
+ {
88
+ id: '1',
89
+ role: 'user',
90
+ metadata: {
91
+ foo: 'bar',
92
+ },
93
+ parts: [{ type: 'text', text: 'Hello, world!' }],
94
+ },
95
+ ],
96
+ });
97
+
98
+ expectTypeOf(messages).toEqualTypeOf<Array<TestMessage>>();
99
+
100
+ expect(messages).toMatchInlineSnapshot(`
101
+ [
102
+ {
103
+ "id": "1",
104
+ "metadata": {
105
+ "foo": "bar",
106
+ },
107
+ "parts": [
108
+ {
109
+ "text": "Hello, world!",
110
+ "type": "text",
111
+ },
112
+ ],
113
+ "role": "user",
114
+ },
115
+ ]
116
+ `);
117
+ });
118
+
119
+ it('should validate a user message with metadata', async () => {
120
+ type TestMessage = UIMessage<{ foo: string }>;
121
+
122
+ const messages = await validateUIMessages<TestMessage>({
123
+ messages: [
124
+ {
125
+ id: '1',
126
+ role: 'user',
127
+ metadata: {
128
+ foo: 'bar',
129
+ },
130
+ parts: [{ type: 'text', text: 'Hello, world!' }],
131
+ },
132
+ ],
133
+ metadataSchema: z.object({
134
+ foo: z.string(),
135
+ }),
136
+ });
137
+
138
+ expectTypeOf(messages).toEqualTypeOf<Array<TestMessage>>();
139
+
140
+ expect(messages).toMatchInlineSnapshot(`
141
+ [
142
+ {
143
+ "id": "1",
144
+ "metadata": {
145
+ "foo": "bar",
146
+ },
147
+ "parts": [
148
+ {
149
+ "text": "Hello, world!",
150
+ "type": "text",
151
+ },
152
+ ],
153
+ "role": "user",
154
+ },
155
+ ]
156
+ `);
157
+ });
158
+
159
+ it('should throw type validation error when metadata is invalid ', async () => {
160
+ await expect(
161
+ validateUIMessages<UIMessage<{ foo: string }>>({
162
+ messages: [
163
+ {
164
+ id: '1',
165
+ role: 'user',
166
+ metadata: { foo: 123 },
167
+ parts: [{ type: 'text', text: 'Hello, world!' }],
168
+ },
169
+ ],
170
+ metadataSchema: z.object({
171
+ foo: z.string(),
172
+ }),
173
+ }),
174
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
175
+ [AI_TypeValidationError: Type validation failed: Value: {"foo":123}.
176
+ Error message: [
177
+ {
178
+ "expected": "string",
179
+ "code": "invalid_type",
180
+ "path": [
181
+ "foo"
182
+ ],
183
+ "message": "Invalid input: expected string, received number"
184
+ }
185
+ ]]
186
+ `);
187
+ });
188
+
189
+ it('should validate text part with provider metadata', async () => {
190
+ const messages = await validateUIMessages({
191
+ messages: [
192
+ {
193
+ id: '1',
194
+ role: 'user',
195
+ parts: [
196
+ {
197
+ type: 'text',
198
+ text: 'Hello, world!',
199
+ providerMetadata: {
200
+ someProvider: {
201
+ custom: 'metadata',
202
+ },
203
+ },
204
+ },
205
+ ],
206
+ },
207
+ ],
208
+ });
209
+
210
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
211
+
212
+ expect(messages).toMatchInlineSnapshot(`
213
+ [
214
+ {
215
+ "id": "1",
216
+ "parts": [
217
+ {
218
+ "providerMetadata": {
219
+ "someProvider": {
220
+ "custom": "metadata",
221
+ },
222
+ },
223
+ "text": "Hello, world!",
224
+ "type": "text",
225
+ },
226
+ ],
227
+ "role": "user",
228
+ },
229
+ ]
230
+ `);
231
+ });
232
+ });
233
+
234
+ describe('text parts', () => {
235
+ it('should validate a user message with a text part', async () => {
236
+ const messages = await validateUIMessages({
237
+ messages: [
238
+ {
239
+ id: '1',
240
+ role: 'user',
241
+ parts: [{ type: 'text', text: 'Hello, world!' }],
242
+ },
243
+ ],
244
+ });
245
+
246
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
247
+
248
+ expect(messages).toMatchInlineSnapshot(`
249
+ [
250
+ {
251
+ "id": "1",
252
+ "parts": [
253
+ {
254
+ "text": "Hello, world!",
255
+ "type": "text",
256
+ },
257
+ ],
258
+ "role": "user",
259
+ },
260
+ ]
261
+ `);
262
+ });
263
+ });
264
+
265
+ describe('reasoning parts', () => {
266
+ it('should validate an assistant message with a reasoning part', async () => {
267
+ const messages = await validateUIMessages({
268
+ messages: [
269
+ {
270
+ id: '1',
271
+ role: 'assistant',
272
+ parts: [{ type: 'reasoning', text: 'Hello, world!' }],
273
+ },
274
+ ],
275
+ });
276
+
277
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
278
+
279
+ expect(messages).toMatchInlineSnapshot(`
280
+ [
281
+ {
282
+ "id": "1",
283
+ "parts": [
284
+ {
285
+ "text": "Hello, world!",
286
+ "type": "reasoning",
287
+ },
288
+ ],
289
+ "role": "assistant",
290
+ },
291
+ ]
292
+ `);
293
+ });
294
+ });
295
+
296
+ describe('source url parts', () => {
297
+ it('should validate an assistant message with a source url part', async () => {
298
+ const messages = await validateUIMessages({
299
+ messages: [
300
+ {
301
+ id: '1',
302
+ role: 'assistant',
303
+ parts: [
304
+ {
305
+ type: 'source-url',
306
+ sourceId: '1',
307
+ url: 'https://example.com',
308
+ title: 'Example',
309
+ },
310
+ ],
311
+ },
312
+ ],
313
+ });
314
+
315
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
316
+
317
+ expect(messages).toMatchInlineSnapshot(`
318
+ [
319
+ {
320
+ "id": "1",
321
+ "parts": [
322
+ {
323
+ "sourceId": "1",
324
+ "title": "Example",
325
+ "type": "source-url",
326
+ "url": "https://example.com",
327
+ },
328
+ ],
329
+ "role": "assistant",
330
+ },
331
+ ]
332
+ `);
333
+ });
334
+ });
335
+
336
+ describe('source document parts', () => {
337
+ it('should validate an assistant message with a source document part', async () => {
338
+ const messages = await validateUIMessages({
339
+ messages: [
340
+ {
341
+ id: '1',
342
+ role: 'assistant',
343
+ parts: [
344
+ {
345
+ type: 'source-document',
346
+ sourceId: '1',
347
+ mediaType: 'text/plain',
348
+ title: 'Example',
349
+ filename: 'example.txt',
350
+ },
351
+ ],
352
+ },
353
+ ],
354
+ });
355
+
356
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
357
+
358
+ expect(messages).toMatchInlineSnapshot(`
359
+ [
360
+ {
361
+ "id": "1",
362
+ "parts": [
363
+ {
364
+ "filename": "example.txt",
365
+ "mediaType": "text/plain",
366
+ "sourceId": "1",
367
+ "title": "Example",
368
+ "type": "source-document",
369
+ },
370
+ ],
371
+ "role": "assistant",
372
+ },
373
+ ]
374
+ `);
375
+ });
376
+ });
377
+
378
+ describe('file parts', () => {
379
+ it('should validate an assistant message with a file part', async () => {
380
+ const messages = await validateUIMessages({
381
+ messages: [
382
+ {
383
+ id: '1',
384
+ role: 'assistant',
385
+ parts: [
386
+ {
387
+ type: 'file',
388
+ mediaType: 'text/plain',
389
+ url: 'https://example.com',
390
+ },
391
+ ],
392
+ },
393
+ ],
394
+ });
395
+
396
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
397
+
398
+ expect(messages).toMatchInlineSnapshot(`
399
+ [
400
+ {
401
+ "id": "1",
402
+ "parts": [
403
+ {
404
+ "mediaType": "text/plain",
405
+ "type": "file",
406
+ "url": "https://example.com",
407
+ },
408
+ ],
409
+ "role": "assistant",
410
+ },
411
+ ]
412
+ `);
413
+ });
414
+ });
415
+
416
+ describe('step start parts', () => {
417
+ it('should validate an assistant message with a step start part', async () => {
418
+ const messages = await validateUIMessages({
419
+ messages: [
420
+ {
421
+ id: '1',
422
+ role: 'assistant',
423
+ parts: [{ type: 'step-start' }],
424
+ },
425
+ ],
426
+ });
427
+
428
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
429
+
430
+ expect(messages).toMatchInlineSnapshot(`
431
+ [
432
+ {
433
+ "id": "1",
434
+ "parts": [
435
+ {
436
+ "type": "step-start",
437
+ },
438
+ ],
439
+ "role": "assistant",
440
+ },
441
+ ]
442
+ `);
443
+ });
444
+ });
445
+
446
+ describe('data parts', () => {
447
+ it('should validate an assistant message with two data parts', async () => {
448
+ type TestMessage = UIMessage<
449
+ never,
450
+ {
451
+ foo: { foo: string };
452
+ bar: { bar: number };
453
+ },
454
+ never
455
+ >;
456
+
457
+ const messages = await validateUIMessages<TestMessage>({
458
+ messages: [
459
+ {
460
+ id: '1',
461
+ role: 'assistant',
462
+ parts: [
463
+ { type: 'data-foo', data: { foo: 'bar' } },
464
+ { type: 'data-bar', data: { bar: 123 } },
465
+ ],
466
+ },
467
+ ],
468
+ dataSchemas: {
469
+ foo: z.object({ foo: z.string() }),
470
+ bar: z.object({ bar: z.number() }),
471
+ },
472
+ });
473
+
474
+ expectTypeOf(messages).toEqualTypeOf<Array<TestMessage>>();
475
+
476
+ expect(messages).toMatchInlineSnapshot(`
477
+ [
478
+ {
479
+ "id": "1",
480
+ "parts": [
481
+ {
482
+ "data": {
483
+ "foo": "bar",
484
+ },
485
+ "type": "data-foo",
486
+ },
487
+ {
488
+ "data": {
489
+ "bar": 123,
490
+ },
491
+ "type": "data-bar",
492
+ },
493
+ ],
494
+ "role": "assistant",
495
+ },
496
+ ]
497
+ `);
498
+ });
499
+
500
+ it('should throw type validation error when data is invalid', async () => {
501
+ await expect(
502
+ validateUIMessages<UIMessage<never, { foo: { foo: string } }>>({
503
+ messages: [
504
+ {
505
+ id: '1',
506
+ role: 'assistant',
507
+ parts: [{ type: 'data-foo', data: { foo: 123 } }],
508
+ },
509
+ ],
510
+ dataSchemas: {
511
+ foo: z.object({ foo: z.string() }),
512
+ },
513
+ }),
514
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
515
+ [AI_TypeValidationError: Type validation failed: Value: {"foo":123}.
516
+ Error message: [
517
+ {
518
+ "expected": "string",
519
+ "code": "invalid_type",
520
+ "path": [
521
+ "foo"
522
+ ],
523
+ "message": "Invalid input: expected string, received number"
524
+ }
525
+ ]]
526
+ `);
527
+ });
528
+
529
+ it('should throw type validation error when there is no data schema for a data part', async () => {
530
+ await expect(
531
+ validateUIMessages<UIMessage<never, { foo: { foo: string } }>>({
532
+ messages: [
533
+ {
534
+ id: '1',
535
+ role: 'assistant',
536
+ parts: [{ type: 'data-bar', data: { foo: 'bar' } }],
537
+ },
538
+ ],
539
+ dataSchemas: {
540
+ foo: z.object({ foo: z.string() }),
541
+ },
542
+ }),
543
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
544
+ [AI_TypeValidationError: Type validation failed: Value: {"foo":"bar"}.
545
+ Error message: No data schema found for data part bar]
546
+ `);
547
+ });
548
+ });
549
+
550
+ describe('dynamic tool parts', () => {
551
+ it('should validate an assistant message with a dynamic tool part in input-streaming state', async () => {
552
+ const messages = await validateUIMessages({
553
+ messages: [
554
+ {
555
+ id: '1',
556
+ role: 'assistant',
557
+ parts: [
558
+ {
559
+ type: 'dynamic-tool',
560
+ toolName: 'foo',
561
+ toolCallId: '1',
562
+ state: 'input-streaming',
563
+ input: { foo: 'bar' },
564
+ },
565
+ ],
566
+ },
567
+ ],
568
+ });
569
+
570
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
571
+
572
+ expect(messages).toMatchInlineSnapshot(`
573
+ [
574
+ {
575
+ "id": "1",
576
+ "parts": [
577
+ {
578
+ "input": {
579
+ "foo": "bar",
580
+ },
581
+ "state": "input-streaming",
582
+ "toolCallId": "1",
583
+ "toolName": "foo",
584
+ "type": "dynamic-tool",
585
+ },
586
+ ],
587
+ "role": "assistant",
588
+ },
589
+ ]
590
+ `);
591
+ });
592
+
593
+ it('should validate an assistant message with a dynamic tool part in input-available state', async () => {
594
+ const messages = await validateUIMessages({
595
+ messages: [
596
+ {
597
+ id: '1',
598
+ role: 'assistant',
599
+ parts: [
600
+ {
601
+ type: 'dynamic-tool',
602
+ toolName: 'foo',
603
+ toolCallId: '1',
604
+ state: 'input-available',
605
+ input: { foo: 'bar' },
606
+ },
607
+ ],
608
+ },
609
+ ],
610
+ });
611
+
612
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
613
+
614
+ expect(messages).toMatchInlineSnapshot(`
615
+ [
616
+ {
617
+ "id": "1",
618
+ "parts": [
619
+ {
620
+ "input": {
621
+ "foo": "bar",
622
+ },
623
+ "state": "input-available",
624
+ "toolCallId": "1",
625
+ "toolName": "foo",
626
+ "type": "dynamic-tool",
627
+ },
628
+ ],
629
+ "role": "assistant",
630
+ },
631
+ ]
632
+ `);
633
+ });
634
+
635
+ it('should validate an assistant message with a dynamic tool part in output-available state', async () => {
636
+ const messages = await validateUIMessages({
637
+ messages: [
638
+ {
639
+ id: '1',
640
+ role: 'assistant',
641
+ parts: [
642
+ {
643
+ type: 'dynamic-tool',
644
+ toolName: 'foo',
645
+ toolCallId: '1',
646
+ state: 'output-available',
647
+ input: { foo: 'bar' },
648
+ output: { result: 'success' },
649
+ },
650
+ ],
651
+ },
652
+ ],
653
+ });
654
+
655
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
656
+
657
+ expect(messages).toMatchInlineSnapshot(`
658
+ [
659
+ {
660
+ "id": "1",
661
+ "parts": [
662
+ {
663
+ "input": {
664
+ "foo": "bar",
665
+ },
666
+ "output": {
667
+ "result": "success",
668
+ },
669
+ "state": "output-available",
670
+ "toolCallId": "1",
671
+ "toolName": "foo",
672
+ "type": "dynamic-tool",
673
+ },
674
+ ],
675
+ "role": "assistant",
676
+ },
677
+ ]
678
+ `);
679
+ });
680
+
681
+ it('should validate an assistant message with a dynamic tool part in output-error state', async () => {
682
+ const messages = await validateUIMessages({
683
+ messages: [
684
+ {
685
+ id: '1',
686
+ role: 'assistant',
687
+ parts: [
688
+ {
689
+ type: 'dynamic-tool',
690
+ toolName: 'foo',
691
+ toolCallId: '1',
692
+ state: 'output-error',
693
+ input: { foo: 'bar' },
694
+ errorText: 'Tool execution failed',
695
+ },
696
+ ],
697
+ },
698
+ ],
699
+ });
700
+
701
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
702
+
703
+ expect(messages).toMatchInlineSnapshot(`
704
+ [
705
+ {
706
+ "id": "1",
707
+ "parts": [
708
+ {
709
+ "errorText": "Tool execution failed",
710
+ "input": {
711
+ "foo": "bar",
712
+ },
713
+ "state": "output-error",
714
+ "toolCallId": "1",
715
+ "toolName": "foo",
716
+ "type": "dynamic-tool",
717
+ },
718
+ ],
719
+ "role": "assistant",
720
+ },
721
+ ]
722
+ `);
723
+ });
724
+ });
725
+
726
+ describe('tool parts', () => {
727
+ const testTool = {
728
+ name: 'foo',
729
+ inputSchema: z.object({ foo: z.string() }),
730
+ outputSchema: z.object({ result: z.string() }),
731
+ };
732
+
733
+ type TestMessage = UIMessage<
734
+ never,
735
+ never,
736
+ { foo: InferUITool<typeof testTool> }
737
+ >;
738
+
739
+ it('should validate an assistant message with a tool part in input-streaming state', async () => {
740
+ const messages = await validateUIMessages({
741
+ messages: [
742
+ {
743
+ id: '1',
744
+ role: 'assistant',
745
+ parts: [
746
+ {
747
+ type: 'tool-foo',
748
+ toolCallId: '1',
749
+ state: 'input-streaming',
750
+ input: { foo: 'bar' },
751
+ providerExecuted: true,
752
+ },
753
+ ],
754
+ },
755
+ ],
756
+ });
757
+
758
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
759
+
760
+ expect(messages).toMatchInlineSnapshot(`
761
+ [
762
+ {
763
+ "id": "1",
764
+ "parts": [
765
+ {
766
+ "input": {
767
+ "foo": "bar",
768
+ },
769
+ "providerExecuted": true,
770
+ "state": "input-streaming",
771
+ "toolCallId": "1",
772
+ "type": "tool-foo",
773
+ },
774
+ ],
775
+ "role": "assistant",
776
+ },
777
+ ]
778
+ `);
779
+ });
780
+
781
+ it('should validate an assistant message with a tool part in input-available state', async () => {
782
+ const messages = await validateUIMessages({
783
+ messages: [
784
+ {
785
+ id: '1',
786
+ role: 'assistant',
787
+ parts: [
788
+ {
789
+ type: 'tool-foo',
790
+ toolCallId: '1',
791
+ state: 'input-available',
792
+ input: { foo: 'bar' },
793
+ providerExecuted: true,
794
+ },
795
+ ],
796
+ },
797
+ ],
798
+ });
799
+
800
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
801
+
802
+ expect(messages).toMatchInlineSnapshot(`
803
+ [
804
+ {
805
+ "id": "1",
806
+ "parts": [
807
+ {
808
+ "input": {
809
+ "foo": "bar",
810
+ },
811
+ "providerExecuted": true,
812
+ "state": "input-available",
813
+ "toolCallId": "1",
814
+ "type": "tool-foo",
815
+ },
816
+ ],
817
+ "role": "assistant",
818
+ },
819
+ ]
820
+ `);
821
+ });
822
+
823
+ it('should validate an assistant message with a tool part in output-available state', async () => {
824
+ const messages = await validateUIMessages({
825
+ messages: [
826
+ {
827
+ id: '1',
828
+ role: 'assistant',
829
+ parts: [
830
+ {
831
+ type: 'tool-foo',
832
+ toolCallId: '1',
833
+ state: 'output-available',
834
+ input: { foo: 'bar' },
835
+ output: { result: 'success' },
836
+ providerExecuted: true,
837
+ },
838
+ ],
839
+ },
840
+ ],
841
+ });
842
+
843
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
844
+
845
+ expect(messages).toMatchInlineSnapshot(`
846
+ [
847
+ {
848
+ "id": "1",
849
+ "parts": [
850
+ {
851
+ "input": {
852
+ "foo": "bar",
853
+ },
854
+ "output": {
855
+ "result": "success",
856
+ },
857
+ "providerExecuted": true,
858
+ "state": "output-available",
859
+ "toolCallId": "1",
860
+ "type": "tool-foo",
861
+ },
862
+ ],
863
+ "role": "assistant",
864
+ },
865
+ ]
866
+ `);
867
+ });
868
+
869
+ it('should validate an assistant message with a tool part in output-error state', async () => {
870
+ const messages = await validateUIMessages({
871
+ messages: [
872
+ {
873
+ id: '1',
874
+ role: 'assistant',
875
+ parts: [
876
+ {
877
+ type: 'tool-foo',
878
+ toolCallId: '1',
879
+ state: 'output-error',
880
+ input: { foo: 'bar' },
881
+ errorText: 'Tool execution failed',
882
+ providerExecuted: true,
883
+ },
884
+ ],
885
+ },
886
+ ],
887
+ });
888
+
889
+ expectTypeOf(messages).toEqualTypeOf<Array<UIMessage>>();
890
+
891
+ expect(messages).toMatchInlineSnapshot(`
892
+ [
893
+ {
894
+ "id": "1",
895
+ "parts": [
896
+ {
897
+ "errorText": "Tool execution failed",
898
+ "input": {
899
+ "foo": "bar",
900
+ },
901
+ "providerExecuted": true,
902
+ "state": "output-error",
903
+ "toolCallId": "1",
904
+ "type": "tool-foo",
905
+ },
906
+ ],
907
+ "role": "assistant",
908
+ },
909
+ ]
910
+ `);
911
+ });
912
+
913
+ it('should validate tool input when state is input-available', async () => {
914
+ const messages = await validateUIMessages<TestMessage>({
915
+ messages: [
916
+ {
917
+ id: '1',
918
+ role: 'assistant',
919
+ parts: [
920
+ {
921
+ type: 'tool-foo',
922
+ toolCallId: '1',
923
+ state: 'input-available',
924
+ input: { foo: 'bar' },
925
+ providerExecuted: true,
926
+ },
927
+ ],
928
+ },
929
+ ],
930
+ tools: {
931
+ foo: testTool,
932
+ },
933
+ });
934
+
935
+ expectTypeOf(messages).toEqualTypeOf<Array<TestMessage>>();
936
+ expect(messages).toMatchInlineSnapshot(`
937
+ [
938
+ {
939
+ "id": "1",
940
+ "parts": [
941
+ {
942
+ "input": {
943
+ "foo": "bar",
944
+ },
945
+ "providerExecuted": true,
946
+ "state": "input-available",
947
+ "toolCallId": "1",
948
+ "type": "tool-foo",
949
+ },
950
+ ],
951
+ "role": "assistant",
952
+ },
953
+ ]
954
+ `);
955
+ });
956
+
957
+ it('should validate tool input and output when state is output-available', async () => {
958
+ const messages = await validateUIMessages<TestMessage>({
959
+ messages: [
960
+ {
961
+ id: '1',
962
+ role: 'assistant',
963
+ parts: [
964
+ {
965
+ type: 'tool-foo',
966
+ toolCallId: '1',
967
+ state: 'output-available',
968
+ input: { foo: 'bar' },
969
+ output: { result: 'success' },
970
+ },
971
+ ],
972
+ },
973
+ ],
974
+ tools: {
975
+ foo: testTool,
976
+ },
977
+ });
978
+
979
+ expectTypeOf(messages).toEqualTypeOf<Array<TestMessage>>();
980
+ expect(messages).toMatchInlineSnapshot(`
981
+ [
982
+ {
983
+ "id": "1",
984
+ "parts": [
985
+ {
986
+ "input": {
987
+ "foo": "bar",
988
+ },
989
+ "output": {
990
+ "result": "success",
991
+ },
992
+ "state": "output-available",
993
+ "toolCallId": "1",
994
+ "type": "tool-foo",
995
+ },
996
+ ],
997
+ "role": "assistant",
998
+ },
999
+ ]
1000
+ `);
1001
+ });
1002
+
1003
+ it('should validate tool input when state is output-error and there is input', async () => {
1004
+ const messages = await validateUIMessages<TestMessage>({
1005
+ messages: [
1006
+ {
1007
+ id: '1',
1008
+ role: 'assistant',
1009
+ parts: [
1010
+ {
1011
+ type: 'tool-foo',
1012
+ toolCallId: '1',
1013
+ state: 'output-error',
1014
+ input: { foo: 'bar' },
1015
+ errorText: 'Tool execution failed',
1016
+ providerExecuted: true,
1017
+ },
1018
+ ],
1019
+ },
1020
+ ],
1021
+ tools: {
1022
+ foo: testTool,
1023
+ },
1024
+ });
1025
+
1026
+ expectTypeOf(messages).toEqualTypeOf<Array<TestMessage>>();
1027
+ expect(messages).toMatchInlineSnapshot(`
1028
+ [
1029
+ {
1030
+ "id": "1",
1031
+ "parts": [
1032
+ {
1033
+ "errorText": "Tool execution failed",
1034
+ "input": {
1035
+ "foo": "bar",
1036
+ },
1037
+ "providerExecuted": true,
1038
+ "state": "output-error",
1039
+ "toolCallId": "1",
1040
+ "type": "tool-foo",
1041
+ },
1042
+ ],
1043
+ "role": "assistant",
1044
+ },
1045
+ ]
1046
+ `);
1047
+ });
1048
+
1049
+ it('should skip tool input validation when state is output-error and there is no input', async () => {
1050
+ const messages = await validateUIMessages<TestMessage>({
1051
+ messages: [
1052
+ {
1053
+ id: '1',
1054
+ role: 'assistant',
1055
+ parts: [
1056
+ {
1057
+ type: 'tool-foo',
1058
+ toolCallId: '1',
1059
+ state: 'output-error',
1060
+ input: undefined,
1061
+ errorText: 'Tool input validation failed',
1062
+ providerExecuted: false,
1063
+ },
1064
+ ],
1065
+ },
1066
+ ],
1067
+ tools: {
1068
+ foo: testTool,
1069
+ },
1070
+ });
1071
+
1072
+ expectTypeOf(messages).toEqualTypeOf<Array<TestMessage>>();
1073
+ expect(messages).toMatchInlineSnapshot(`
1074
+ [
1075
+ {
1076
+ "id": "1",
1077
+ "parts": [
1078
+ {
1079
+ "errorText": "Tool input validation failed",
1080
+ "input": undefined,
1081
+ "providerExecuted": false,
1082
+ "state": "output-error",
1083
+ "toolCallId": "1",
1084
+ "type": "tool-foo",
1085
+ },
1086
+ ],
1087
+ "role": "assistant",
1088
+ },
1089
+ ]
1090
+ `);
1091
+ });
1092
+
1093
+ it('should preserve rawInput when state is output-error', async () => {
1094
+ const inputMessages = [
1095
+ {
1096
+ id: '1',
1097
+ role: 'assistant' as const,
1098
+ parts: [
1099
+ {
1100
+ type: 'tool-foo' as const,
1101
+ toolCallId: '1',
1102
+ state: 'output-error' as const,
1103
+ input: undefined,
1104
+ rawInput: { foo: 'bar' },
1105
+ errorText: 'Tool input validation failed',
1106
+ providerExecuted: false,
1107
+ },
1108
+ ],
1109
+ },
1110
+ ];
1111
+
1112
+ const result = await validateUIMessages<TestMessage>({
1113
+ messages: inputMessages,
1114
+ tools: {
1115
+ foo: testTool,
1116
+ },
1117
+ });
1118
+
1119
+ expect(result).toEqual(inputMessages);
1120
+ });
1121
+
1122
+ it('should throw error when no tool schema is found', async () => {
1123
+ await expect(
1124
+ validateUIMessages<TestMessage>({
1125
+ messages: [
1126
+ {
1127
+ id: '1',
1128
+ role: 'assistant',
1129
+ parts: [
1130
+ {
1131
+ type: 'tool-bar',
1132
+ toolCallId: '1',
1133
+ state: 'input-available',
1134
+ input: { foo: 'bar' },
1135
+ providerExecuted: true,
1136
+ },
1137
+ ],
1138
+ },
1139
+ ],
1140
+ tools: {
1141
+ foo: testTool,
1142
+ },
1143
+ }),
1144
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
1145
+ [AI_TypeValidationError: Type validation failed: Value: {"foo":"bar"}.
1146
+ Error message: No tool schema found for tool part bar]
1147
+ `);
1148
+ });
1149
+
1150
+ it('should throw error when tool input validation fails', async () => {
1151
+ await expect(
1152
+ validateUIMessages<TestMessage>({
1153
+ messages: [
1154
+ {
1155
+ id: '1',
1156
+ role: 'assistant',
1157
+ parts: [
1158
+ {
1159
+ type: 'tool-foo',
1160
+ toolCallId: '1',
1161
+ state: 'input-available',
1162
+ input: { foo: 123 }, // wrong type
1163
+ providerExecuted: true,
1164
+ },
1165
+ ],
1166
+ },
1167
+ ],
1168
+ tools: {
1169
+ foo: testTool,
1170
+ },
1171
+ }),
1172
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
1173
+ [AI_TypeValidationError: Type validation failed: Value: {"foo":123}.
1174
+ Error message: [
1175
+ {
1176
+ "expected": "string",
1177
+ "code": "invalid_type",
1178
+ "path": [
1179
+ "foo"
1180
+ ],
1181
+ "message": "Invalid input: expected string, received number"
1182
+ }
1183
+ ]]
1184
+ `);
1185
+ });
1186
+
1187
+ it('should throw error when tool output validation fails', async () => {
1188
+ await expect(
1189
+ validateUIMessages<TestMessage>({
1190
+ messages: [
1191
+ {
1192
+ id: '1',
1193
+ role: 'assistant',
1194
+ parts: [
1195
+ {
1196
+ type: 'tool-foo',
1197
+ toolCallId: '1',
1198
+ state: 'output-available',
1199
+ providerExecuted: true,
1200
+ input: { foo: 'bar' },
1201
+ output: { result: 123 }, // wrong type
1202
+ },
1203
+ ],
1204
+ },
1205
+ ],
1206
+ tools: {
1207
+ foo: testTool,
1208
+ },
1209
+ }),
1210
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
1211
+ [AI_TypeValidationError: Type validation failed: Value: {"result":123}.
1212
+ Error message: [
1213
+ {
1214
+ "expected": "string",
1215
+ "code": "invalid_type",
1216
+ "path": [
1217
+ "result"
1218
+ ],
1219
+ "message": "Invalid input: expected string, received number"
1220
+ }
1221
+ ]]
1222
+ `);
1223
+ });
1224
+
1225
+ it('should not validate input in input-streaming state', async () => {
1226
+ const messages = await validateUIMessages<TestMessage>({
1227
+ messages: [
1228
+ {
1229
+ id: '1',
1230
+ role: 'assistant',
1231
+ parts: [
1232
+ {
1233
+ type: 'tool-foo',
1234
+ toolCallId: '1',
1235
+ state: 'input-streaming',
1236
+ input: { foo: 123 }, // wrong type but should not be validated
1237
+ providerExecuted: true,
1238
+ },
1239
+ ],
1240
+ },
1241
+ ],
1242
+ tools: {
1243
+ foo: testTool,
1244
+ },
1245
+ });
1246
+
1247
+ expectTypeOf(messages).toEqualTypeOf<Array<TestMessage>>();
1248
+ expect(messages).toMatchInlineSnapshot(`
1249
+ [
1250
+ {
1251
+ "id": "1",
1252
+ "parts": [
1253
+ {
1254
+ "input": {
1255
+ "foo": 123,
1256
+ },
1257
+ "providerExecuted": true,
1258
+ "state": "input-streaming",
1259
+ "toolCallId": "1",
1260
+ "type": "tool-foo",
1261
+ },
1262
+ ],
1263
+ "role": "assistant",
1264
+ },
1265
+ ]
1266
+ `);
1267
+ });
1268
+ });
1269
+ });
1270
+
1271
+ export function expectToBe<T extends boolean>(
1272
+ value: boolean,
1273
+ expected: T,
1274
+ ): asserts value is T {
1275
+ expect(value).toBe(expected);
1276
+ }
1277
+
1278
+ describe('safeValidateUIMessages', () => {
1279
+ it('should return success result for valid messages', async () => {
1280
+ const result = await safeValidateUIMessages({
1281
+ messages: [
1282
+ {
1283
+ id: '1',
1284
+ role: 'user',
1285
+ parts: [{ type: 'text', text: 'Hello, world!' }],
1286
+ },
1287
+ ],
1288
+ });
1289
+
1290
+ expectToBe(result.success, true);
1291
+ expect(result.data).toMatchInlineSnapshot(`
1292
+ [
1293
+ {
1294
+ "id": "1",
1295
+ "parts": [
1296
+ {
1297
+ "text": "Hello, world!",
1298
+ "type": "text",
1299
+ },
1300
+ ],
1301
+ "role": "user",
1302
+ },
1303
+ ]
1304
+ `);
1305
+ });
1306
+
1307
+ it('should return failure result when messages parameter is null', async () => {
1308
+ const result = await safeValidateUIMessages({
1309
+ messages: null,
1310
+ });
1311
+
1312
+ expectToBe(result.success, false);
1313
+ expect(result.error.name).toBe('AI_InvalidArgumentError');
1314
+ expect(result.error.message).toBe(
1315
+ 'Invalid argument for parameter messages: messages parameter must be provided',
1316
+ );
1317
+ });
1318
+
1319
+ it('should return failure result when messages array is empty', async () => {
1320
+ const result = await safeValidateUIMessages({
1321
+ messages: [],
1322
+ });
1323
+
1324
+ expectToBe(result.success, false);
1325
+ expect(result.error.name).toBe('AI_TypeValidationError');
1326
+ expect(result.error.message).toContain('Type validation failed');
1327
+ expect(result.error.message).toContain('Messages array must not be empty');
1328
+ });
1329
+
1330
+ it('should return failure result when message has empty parts array', async () => {
1331
+ const result = await safeValidateUIMessages({
1332
+ messages: [
1333
+ {
1334
+ id: '1',
1335
+ role: 'user',
1336
+ parts: [],
1337
+ },
1338
+ ],
1339
+ });
1340
+
1341
+ expectToBe(result.success, false);
1342
+ expect(result.error.name).toBe('AI_TypeValidationError');
1343
+ expect(result.error.message).toContain('Type validation failed');
1344
+ });
1345
+
1346
+ it('should return failure result when metadata validation fails', async () => {
1347
+ const result = await safeValidateUIMessages<UIMessage<{ foo: string }>>({
1348
+ messages: [
1349
+ {
1350
+ id: '1',
1351
+ role: 'user',
1352
+ metadata: { foo: 123 },
1353
+ parts: [{ type: 'text', text: 'Hello, world!' }],
1354
+ },
1355
+ ],
1356
+ metadataSchema: z.object({ foo: z.string() }),
1357
+ });
1358
+
1359
+ expectToBe(result.success, false);
1360
+ expect(result.error.name).toBe('AI_TypeValidationError');
1361
+ expect(result.error.message).toContain('Type validation failed');
1362
+ });
1363
+
1364
+ it('should return failure result when tool input validation fails', async () => {
1365
+ const testTool = {
1366
+ name: 'foo',
1367
+ inputSchema: z.object({ foo: z.string() }),
1368
+ outputSchema: z.object({ result: z.string() }),
1369
+ };
1370
+
1371
+ const result = await safeValidateUIMessages({
1372
+ messages: [
1373
+ {
1374
+ id: '1',
1375
+ role: 'assistant',
1376
+ parts: [
1377
+ {
1378
+ type: 'tool-foo',
1379
+ toolCallId: '1',
1380
+ state: 'input-available',
1381
+ input: { foo: 123 },
1382
+ providerExecuted: true,
1383
+ },
1384
+ ],
1385
+ },
1386
+ ],
1387
+ tools: { foo: testTool },
1388
+ });
1389
+
1390
+ expectToBe(result.success, false);
1391
+ expect(result.error.name).toBe('AI_TypeValidationError');
1392
+ expect(result.error.message).toContain('Type validation failed');
1393
+ });
1394
+
1395
+ it('should return failure result when data schema is missing', async () => {
1396
+ const result = await safeValidateUIMessages({
1397
+ messages: [
1398
+ {
1399
+ id: '1',
1400
+ role: 'assistant',
1401
+ parts: [{ type: 'data-bar', data: { foo: 'bar' } }],
1402
+ },
1403
+ ],
1404
+ dataSchemas: {
1405
+ foo: z.object({ foo: z.string() }),
1406
+ },
1407
+ });
1408
+
1409
+ expectToBe(result.success, false);
1410
+ expect(result.error.name).toBe('AI_TypeValidationError');
1411
+ expect(result.error.message).toContain(
1412
+ 'No data schema found for data part bar',
1413
+ );
1414
+ });
1415
+
1416
+ it('should return failure result for invalid message structure', async () => {
1417
+ const result = await safeValidateUIMessages({
1418
+ messages: [
1419
+ {
1420
+ role: 'user',
1421
+ },
1422
+ ],
1423
+ });
1424
+
1425
+ expectToBe(result.success, false);
1426
+ expect(result.error.name).toBe('AI_TypeValidationError');
1427
+ });
1428
+ });