ai 6.0.33 → 6.0.35

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 (357) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.d.mts +50 -21
  3. package/dist/index.d.ts +50 -21
  4. package/dist/index.js +348 -286
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +280 -219
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/internal/index.js +1 -1
  9. package/dist/internal/index.mjs +1 -1
  10. package/docs/02-foundations/03-prompts.mdx +2 -2
  11. package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +1 -1
  12. package/docs/07-reference/01-ai-sdk-core/28-output.mdx +1 -1
  13. package/docs/07-reference/05-ai-sdk-errors/ai-ui-message-stream-error.mdx +67 -0
  14. package/package.json +6 -4
  15. package/src/agent/agent.ts +116 -0
  16. package/src/agent/create-agent-ui-stream-response.test.ts +258 -0
  17. package/src/agent/create-agent-ui-stream-response.ts +50 -0
  18. package/src/agent/create-agent-ui-stream.ts +73 -0
  19. package/src/agent/index.ts +33 -0
  20. package/src/agent/infer-agent-tools.ts +7 -0
  21. package/src/agent/infer-agent-ui-message.test-d.ts +54 -0
  22. package/src/agent/infer-agent-ui-message.ts +11 -0
  23. package/src/agent/pipe-agent-ui-stream-to-response.ts +52 -0
  24. package/src/agent/tool-loop-agent-on-finish-callback.ts +31 -0
  25. package/src/agent/tool-loop-agent-on-step-finish-callback.ts +11 -0
  26. package/src/agent/tool-loop-agent-settings.ts +182 -0
  27. package/src/agent/tool-loop-agent.test-d.ts +114 -0
  28. package/src/agent/tool-loop-agent.test.ts +442 -0
  29. package/src/agent/tool-loop-agent.ts +114 -0
  30. package/src/embed/__snapshots__/embed-many.test.ts.snap +191 -0
  31. package/src/embed/__snapshots__/embed.test.ts.snap +81 -0
  32. package/src/embed/embed-many-result.ts +53 -0
  33. package/src/embed/embed-many.test.ts +653 -0
  34. package/src/embed/embed-many.ts +378 -0
  35. package/src/embed/embed-result.ts +50 -0
  36. package/src/embed/embed.test.ts +298 -0
  37. package/src/embed/embed.ts +211 -0
  38. package/src/embed/index.ts +4 -0
  39. package/src/error/index.ts +35 -0
  40. package/src/error/invalid-argument-error.ts +34 -0
  41. package/src/error/invalid-stream-part-error.ts +28 -0
  42. package/src/error/invalid-tool-approval-error.ts +26 -0
  43. package/src/error/invalid-tool-input-error.ts +33 -0
  44. package/src/error/no-image-generated-error.ts +39 -0
  45. package/src/error/no-object-generated-error.ts +70 -0
  46. package/src/error/no-output-generated-error.ts +26 -0
  47. package/src/error/no-speech-generated-error.ts +18 -0
  48. package/src/error/no-such-tool-error.ts +35 -0
  49. package/src/error/no-transcript-generated-error.ts +20 -0
  50. package/src/error/tool-call-not-found-for-approval-error.ts +32 -0
  51. package/src/error/tool-call-repair-error.ts +30 -0
  52. package/src/error/ui-message-stream-error.ts +48 -0
  53. package/src/error/unsupported-model-version-error.ts +23 -0
  54. package/src/error/verify-no-object-generated-error.ts +27 -0
  55. package/src/generate-image/generate-image-result.ts +42 -0
  56. package/src/generate-image/generate-image.test.ts +1420 -0
  57. package/src/generate-image/generate-image.ts +360 -0
  58. package/src/generate-image/index.ts +18 -0
  59. package/src/generate-object/__snapshots__/generate-object.test.ts.snap +133 -0
  60. package/src/generate-object/__snapshots__/stream-object.test.ts.snap +297 -0
  61. package/src/generate-object/generate-object-result.ts +67 -0
  62. package/src/generate-object/generate-object.test-d.ts +49 -0
  63. package/src/generate-object/generate-object.test.ts +1191 -0
  64. package/src/generate-object/generate-object.ts +518 -0
  65. package/src/generate-object/index.ts +9 -0
  66. package/src/generate-object/inject-json-instruction.test.ts +181 -0
  67. package/src/generate-object/inject-json-instruction.ts +30 -0
  68. package/src/generate-object/output-strategy.ts +415 -0
  69. package/src/generate-object/parse-and-validate-object-result.ts +111 -0
  70. package/src/generate-object/repair-text.ts +12 -0
  71. package/src/generate-object/stream-object-result.ts +120 -0
  72. package/src/generate-object/stream-object.test-d.ts +74 -0
  73. package/src/generate-object/stream-object.test.ts +1950 -0
  74. package/src/generate-object/stream-object.ts +986 -0
  75. package/src/generate-object/validate-object-generation-input.ts +144 -0
  76. package/src/generate-speech/generate-speech-result.ts +30 -0
  77. package/src/generate-speech/generate-speech.test.ts +300 -0
  78. package/src/generate-speech/generate-speech.ts +190 -0
  79. package/src/generate-speech/generated-audio-file.ts +65 -0
  80. package/src/generate-speech/index.ts +3 -0
  81. package/src/generate-text/__snapshots__/generate-text.test.ts.snap +1872 -0
  82. package/src/generate-text/__snapshots__/stream-text.test.ts.snap +1255 -0
  83. package/src/generate-text/collect-tool-approvals.test.ts +553 -0
  84. package/src/generate-text/collect-tool-approvals.ts +116 -0
  85. package/src/generate-text/content-part.ts +25 -0
  86. package/src/generate-text/execute-tool-call.ts +129 -0
  87. package/src/generate-text/extract-reasoning-content.ts +17 -0
  88. package/src/generate-text/extract-text-content.ts +15 -0
  89. package/src/generate-text/generate-text-result.ts +168 -0
  90. package/src/generate-text/generate-text.test-d.ts +68 -0
  91. package/src/generate-text/generate-text.test.ts +7011 -0
  92. package/src/generate-text/generate-text.ts +1223 -0
  93. package/src/generate-text/generated-file.ts +70 -0
  94. package/src/generate-text/index.ts +57 -0
  95. package/src/generate-text/is-approval-needed.ts +29 -0
  96. package/src/generate-text/output-utils.ts +23 -0
  97. package/src/generate-text/output.test.ts +698 -0
  98. package/src/generate-text/output.ts +590 -0
  99. package/src/generate-text/parse-tool-call.test.ts +570 -0
  100. package/src/generate-text/parse-tool-call.ts +188 -0
  101. package/src/generate-text/prepare-step.ts +103 -0
  102. package/src/generate-text/prune-messages.test.ts +720 -0
  103. package/src/generate-text/prune-messages.ts +167 -0
  104. package/src/generate-text/reasoning-output.ts +20 -0
  105. package/src/generate-text/reasoning.ts +8 -0
  106. package/src/generate-text/response-message.ts +10 -0
  107. package/src/generate-text/run-tools-transformation.test.ts +1143 -0
  108. package/src/generate-text/run-tools-transformation.ts +420 -0
  109. package/src/generate-text/smooth-stream.test.ts +2101 -0
  110. package/src/generate-text/smooth-stream.ts +162 -0
  111. package/src/generate-text/step-result.ts +238 -0
  112. package/src/generate-text/stop-condition.ts +29 -0
  113. package/src/generate-text/stream-text-result.ts +463 -0
  114. package/src/generate-text/stream-text.test-d.ts +200 -0
  115. package/src/generate-text/stream-text.test.ts +19979 -0
  116. package/src/generate-text/stream-text.ts +2505 -0
  117. package/src/generate-text/to-response-messages.test.ts +922 -0
  118. package/src/generate-text/to-response-messages.ts +163 -0
  119. package/src/generate-text/tool-approval-request-output.ts +21 -0
  120. package/src/generate-text/tool-call-repair-function.ts +27 -0
  121. package/src/generate-text/tool-call.ts +47 -0
  122. package/src/generate-text/tool-error.ts +34 -0
  123. package/src/generate-text/tool-output-denied.ts +21 -0
  124. package/src/generate-text/tool-output.ts +7 -0
  125. package/src/generate-text/tool-result.ts +36 -0
  126. package/src/generate-text/tool-set.ts +14 -0
  127. package/src/global.ts +24 -0
  128. package/src/index.ts +50 -0
  129. package/src/logger/index.ts +6 -0
  130. package/src/logger/log-warnings.test.ts +351 -0
  131. package/src/logger/log-warnings.ts +119 -0
  132. package/src/middleware/__snapshots__/simulate-streaming-middleware.test.ts.snap +64 -0
  133. package/src/middleware/add-tool-input-examples-middleware.test.ts +476 -0
  134. package/src/middleware/add-tool-input-examples-middleware.ts +90 -0
  135. package/src/middleware/default-embedding-settings-middleware.test.ts +126 -0
  136. package/src/middleware/default-embedding-settings-middleware.ts +22 -0
  137. package/src/middleware/default-settings-middleware.test.ts +388 -0
  138. package/src/middleware/default-settings-middleware.ts +33 -0
  139. package/src/middleware/extract-json-middleware.test.ts +827 -0
  140. package/src/middleware/extract-json-middleware.ts +197 -0
  141. package/src/middleware/extract-reasoning-middleware.test.ts +1028 -0
  142. package/src/middleware/extract-reasoning-middleware.ts +238 -0
  143. package/src/middleware/index.ts +10 -0
  144. package/src/middleware/simulate-streaming-middleware.test.ts +911 -0
  145. package/src/middleware/simulate-streaming-middleware.ts +79 -0
  146. package/src/middleware/wrap-embedding-model.test.ts +358 -0
  147. package/src/middleware/wrap-embedding-model.ts +86 -0
  148. package/src/middleware/wrap-image-model.test.ts +423 -0
  149. package/src/middleware/wrap-image-model.ts +85 -0
  150. package/src/middleware/wrap-language-model.test.ts +518 -0
  151. package/src/middleware/wrap-language-model.ts +104 -0
  152. package/src/middleware/wrap-provider.test.ts +120 -0
  153. package/src/middleware/wrap-provider.ts +51 -0
  154. package/src/model/as-embedding-model-v3.test.ts +319 -0
  155. package/src/model/as-embedding-model-v3.ts +24 -0
  156. package/src/model/as-image-model-v3.test.ts +409 -0
  157. package/src/model/as-image-model-v3.ts +24 -0
  158. package/src/model/as-language-model-v3.test.ts +508 -0
  159. package/src/model/as-language-model-v3.ts +103 -0
  160. package/src/model/as-provider-v3.ts +36 -0
  161. package/src/model/as-speech-model-v3.test.ts +356 -0
  162. package/src/model/as-speech-model-v3.ts +24 -0
  163. package/src/model/as-transcription-model-v3.test.ts +529 -0
  164. package/src/model/as-transcription-model-v3.ts +24 -0
  165. package/src/model/resolve-model.test.ts +244 -0
  166. package/src/model/resolve-model.ts +126 -0
  167. package/src/prompt/call-settings.ts +148 -0
  168. package/src/prompt/content-part.ts +209 -0
  169. package/src/prompt/convert-to-language-model-prompt.test.ts +2018 -0
  170. package/src/prompt/convert-to-language-model-prompt.ts +442 -0
  171. package/src/prompt/create-tool-model-output.test.ts +508 -0
  172. package/src/prompt/create-tool-model-output.ts +34 -0
  173. package/src/prompt/data-content.test.ts +15 -0
  174. package/src/prompt/data-content.ts +134 -0
  175. package/src/prompt/index.ts +27 -0
  176. package/src/prompt/invalid-data-content-error.ts +29 -0
  177. package/src/prompt/invalid-message-role-error.ts +27 -0
  178. package/src/prompt/message-conversion-error.ts +28 -0
  179. package/src/prompt/message.ts +68 -0
  180. package/src/prompt/prepare-call-settings.test.ts +159 -0
  181. package/src/prompt/prepare-call-settings.ts +108 -0
  182. package/src/prompt/prepare-tools-and-tool-choice.test.ts +461 -0
  183. package/src/prompt/prepare-tools-and-tool-choice.ts +86 -0
  184. package/src/prompt/prompt.ts +43 -0
  185. package/src/prompt/split-data-url.ts +17 -0
  186. package/src/prompt/standardize-prompt.test.ts +82 -0
  187. package/src/prompt/standardize-prompt.ts +99 -0
  188. package/src/prompt/wrap-gateway-error.ts +29 -0
  189. package/src/registry/custom-provider.test.ts +211 -0
  190. package/src/registry/custom-provider.ts +155 -0
  191. package/src/registry/index.ts +7 -0
  192. package/src/registry/no-such-provider-error.ts +41 -0
  193. package/src/registry/provider-registry.test.ts +691 -0
  194. package/src/registry/provider-registry.ts +328 -0
  195. package/src/rerank/index.ts +2 -0
  196. package/src/rerank/rerank-result.ts +70 -0
  197. package/src/rerank/rerank.test.ts +516 -0
  198. package/src/rerank/rerank.ts +237 -0
  199. package/src/telemetry/assemble-operation-name.ts +21 -0
  200. package/src/telemetry/get-base-telemetry-attributes.ts +53 -0
  201. package/src/telemetry/get-tracer.ts +20 -0
  202. package/src/telemetry/noop-tracer.ts +69 -0
  203. package/src/telemetry/record-span.ts +63 -0
  204. package/src/telemetry/select-telemetry-attributes.ts +78 -0
  205. package/src/telemetry/select-temetry-attributes.test.ts +114 -0
  206. package/src/telemetry/stringify-for-telemetry.test.ts +114 -0
  207. package/src/telemetry/stringify-for-telemetry.ts +33 -0
  208. package/src/telemetry/telemetry-settings.ts +44 -0
  209. package/src/test/mock-embedding-model-v2.ts +35 -0
  210. package/src/test/mock-embedding-model-v3.ts +48 -0
  211. package/src/test/mock-image-model-v2.ts +28 -0
  212. package/src/test/mock-image-model-v3.ts +28 -0
  213. package/src/test/mock-language-model-v2.ts +72 -0
  214. package/src/test/mock-language-model-v3.ts +77 -0
  215. package/src/test/mock-provider-v2.ts +68 -0
  216. package/src/test/mock-provider-v3.ts +80 -0
  217. package/src/test/mock-reranking-model-v3.ts +25 -0
  218. package/src/test/mock-server-response.ts +69 -0
  219. package/src/test/mock-speech-model-v2.ts +24 -0
  220. package/src/test/mock-speech-model-v3.ts +24 -0
  221. package/src/test/mock-tracer.ts +156 -0
  222. package/src/test/mock-transcription-model-v2.ts +24 -0
  223. package/src/test/mock-transcription-model-v3.ts +24 -0
  224. package/src/test/mock-values.ts +4 -0
  225. package/src/test/not-implemented.ts +3 -0
  226. package/src/text-stream/create-text-stream-response.test.ts +38 -0
  227. package/src/text-stream/create-text-stream-response.ts +18 -0
  228. package/src/text-stream/index.ts +2 -0
  229. package/src/text-stream/pipe-text-stream-to-response.test.ts +38 -0
  230. package/src/text-stream/pipe-text-stream-to-response.ts +26 -0
  231. package/src/transcribe/index.ts +2 -0
  232. package/src/transcribe/transcribe-result.ts +60 -0
  233. package/src/transcribe/transcribe.test.ts +313 -0
  234. package/src/transcribe/transcribe.ts +173 -0
  235. package/src/types/embedding-model-middleware.ts +3 -0
  236. package/src/types/embedding-model.ts +18 -0
  237. package/src/types/image-model-middleware.ts +3 -0
  238. package/src/types/image-model-response-metadata.ts +16 -0
  239. package/src/types/image-model.ts +19 -0
  240. package/src/types/index.ts +29 -0
  241. package/src/types/json-value.ts +15 -0
  242. package/src/types/language-model-middleware.ts +3 -0
  243. package/src/types/language-model-request-metadata.ts +6 -0
  244. package/src/types/language-model-response-metadata.ts +21 -0
  245. package/src/types/language-model.ts +104 -0
  246. package/src/types/provider-metadata.ts +16 -0
  247. package/src/types/provider.ts +55 -0
  248. package/src/types/reranking-model.ts +6 -0
  249. package/src/types/speech-model-response-metadata.ts +21 -0
  250. package/src/types/speech-model.ts +6 -0
  251. package/src/types/transcription-model-response-metadata.ts +16 -0
  252. package/src/types/transcription-model.ts +9 -0
  253. package/src/types/usage.ts +200 -0
  254. package/src/types/warning.ts +7 -0
  255. package/src/ui/__snapshots__/append-response-messages.test.ts.snap +416 -0
  256. package/src/ui/__snapshots__/convert-to-model-messages.test.ts.snap +419 -0
  257. package/src/ui/__snapshots__/process-chat-text-response.test.ts.snap +142 -0
  258. package/src/ui/call-completion-api.ts +157 -0
  259. package/src/ui/chat-transport.ts +83 -0
  260. package/src/ui/chat.test-d.ts +233 -0
  261. package/src/ui/chat.test.ts +2695 -0
  262. package/src/ui/chat.ts +716 -0
  263. package/src/ui/convert-file-list-to-file-ui-parts.ts +36 -0
  264. package/src/ui/convert-to-model-messages.test.ts +2775 -0
  265. package/src/ui/convert-to-model-messages.ts +373 -0
  266. package/src/ui/default-chat-transport.ts +36 -0
  267. package/src/ui/direct-chat-transport.test.ts +446 -0
  268. package/src/ui/direct-chat-transport.ts +118 -0
  269. package/src/ui/http-chat-transport.test.ts +185 -0
  270. package/src/ui/http-chat-transport.ts +292 -0
  271. package/src/ui/index.ts +71 -0
  272. package/src/ui/last-assistant-message-is-complete-with-approval-responses.ts +44 -0
  273. package/src/ui/last-assistant-message-is-complete-with-tool-calls.test.ts +371 -0
  274. package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +39 -0
  275. package/src/ui/process-text-stream.test.ts +38 -0
  276. package/src/ui/process-text-stream.ts +16 -0
  277. package/src/ui/process-ui-message-stream.test.ts +8294 -0
  278. package/src/ui/process-ui-message-stream.ts +761 -0
  279. package/src/ui/text-stream-chat-transport.ts +23 -0
  280. package/src/ui/transform-text-to-ui-message-stream.test.ts +124 -0
  281. package/src/ui/transform-text-to-ui-message-stream.ts +27 -0
  282. package/src/ui/ui-messages.test.ts +48 -0
  283. package/src/ui/ui-messages.ts +534 -0
  284. package/src/ui/use-completion.ts +84 -0
  285. package/src/ui/validate-ui-messages.test.ts +1428 -0
  286. package/src/ui/validate-ui-messages.ts +476 -0
  287. package/src/ui-message-stream/create-ui-message-stream-response.test.ts +266 -0
  288. package/src/ui-message-stream/create-ui-message-stream-response.ts +32 -0
  289. package/src/ui-message-stream/create-ui-message-stream.test.ts +639 -0
  290. package/src/ui-message-stream/create-ui-message-stream.ts +124 -0
  291. package/src/ui-message-stream/get-response-ui-message-id.test.ts +55 -0
  292. package/src/ui-message-stream/get-response-ui-message-id.ts +24 -0
  293. package/src/ui-message-stream/handle-ui-message-stream-finish.test.ts +429 -0
  294. package/src/ui-message-stream/handle-ui-message-stream-finish.ts +135 -0
  295. package/src/ui-message-stream/index.ts +13 -0
  296. package/src/ui-message-stream/json-to-sse-transform-stream.ts +12 -0
  297. package/src/ui-message-stream/pipe-ui-message-stream-to-response.test.ts +90 -0
  298. package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +40 -0
  299. package/src/ui-message-stream/read-ui-message-stream.test.ts +122 -0
  300. package/src/ui-message-stream/read-ui-message-stream.ts +87 -0
  301. package/src/ui-message-stream/ui-message-chunks.test-d.ts +18 -0
  302. package/src/ui-message-stream/ui-message-chunks.ts +344 -0
  303. package/src/ui-message-stream/ui-message-stream-headers.ts +7 -0
  304. package/src/ui-message-stream/ui-message-stream-on-finish-callback.ts +32 -0
  305. package/src/ui-message-stream/ui-message-stream-response-init.ts +5 -0
  306. package/src/ui-message-stream/ui-message-stream-writer.ts +24 -0
  307. package/src/util/as-array.ts +3 -0
  308. package/src/util/async-iterable-stream.test.ts +241 -0
  309. package/src/util/async-iterable-stream.ts +94 -0
  310. package/src/util/consume-stream.ts +29 -0
  311. package/src/util/cosine-similarity.test.ts +57 -0
  312. package/src/util/cosine-similarity.ts +47 -0
  313. package/src/util/create-resolvable-promise.ts +30 -0
  314. package/src/util/create-stitchable-stream.test.ts +239 -0
  315. package/src/util/create-stitchable-stream.ts +112 -0
  316. package/src/util/data-url.ts +17 -0
  317. package/src/util/deep-partial.ts +84 -0
  318. package/src/util/detect-media-type.test.ts +670 -0
  319. package/src/util/detect-media-type.ts +184 -0
  320. package/src/util/download/download-function.ts +45 -0
  321. package/src/util/download/download.test.ts +69 -0
  322. package/src/util/download/download.ts +46 -0
  323. package/src/util/error-handler.ts +1 -0
  324. package/src/util/fix-json.test.ts +279 -0
  325. package/src/util/fix-json.ts +401 -0
  326. package/src/util/get-potential-start-index.test.ts +34 -0
  327. package/src/util/get-potential-start-index.ts +30 -0
  328. package/src/util/index.ts +11 -0
  329. package/src/util/is-deep-equal-data.test.ts +119 -0
  330. package/src/util/is-deep-equal-data.ts +48 -0
  331. package/src/util/is-non-empty-object.ts +5 -0
  332. package/src/util/job.ts +1 -0
  333. package/src/util/log-v2-compatibility-warning.ts +21 -0
  334. package/src/util/merge-abort-signals.test.ts +155 -0
  335. package/src/util/merge-abort-signals.ts +43 -0
  336. package/src/util/merge-objects.test.ts +118 -0
  337. package/src/util/merge-objects.ts +79 -0
  338. package/src/util/now.ts +4 -0
  339. package/src/util/parse-partial-json.test.ts +80 -0
  340. package/src/util/parse-partial-json.ts +30 -0
  341. package/src/util/prepare-headers.test.ts +51 -0
  342. package/src/util/prepare-headers.ts +14 -0
  343. package/src/util/prepare-retries.test.ts +10 -0
  344. package/src/util/prepare-retries.ts +47 -0
  345. package/src/util/retry-error.ts +41 -0
  346. package/src/util/retry-with-exponential-backoff.test.ts +446 -0
  347. package/src/util/retry-with-exponential-backoff.ts +154 -0
  348. package/src/util/serial-job-executor.test.ts +162 -0
  349. package/src/util/serial-job-executor.ts +36 -0
  350. package/src/util/simulate-readable-stream.test.ts +98 -0
  351. package/src/util/simulate-readable-stream.ts +39 -0
  352. package/src/util/split-array.test.ts +60 -0
  353. package/src/util/split-array.ts +20 -0
  354. package/src/util/value-of.ts +65 -0
  355. package/src/util/write-to-server-response.test.ts +266 -0
  356. package/src/util/write-to-server-response.ts +49 -0
  357. package/src/version.ts +5 -0
@@ -0,0 +1,1950 @@
1
+ import {
2
+ JSONParseError,
3
+ SharedV3Warning,
4
+ LanguageModelV3StreamPart,
5
+ TypeValidationError,
6
+ LanguageModelV3Usage,
7
+ } from '@ai-sdk/provider';
8
+ import { jsonSchema } from '@ai-sdk/provider-utils';
9
+ import {
10
+ convertArrayToReadableStream,
11
+ convertAsyncIterableToArray,
12
+ convertReadableStreamToArray,
13
+ } from '@ai-sdk/provider-utils/test';
14
+ import assert, { fail } from 'node:assert';
15
+ import { afterEach, beforeEach, describe, expect, it, vitest } from 'vitest';
16
+ import { z } from 'zod/v4';
17
+ import { NoObjectGeneratedError } from '../error/no-object-generated-error';
18
+ import { verifyNoObjectGeneratedError } from '../error/verify-no-object-generated-error';
19
+ import * as logWarningsModule from '../logger/log-warnings';
20
+ import { MockLanguageModelV3 } from '../test/mock-language-model-v3';
21
+ import { createMockServerResponse } from '../test/mock-server-response';
22
+ import { MockTracer } from '../test/mock-tracer';
23
+ import { AsyncIterableStream } from '../util/async-iterable-stream';
24
+ import { streamObject } from './stream-object';
25
+ import { StreamObjectResult } from './stream-object-result';
26
+ import { asLanguageModelUsage } from '../types/usage';
27
+
28
+ const testUsage: LanguageModelV3Usage = {
29
+ inputTokens: {
30
+ total: 3,
31
+ noCache: 3,
32
+ cacheRead: undefined,
33
+ cacheWrite: undefined,
34
+ },
35
+ outputTokens: {
36
+ total: 10,
37
+ text: 10,
38
+ reasoning: undefined,
39
+ },
40
+ };
41
+ function createTestModel({
42
+ warnings = [],
43
+ stream = convertArrayToReadableStream([
44
+ {
45
+ type: 'stream-start',
46
+ warnings,
47
+ },
48
+ {
49
+ type: 'response-metadata',
50
+ id: 'id-0',
51
+ modelId: 'mock-model-id',
52
+ timestamp: new Date(0),
53
+ },
54
+ { type: 'text-start', id: '1' },
55
+ { type: 'text-delta', id: '1', delta: '{ ' },
56
+ { type: 'text-delta', id: '1', delta: '"content": ' },
57
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
58
+ { type: 'text-delta', id: '1', delta: `world` },
59
+ { type: 'text-delta', id: '1', delta: `!"` },
60
+ { type: 'text-delta', id: '1', delta: ' }' },
61
+ { type: 'text-end', id: '1' },
62
+ {
63
+ type: 'finish',
64
+ finishReason: { unified: 'stop', raw: 'stop' },
65
+ usage: testUsage,
66
+ providerMetadata: {
67
+ testProvider: {
68
+ testKey: 'testValue',
69
+ },
70
+ },
71
+ },
72
+ ]),
73
+ request = undefined,
74
+ response = undefined,
75
+ }: {
76
+ stream?: ReadableStream<LanguageModelV3StreamPart>;
77
+ request?: { body: string };
78
+ response?: { headers: Record<string, string> };
79
+ warnings?: SharedV3Warning[];
80
+ } = {}) {
81
+ return new MockLanguageModelV3({
82
+ doStream: async () => ({ stream, request, response, warnings }),
83
+ });
84
+ }
85
+
86
+ describe('streamObject', () => {
87
+ let logWarningsSpy: ReturnType<typeof vitest.spyOn>;
88
+
89
+ beforeEach(() => {
90
+ logWarningsSpy = vitest
91
+ .spyOn(logWarningsModule, 'logWarnings')
92
+ .mockImplementation(() => {});
93
+ });
94
+
95
+ afterEach(() => {
96
+ logWarningsSpy.mockRestore();
97
+ });
98
+ describe('output = "object"', () => {
99
+ describe('result.objectStream', () => {
100
+ it('should send object deltas', async () => {
101
+ const mockModel = createTestModel();
102
+
103
+ const result = streamObject({
104
+ model: mockModel,
105
+ schema: z.object({ content: z.string() }),
106
+ prompt: 'prompt',
107
+ });
108
+
109
+ expect(await convertAsyncIterableToArray(result.partialObjectStream))
110
+ .toMatchInlineSnapshot(`
111
+ [
112
+ {},
113
+ {
114
+ "content": "Hello, ",
115
+ },
116
+ {
117
+ "content": "Hello, world",
118
+ },
119
+ {
120
+ "content": "Hello, world!",
121
+ },
122
+ ]
123
+ `);
124
+
125
+ expect(mockModel.doStreamCalls[0].responseFormat)
126
+ .toMatchInlineSnapshot(`
127
+ {
128
+ "description": undefined,
129
+ "name": undefined,
130
+ "schema": {
131
+ "$schema": "http://json-schema.org/draft-07/schema#",
132
+ "additionalProperties": false,
133
+ "properties": {
134
+ "content": {
135
+ "type": "string",
136
+ },
137
+ },
138
+ "required": [
139
+ "content",
140
+ ],
141
+ "type": "object",
142
+ },
143
+ "type": "json",
144
+ }
145
+ `);
146
+ });
147
+
148
+ it('should use name and description', async () => {
149
+ const model = createTestModel();
150
+
151
+ const result = streamObject({
152
+ model,
153
+ schema: z.object({ content: z.string() }),
154
+ schemaName: 'test-name',
155
+ schemaDescription: 'test description',
156
+ prompt: 'prompt',
157
+ });
158
+
159
+ expect(await convertAsyncIterableToArray(result.partialObjectStream))
160
+ .toMatchInlineSnapshot(`
161
+ [
162
+ {},
163
+ {
164
+ "content": "Hello, ",
165
+ },
166
+ {
167
+ "content": "Hello, world",
168
+ },
169
+ {
170
+ "content": "Hello, world!",
171
+ },
172
+ ]
173
+ `);
174
+ expect(model.doStreamCalls[0].prompt).toMatchInlineSnapshot(`
175
+ [
176
+ {
177
+ "content": [
178
+ {
179
+ "text": "prompt",
180
+ "type": "text",
181
+ },
182
+ ],
183
+ "providerOptions": undefined,
184
+ "role": "user",
185
+ },
186
+ ]
187
+ `);
188
+ expect(model.doStreamCalls[0].responseFormat).toMatchInlineSnapshot(`
189
+ {
190
+ "description": "test description",
191
+ "name": "test-name",
192
+ "schema": {
193
+ "$schema": "http://json-schema.org/draft-07/schema#",
194
+ "additionalProperties": false,
195
+ "properties": {
196
+ "content": {
197
+ "type": "string",
198
+ },
199
+ },
200
+ "required": [
201
+ "content",
202
+ ],
203
+ "type": "object",
204
+ },
205
+ "type": "json",
206
+ }
207
+ `);
208
+ });
209
+
210
+ it('should suppress error in partialObjectStream', async () => {
211
+ const result = streamObject({
212
+ model: new MockLanguageModelV3({
213
+ doStream: async () => {
214
+ throw new Error('test error');
215
+ },
216
+ }),
217
+ schema: z.object({ content: z.string() }),
218
+ prompt: 'prompt',
219
+ onError: () => {},
220
+ });
221
+
222
+ expect(
223
+ await convertAsyncIterableToArray(result.partialObjectStream),
224
+ ).toStrictEqual([]);
225
+ });
226
+
227
+ it('should invoke onError callback with Error', async () => {
228
+ const result: Array<{ error: unknown }> = [];
229
+
230
+ const resultObject = streamObject({
231
+ model: new MockLanguageModelV3({
232
+ doStream: async () => {
233
+ throw new Error('test error');
234
+ },
235
+ }),
236
+ schema: z.object({ content: z.string() }),
237
+ prompt: 'prompt',
238
+ onError(event) {
239
+ result.push(event);
240
+ },
241
+ });
242
+
243
+ // consume stream
244
+ await convertAsyncIterableToArray(resultObject.partialObjectStream);
245
+
246
+ expect(result).toStrictEqual([{ error: new Error('test error') }]);
247
+ });
248
+ });
249
+
250
+ describe('result.fullStream', () => {
251
+ it('should send full stream data', async () => {
252
+ const result = streamObject({
253
+ model: createTestModel(),
254
+ schema: z.object({ content: z.string() }),
255
+ prompt: 'prompt',
256
+ });
257
+
258
+ expect(
259
+ await convertAsyncIterableToArray(result.fullStream),
260
+ ).toMatchSnapshot();
261
+ });
262
+ });
263
+
264
+ describe('result.textStream', () => {
265
+ it('should send text stream', async () => {
266
+ const result = streamObject({
267
+ model: createTestModel(),
268
+ schema: z.object({ content: z.string() }),
269
+ prompt: 'prompt',
270
+ });
271
+
272
+ assert.deepStrictEqual(
273
+ await convertAsyncIterableToArray(result.textStream),
274
+ ['{ ', '"content": "Hello, ', 'world', '!"', ' }'],
275
+ );
276
+ });
277
+ });
278
+
279
+ describe('result.toTextStreamResponse', () => {
280
+ it('should create a Response with a text stream', async () => {
281
+ const result = streamObject({
282
+ model: createTestModel(),
283
+ schema: z.object({ content: z.string() }),
284
+ prompt: 'prompt',
285
+ });
286
+
287
+ const response = result.toTextStreamResponse();
288
+
289
+ assert.strictEqual(response.status, 200);
290
+ assert.strictEqual(
291
+ response.headers.get('Content-Type'),
292
+ 'text/plain; charset=utf-8',
293
+ );
294
+
295
+ assert.deepStrictEqual(
296
+ await convertReadableStreamToArray(
297
+ response.body!.pipeThrough(new TextDecoderStream()),
298
+ ),
299
+ ['{ ', '"content": "Hello, ', 'world', '!"', ' }'],
300
+ );
301
+ });
302
+ });
303
+
304
+ describe('result.pipeTextStreamToResponse', async () => {
305
+ it('should write text deltas to a Node.js response-like object', async () => {
306
+ const mockResponse = createMockServerResponse();
307
+
308
+ const result = streamObject({
309
+ model: createTestModel(),
310
+ schema: z.object({ content: z.string() }),
311
+ prompt: 'prompt',
312
+ });
313
+
314
+ result.pipeTextStreamToResponse(mockResponse);
315
+
316
+ await mockResponse.waitForEnd();
317
+
318
+ expect(mockResponse.statusCode).toBe(200);
319
+ expect(mockResponse.headers).toMatchInlineSnapshot(`
320
+ {
321
+ "content-type": "text/plain; charset=utf-8",
322
+ }
323
+ `);
324
+ expect(mockResponse.getDecodedChunks()).toMatchInlineSnapshot(`
325
+ [
326
+ "{ ",
327
+ ""content": "Hello, ",
328
+ "world",
329
+ "!"",
330
+ " }",
331
+ ]
332
+ `);
333
+ });
334
+ });
335
+
336
+ describe('result.usage', () => {
337
+ it('should resolve with token usage', async () => {
338
+ const result = streamObject({
339
+ model: createTestModel({
340
+ stream: convertArrayToReadableStream([
341
+ { type: 'text-start', id: '1' },
342
+ {
343
+ type: 'text-delta',
344
+ id: '1',
345
+ delta: '{ "content": "Hello, world!" }',
346
+ },
347
+ { type: 'text-end', id: '1' },
348
+ {
349
+ type: 'finish',
350
+ finishReason: { unified: 'stop', raw: 'stop' },
351
+ usage: testUsage,
352
+ },
353
+ ]),
354
+ }),
355
+ schema: z.object({ content: z.string() }),
356
+ prompt: 'prompt',
357
+ });
358
+
359
+ // consume stream (runs in parallel)
360
+ convertAsyncIterableToArray(result.partialObjectStream);
361
+
362
+ expect(await result.usage).toMatchInlineSnapshot(`
363
+ {
364
+ "cachedInputTokens": undefined,
365
+ "inputTokenDetails": {
366
+ "cacheReadTokens": undefined,
367
+ "cacheWriteTokens": undefined,
368
+ "noCacheTokens": 3,
369
+ },
370
+ "inputTokens": 3,
371
+ "outputTokenDetails": {
372
+ "reasoningTokens": undefined,
373
+ "textTokens": 10,
374
+ },
375
+ "outputTokens": 10,
376
+ "raw": undefined,
377
+ "reasoningTokens": undefined,
378
+ "totalTokens": 13,
379
+ }
380
+ `);
381
+ });
382
+ });
383
+
384
+ describe('result.providerMetadata', () => {
385
+ it('should resolve with provider metadata', async () => {
386
+ const result = streamObject({
387
+ model: createTestModel({
388
+ stream: convertArrayToReadableStream([
389
+ { type: 'text-start', id: '1' },
390
+ {
391
+ type: 'text-delta',
392
+ id: '1',
393
+ delta: '{ "content": "Hello, world!" }',
394
+ },
395
+ { type: 'text-end', id: '1' },
396
+ {
397
+ type: 'finish',
398
+ finishReason: { unified: 'stop', raw: 'stop' },
399
+ usage: testUsage,
400
+ providerMetadata: {
401
+ testProvider: { testKey: 'testValue' },
402
+ },
403
+ },
404
+ ]),
405
+ }),
406
+ schema: z.object({ content: z.string() }),
407
+ prompt: 'prompt',
408
+ });
409
+
410
+ // consume stream (runs in parallel)
411
+ convertAsyncIterableToArray(result.partialObjectStream);
412
+
413
+ expect(await result.providerMetadata).toStrictEqual({
414
+ testProvider: { testKey: 'testValue' },
415
+ });
416
+ });
417
+ });
418
+
419
+ describe('result.response', () => {
420
+ it('should resolve with response information', async () => {
421
+ const result = streamObject({
422
+ model: createTestModel({
423
+ stream: convertArrayToReadableStream([
424
+ {
425
+ type: 'response-metadata',
426
+ id: 'id-0',
427
+ modelId: 'mock-model-id',
428
+ timestamp: new Date(0),
429
+ },
430
+ { type: 'text-start', id: '1' },
431
+ {
432
+ type: 'text-delta',
433
+ id: '1',
434
+ delta: '{"content": "Hello, world!"}',
435
+ },
436
+ { type: 'text-end', id: '1' },
437
+ {
438
+ type: 'finish',
439
+ finishReason: { unified: 'stop', raw: 'stop' },
440
+ usage: testUsage,
441
+ },
442
+ ]),
443
+ response: { headers: { call: '2' } },
444
+ }),
445
+ schema: z.object({ content: z.string() }),
446
+ prompt: 'prompt',
447
+ });
448
+
449
+ // consume stream (runs in parallel)
450
+ convertAsyncIterableToArray(result.partialObjectStream);
451
+
452
+ expect(await result.response).toStrictEqual({
453
+ id: 'id-0',
454
+ modelId: 'mock-model-id',
455
+ timestamp: new Date(0),
456
+ headers: { call: '2' },
457
+ });
458
+ });
459
+ });
460
+
461
+ describe('result.request', () => {
462
+ it('should contain request information', async () => {
463
+ const result = streamObject({
464
+ model: new MockLanguageModelV3({
465
+ doStream: async () => ({
466
+ stream: convertArrayToReadableStream([
467
+ {
468
+ type: 'response-metadata',
469
+ id: 'id-0',
470
+ modelId: 'mock-model-id',
471
+ timestamp: new Date(0),
472
+ },
473
+ { type: 'text-start', id: '1' },
474
+ {
475
+ type: 'text-delta',
476
+ id: '1',
477
+ delta: '{"content": "Hello, world!"}',
478
+ },
479
+ { type: 'text-end', id: '1' },
480
+ {
481
+ type: 'finish',
482
+ finishReason: { unified: 'stop', raw: 'stop' },
483
+ usage: testUsage,
484
+ },
485
+ ]),
486
+ request: { body: 'test body' },
487
+ }),
488
+ }),
489
+ schema: z.object({ content: z.string() }),
490
+ prompt: 'prompt',
491
+ });
492
+
493
+ // consume stream (runs in parallel)
494
+ await convertAsyncIterableToArray(result.partialObjectStream);
495
+
496
+ expect(await result.request).toStrictEqual({
497
+ body: 'test body',
498
+ });
499
+ });
500
+ });
501
+
502
+ describe('result.object', () => {
503
+ it('should resolve with typed object', async () => {
504
+ const result = streamObject({
505
+ model: new MockLanguageModelV3({
506
+ doStream: async () => ({
507
+ stream: convertArrayToReadableStream([
508
+ { type: 'text-start', id: '1' },
509
+ { type: 'text-delta', id: '1', delta: '{ ' },
510
+ { type: 'text-delta', id: '1', delta: '"content": ' },
511
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
512
+ { type: 'text-delta', id: '1', delta: `world` },
513
+ { type: 'text-delta', id: '1', delta: `!"` },
514
+ { type: 'text-delta', id: '1', delta: ' }' },
515
+ { type: 'text-end', id: '1' },
516
+ {
517
+ type: 'finish',
518
+ finishReason: { unified: 'stop', raw: 'stop' },
519
+ usage: testUsage,
520
+ },
521
+ ]),
522
+ }),
523
+ }),
524
+ schema: z.object({ content: z.string() }),
525
+ prompt: 'prompt',
526
+ });
527
+
528
+ // consume stream (runs in parallel)
529
+ convertAsyncIterableToArray(result.partialObjectStream);
530
+
531
+ assert.deepStrictEqual(await result.object, {
532
+ content: 'Hello, world!',
533
+ });
534
+ });
535
+
536
+ it('should reject object promise when the streamed object does not match the schema', async () => {
537
+ const result = streamObject({
538
+ model: new MockLanguageModelV3({
539
+ doStream: async () => ({
540
+ stream: convertArrayToReadableStream([
541
+ { type: 'text-start', id: '1' },
542
+ { type: 'text-delta', id: '1', delta: '{ ' },
543
+ { type: 'text-delta', id: '1', delta: '"invalid": ' },
544
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
545
+ { type: 'text-delta', id: '1', delta: `world` },
546
+ { type: 'text-delta', id: '1', delta: `!"` },
547
+ { type: 'text-delta', id: '1', delta: ' }' },
548
+ { type: 'text-end', id: '1' },
549
+ {
550
+ type: 'finish',
551
+ finishReason: { unified: 'stop', raw: 'stop' },
552
+ usage: testUsage,
553
+ },
554
+ ]),
555
+ }),
556
+ }),
557
+ schema: z.object({ content: z.string() }),
558
+ prompt: 'prompt',
559
+ });
560
+
561
+ // consume stream (runs in parallel)
562
+ convertAsyncIterableToArray(result.partialObjectStream);
563
+
564
+ expect(result.object).rejects.toThrow(NoObjectGeneratedError);
565
+ });
566
+
567
+ it('should not lead to unhandled promise rejections when the streamed object does not match the schema', async () => {
568
+ const result = streamObject({
569
+ model: new MockLanguageModelV3({
570
+ doStream: async () => ({
571
+ stream: convertArrayToReadableStream([
572
+ { type: 'text-start', id: '1' },
573
+ { type: 'text-delta', id: '1', delta: '{ ' },
574
+ { type: 'text-delta', id: '1', delta: '"invalid": ' },
575
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
576
+ { type: 'text-delta', id: '1', delta: `world` },
577
+ { type: 'text-delta', id: '1', delta: `!"` },
578
+ { type: 'text-delta', id: '1', delta: ' }' },
579
+ { type: 'text-end', id: '1' },
580
+ {
581
+ type: 'finish',
582
+ finishReason: { unified: 'stop', raw: 'stop' },
583
+ usage: testUsage,
584
+ },
585
+ ]),
586
+ }),
587
+ }),
588
+ schema: z.object({ content: z.string() }),
589
+ prompt: 'prompt',
590
+ });
591
+
592
+ // consume stream (runs in parallel)
593
+ convertAsyncIterableToArray(result.partialObjectStream);
594
+
595
+ // unhandled promise rejection should not be thrown (Vitest does this automatically)
596
+ });
597
+ });
598
+
599
+ describe('result.finishReason', () => {
600
+ it('should resolve with finish reason', async () => {
601
+ const result = streamObject({
602
+ model: new MockLanguageModelV3({
603
+ doStream: async () => ({
604
+ stream: convertArrayToReadableStream([
605
+ { type: 'text-start', id: '1' },
606
+ { type: 'text-delta', id: '1', delta: '{ ' },
607
+ { type: 'text-delta', id: '1', delta: '"content": ' },
608
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
609
+ { type: 'text-delta', id: '1', delta: `world` },
610
+ { type: 'text-delta', id: '1', delta: `!"` },
611
+ { type: 'text-delta', id: '1', delta: ' }' },
612
+ { type: 'text-end', id: '1' },
613
+ {
614
+ type: 'finish',
615
+ finishReason: { unified: 'stop', raw: 'stop' },
616
+ usage: testUsage,
617
+ },
618
+ ]),
619
+ }),
620
+ }),
621
+ schema: z.object({ content: z.string() }),
622
+ prompt: 'prompt',
623
+ });
624
+
625
+ // consume stream (runs in parallel)
626
+ convertAsyncIterableToArray(result.partialObjectStream);
627
+
628
+ expect(await result.finishReason).toStrictEqual('stop');
629
+ });
630
+ });
631
+
632
+ describe('options.onFinish', () => {
633
+ it('should be called when a valid object is generated', async () => {
634
+ let result: Parameters<
635
+ Required<Parameters<typeof streamObject>[0]>['onFinish']
636
+ >[0];
637
+
638
+ const { partialObjectStream } = streamObject({
639
+ model: new MockLanguageModelV3({
640
+ doStream: async () => ({
641
+ stream: convertArrayToReadableStream([
642
+ {
643
+ type: 'response-metadata',
644
+ id: 'id-0',
645
+ modelId: 'mock-model-id',
646
+ timestamp: new Date(0),
647
+ },
648
+ { type: 'text-start', id: '1' },
649
+ {
650
+ type: 'text-delta',
651
+ id: '1',
652
+ delta: '{ "content": "Hello, world!" }',
653
+ },
654
+ { type: 'text-end', id: '1' },
655
+ {
656
+ type: 'finish',
657
+ finishReason: { unified: 'stop', raw: 'stop' },
658
+ usage: testUsage,
659
+ providerMetadata: {
660
+ testProvider: { testKey: 'testValue' },
661
+ },
662
+ },
663
+ ]),
664
+ }),
665
+ }),
666
+ schema: z.object({ content: z.string() }),
667
+ prompt: 'prompt',
668
+ onFinish: async event => {
669
+ result = event as unknown as typeof result;
670
+ },
671
+ });
672
+
673
+ // consume stream
674
+ await convertAsyncIterableToArray(partialObjectStream);
675
+
676
+ expect(result!).toMatchSnapshot();
677
+ });
678
+
679
+ it("should be called when object doesn't match the schema", async () => {
680
+ let result: Parameters<
681
+ Required<Parameters<typeof streamObject>[0]>['onFinish']
682
+ >[0];
683
+
684
+ const { partialObjectStream, object } = streamObject({
685
+ model: new MockLanguageModelV3({
686
+ doStream: async () => ({
687
+ stream: convertArrayToReadableStream([
688
+ {
689
+ type: 'response-metadata',
690
+ id: 'id-0',
691
+ modelId: 'mock-model-id',
692
+ timestamp: new Date(0),
693
+ },
694
+ { type: 'text-start', id: '1' },
695
+ { type: 'text-delta', id: '1', delta: '{ ' },
696
+ { type: 'text-delta', id: '1', delta: '"invalid": ' },
697
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
698
+ { type: 'text-delta', id: '1', delta: `world` },
699
+ { type: 'text-delta', id: '1', delta: `!"` },
700
+ { type: 'text-delta', id: '1', delta: ' }' },
701
+ { type: 'text-end', id: '1' },
702
+ {
703
+ type: 'finish',
704
+ finishReason: { unified: 'stop', raw: 'stop' },
705
+ usage: testUsage,
706
+ },
707
+ ]),
708
+ }),
709
+ }),
710
+ schema: z.object({ content: z.string() }),
711
+ prompt: 'prompt',
712
+ onFinish: async event => {
713
+ result = event as unknown as typeof result;
714
+ },
715
+ });
716
+
717
+ // consume stream
718
+ await convertAsyncIterableToArray(partialObjectStream);
719
+
720
+ // consume expected error rejection
721
+ await object.catch(() => {});
722
+
723
+ expect(result!).toMatchSnapshot();
724
+ });
725
+ });
726
+
727
+ describe('options.headers', () => {
728
+ it('should pass headers to model', async () => {
729
+ const result = streamObject({
730
+ model: new MockLanguageModelV3({
731
+ doStream: async ({ headers }) => {
732
+ expect(headers).toStrictEqual({
733
+ 'custom-request-header': 'request-header-value',
734
+ });
735
+
736
+ return {
737
+ stream: convertArrayToReadableStream([
738
+ { type: 'text-start', id: '1' },
739
+ {
740
+ type: 'text-delta',
741
+ id: '1',
742
+ delta: `{ "content": "headers test" }`,
743
+ },
744
+ { type: 'text-end', id: '1' },
745
+ {
746
+ type: 'finish',
747
+ finishReason: { unified: 'stop', raw: 'stop' },
748
+ usage: testUsage,
749
+ },
750
+ ]),
751
+ };
752
+ },
753
+ }),
754
+ schema: z.object({ content: z.string() }),
755
+ prompt: 'prompt',
756
+ headers: { 'custom-request-header': 'request-header-value' },
757
+ });
758
+
759
+ expect(
760
+ await convertAsyncIterableToArray(result.partialObjectStream),
761
+ ).toStrictEqual([{ content: 'headers test' }]);
762
+ });
763
+ });
764
+
765
+ describe('options.providerOptions', () => {
766
+ it('should pass provider options to model', async () => {
767
+ const result = streamObject({
768
+ model: new MockLanguageModelV3({
769
+ doStream: async ({ providerOptions }) => {
770
+ expect(providerOptions).toStrictEqual({
771
+ aProvider: { someKey: 'someValue' },
772
+ });
773
+
774
+ return {
775
+ stream: convertArrayToReadableStream([
776
+ { type: 'text-start', id: '1' },
777
+ {
778
+ type: 'text-delta',
779
+ id: '1',
780
+ delta: `{ "content": "provider metadata test" }`,
781
+ },
782
+ { type: 'text-end', id: '1' },
783
+ {
784
+ type: 'finish',
785
+ finishReason: { unified: 'stop', raw: 'stop' },
786
+ usage: testUsage,
787
+ },
788
+ ]),
789
+ };
790
+ },
791
+ }),
792
+ schema: z.object({ content: z.string() }),
793
+ prompt: 'prompt',
794
+ providerOptions: {
795
+ aProvider: { someKey: 'someValue' },
796
+ },
797
+ });
798
+
799
+ expect(
800
+ await convertAsyncIterableToArray(result.partialObjectStream),
801
+ ).toStrictEqual([{ content: 'provider metadata test' }]);
802
+ });
803
+ });
804
+
805
+ describe('custom schema', () => {
806
+ it('should send object deltas', async () => {
807
+ const mockModel = createTestModel();
808
+
809
+ const result = streamObject({
810
+ model: mockModel,
811
+ schema: jsonSchema({
812
+ type: 'object',
813
+ properties: { content: { type: 'string' } },
814
+ required: ['content'],
815
+ additionalProperties: false,
816
+ }),
817
+ prompt: 'prompt',
818
+ });
819
+
820
+ expect(await convertAsyncIterableToArray(result.partialObjectStream))
821
+ .toMatchInlineSnapshot(`
822
+ [
823
+ {},
824
+ {
825
+ "content": "Hello, ",
826
+ },
827
+ {
828
+ "content": "Hello, world",
829
+ },
830
+ {
831
+ "content": "Hello, world!",
832
+ },
833
+ ]
834
+ `);
835
+
836
+ expect(mockModel.doStreamCalls[0].responseFormat)
837
+ .toMatchInlineSnapshot(`
838
+ {
839
+ "description": undefined,
840
+ "name": undefined,
841
+ "schema": {
842
+ "additionalProperties": false,
843
+ "properties": {
844
+ "content": {
845
+ "type": "string",
846
+ },
847
+ },
848
+ "required": [
849
+ "content",
850
+ ],
851
+ "type": "object",
852
+ },
853
+ "type": "json",
854
+ }
855
+ `);
856
+ });
857
+ });
858
+
859
+ describe('error handling', () => {
860
+ it('should throw NoObjectGeneratedError when schema validation fails', async () => {
861
+ const result = streamObject({
862
+ model: new MockLanguageModelV3({
863
+ doStream: async () => ({
864
+ stream: convertArrayToReadableStream([
865
+ { type: 'text-start', id: '1' },
866
+ { type: 'text-delta', id: '1', delta: '{ "content": 123 }' },
867
+ { type: 'text-end', id: '1' },
868
+ {
869
+ type: 'response-metadata',
870
+ id: 'id-1',
871
+ timestamp: new Date(123),
872
+ modelId: 'model-1',
873
+ },
874
+ {
875
+ type: 'finish',
876
+ finishReason: { unified: 'stop', raw: 'stop' },
877
+ usage: testUsage,
878
+ },
879
+ ]),
880
+ }),
881
+ }),
882
+ schema: z.object({ content: z.string() }),
883
+ prompt: 'prompt',
884
+ });
885
+
886
+ try {
887
+ await convertAsyncIterableToArray(result.partialObjectStream);
888
+ await result.object;
889
+ fail('must throw error');
890
+ } catch (error) {
891
+ verifyNoObjectGeneratedError(error, {
892
+ message: 'No object generated: response did not match schema.',
893
+ response: {
894
+ id: 'id-1',
895
+ timestamp: new Date(123),
896
+ modelId: 'model-1',
897
+ },
898
+ usage: asLanguageModelUsage(testUsage),
899
+ finishReason: 'stop',
900
+ });
901
+ }
902
+ });
903
+
904
+ it('should throw NoObjectGeneratedError when parsing fails', async () => {
905
+ const result = streamObject({
906
+ model: new MockLanguageModelV3({
907
+ doStream: async () => ({
908
+ stream: convertArrayToReadableStream([
909
+ { type: 'text-start', id: '1' },
910
+ { type: 'text-delta', id: '1', delta: '{ broken json' },
911
+ { type: 'text-end', id: '1' },
912
+ {
913
+ type: 'response-metadata',
914
+ id: 'id-1',
915
+ timestamp: new Date(123),
916
+ modelId: 'model-1',
917
+ },
918
+ {
919
+ type: 'finish',
920
+ finishReason: { unified: 'stop', raw: 'stop' },
921
+ usage: testUsage,
922
+ },
923
+ ]),
924
+ }),
925
+ }),
926
+ schema: z.object({ content: z.string() }),
927
+ prompt: 'prompt',
928
+ });
929
+
930
+ try {
931
+ await convertAsyncIterableToArray(result.partialObjectStream);
932
+ await result.object;
933
+ fail('must throw error');
934
+ } catch (error) {
935
+ verifyNoObjectGeneratedError(error, {
936
+ message: 'No object generated: could not parse the response.',
937
+ response: {
938
+ id: 'id-1',
939
+ timestamp: new Date(123),
940
+ modelId: 'model-1',
941
+ },
942
+ usage: asLanguageModelUsage(testUsage),
943
+ finishReason: 'stop',
944
+ });
945
+ }
946
+ });
947
+
948
+ it('should throw NoObjectGeneratedError when no text is generated', async () => {
949
+ const result = streamObject({
950
+ model: new MockLanguageModelV3({
951
+ doStream: async () => ({
952
+ stream: convertArrayToReadableStream([
953
+ {
954
+ type: 'response-metadata',
955
+ id: 'id-1',
956
+ timestamp: new Date(123),
957
+ modelId: 'model-1',
958
+ },
959
+ {
960
+ type: 'finish',
961
+ finishReason: { unified: 'stop', raw: 'stop' },
962
+ usage: testUsage,
963
+ },
964
+ ]),
965
+ }),
966
+ }),
967
+ schema: z.object({ content: z.string() }),
968
+ prompt: 'prompt',
969
+ });
970
+
971
+ try {
972
+ await convertAsyncIterableToArray(result.partialObjectStream);
973
+ await result.object;
974
+ fail('must throw error');
975
+ } catch (error) {
976
+ verifyNoObjectGeneratedError(error, {
977
+ message: 'No object generated: could not parse the response.',
978
+ response: {
979
+ id: 'id-1',
980
+ timestamp: new Date(123),
981
+ modelId: 'model-1',
982
+ },
983
+ usage: asLanguageModelUsage(testUsage),
984
+ finishReason: 'stop',
985
+ });
986
+ }
987
+ });
988
+ });
989
+ });
990
+
991
+ describe('output = "array"', () => {
992
+ describe('array with 3 elements', () => {
993
+ let result: StreamObjectResult<
994
+ { content: string }[],
995
+ { content: string }[],
996
+ AsyncIterableStream<{ content: string }>
997
+ >;
998
+
999
+ let onFinishResult: Parameters<
1000
+ Required<Parameters<typeof streamObject>[0]>['onFinish']
1001
+ >[0];
1002
+
1003
+ beforeEach(async () => {
1004
+ result = streamObject({
1005
+ model: createTestModel({
1006
+ stream: convertArrayToReadableStream([
1007
+ { type: 'text-start', id: '1' },
1008
+ { type: 'text-delta', id: '1', delta: '{"elements":[' },
1009
+ // first element:
1010
+ { type: 'text-delta', id: '1', delta: '{' },
1011
+ { type: 'text-delta', id: '1', delta: '"content":' },
1012
+ { type: 'text-delta', id: '1', delta: `"element 1"` },
1013
+ { type: 'text-delta', id: '1', delta: '},' },
1014
+ // second element:
1015
+ { type: 'text-delta', id: '1', delta: '{ ' },
1016
+ { type: 'text-delta', id: '1', delta: '"content": ' },
1017
+ { type: 'text-delta', id: '1', delta: `"element 2"` },
1018
+ { type: 'text-delta', id: '1', delta: '},' },
1019
+ // third element:
1020
+ { type: 'text-delta', id: '1', delta: '{' },
1021
+ { type: 'text-delta', id: '1', delta: '"content":' },
1022
+ { type: 'text-delta', id: '1', delta: `"element 3"` },
1023
+ { type: 'text-delta', id: '1', delta: '}' },
1024
+ // end of array
1025
+ { type: 'text-delta', id: '1', delta: ']' },
1026
+ { type: 'text-delta', id: '1', delta: '}' },
1027
+ { type: 'text-end', id: '1' },
1028
+ // finish
1029
+ {
1030
+ type: 'finish',
1031
+ finishReason: { unified: 'stop', raw: 'stop' },
1032
+ usage: testUsage,
1033
+ },
1034
+ ]),
1035
+ }),
1036
+ schema: z.object({ content: z.string() }),
1037
+ output: 'array',
1038
+ prompt: 'prompt',
1039
+ onFinish: async event => {
1040
+ onFinishResult = event as unknown as typeof onFinishResult;
1041
+ },
1042
+ });
1043
+ });
1044
+
1045
+ it('should stream only complete objects in partialObjectStream', async () => {
1046
+ assert.deepStrictEqual(
1047
+ await convertAsyncIterableToArray(result.partialObjectStream),
1048
+ [
1049
+ [],
1050
+ [{ content: 'element 1' }],
1051
+ [{ content: 'element 1' }, { content: 'element 2' }],
1052
+ [
1053
+ { content: 'element 1' },
1054
+ { content: 'element 2' },
1055
+ { content: 'element 3' },
1056
+ ],
1057
+ ],
1058
+ );
1059
+ });
1060
+
1061
+ it('should stream only complete objects in textStream', async () => {
1062
+ assert.deepStrictEqual(
1063
+ await convertAsyncIterableToArray(result.textStream),
1064
+ [
1065
+ '[',
1066
+ '{"content":"element 1"}',
1067
+ ',{"content":"element 2"}',
1068
+ ',{"content":"element 3"}]',
1069
+ ],
1070
+ );
1071
+ });
1072
+
1073
+ it('should have the correct object result', async () => {
1074
+ // consume stream
1075
+ await convertAsyncIterableToArray(result.partialObjectStream);
1076
+
1077
+ expect(await result.object).toStrictEqual([
1078
+ { content: 'element 1' },
1079
+ { content: 'element 2' },
1080
+ { content: 'element 3' },
1081
+ ]);
1082
+ });
1083
+
1084
+ it('should call onFinish callback with full array', async () => {
1085
+ expect(onFinishResult.object).toStrictEqual([
1086
+ { content: 'element 1' },
1087
+ { content: 'element 2' },
1088
+ { content: 'element 3' },
1089
+ ]);
1090
+ });
1091
+
1092
+ it('should stream elements individually in elementStream', async () => {
1093
+ assert.deepStrictEqual(
1094
+ await convertAsyncIterableToArray(result.elementStream),
1095
+ [
1096
+ { content: 'element 1' },
1097
+ { content: 'element 2' },
1098
+ { content: 'element 3' },
1099
+ ],
1100
+ );
1101
+ });
1102
+ });
1103
+
1104
+ describe('array with 2 elements streamed in 1 chunk', () => {
1105
+ let result: StreamObjectResult<
1106
+ { content: string }[],
1107
+ { content: string }[],
1108
+ AsyncIterableStream<{ content: string }>
1109
+ >;
1110
+
1111
+ let onFinishResult: Parameters<
1112
+ Required<Parameters<typeof streamObject>[0]>['onFinish']
1113
+ >[0];
1114
+
1115
+ beforeEach(async () => {
1116
+ result = streamObject({
1117
+ model: createTestModel({
1118
+ stream: convertArrayToReadableStream([
1119
+ {
1120
+ type: 'text-start',
1121
+ id: '1',
1122
+ },
1123
+ {
1124
+ type: 'text-delta',
1125
+ id: '1',
1126
+ delta:
1127
+ '{"elements":[{"content":"element 1"},{"content":"element 2"}]}',
1128
+ },
1129
+ {
1130
+ type: 'text-end',
1131
+ id: '1',
1132
+ },
1133
+ {
1134
+ type: 'finish',
1135
+ finishReason: { unified: 'stop', raw: 'stop' },
1136
+ usage: testUsage,
1137
+ },
1138
+ ]),
1139
+ }),
1140
+ schema: z.object({ content: z.string() }),
1141
+ output: 'array',
1142
+ prompt: 'prompt',
1143
+ onFinish: async event => {
1144
+ onFinishResult = event as unknown as typeof onFinishResult;
1145
+ },
1146
+ });
1147
+ });
1148
+
1149
+ it('should stream only complete objects in partialObjectStream', async () => {
1150
+ assert.deepStrictEqual(
1151
+ await convertAsyncIterableToArray(result.partialObjectStream),
1152
+ [[{ content: 'element 1' }, { content: 'element 2' }]],
1153
+ );
1154
+ });
1155
+
1156
+ it('should stream only complete objects in textStream', async () => {
1157
+ assert.deepStrictEqual(
1158
+ await convertAsyncIterableToArray(result.textStream),
1159
+ ['[{"content":"element 1"},{"content":"element 2"}]'],
1160
+ );
1161
+ });
1162
+
1163
+ it('should have the correct object result', async () => {
1164
+ // consume stream
1165
+ await convertAsyncIterableToArray(result.partialObjectStream);
1166
+
1167
+ expect(await result.object).toStrictEqual([
1168
+ { content: 'element 1' },
1169
+ { content: 'element 2' },
1170
+ ]);
1171
+ });
1172
+
1173
+ it('should call onFinish callback with full array', async () => {
1174
+ expect(onFinishResult.object).toStrictEqual([
1175
+ { content: 'element 1' },
1176
+ { content: 'element 2' },
1177
+ ]);
1178
+ });
1179
+
1180
+ it('should stream elements individually in elementStream', async () => {
1181
+ assert.deepStrictEqual(
1182
+ await convertAsyncIterableToArray(result.elementStream),
1183
+ [{ content: 'element 1' }, { content: 'element 2' }],
1184
+ );
1185
+ });
1186
+ });
1187
+ });
1188
+
1189
+ describe('output = "enum"', () => {
1190
+ it('should stream an enum value', async () => {
1191
+ const mockModel = createTestModel({
1192
+ stream: convertArrayToReadableStream([
1193
+ { type: 'text-start', id: '1' },
1194
+ { type: 'text-delta', id: '1', delta: '{ ' },
1195
+ { type: 'text-delta', id: '1', delta: '"result": ' },
1196
+ { type: 'text-delta', id: '1', delta: `"su` },
1197
+ { type: 'text-delta', id: '1', delta: `nny` },
1198
+ { type: 'text-delta', id: '1', delta: `"` },
1199
+ { type: 'text-delta', id: '1', delta: ' }' },
1200
+ { type: 'text-end', id: '1' },
1201
+ {
1202
+ type: 'finish',
1203
+ finishReason: { unified: 'stop', raw: 'stop' },
1204
+ usage: testUsage,
1205
+ },
1206
+ ]),
1207
+ });
1208
+
1209
+ const result = streamObject({
1210
+ model: mockModel,
1211
+ output: 'enum',
1212
+ enum: ['sunny', 'rainy', 'snowy'],
1213
+ prompt: 'prompt',
1214
+ });
1215
+
1216
+ expect(await convertAsyncIterableToArray(result.partialObjectStream))
1217
+ .toMatchInlineSnapshot(`
1218
+ [
1219
+ "sunny",
1220
+ ]
1221
+ `);
1222
+
1223
+ expect(mockModel.doStreamCalls[0].responseFormat).toMatchInlineSnapshot(`
1224
+ {
1225
+ "description": undefined,
1226
+ "name": undefined,
1227
+ "schema": {
1228
+ "$schema": "http://json-schema.org/draft-07/schema#",
1229
+ "additionalProperties": false,
1230
+ "properties": {
1231
+ "result": {
1232
+ "enum": [
1233
+ "sunny",
1234
+ "rainy",
1235
+ "snowy",
1236
+ ],
1237
+ "type": "string",
1238
+ },
1239
+ },
1240
+ "required": [
1241
+ "result",
1242
+ ],
1243
+ "type": "object",
1244
+ },
1245
+ "type": "json",
1246
+ }
1247
+ `);
1248
+ });
1249
+
1250
+ it('should not stream incorrect values', async () => {
1251
+ const mockModel = new MockLanguageModelV3({
1252
+ doStream: {
1253
+ stream: convertArrayToReadableStream([
1254
+ { type: 'text-start', id: '1' },
1255
+ { type: 'text-delta', id: '1', delta: '{ ' },
1256
+ { type: 'text-delta', id: '1', delta: '"result": ' },
1257
+ { type: 'text-delta', id: '1', delta: `"foo` },
1258
+ { type: 'text-delta', id: '1', delta: `bar` },
1259
+ { type: 'text-delta', id: '1', delta: `"` },
1260
+ { type: 'text-delta', id: '1', delta: ' }' },
1261
+ { type: 'text-end', id: '1' },
1262
+ {
1263
+ type: 'finish',
1264
+ finishReason: { unified: 'stop', raw: 'stop' },
1265
+ usage: testUsage,
1266
+ },
1267
+ ]),
1268
+ },
1269
+ });
1270
+
1271
+ const result = streamObject({
1272
+ model: mockModel,
1273
+ output: 'enum',
1274
+ enum: ['sunny', 'rainy', 'snowy'],
1275
+ prompt: 'prompt',
1276
+ });
1277
+
1278
+ expect(
1279
+ await convertAsyncIterableToArray(result.partialObjectStream),
1280
+ ).toMatchInlineSnapshot(`[]`);
1281
+ });
1282
+
1283
+ it('should handle ambiguous values', async () => {
1284
+ const mockModel = createTestModel({
1285
+ stream: convertArrayToReadableStream([
1286
+ { type: 'text-start', id: '1' },
1287
+ { type: 'text-delta', id: '1', delta: '{ ' },
1288
+ { type: 'text-delta', id: '1', delta: '"result": ' },
1289
+ { type: 'text-delta', id: '1', delta: `"foo` },
1290
+ { type: 'text-delta', id: '1', delta: `bar` },
1291
+ { type: 'text-delta', id: '1', delta: `"` },
1292
+ { type: 'text-delta', id: '1', delta: ' }' },
1293
+ {
1294
+ type: 'finish',
1295
+ finishReason: { unified: 'stop', raw: 'stop' },
1296
+ usage: testUsage,
1297
+ },
1298
+ ]),
1299
+ });
1300
+
1301
+ const result = streamObject({
1302
+ model: mockModel,
1303
+ output: 'enum',
1304
+ enum: ['foobar', 'foobar2'],
1305
+ prompt: 'prompt',
1306
+ });
1307
+
1308
+ expect(await convertAsyncIterableToArray(result.partialObjectStream))
1309
+ .toMatchInlineSnapshot(`
1310
+ [
1311
+ "foo",
1312
+ "foobar",
1313
+ ]
1314
+ `);
1315
+ });
1316
+
1317
+ it('should handle non-ambiguous values', async () => {
1318
+ const mockModel = createTestModel({
1319
+ stream: convertArrayToReadableStream([
1320
+ { type: 'text-start', id: '1' },
1321
+ { type: 'text-delta', id: '1', delta: '{ ' },
1322
+ { type: 'text-delta', id: '1', delta: '"result": ' },
1323
+ { type: 'text-delta', id: '1', delta: `"foo` },
1324
+ { type: 'text-delta', id: '1', delta: `bar` },
1325
+ { type: 'text-delta', id: '1', delta: `"` },
1326
+ { type: 'text-delta', id: '1', delta: ' }' },
1327
+ { type: 'text-end', id: '1' },
1328
+ {
1329
+ type: 'finish',
1330
+ finishReason: { unified: 'stop', raw: 'stop' },
1331
+ usage: testUsage,
1332
+ },
1333
+ ]),
1334
+ });
1335
+
1336
+ const result = streamObject({
1337
+ model: mockModel,
1338
+ output: 'enum',
1339
+ enum: ['foobar', 'barfoo'],
1340
+ prompt: 'prompt',
1341
+ });
1342
+
1343
+ expect(await convertAsyncIterableToArray(result.partialObjectStream))
1344
+ .toMatchInlineSnapshot(`
1345
+ [
1346
+ "foobar",
1347
+ ]
1348
+ `);
1349
+ });
1350
+ });
1351
+
1352
+ describe('output = "no-schema"', () => {
1353
+ it('should send object deltas', async () => {
1354
+ const mockModel = createTestModel({
1355
+ stream: convertArrayToReadableStream([
1356
+ { type: 'text-start', id: '1' },
1357
+ { type: 'text-delta', id: '1', delta: '{ ' },
1358
+ { type: 'text-delta', id: '1', delta: '"content": ' },
1359
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
1360
+ { type: 'text-delta', id: '1', delta: `world` },
1361
+ { type: 'text-delta', id: '1', delta: `!"` },
1362
+ { type: 'text-delta', id: '1', delta: ' }' },
1363
+ { type: 'text-end', id: '1' },
1364
+ {
1365
+ type: 'finish',
1366
+ finishReason: { unified: 'stop', raw: 'stop' },
1367
+ usage: testUsage,
1368
+ },
1369
+ ]),
1370
+ });
1371
+
1372
+ const result = streamObject({
1373
+ model: mockModel,
1374
+ output: 'no-schema',
1375
+ prompt: 'prompt',
1376
+ });
1377
+
1378
+ expect(await convertAsyncIterableToArray(result.partialObjectStream))
1379
+ .toMatchInlineSnapshot(`
1380
+ [
1381
+ {},
1382
+ {
1383
+ "content": "Hello, ",
1384
+ },
1385
+ {
1386
+ "content": "Hello, world",
1387
+ },
1388
+ {
1389
+ "content": "Hello, world!",
1390
+ },
1391
+ ]
1392
+ `);
1393
+
1394
+ expect(mockModel.doStreamCalls[0].responseFormat).toMatchInlineSnapshot(`
1395
+ {
1396
+ "description": undefined,
1397
+ "name": undefined,
1398
+ "schema": undefined,
1399
+ "type": "json",
1400
+ }
1401
+ `);
1402
+ });
1403
+ });
1404
+
1405
+ describe('telemetry', () => {
1406
+ let tracer: MockTracer;
1407
+
1408
+ beforeEach(() => {
1409
+ tracer = new MockTracer();
1410
+ });
1411
+
1412
+ it('should not record any telemetry data when not explicitly enabled', async () => {
1413
+ const result = streamObject({
1414
+ model: new MockLanguageModelV3({
1415
+ doStream: async () => ({
1416
+ stream: convertArrayToReadableStream([
1417
+ {
1418
+ type: 'response-metadata',
1419
+ id: 'id-0',
1420
+ modelId: 'mock-model-id',
1421
+ timestamp: new Date(0),
1422
+ },
1423
+ { type: 'text-start', id: '1' },
1424
+ { type: 'text-delta', id: '1', delta: '{ ' },
1425
+ { type: 'text-delta', id: '1', delta: '"content": ' },
1426
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
1427
+ { type: 'text-delta', id: '1', delta: `world` },
1428
+ { type: 'text-delta', id: '1', delta: `!"` },
1429
+ { type: 'text-delta', id: '1', delta: ' }' },
1430
+ { type: 'text-end', id: '1' },
1431
+ {
1432
+ type: 'finish',
1433
+ finishReason: { unified: 'stop', raw: 'stop' },
1434
+ usage: testUsage,
1435
+ },
1436
+ ]),
1437
+ }),
1438
+ }),
1439
+ schema: z.object({ content: z.string() }),
1440
+ prompt: 'prompt',
1441
+ _internal: { now: () => 0 },
1442
+ });
1443
+
1444
+ // consume stream
1445
+ await convertAsyncIterableToArray(result.partialObjectStream);
1446
+
1447
+ expect(tracer.jsonSpans).toMatchSnapshot();
1448
+ });
1449
+
1450
+ it('should record telemetry data when enabled', async () => {
1451
+ const result = streamObject({
1452
+ model: createTestModel({
1453
+ stream: convertArrayToReadableStream([
1454
+ {
1455
+ type: 'response-metadata',
1456
+ id: 'id-0',
1457
+ modelId: 'mock-model-id',
1458
+ timestamp: new Date(0),
1459
+ },
1460
+ { type: 'text-start', id: '1' },
1461
+ { type: 'text-delta', id: '1', delta: '{ ' },
1462
+ { type: 'text-delta', id: '1', delta: '"content": ' },
1463
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
1464
+ { type: 'text-delta', id: '1', delta: `world` },
1465
+ { type: 'text-delta', id: '1', delta: `!"` },
1466
+ { type: 'text-delta', id: '1', delta: ' }' },
1467
+ { type: 'text-end', id: '1' },
1468
+ {
1469
+ type: 'finish',
1470
+ finishReason: { unified: 'stop', raw: 'stop' },
1471
+ usage: testUsage,
1472
+ providerMetadata: {
1473
+ testProvider: {
1474
+ testKey: 'testValue',
1475
+ },
1476
+ },
1477
+ },
1478
+ ]),
1479
+ }),
1480
+ schema: z.object({ content: z.string() }),
1481
+ schemaName: 'test-name',
1482
+ schemaDescription: 'test description',
1483
+ prompt: 'prompt',
1484
+ topK: 0.1,
1485
+ topP: 0.2,
1486
+ frequencyPenalty: 0.3,
1487
+ presencePenalty: 0.4,
1488
+ temperature: 0.5,
1489
+ headers: {
1490
+ header1: 'value1',
1491
+ header2: 'value2',
1492
+ },
1493
+ experimental_telemetry: {
1494
+ isEnabled: true,
1495
+ functionId: 'test-function-id',
1496
+ metadata: {
1497
+ test1: 'value1',
1498
+ test2: false,
1499
+ },
1500
+ tracer,
1501
+ },
1502
+ _internal: { now: () => 0 },
1503
+ });
1504
+
1505
+ // consume stream
1506
+ await convertAsyncIterableToArray(result.partialObjectStream);
1507
+
1508
+ expect(tracer.jsonSpans).toMatchSnapshot();
1509
+ });
1510
+
1511
+ it('should not record telemetry inputs / outputs when disabled', async () => {
1512
+ const result = streamObject({
1513
+ model: new MockLanguageModelV3({
1514
+ doStream: async () => ({
1515
+ stream: convertArrayToReadableStream([
1516
+ {
1517
+ type: 'response-metadata',
1518
+ id: 'id-0',
1519
+ modelId: 'mock-model-id',
1520
+ timestamp: new Date(0),
1521
+ },
1522
+ { type: 'text-start', id: '1' },
1523
+ { type: 'text-delta', id: '1', delta: '{ ' },
1524
+ { type: 'text-delta', id: '1', delta: '"content": ' },
1525
+ { type: 'text-delta', id: '1', delta: `"Hello, ` },
1526
+ { type: 'text-delta', id: '1', delta: `world` },
1527
+ { type: 'text-delta', id: '1', delta: `!"` },
1528
+ { type: 'text-delta', id: '1', delta: ' }' },
1529
+ { type: 'text-end', id: '1' },
1530
+ {
1531
+ type: 'finish',
1532
+ finishReason: { unified: 'stop', raw: 'stop' },
1533
+ usage: testUsage,
1534
+ },
1535
+ ]),
1536
+ }),
1537
+ }),
1538
+ schema: z.object({ content: z.string() }),
1539
+ prompt: 'prompt',
1540
+ experimental_telemetry: {
1541
+ isEnabled: true,
1542
+ recordInputs: false,
1543
+ recordOutputs: false,
1544
+ tracer,
1545
+ },
1546
+ _internal: { now: () => 0 },
1547
+ });
1548
+
1549
+ // consume stream
1550
+ await convertAsyncIterableToArray(result.partialObjectStream);
1551
+
1552
+ expect(tracer.jsonSpans).toMatchSnapshot();
1553
+ });
1554
+ });
1555
+
1556
+ describe('options.messages', () => {
1557
+ it('should support models that use "this" context in supportedUrls', async () => {
1558
+ let supportedUrlsCalled = false;
1559
+ class MockLanguageModelWithImageSupport extends MockLanguageModelV3 {
1560
+ constructor() {
1561
+ super({
1562
+ supportedUrls: () => {
1563
+ supportedUrlsCalled = true;
1564
+ // Reference 'this' to verify context
1565
+ return this.modelId === 'mock-model-id'
1566
+ ? ({ 'image/*': [/^https:\/\/.*$/] } as Record<
1567
+ string,
1568
+ RegExp[]
1569
+ >)
1570
+ : {};
1571
+ },
1572
+ doStream: async () => ({
1573
+ stream: convertArrayToReadableStream([
1574
+ { type: 'text-start', id: '1' },
1575
+ {
1576
+ type: 'text-delta',
1577
+ id: '1',
1578
+ delta: '{ "content": "Hello, world!" }',
1579
+ },
1580
+ { type: 'text-end', id: '1' },
1581
+ {
1582
+ type: 'finish',
1583
+ finishReason: { unified: 'stop', raw: 'stop' },
1584
+ usage: testUsage,
1585
+ },
1586
+ ]),
1587
+ }),
1588
+ });
1589
+ }
1590
+ }
1591
+
1592
+ const model = new MockLanguageModelWithImageSupport();
1593
+
1594
+ const result = streamObject({
1595
+ model,
1596
+ schema: z.object({ content: z.string() }),
1597
+ messages: [
1598
+ {
1599
+ role: 'user',
1600
+ content: [{ type: 'image', image: 'https://example.com/test.jpg' }],
1601
+ },
1602
+ ],
1603
+ });
1604
+
1605
+ const chunks = await convertAsyncIterableToArray(result.textStream);
1606
+ expect(chunks.join('')).toBe('{ "content": "Hello, world!" }');
1607
+ expect(supportedUrlsCalled).toBe(true);
1608
+ });
1609
+ });
1610
+
1611
+ describe('options.experimental_repairText', () => {
1612
+ it('should be able to repair a JSONParseError', async () => {
1613
+ const result = streamObject({
1614
+ model: new MockLanguageModelV3({
1615
+ doStream: async () => ({
1616
+ stream: convertArrayToReadableStream([
1617
+ {
1618
+ type: 'response-metadata',
1619
+ id: 'id-0',
1620
+ modelId: 'mock-model-id',
1621
+ timestamp: new Date(0),
1622
+ },
1623
+ { type: 'text-start', id: '1' },
1624
+ {
1625
+ type: 'text-delta',
1626
+ id: '1',
1627
+ delta: '{ "content": "provider metadata test" ',
1628
+ },
1629
+ { type: 'text-end', id: '1' },
1630
+ {
1631
+ type: 'finish',
1632
+ finishReason: { unified: 'stop', raw: 'stop' },
1633
+ usage: testUsage,
1634
+ },
1635
+ ]),
1636
+ }),
1637
+ }),
1638
+ schema: z.object({ content: z.string() }),
1639
+ prompt: 'prompt',
1640
+ experimental_repairText: async ({ text, error }) => {
1641
+ expect(error).toBeInstanceOf(JSONParseError);
1642
+ expect(text).toStrictEqual('{ "content": "provider metadata test" ');
1643
+ return text + '}';
1644
+ },
1645
+ });
1646
+
1647
+ // consume stream
1648
+ await convertAsyncIterableToArray(result.partialObjectStream);
1649
+
1650
+ expect(await result.object).toStrictEqual({
1651
+ content: 'provider metadata test',
1652
+ });
1653
+ });
1654
+
1655
+ it('should be able to repair a TypeValidationError', async () => {
1656
+ const result = streamObject({
1657
+ model: new MockLanguageModelV3({
1658
+ doStream: async () => ({
1659
+ stream: convertArrayToReadableStream([
1660
+ {
1661
+ type: 'response-metadata',
1662
+ id: 'id-0',
1663
+ modelId: 'mock-model-id',
1664
+ timestamp: new Date(0),
1665
+ },
1666
+ { type: 'text-start', id: '1' },
1667
+ {
1668
+ type: 'text-delta',
1669
+ id: '1',
1670
+ delta: '{ "content-a": "provider metadata test" }',
1671
+ },
1672
+ { type: 'text-end', id: '1' },
1673
+ {
1674
+ type: 'finish',
1675
+ finishReason: { unified: 'stop', raw: 'stop' },
1676
+ usage: testUsage,
1677
+ },
1678
+ ]),
1679
+ }),
1680
+ }),
1681
+ schema: z.object({ content: z.string() }),
1682
+ prompt: 'prompt',
1683
+ experimental_repairText: async ({ text, error }) => {
1684
+ expect(error).toBeInstanceOf(TypeValidationError);
1685
+ expect(text).toStrictEqual(
1686
+ '{ "content-a": "provider metadata test" }',
1687
+ );
1688
+ return `{ "content": "provider metadata test" }`;
1689
+ },
1690
+ });
1691
+
1692
+ // consume stream
1693
+ await convertAsyncIterableToArray(result.partialObjectStream);
1694
+
1695
+ expect(await result.object).toStrictEqual({
1696
+ content: 'provider metadata test',
1697
+ });
1698
+ });
1699
+
1700
+ it('should be able to handle repair that returns null', async () => {
1701
+ const result = streamObject({
1702
+ model: new MockLanguageModelV3({
1703
+ doStream: async () => ({
1704
+ stream: convertArrayToReadableStream([
1705
+ {
1706
+ type: 'response-metadata',
1707
+ id: 'id-0',
1708
+ modelId: 'mock-model-id',
1709
+ timestamp: new Date(0),
1710
+ },
1711
+ { type: 'text-start', id: '1' },
1712
+ {
1713
+ type: 'text-delta',
1714
+ id: '1',
1715
+ delta: '{ "content-a": "provider metadata test" }',
1716
+ },
1717
+ { type: 'text-end', id: '1' },
1718
+ {
1719
+ type: 'finish',
1720
+ finishReason: { unified: 'stop', raw: 'stop' },
1721
+ usage: testUsage,
1722
+ },
1723
+ ]),
1724
+ }),
1725
+ }),
1726
+ schema: z.object({ content: z.string() }),
1727
+ prompt: 'prompt',
1728
+ experimental_repairText: async ({ text, error }) => {
1729
+ expect(error).toBeInstanceOf(TypeValidationError);
1730
+ expect(text).toStrictEqual(
1731
+ '{ "content-a": "provider metadata test" }',
1732
+ );
1733
+ return null;
1734
+ },
1735
+ });
1736
+
1737
+ // consume stream
1738
+ await convertAsyncIterableToArray(result.partialObjectStream);
1739
+
1740
+ expect(result.object).rejects.toThrow(
1741
+ 'No object generated: response did not match schema.',
1742
+ );
1743
+ });
1744
+
1745
+ it('should be able to repair JSON wrapped with markdown code blocks', async () => {
1746
+ const result = streamObject({
1747
+ model: new MockLanguageModelV3({
1748
+ doStream: async () => ({
1749
+ stream: convertArrayToReadableStream([
1750
+ {
1751
+ type: 'response-metadata',
1752
+ id: 'id-0',
1753
+ modelId: 'mock-model-id',
1754
+ timestamp: new Date(0),
1755
+ },
1756
+ { type: 'text-start', id: '1' },
1757
+ {
1758
+ type: 'text-delta',
1759
+ id: '1',
1760
+ delta: '```json\n{ "content": "test message" }\n```',
1761
+ },
1762
+ { type: 'text-end', id: '1' },
1763
+ {
1764
+ type: 'finish',
1765
+ finishReason: { unified: 'stop', raw: 'stop' },
1766
+ usage: testUsage,
1767
+ },
1768
+ ]),
1769
+ }),
1770
+ }),
1771
+ schema: z.object({ content: z.string() }),
1772
+ prompt: 'prompt',
1773
+ experimental_repairText: async ({ text, error }) => {
1774
+ expect(error).toBeInstanceOf(JSONParseError);
1775
+ expect(text).toStrictEqual(
1776
+ '```json\n{ "content": "test message" }\n```',
1777
+ );
1778
+
1779
+ // Remove markdown code block wrapper
1780
+ const cleaned = text
1781
+ .replace(/^```json\s*/, '')
1782
+ .replace(/\s*```$/, '');
1783
+ return cleaned;
1784
+ },
1785
+ });
1786
+
1787
+ // consume stream
1788
+ await convertAsyncIterableToArray(result.partialObjectStream);
1789
+
1790
+ expect(await result.object).toStrictEqual({
1791
+ content: 'test message',
1792
+ });
1793
+ });
1794
+
1795
+ it('should throw NoObjectGeneratedError when parsing fails with repairText', async () => {
1796
+ const result = streamObject({
1797
+ model: new MockLanguageModelV3({
1798
+ doStream: async () => ({
1799
+ stream: convertArrayToReadableStream([
1800
+ {
1801
+ type: 'response-metadata',
1802
+ id: 'id-0',
1803
+ modelId: 'mock-model-id',
1804
+ timestamp: new Date(0),
1805
+ },
1806
+ { type: 'text-start', id: '1' },
1807
+ { type: 'text-delta', id: '1', delta: '{ broken json' },
1808
+ { type: 'text-end', id: '1' },
1809
+ {
1810
+ type: 'finish',
1811
+ finishReason: { unified: 'stop', raw: 'stop' },
1812
+ usage: testUsage,
1813
+ },
1814
+ ]),
1815
+ }),
1816
+ }),
1817
+ schema: z.object({ content: z.string() }),
1818
+ prompt: 'prompt',
1819
+ experimental_repairText: async ({ text }) => text + '{',
1820
+ });
1821
+
1822
+ try {
1823
+ await convertAsyncIterableToArray(result.partialObjectStream);
1824
+ await result.object;
1825
+ fail('must throw error');
1826
+ } catch (error) {
1827
+ verifyNoObjectGeneratedError(error, {
1828
+ message: 'No object generated: could not parse the response.',
1829
+ response: {
1830
+ id: 'id-0',
1831
+ timestamp: new Date(0),
1832
+ modelId: 'mock-model-id',
1833
+ },
1834
+ usage: asLanguageModelUsage(testUsage),
1835
+ finishReason: 'stop',
1836
+ });
1837
+ }
1838
+ });
1839
+ });
1840
+
1841
+ describe('warnings', () => {
1842
+ it('should resolve warnings promise with undefined when no warnings are present', async () => {
1843
+ const mockModel = createTestModel({
1844
+ warnings: [], // No warnings
1845
+ });
1846
+
1847
+ const result = streamObject({
1848
+ model: mockModel,
1849
+ schema: z.object({ content: z.string() }),
1850
+ prompt: 'prompt',
1851
+ });
1852
+
1853
+ // Consume the stream to completion
1854
+ await convertAsyncIterableToArray(result.partialObjectStream);
1855
+
1856
+ // Wait for the warnings promise to resolve
1857
+ const warnings = await result.warnings;
1858
+
1859
+ expect(warnings).toEqual([]);
1860
+ });
1861
+
1862
+ it('should resolve warnings promise with warnings when warnings are present', async () => {
1863
+ const expectedWarnings: SharedV3Warning[] = [
1864
+ {
1865
+ type: 'unsupported',
1866
+ feature: 'frequency_penalty',
1867
+ details: 'This model does not support the frequency_penalty setting.',
1868
+ },
1869
+ {
1870
+ type: 'other',
1871
+ message: 'Test warning message',
1872
+ },
1873
+ ];
1874
+
1875
+ const mockModel = createTestModel({
1876
+ warnings: expectedWarnings,
1877
+ });
1878
+
1879
+ const result = streamObject({
1880
+ model: mockModel,
1881
+ schema: z.object({ content: z.string() }),
1882
+ prompt: 'prompt',
1883
+ });
1884
+
1885
+ // Consume the stream to completion
1886
+ await convertAsyncIterableToArray(result.partialObjectStream);
1887
+
1888
+ // Wait for the warnings promise to resolve
1889
+ const warnings = await result.warnings;
1890
+
1891
+ expect(warnings).toEqual(expectedWarnings);
1892
+ });
1893
+
1894
+ it('should call logWarnings with the correct warnings', async () => {
1895
+ const expectedWarnings: SharedV3Warning[] = [
1896
+ {
1897
+ type: 'other',
1898
+ message: 'Setting is not supported',
1899
+ },
1900
+ {
1901
+ type: 'unsupported',
1902
+ feature: 'temperature',
1903
+ details: 'Temperature parameter not supported',
1904
+ },
1905
+ ];
1906
+
1907
+ const mockModel = createTestModel({
1908
+ warnings: expectedWarnings,
1909
+ });
1910
+
1911
+ const result = streamObject({
1912
+ model: mockModel,
1913
+ schema: z.object({ content: z.string() }),
1914
+ prompt: 'prompt',
1915
+ });
1916
+
1917
+ // Consume the stream to completion
1918
+ await convertAsyncIterableToArray(result.partialObjectStream);
1919
+
1920
+ expect(logWarningsSpy).toHaveBeenCalledOnce();
1921
+ expect(logWarningsSpy).toHaveBeenCalledWith({
1922
+ warnings: expectedWarnings,
1923
+ provider: 'mock-provider',
1924
+ model: 'mock-model-id',
1925
+ });
1926
+ });
1927
+
1928
+ it('should call logWarnings with empty array when no warnings are present', async () => {
1929
+ const mockModel = createTestModel({
1930
+ warnings: [], // no warnings
1931
+ });
1932
+
1933
+ const result = streamObject({
1934
+ model: mockModel,
1935
+ schema: z.object({ content: z.string() }),
1936
+ prompt: 'prompt',
1937
+ });
1938
+
1939
+ // Consume the stream to completion
1940
+ await convertAsyncIterableToArray(result.partialObjectStream);
1941
+
1942
+ expect(logWarningsSpy).toHaveBeenCalledOnce();
1943
+ expect(logWarningsSpy).toHaveBeenCalledWith({
1944
+ warnings: [],
1945
+ provider: 'mock-provider',
1946
+ model: 'mock-model-id',
1947
+ });
1948
+ });
1949
+ });
1950
+ });