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,181 @@
1
+ import { JSONSchema7 } from '@ai-sdk/provider';
2
+ import { injectJsonInstruction } from './inject-json-instruction';
3
+ import { describe, it, expect } from 'vitest';
4
+
5
+ const basicSchema: JSONSchema7 = {
6
+ type: 'object',
7
+ properties: {
8
+ name: { type: 'string' },
9
+ age: { type: 'number' },
10
+ },
11
+ required: ['name', 'age'],
12
+ };
13
+
14
+ it('should handle basic case with prompt and schema', () => {
15
+ const result = injectJsonInstruction({
16
+ prompt: 'Generate a person',
17
+ schema: basicSchema,
18
+ });
19
+ expect(result).toBe(
20
+ 'Generate a person\n\n' +
21
+ 'JSON schema:\n' +
22
+ '{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}\n' +
23
+ 'You MUST answer with a JSON object that matches the JSON schema above.',
24
+ );
25
+ });
26
+
27
+ it('should handle only prompt, no schema', () => {
28
+ const result = injectJsonInstruction({
29
+ prompt: 'Generate a person',
30
+ });
31
+ expect(result).toBe('Generate a person\n\nYou MUST answer with JSON.');
32
+ });
33
+
34
+ it('should handle only schema, no prompt', () => {
35
+ const result = injectJsonInstruction({
36
+ schema: basicSchema,
37
+ });
38
+ expect(result).toBe(
39
+ 'JSON schema:\n' +
40
+ '{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}\n' +
41
+ 'You MUST answer with a JSON object that matches the JSON schema above.',
42
+ );
43
+ });
44
+
45
+ it('should handle no prompt, no schema', () => {
46
+ const result = injectJsonInstruction({});
47
+ expect(result).toBe('You MUST answer with JSON.');
48
+ });
49
+
50
+ it('should handle custom schemaPrefix and schemaSuffix', () => {
51
+ const result = injectJsonInstruction({
52
+ prompt: 'Generate a person',
53
+ schema: basicSchema,
54
+ schemaPrefix: 'Custom prefix:',
55
+ schemaSuffix: 'Custom suffix',
56
+ });
57
+ expect(result).toBe(
58
+ 'Generate a person\n\n' +
59
+ 'Custom prefix:\n' +
60
+ '{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}\n' +
61
+ 'Custom suffix',
62
+ );
63
+ });
64
+
65
+ it('should handle empty string prompt', () => {
66
+ const result = injectJsonInstruction({
67
+ prompt: '',
68
+ schema: basicSchema,
69
+ });
70
+ expect(result).toBe(
71
+ 'JSON schema:\n' +
72
+ '{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}\n' +
73
+ 'You MUST answer with a JSON object that matches the JSON schema above.',
74
+ );
75
+ });
76
+
77
+ it('should handle empty object schema', () => {
78
+ const result = injectJsonInstruction({
79
+ prompt: 'Generate something',
80
+ schema: {},
81
+ });
82
+ expect(result).toBe(
83
+ 'Generate something\n\n' +
84
+ 'JSON schema:\n' +
85
+ '{}\n' +
86
+ 'You MUST answer with a JSON object that matches the JSON schema above.',
87
+ );
88
+ });
89
+
90
+ it('should handle complex nested schema', () => {
91
+ const complexSchema: JSONSchema7 = {
92
+ type: 'object',
93
+ properties: {
94
+ person: {
95
+ type: 'object',
96
+ properties: {
97
+ name: { type: 'string' },
98
+ age: { type: 'number' },
99
+ address: {
100
+ type: 'object',
101
+ properties: {
102
+ street: { type: 'string' },
103
+ city: { type: 'string' },
104
+ },
105
+ },
106
+ },
107
+ },
108
+ },
109
+ };
110
+ const result = injectJsonInstruction({
111
+ prompt: 'Generate a complex person',
112
+ schema: complexSchema,
113
+ });
114
+ expect(result).toBe(
115
+ 'Generate a complex person\n\n' +
116
+ 'JSON schema:\n' +
117
+ '{"type":"object","properties":{"person":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"address":{"type":"object","properties":{"street":{"type":"string"},"city":{"type":"string"}}}}}}}\n' +
118
+ 'You MUST answer with a JSON object that matches the JSON schema above.',
119
+ );
120
+ });
121
+
122
+ it('should handle schema with special characters', () => {
123
+ const specialSchema: JSONSchema7 = {
124
+ type: 'object',
125
+ properties: {
126
+ 'special@property': { type: 'string' },
127
+ 'emoji😊': { type: 'string' },
128
+ },
129
+ };
130
+ const result = injectJsonInstruction({
131
+ schema: specialSchema,
132
+ });
133
+ expect(result).toBe(
134
+ 'JSON schema:\n' +
135
+ '{"type":"object","properties":{"special@property":{"type":"string"},"emoji😊":{"type":"string"}}}\n' +
136
+ 'You MUST answer with a JSON object that matches the JSON schema above.',
137
+ );
138
+ });
139
+
140
+ it('should handle very long prompt and schema', () => {
141
+ const longPrompt = 'A'.repeat(1000);
142
+ const longSchema: JSONSchema7 = {
143
+ type: 'object',
144
+ properties: {},
145
+ };
146
+ for (let i = 0; i < 100; i++) {
147
+ longSchema.properties![`prop${i}`] = { type: 'string' };
148
+ }
149
+ const result = injectJsonInstruction({
150
+ prompt: longPrompt,
151
+ schema: longSchema,
152
+ });
153
+ expect(result).toBe(
154
+ longPrompt +
155
+ '\n\n' +
156
+ 'JSON schema:\n' +
157
+ JSON.stringify(longSchema) +
158
+ '\n' +
159
+ 'You MUST answer with a JSON object that matches the JSON schema above.',
160
+ );
161
+ });
162
+
163
+ it('should handle null values for optional parameters', () => {
164
+ const result = injectJsonInstruction({
165
+ prompt: null as any,
166
+ schema: null as any,
167
+ schemaPrefix: null as any,
168
+ schemaSuffix: null as any,
169
+ });
170
+ expect(result).toBe('');
171
+ });
172
+
173
+ it('should handle undefined values for optional parameters', () => {
174
+ const result = injectJsonInstruction({
175
+ prompt: undefined,
176
+ schema: undefined,
177
+ schemaPrefix: undefined,
178
+ schemaSuffix: undefined,
179
+ });
180
+ expect(result).toBe('You MUST answer with JSON.');
181
+ });
@@ -0,0 +1,30 @@
1
+ import { JSONSchema7 } from '@ai-sdk/provider';
2
+
3
+ const DEFAULT_SCHEMA_PREFIX = 'JSON schema:';
4
+ const DEFAULT_SCHEMA_SUFFIX =
5
+ 'You MUST answer with a JSON object that matches the JSON schema above.';
6
+ const DEFAULT_GENERIC_SUFFIX = 'You MUST answer with JSON.';
7
+
8
+ export function injectJsonInstruction({
9
+ prompt,
10
+ schema,
11
+ schemaPrefix = schema != null ? DEFAULT_SCHEMA_PREFIX : undefined,
12
+ schemaSuffix = schema != null
13
+ ? DEFAULT_SCHEMA_SUFFIX
14
+ : DEFAULT_GENERIC_SUFFIX,
15
+ }: {
16
+ prompt?: string;
17
+ schema?: JSONSchema7;
18
+ schemaPrefix?: string;
19
+ schemaSuffix?: string;
20
+ }): string {
21
+ return [
22
+ prompt != null && prompt.length > 0 ? prompt : undefined,
23
+ prompt != null && prompt.length > 0 ? '' : undefined, // add a newline if prompt is not null
24
+ schemaPrefix,
25
+ schema != null ? JSON.stringify(schema) : undefined,
26
+ schemaSuffix,
27
+ ]
28
+ .filter(line => line != null)
29
+ .join('\n');
30
+ }
@@ -0,0 +1,415 @@
1
+ import {
2
+ isJSONArray,
3
+ isJSONObject,
4
+ JSONObject,
5
+ JSONSchema7,
6
+ JSONValue,
7
+ TypeValidationError,
8
+ UnsupportedFunctionalityError,
9
+ } from '@ai-sdk/provider';
10
+ import {
11
+ asSchema,
12
+ FlexibleSchema,
13
+ safeValidateTypes,
14
+ Schema,
15
+ ValidationResult,
16
+ } from '@ai-sdk/provider-utils';
17
+ import { NoObjectGeneratedError } from '../error/no-object-generated-error';
18
+ import {
19
+ FinishReason,
20
+ LanguageModelResponseMetadata,
21
+ LanguageModelUsage,
22
+ } from '../types';
23
+ import {
24
+ AsyncIterableStream,
25
+ createAsyncIterableStream,
26
+ } from '../util/async-iterable-stream';
27
+ import { DeepPartial } from '../util/deep-partial';
28
+ import { ObjectStreamPart } from './stream-object-result';
29
+
30
+ export interface OutputStrategy<PARTIAL, RESULT, ELEMENT_STREAM> {
31
+ readonly type: 'object' | 'array' | 'enum' | 'no-schema';
32
+
33
+ jsonSchema(): Promise<JSONSchema7 | undefined>;
34
+
35
+ validatePartialResult({
36
+ value,
37
+ textDelta,
38
+ isFinalDelta,
39
+ }: {
40
+ value: JSONValue;
41
+ textDelta: string;
42
+ isFirstDelta: boolean;
43
+ isFinalDelta: boolean;
44
+ latestObject: PARTIAL | undefined;
45
+ }): Promise<
46
+ ValidationResult<{
47
+ partial: PARTIAL;
48
+ textDelta: string;
49
+ }>
50
+ >;
51
+ validateFinalResult(
52
+ value: JSONValue | undefined,
53
+ context: {
54
+ text: string;
55
+ response: LanguageModelResponseMetadata;
56
+ usage: LanguageModelUsage;
57
+ },
58
+ ): Promise<ValidationResult<RESULT>>;
59
+
60
+ createElementStream(
61
+ originalStream: ReadableStream<ObjectStreamPart<PARTIAL>>,
62
+ ): ELEMENT_STREAM;
63
+ }
64
+
65
+ const noSchemaOutputStrategy: OutputStrategy<JSONValue, JSONValue, never> = {
66
+ type: 'no-schema',
67
+ jsonSchema: async () => undefined,
68
+
69
+ async validatePartialResult({ value, textDelta }) {
70
+ return { success: true, value: { partial: value, textDelta } };
71
+ },
72
+
73
+ async validateFinalResult(
74
+ value: JSONValue | undefined,
75
+ context: {
76
+ text: string;
77
+ response: LanguageModelResponseMetadata;
78
+ usage: LanguageModelUsage;
79
+ finishReason: FinishReason;
80
+ },
81
+ ): Promise<ValidationResult<JSONValue>> {
82
+ return value === undefined
83
+ ? {
84
+ success: false,
85
+ error: new NoObjectGeneratedError({
86
+ message: 'No object generated: response did not match schema.',
87
+ text: context.text,
88
+ response: context.response,
89
+ usage: context.usage,
90
+ finishReason: context.finishReason,
91
+ }),
92
+ }
93
+ : { success: true, value };
94
+ },
95
+
96
+ createElementStream() {
97
+ throw new UnsupportedFunctionalityError({
98
+ functionality: 'element streams in no-schema mode',
99
+ });
100
+ },
101
+ };
102
+
103
+ const objectOutputStrategy = <OBJECT>(
104
+ schema: Schema<OBJECT>,
105
+ ): OutputStrategy<DeepPartial<OBJECT>, OBJECT, never> => ({
106
+ type: 'object',
107
+ jsonSchema: async () => await schema.jsonSchema,
108
+
109
+ async validatePartialResult({ value, textDelta }) {
110
+ return {
111
+ success: true,
112
+ value: {
113
+ // Note: currently no validation of partial results:
114
+ partial: value as DeepPartial<OBJECT>,
115
+ textDelta,
116
+ },
117
+ };
118
+ },
119
+
120
+ async validateFinalResult(
121
+ value: JSONValue | undefined,
122
+ ): Promise<ValidationResult<OBJECT>> {
123
+ return safeValidateTypes({ value, schema });
124
+ },
125
+
126
+ createElementStream() {
127
+ throw new UnsupportedFunctionalityError({
128
+ functionality: 'element streams in object mode',
129
+ });
130
+ },
131
+ });
132
+
133
+ const arrayOutputStrategy = <ELEMENT>(
134
+ schema: Schema<ELEMENT>,
135
+ ): OutputStrategy<ELEMENT[], ELEMENT[], AsyncIterableStream<ELEMENT>> => {
136
+ return {
137
+ type: 'array',
138
+
139
+ // wrap in object that contains array of elements, since most LLMs will not
140
+ // be able to generate an array directly:
141
+ // possible future optimization: use arrays directly when model supports grammar-guided generation
142
+ jsonSchema: async () => {
143
+ // remove $schema from schema.jsonSchema:
144
+ const { $schema, ...itemSchema } = await schema.jsonSchema;
145
+
146
+ return {
147
+ $schema: 'http://json-schema.org/draft-07/schema#',
148
+ type: 'object',
149
+ properties: {
150
+ elements: { type: 'array', items: itemSchema },
151
+ },
152
+ required: ['elements'],
153
+ additionalProperties: false,
154
+ };
155
+ },
156
+
157
+ async validatePartialResult({
158
+ value,
159
+ latestObject,
160
+ isFirstDelta,
161
+ isFinalDelta,
162
+ }) {
163
+ // check that the value is an object that contains an array of elements:
164
+ if (!isJSONObject(value) || !isJSONArray(value.elements)) {
165
+ return {
166
+ success: false,
167
+ error: new TypeValidationError({
168
+ value,
169
+ cause: 'value must be an object that contains an array of elements',
170
+ }),
171
+ };
172
+ }
173
+
174
+ const inputArray = value.elements as Array<JSONObject>;
175
+ const resultArray: Array<ELEMENT> = [];
176
+
177
+ for (let i = 0; i < inputArray.length; i++) {
178
+ const element = inputArray[i];
179
+ const result = await safeValidateTypes({ value: element, schema });
180
+
181
+ // special treatment for last processed element:
182
+ // ignore parse or validation failures, since they indicate that the
183
+ // last element is incomplete and should not be included in the result,
184
+ // unless it is the final delta
185
+ if (i === inputArray.length - 1 && !isFinalDelta) {
186
+ continue;
187
+ }
188
+
189
+ if (!result.success) {
190
+ return result;
191
+ }
192
+
193
+ resultArray.push(result.value);
194
+ }
195
+
196
+ // calculate delta:
197
+ const publishedElementCount = latestObject?.length ?? 0;
198
+
199
+ let textDelta = '';
200
+
201
+ if (isFirstDelta) {
202
+ textDelta += '[';
203
+ }
204
+
205
+ if (publishedElementCount > 0) {
206
+ textDelta += ',';
207
+ }
208
+
209
+ textDelta += resultArray
210
+ .slice(publishedElementCount) // only new elements
211
+ .map(element => JSON.stringify(element))
212
+ .join(',');
213
+
214
+ if (isFinalDelta) {
215
+ textDelta += ']';
216
+ }
217
+
218
+ return {
219
+ success: true,
220
+ value: {
221
+ partial: resultArray,
222
+ textDelta,
223
+ },
224
+ };
225
+ },
226
+
227
+ async validateFinalResult(
228
+ value: JSONValue | undefined,
229
+ ): Promise<ValidationResult<Array<ELEMENT>>> {
230
+ // check that the value is an object that contains an array of elements:
231
+ if (!isJSONObject(value) || !isJSONArray(value.elements)) {
232
+ return {
233
+ success: false,
234
+ error: new TypeValidationError({
235
+ value,
236
+ cause: 'value must be an object that contains an array of elements',
237
+ }),
238
+ };
239
+ }
240
+
241
+ const inputArray = value.elements as Array<JSONObject>;
242
+
243
+ // check that each element in the array is of the correct type:
244
+ for (const element of inputArray) {
245
+ const result = await safeValidateTypes({ value: element, schema });
246
+ if (!result.success) {
247
+ return result;
248
+ }
249
+ }
250
+
251
+ return { success: true, value: inputArray as Array<ELEMENT> };
252
+ },
253
+
254
+ createElementStream(
255
+ originalStream: ReadableStream<ObjectStreamPart<ELEMENT[]>>,
256
+ ) {
257
+ let publishedElements = 0;
258
+
259
+ return createAsyncIterableStream(
260
+ originalStream.pipeThrough(
261
+ new TransformStream<ObjectStreamPart<ELEMENT[]>, ELEMENT>({
262
+ transform(chunk, controller) {
263
+ switch (chunk.type) {
264
+ case 'object': {
265
+ const array = chunk.object;
266
+
267
+ // publish new elements one by one:
268
+ for (
269
+ ;
270
+ publishedElements < array.length;
271
+ publishedElements++
272
+ ) {
273
+ controller.enqueue(array[publishedElements]);
274
+ }
275
+
276
+ break;
277
+ }
278
+
279
+ case 'text-delta':
280
+ case 'finish':
281
+ case 'error': // suppress error (use onError instead)
282
+ break;
283
+
284
+ default: {
285
+ const _exhaustiveCheck: never = chunk;
286
+ throw new Error(
287
+ `Unsupported chunk type: ${_exhaustiveCheck}`,
288
+ );
289
+ }
290
+ }
291
+ },
292
+ }),
293
+ ),
294
+ );
295
+ },
296
+ };
297
+ };
298
+
299
+ const enumOutputStrategy = <ENUM extends string>(
300
+ enumValues: Array<ENUM>,
301
+ ): OutputStrategy<string, ENUM, never> => {
302
+ return {
303
+ type: 'enum',
304
+
305
+ // wrap in object that contains result, since most LLMs will not
306
+ // be able to generate an enum value directly:
307
+ // possible future optimization: use enums directly when model supports top-level enums
308
+ jsonSchema: async () => ({
309
+ $schema: 'http://json-schema.org/draft-07/schema#',
310
+ type: 'object',
311
+ properties: {
312
+ result: { type: 'string', enum: enumValues },
313
+ },
314
+ required: ['result'],
315
+ additionalProperties: false,
316
+ }),
317
+
318
+ async validateFinalResult(
319
+ value: JSONValue | undefined,
320
+ ): Promise<ValidationResult<ENUM>> {
321
+ // check that the value is an object that contains an array of elements:
322
+ if (!isJSONObject(value) || typeof value.result !== 'string') {
323
+ return {
324
+ success: false,
325
+ error: new TypeValidationError({
326
+ value,
327
+ cause:
328
+ 'value must be an object that contains a string in the "result" property.',
329
+ }),
330
+ };
331
+ }
332
+
333
+ const result = value.result as string;
334
+
335
+ return enumValues.includes(result as ENUM)
336
+ ? { success: true, value: result as ENUM }
337
+ : {
338
+ success: false,
339
+ error: new TypeValidationError({
340
+ value,
341
+ cause: 'value must be a string in the enum',
342
+ }),
343
+ };
344
+ },
345
+
346
+ async validatePartialResult({ value, textDelta }) {
347
+ if (!isJSONObject(value) || typeof value.result !== 'string') {
348
+ return {
349
+ success: false,
350
+ error: new TypeValidationError({
351
+ value,
352
+ cause:
353
+ 'value must be an object that contains a string in the "result" property.',
354
+ }),
355
+ };
356
+ }
357
+
358
+ const result = value.result as string;
359
+ const possibleEnumValues = enumValues.filter(enumValue =>
360
+ enumValue.startsWith(result),
361
+ );
362
+
363
+ if (value.result.length === 0 || possibleEnumValues.length === 0) {
364
+ return {
365
+ success: false,
366
+ error: new TypeValidationError({
367
+ value,
368
+ cause: 'value must be a string in the enum',
369
+ }),
370
+ };
371
+ }
372
+
373
+ return {
374
+ success: true,
375
+ value: {
376
+ partial:
377
+ possibleEnumValues.length > 1 ? result : possibleEnumValues[0],
378
+ textDelta,
379
+ },
380
+ };
381
+ },
382
+
383
+ createElementStream() {
384
+ // no streaming in enum mode
385
+ throw new UnsupportedFunctionalityError({
386
+ functionality: 'element streams in enum mode',
387
+ });
388
+ },
389
+ };
390
+ };
391
+
392
+ export function getOutputStrategy<SCHEMA>({
393
+ output,
394
+ schema,
395
+ enumValues,
396
+ }: {
397
+ output: 'object' | 'array' | 'enum' | 'no-schema';
398
+ schema?: FlexibleSchema<SCHEMA>;
399
+ enumValues?: Array<SCHEMA>;
400
+ }): OutputStrategy<any, any, any> {
401
+ switch (output) {
402
+ case 'object':
403
+ return objectOutputStrategy(asSchema(schema!));
404
+ case 'array':
405
+ return arrayOutputStrategy(asSchema(schema!));
406
+ case 'enum':
407
+ return enumOutputStrategy(enumValues! as Array<string>);
408
+ case 'no-schema':
409
+ return noSchemaOutputStrategy;
410
+ default: {
411
+ const _exhaustiveCheck: never = output;
412
+ throw new Error(`Unsupported output: ${_exhaustiveCheck}`);
413
+ }
414
+ }
415
+ }