ai-functions 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-test.log +105 -0
  3. package/README.md +190 -86
  4. package/TODO.md +138 -0
  5. package/dist/ai-promise.d.ts +219 -0
  6. package/dist/ai-promise.d.ts.map +1 -0
  7. package/dist/ai-promise.js +610 -0
  8. package/dist/ai-promise.js.map +1 -0
  9. package/dist/ai.d.ts +285 -0
  10. package/dist/ai.d.ts.map +1 -0
  11. package/dist/ai.js +842 -0
  12. package/dist/ai.js.map +1 -0
  13. package/dist/batch/anthropic.d.ts +23 -0
  14. package/dist/batch/anthropic.d.ts.map +1 -0
  15. package/dist/batch/anthropic.js +257 -0
  16. package/dist/batch/anthropic.js.map +1 -0
  17. package/dist/batch/bedrock.d.ts +64 -0
  18. package/dist/batch/bedrock.d.ts.map +1 -0
  19. package/dist/batch/bedrock.js +586 -0
  20. package/dist/batch/bedrock.js.map +1 -0
  21. package/dist/batch/cloudflare.d.ts +37 -0
  22. package/dist/batch/cloudflare.d.ts.map +1 -0
  23. package/dist/batch/cloudflare.js +289 -0
  24. package/dist/batch/cloudflare.js.map +1 -0
  25. package/dist/batch/google.d.ts +41 -0
  26. package/dist/batch/google.d.ts.map +1 -0
  27. package/dist/batch/google.js +360 -0
  28. package/dist/batch/google.js.map +1 -0
  29. package/dist/batch/index.d.ts +31 -0
  30. package/dist/batch/index.d.ts.map +1 -0
  31. package/dist/batch/index.js +31 -0
  32. package/dist/batch/index.js.map +1 -0
  33. package/dist/batch/memory.d.ts +44 -0
  34. package/dist/batch/memory.d.ts.map +1 -0
  35. package/dist/batch/memory.js +188 -0
  36. package/dist/batch/memory.js.map +1 -0
  37. package/dist/batch/openai.d.ts +37 -0
  38. package/dist/batch/openai.d.ts.map +1 -0
  39. package/dist/batch/openai.js +403 -0
  40. package/dist/batch/openai.js.map +1 -0
  41. package/dist/batch-map.d.ts +125 -0
  42. package/dist/batch-map.d.ts.map +1 -0
  43. package/dist/batch-map.js +406 -0
  44. package/dist/batch-map.js.map +1 -0
  45. package/dist/batch-queue.d.ts +273 -0
  46. package/dist/batch-queue.d.ts.map +1 -0
  47. package/dist/batch-queue.js +271 -0
  48. package/dist/batch-queue.js.map +1 -0
  49. package/dist/context.d.ts +133 -0
  50. package/dist/context.d.ts.map +1 -0
  51. package/dist/context.js +267 -0
  52. package/dist/context.js.map +1 -0
  53. package/dist/embeddings.d.ts +123 -0
  54. package/dist/embeddings.d.ts.map +1 -0
  55. package/dist/embeddings.js +170 -0
  56. package/dist/embeddings.js.map +1 -0
  57. package/dist/eval/index.d.ts +8 -0
  58. package/dist/eval/index.d.ts.map +1 -0
  59. package/dist/eval/index.js +8 -0
  60. package/dist/eval/index.js.map +1 -0
  61. package/dist/eval/models.d.ts +66 -0
  62. package/dist/eval/models.d.ts.map +1 -0
  63. package/dist/eval/models.js +120 -0
  64. package/dist/eval/models.js.map +1 -0
  65. package/dist/eval/runner.d.ts +64 -0
  66. package/dist/eval/runner.d.ts.map +1 -0
  67. package/dist/eval/runner.js +148 -0
  68. package/dist/eval/runner.js.map +1 -0
  69. package/dist/generate.d.ts +168 -0
  70. package/dist/generate.d.ts.map +1 -0
  71. package/dist/generate.js +174 -0
  72. package/dist/generate.js.map +1 -0
  73. package/dist/index.d.ts +29 -4
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +53 -52
  76. package/dist/index.js.map +1 -1
  77. package/dist/primitives.d.ts +292 -0
  78. package/dist/primitives.d.ts.map +1 -0
  79. package/dist/primitives.js +471 -0
  80. package/dist/primitives.js.map +1 -0
  81. package/dist/providers/cloudflare.d.ts +9 -0
  82. package/dist/providers/cloudflare.d.ts.map +1 -0
  83. package/dist/providers/cloudflare.js +9 -0
  84. package/dist/providers/cloudflare.js.map +1 -0
  85. package/dist/providers/index.d.ts +9 -0
  86. package/dist/providers/index.d.ts.map +1 -0
  87. package/dist/providers/index.js +9 -0
  88. package/dist/providers/index.js.map +1 -0
  89. package/dist/schema.d.ts +54 -0
  90. package/dist/schema.d.ts.map +1 -0
  91. package/dist/schema.js +109 -0
  92. package/dist/schema.js.map +1 -0
  93. package/dist/template.d.ts +73 -0
  94. package/dist/template.d.ts.map +1 -0
  95. package/dist/template.js +129 -0
  96. package/dist/template.js.map +1 -0
  97. package/dist/types.d.ts +474 -106
  98. package/dist/types.d.ts.map +1 -1
  99. package/dist/types.js +4 -8
  100. package/dist/types.js.map +1 -1
  101. package/evalite.config.ts +19 -0
  102. package/evals/README.md +212 -0
  103. package/evals/classification.eval.ts +108 -0
  104. package/evals/marketing.eval.ts +370 -0
  105. package/evals/math.eval.ts +94 -0
  106. package/evals/run-evals.ts +166 -0
  107. package/evals/structured-output.eval.ts +143 -0
  108. package/evals/writing.eval.ts +117 -0
  109. package/examples/batch-blog-posts.ts +160 -0
  110. package/package.json +57 -57
  111. package/src/ai-promise.ts +784 -0
  112. package/src/ai.ts +1183 -0
  113. package/src/batch/anthropic.ts +375 -0
  114. package/src/batch/bedrock.ts +801 -0
  115. package/src/batch/cloudflare.ts +421 -0
  116. package/src/batch/google.ts +491 -0
  117. package/src/batch/index.ts +31 -0
  118. package/src/batch/memory.ts +253 -0
  119. package/src/batch/openai.ts +557 -0
  120. package/src/batch-map.ts +534 -0
  121. package/src/batch-queue.ts +493 -0
  122. package/src/context.ts +332 -0
  123. package/src/embeddings.ts +244 -0
  124. package/src/eval/index.ts +8 -0
  125. package/src/eval/models.ts +158 -0
  126. package/src/eval/runner.ts +217 -0
  127. package/src/generate.ts +245 -0
  128. package/src/index.ts +154 -0
  129. package/src/primitives.ts +612 -0
  130. package/src/providers/cloudflare.ts +15 -0
  131. package/src/providers/index.ts +14 -0
  132. package/src/schema.ts +147 -0
  133. package/src/template.ts +209 -0
  134. package/src/types.ts +540 -0
  135. package/test/README.md +105 -0
  136. package/test/ai-proxy.test.ts +192 -0
  137. package/test/async-iterators.test.ts +327 -0
  138. package/test/batch-background.test.ts +482 -0
  139. package/test/batch-blog-posts.test.ts +387 -0
  140. package/test/blog-generation.test.ts +510 -0
  141. package/test/browse-read.test.ts +611 -0
  142. package/test/core-functions.test.ts +694 -0
  143. package/test/decide.test.ts +393 -0
  144. package/test/define.test.ts +274 -0
  145. package/test/e2e-bedrock-manual.ts +163 -0
  146. package/test/e2e-bedrock.test.ts +191 -0
  147. package/test/e2e-flex-gateway.ts +157 -0
  148. package/test/e2e-flex-manual.ts +183 -0
  149. package/test/e2e-flex.test.ts +209 -0
  150. package/test/e2e-google-manual.ts +178 -0
  151. package/test/e2e-google.test.ts +216 -0
  152. package/test/embeddings.test.ts +284 -0
  153. package/test/evals/define-function.eval.test.ts +379 -0
  154. package/test/evals/primitives.eval.test.ts +384 -0
  155. package/test/function-types.test.ts +492 -0
  156. package/test/generate-core.test.ts +319 -0
  157. package/test/generate.test.ts +163 -0
  158. package/test/implicit-batch.test.ts +422 -0
  159. package/test/schema.test.ts +109 -0
  160. package/test/tagged-templates.test.ts +302 -0
  161. package/tsconfig.json +10 -0
  162. package/vitest.config.ts +42 -0
  163. package/LICENSE +0 -21
  164. package/bin/cli.js +0 -5
  165. package/dist/cli/index.d.ts +0 -10
  166. package/dist/cli/index.d.ts.map +0 -1
  167. package/dist/cli/index.js +0 -38
  168. package/dist/cli/index.js.map +0 -1
  169. package/dist/cli/index.test.d.ts +0 -2
  170. package/dist/cli/index.test.d.ts.map +0 -1
  171. package/dist/cli/index.test.js +0 -35
  172. package/dist/cli/index.test.js.map +0 -1
  173. package/dist/constants/models.d.ts +0 -10
  174. package/dist/constants/models.d.ts.map +0 -1
  175. package/dist/constants/models.js +0 -12
  176. package/dist/constants/models.js.map +0 -1
  177. package/dist/converters/index.d.ts +0 -3
  178. package/dist/converters/index.d.ts.map +0 -1
  179. package/dist/converters/index.js +0 -3
  180. package/dist/converters/index.js.map +0 -1
  181. package/dist/converters/model.d.ts +0 -4
  182. package/dist/converters/model.d.ts.map +0 -1
  183. package/dist/converters/model.js +0 -19
  184. package/dist/converters/model.js.map +0 -1
  185. package/dist/converters/schema.d.ts +0 -4
  186. package/dist/converters/schema.d.ts.map +0 -1
  187. package/dist/converters/schema.js +0 -25
  188. package/dist/converters/schema.js.map +0 -1
  189. package/dist/core/responses.d.ts +0 -5
  190. package/dist/core/responses.d.ts.map +0 -1
  191. package/dist/core/responses.js +0 -16
  192. package/dist/core/responses.js.map +0 -1
  193. package/dist/core/responses.test.d.ts +0 -2
  194. package/dist/core/responses.test.d.ts.map +0 -1
  195. package/dist/core/responses.test.js +0 -31
  196. package/dist/core/responses.test.js.map +0 -1
  197. package/dist/errors.d.ts +0 -6
  198. package/dist/errors.d.ts.map +0 -1
  199. package/dist/errors.js +0 -9
  200. package/dist/errors.js.map +0 -1
  201. package/dist/examples/streaming.test.d.ts +0 -2
  202. package/dist/examples/streaming.test.d.ts.map +0 -1
  203. package/dist/examples/streaming.test.js +0 -176
  204. package/dist/examples/streaming.test.js.map +0 -1
  205. package/dist/factory/__tests__/index.test.d.ts +0 -2
  206. package/dist/factory/__tests__/index.test.d.ts.map +0 -1
  207. package/dist/factory/__tests__/index.test.js +0 -430
  208. package/dist/factory/__tests__/index.test.js.map +0 -1
  209. package/dist/factory/__tests__/list.test.d.ts +0 -2
  210. package/dist/factory/__tests__/list.test.d.ts.map +0 -1
  211. package/dist/factory/__tests__/list.test.js +0 -92
  212. package/dist/factory/__tests__/list.test.js.map +0 -1
  213. package/dist/factory/index.d.ts +0 -20
  214. package/dist/factory/index.d.ts.map +0 -1
  215. package/dist/factory/index.js +0 -287
  216. package/dist/factory/index.js.map +0 -1
  217. package/dist/factory/index.test.d.ts +0 -2
  218. package/dist/factory/index.test.d.ts.map +0 -1
  219. package/dist/factory/index.test.js +0 -287
  220. package/dist/factory/index.test.js.map +0 -1
  221. package/dist/factory/list.d.ts +0 -3
  222. package/dist/factory/list.d.ts.map +0 -1
  223. package/dist/factory/list.js +0 -221
  224. package/dist/factory/list.js.map +0 -1
  225. package/dist/factory/list.test.d.ts +0 -2
  226. package/dist/factory/list.test.d.ts.map +0 -1
  227. package/dist/factory/list.test.js +0 -84
  228. package/dist/factory/list.test.js.map +0 -1
  229. package/dist/generate/index.d.ts +0 -5
  230. package/dist/generate/index.d.ts.map +0 -1
  231. package/dist/generate/index.js +0 -17
  232. package/dist/generate/index.js.map +0 -1
  233. package/dist/index.test.d.ts +0 -2
  234. package/dist/index.test.d.ts.map +0 -1
  235. package/dist/index.test.js +0 -59
  236. package/dist/index.test.js.map +0 -1
  237. package/dist/list/await.d.ts +0 -3
  238. package/dist/list/await.d.ts.map +0 -1
  239. package/dist/list/await.js +0 -28
  240. package/dist/list/await.js.map +0 -1
  241. package/dist/list/constants.d.ts +0 -4
  242. package/dist/list/constants.d.ts.map +0 -1
  243. package/dist/list/constants.js +0 -5
  244. package/dist/list/constants.js.map +0 -1
  245. package/dist/list/create-function.d.ts +0 -3
  246. package/dist/list/create-function.d.ts.map +0 -1
  247. package/dist/list/create-function.js +0 -11
  248. package/dist/list/create-function.js.map +0 -1
  249. package/dist/list/index.d.ts +0 -4
  250. package/dist/list/index.d.ts.map +0 -1
  251. package/dist/list/index.js +0 -5
  252. package/dist/list/index.js.map +0 -1
  253. package/dist/list/prompt.d.ts +0 -3
  254. package/dist/list/prompt.d.ts.map +0 -1
  255. package/dist/list/prompt.js +0 -6
  256. package/dist/list/prompt.js.map +0 -1
  257. package/dist/list/schemas.d.ts +0 -4
  258. package/dist/list/schemas.d.ts.map +0 -1
  259. package/dist/list/schemas.js +0 -8
  260. package/dist/list/schemas.js.map +0 -1
  261. package/dist/list/stream.d.ts +0 -3
  262. package/dist/list/stream.d.ts.map +0 -1
  263. package/dist/list/stream.js +0 -33
  264. package/dist/list/stream.js.map +0 -1
  265. package/dist/list/types.d.ts +0 -11
  266. package/dist/list/types.d.ts.map +0 -1
  267. package/dist/list/types.js +0 -2
  268. package/dist/list/types.js.map +0 -1
  269. package/dist/list/validation.d.ts +0 -3
  270. package/dist/list/validation.d.ts.map +0 -1
  271. package/dist/list/validation.js +0 -12
  272. package/dist/list/validation.js.map +0 -1
  273. package/dist/providers/config.d.ts +0 -4
  274. package/dist/providers/config.d.ts.map +0 -1
  275. package/dist/providers/config.js +0 -21
  276. package/dist/providers/config.js.map +0 -1
  277. package/dist/providers/config.test.d.ts +0 -2
  278. package/dist/providers/config.test.d.ts.map +0 -1
  279. package/dist/providers/config.test.js +0 -37
  280. package/dist/providers/config.test.js.map +0 -1
  281. package/dist/proxy/constants.d.ts +0 -4
  282. package/dist/proxy/constants.d.ts.map +0 -1
  283. package/dist/proxy/constants.js +0 -5
  284. package/dist/proxy/constants.js.map +0 -1
  285. package/dist/proxy/create-function.d.ts +0 -4
  286. package/dist/proxy/create-function.d.ts.map +0 -1
  287. package/dist/proxy/create-function.js +0 -24
  288. package/dist/proxy/create-function.js.map +0 -1
  289. package/dist/proxy/create-proxy.d.ts +0 -2
  290. package/dist/proxy/create-proxy.d.ts.map +0 -1
  291. package/dist/proxy/create-proxy.js +0 -11
  292. package/dist/proxy/create-proxy.js.map +0 -1
  293. package/dist/proxy/function-generator.d.ts +0 -9
  294. package/dist/proxy/function-generator.d.ts.map +0 -1
  295. package/dist/proxy/function-generator.js +0 -29
  296. package/dist/proxy/function-generator.js.map +0 -1
  297. package/dist/proxy/index.d.ts +0 -4
  298. package/dist/proxy/index.d.ts.map +0 -1
  299. package/dist/proxy/index.js +0 -4
  300. package/dist/proxy/index.js.map +0 -1
  301. package/dist/proxy/prompt.d.ts +0 -2
  302. package/dist/proxy/prompt.d.ts.map +0 -1
  303. package/dist/proxy/prompt.js +0 -6
  304. package/dist/proxy/prompt.js.map +0 -1
  305. package/dist/proxy/types.d.ts +0 -7
  306. package/dist/proxy/types.d.ts.map +0 -1
  307. package/dist/proxy/types.js +0 -2
  308. package/dist/proxy/types.js.map +0 -1
  309. package/dist/queue/manager.d.ts +0 -5
  310. package/dist/queue/manager.d.ts.map +0 -1
  311. package/dist/queue/manager.js +0 -37
  312. package/dist/queue/manager.js.map +0 -1
  313. package/dist/queue/manager.test.d.ts +0 -2
  314. package/dist/queue/manager.test.d.ts.map +0 -1
  315. package/dist/queue/manager.test.js +0 -52
  316. package/dist/queue/manager.test.js.map +0 -1
  317. package/dist/schema-converter.d.ts +0 -4
  318. package/dist/schema-converter.d.ts.map +0 -1
  319. package/dist/schema-converter.js +0 -30
  320. package/dist/schema-converter.js.map +0 -1
  321. package/dist/stream/index.d.ts +0 -7
  322. package/dist/stream/index.d.ts.map +0 -1
  323. package/dist/stream/index.js +0 -23
  324. package/dist/stream/index.js.map +0 -1
  325. package/dist/streaming/utils.d.ts +0 -4
  326. package/dist/streaming/utils.d.ts.map +0 -1
  327. package/dist/streaming/utils.js +0 -131
  328. package/dist/streaming/utils.js.map +0 -1
  329. package/dist/streaming/utils.test.d.ts +0 -2
  330. package/dist/streaming/utils.test.d.ts.map +0 -1
  331. package/dist/streaming/utils.test.js +0 -84
  332. package/dist/streaming/utils.test.js.map +0 -1
  333. package/dist/templates/result.d.ts +0 -7
  334. package/dist/templates/result.d.ts.map +0 -1
  335. package/dist/templates/result.js +0 -40
  336. package/dist/templates/result.js.map +0 -1
  337. package/dist/templates/result.test.d.ts +0 -2
  338. package/dist/templates/result.test.d.ts.map +0 -1
  339. package/dist/templates/result.test.js +0 -75
  340. package/dist/templates/result.test.js.map +0 -1
  341. package/dist/test/setup.d.ts +0 -2
  342. package/dist/test/setup.d.ts.map +0 -1
  343. package/dist/test/setup.js +0 -21
  344. package/dist/test/setup.js.map +0 -1
  345. package/dist/test-types.d.ts +0 -13
  346. package/dist/test-types.d.ts.map +0 -1
  347. package/dist/test-types.js +0 -55
  348. package/dist/test-types.js.map +0 -1
  349. package/dist/types/index.d.ts +0 -4
  350. package/dist/types/index.d.ts.map +0 -1
  351. package/dist/types/index.js +0 -4
  352. package/dist/types/index.js.map +0 -1
  353. package/dist/types/list.d.ts +0 -10
  354. package/dist/types/list.d.ts.map +0 -1
  355. package/dist/types/list.js +0 -2
  356. package/dist/types/list.js.map +0 -1
  357. package/dist/types/model.d.ts +0 -7
  358. package/dist/types/model.d.ts.map +0 -1
  359. package/dist/types/model.js +0 -2
  360. package/dist/types/model.js.map +0 -1
  361. package/dist/types/options.d.ts +0 -25
  362. package/dist/types/options.d.ts.map +0 -1
  363. package/dist/types/options.js +0 -2
  364. package/dist/types/options.js.map +0 -1
  365. package/dist/types/schema.d.ts +0 -5
  366. package/dist/types/schema.d.ts.map +0 -1
  367. package/dist/types/schema.js +0 -2
  368. package/dist/types/schema.js.map +0 -1
  369. package/dist/utils/__tests__/request-handler.test.d.ts +0 -2
  370. package/dist/utils/__tests__/request-handler.test.d.ts.map +0 -1
  371. package/dist/utils/__tests__/request-handler.test.js +0 -134
  372. package/dist/utils/__tests__/request-handler.test.js.map +0 -1
  373. package/dist/utils/__tests__/schema.test.d.ts +0 -2
  374. package/dist/utils/__tests__/schema.test.d.ts.map +0 -1
  375. package/dist/utils/__tests__/schema.test.js +0 -49
  376. package/dist/utils/__tests__/schema.test.js.map +0 -1
  377. package/dist/utils/__tests__/stream-progress.test.d.ts +0 -2
  378. package/dist/utils/__tests__/stream-progress.test.d.ts.map +0 -1
  379. package/dist/utils/__tests__/stream-progress.test.js +0 -85
  380. package/dist/utils/__tests__/stream-progress.test.js.map +0 -1
  381. package/dist/utils/index.d.ts +0 -2
  382. package/dist/utils/index.d.ts.map +0 -1
  383. package/dist/utils/index.js +0 -2
  384. package/dist/utils/index.js.map +0 -1
  385. package/dist/utils/request-handler.d.ts +0 -17
  386. package/dist/utils/request-handler.d.ts.map +0 -1
  387. package/dist/utils/request-handler.js +0 -105
  388. package/dist/utils/request-handler.js.map +0 -1
  389. package/dist/utils/schema.d.ts +0 -11
  390. package/dist/utils/schema.d.ts.map +0 -1
  391. package/dist/utils/schema.js +0 -51
  392. package/dist/utils/schema.js.map +0 -1
  393. package/dist/utils/stream-progress.d.ts +0 -17
  394. package/dist/utils/stream-progress.d.ts.map +0 -1
  395. package/dist/utils/stream-progress.js +0 -86
  396. package/dist/utils/stream-progress.js.map +0 -1
  397. package/dist/utils/validation.d.ts +0 -3
  398. package/dist/utils/validation.d.ts.map +0 -1
  399. package/dist/utils/validation.js +0 -30
  400. package/dist/utils/validation.js.map +0 -1
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Tests for the core generate() primitive
3
+ *
4
+ * generate(type, prompt, opts?) is the foundation that all other functions use.
5
+ */
6
+
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
8
+
9
+ // ============================================================================
10
+ // Mock for underlying AI calls
11
+ // ============================================================================
12
+
13
+ const mockAICall = vi.fn()
14
+
15
+ // Mock generate implementation
16
+ async function generate(
17
+ type: string,
18
+ prompt: string,
19
+ options?: Record<string, unknown>
20
+ ): Promise<unknown> {
21
+ return mockAICall(type, prompt, options)
22
+ }
23
+
24
+ // ============================================================================
25
+ // generate(type, prompt, opts) signature tests
26
+ // ============================================================================
27
+
28
+ describe('generate(type, prompt, opts)', () => {
29
+ beforeEach(() => {
30
+ mockAICall.mockReset()
31
+ })
32
+
33
+ describe('type: json', () => {
34
+ it('generates JSON without schema (AI infers structure)', async () => {
35
+ mockAICall.mockResolvedValue({
36
+ competitors: ['Competitor A', 'Competitor B'],
37
+ marketSize: 1000000,
38
+ trends: ['AI adoption', 'Cloud migration'],
39
+ })
40
+
41
+ const result = await generate('json', 'competitive analysis of Acme Corp')
42
+
43
+ expect(mockAICall).toHaveBeenCalledWith('json', 'competitive analysis of Acme Corp', undefined)
44
+ expect(result).toHaveProperty('competitors')
45
+ expect(result).toHaveProperty('marketSize')
46
+ })
47
+
48
+ it('generates JSON with schema (typed, validated)', async () => {
49
+ mockAICall.mockResolvedValue({
50
+ name: 'Spaghetti Carbonara',
51
+ servings: 4,
52
+ ingredients: ['pasta', 'eggs', 'bacon'],
53
+ steps: ['Boil pasta', 'Cook bacon', 'Mix eggs'],
54
+ })
55
+
56
+ const result = await generate('json', 'Italian pasta recipe', {
57
+ schema: {
58
+ name: 'Recipe name',
59
+ servings: 'Number of servings (number)',
60
+ ingredients: ['List of ingredients'],
61
+ steps: ['Cooking steps'],
62
+ },
63
+ })
64
+
65
+ expect(mockAICall).toHaveBeenCalledWith(
66
+ 'json',
67
+ 'Italian pasta recipe',
68
+ expect.objectContaining({ schema: expect.any(Object) })
69
+ )
70
+ expect(result).toHaveProperty('name')
71
+ expect(result).toHaveProperty('servings')
72
+ expect(typeof (result as { servings: number }).servings).toBe('number')
73
+ })
74
+ })
75
+
76
+ describe('type: text', () => {
77
+ it('generates plain text', async () => {
78
+ mockAICall.mockResolvedValue('This is the generated text content.')
79
+
80
+ const result = await generate('text', 'Write a paragraph about AI')
81
+
82
+ expect(mockAICall).toHaveBeenCalledWith('text', 'Write a paragraph about AI', undefined)
83
+ expect(typeof result).toBe('string')
84
+ })
85
+ })
86
+
87
+ describe('type: code', () => {
88
+ it('generates code with language option', async () => {
89
+ mockAICall.mockResolvedValue(`
90
+ function validateEmail(email: string): boolean {
91
+ return /^[^@]+@[^@]+\\.[^@]+$/.test(email);
92
+ }
93
+ `.trim())
94
+
95
+ const result = await generate('code', 'email validation function', {
96
+ language: 'typescript',
97
+ })
98
+
99
+ expect(mockAICall).toHaveBeenCalledWith(
100
+ 'code',
101
+ 'email validation function',
102
+ expect.objectContaining({ language: 'typescript' })
103
+ )
104
+ expect(typeof result).toBe('string')
105
+ expect(result).toContain('function')
106
+ })
107
+
108
+ it('generates code in different languages', async () => {
109
+ mockAICall.mockResolvedValue('def validate_email(email):\n return "@" in email')
110
+
111
+ await generate('code', 'email validation', { language: 'python' })
112
+
113
+ expect(mockAICall).toHaveBeenCalledWith(
114
+ 'code',
115
+ 'email validation',
116
+ expect.objectContaining({ language: 'python' })
117
+ )
118
+ })
119
+ })
120
+
121
+ describe('type: markdown', () => {
122
+ it('generates markdown content', async () => {
123
+ mockAICall.mockResolvedValue('# README\n\n## Features\n\n- Feature 1\n- Feature 2')
124
+
125
+ const result = await generate('markdown', 'README for a TypeScript library')
126
+
127
+ expect(mockAICall).toHaveBeenCalledWith('markdown', 'README for a TypeScript library', undefined)
128
+ expect(typeof result).toBe('string')
129
+ expect(result).toContain('#')
130
+ })
131
+ })
132
+
133
+ describe('type: yaml', () => {
134
+ it('generates YAML content', async () => {
135
+ mockAICall.mockResolvedValue(`
136
+ apiVersion: apps/v1
137
+ kind: Deployment
138
+ metadata:
139
+ name: my-app
140
+ `.trim())
141
+
142
+ const result = await generate('yaml', 'kubernetes deployment for my-app')
143
+
144
+ expect(mockAICall).toHaveBeenCalledWith('yaml', 'kubernetes deployment for my-app', undefined)
145
+ expect(typeof result).toBe('string')
146
+ expect(result).toContain('apiVersion')
147
+ })
148
+ })
149
+
150
+ describe('type: list', () => {
151
+ it('generates a list of items', async () => {
152
+ mockAICall.mockResolvedValue(['Item 1', 'Item 2', 'Item 3'])
153
+
154
+ const result = await generate('list', '5 startup ideas')
155
+
156
+ expect(mockAICall).toHaveBeenCalledWith('list', '5 startup ideas', undefined)
157
+ expect(Array.isArray(result)).toBe(true)
158
+ })
159
+ })
160
+
161
+ describe('type: diagram', () => {
162
+ it('generates diagram code', async () => {
163
+ mockAICall.mockResolvedValue('graph TD\n A[Start] --> B[Process]\n B --> C[End]')
164
+
165
+ const result = await generate('diagram', 'user flow for authentication', {
166
+ format: 'mermaid',
167
+ })
168
+
169
+ expect(mockAICall).toHaveBeenCalledWith(
170
+ 'diagram',
171
+ 'user flow for authentication',
172
+ expect.objectContaining({ format: 'mermaid' })
173
+ )
174
+ expect(typeof result).toBe('string')
175
+ })
176
+ })
177
+
178
+ describe('type: slides', () => {
179
+ it('generates presentation slides', async () => {
180
+ mockAICall.mockResolvedValue('---\ntheme: default\n---\n\n# Title\n\nContent')
181
+
182
+ const result = await generate('slides', 'quarterly review presentation', {
183
+ format: 'slidev',
184
+ slides: 10,
185
+ })
186
+
187
+ expect(mockAICall).toHaveBeenCalledWith(
188
+ 'slides',
189
+ 'quarterly review presentation',
190
+ expect.objectContaining({ format: 'slidev', slides: 10 })
191
+ )
192
+ })
193
+ })
194
+ })
195
+
196
+ // ============================================================================
197
+ // Tagged template support on generate
198
+ // ============================================================================
199
+
200
+ describe('generate as tagged template', () => {
201
+ beforeEach(() => {
202
+ mockAICall.mockReset()
203
+ })
204
+
205
+ // Note: This tests the concept - actual implementation would need the template wrapper
206
+ it('should support tagged template syntax (conceptual)', async () => {
207
+ mockAICall.mockResolvedValue({ analysis: 'Result' })
208
+
209
+ // This would be: generate`analysis of ${company}`
210
+ const company = 'Acme Corp'
211
+ const prompt = `analysis of ${company}`
212
+
213
+ const result = await generate('json', prompt)
214
+
215
+ expect(result).toHaveProperty('analysis')
216
+ })
217
+
218
+ it('should convert objects to YAML in templates (conceptual)', async () => {
219
+ mockAICall.mockResolvedValue('Generated content')
220
+
221
+ const context = {
222
+ brand: 'TechCo',
223
+ audience: 'developers',
224
+ }
225
+
226
+ // This would be: generate`content for ${{ context }}`
227
+ // The object would be converted to YAML
228
+ const prompt = `content for\ncontext:\n brand: TechCo\n audience: developers`
229
+
230
+ await generate('text', prompt)
231
+
232
+ expect(mockAICall).toHaveBeenCalledWith('text', expect.stringContaining('brand: TechCo'), undefined)
233
+ })
234
+ })
235
+
236
+ // ============================================================================
237
+ // Options parameter tests
238
+ // ============================================================================
239
+
240
+ describe('generate options', () => {
241
+ beforeEach(() => {
242
+ mockAICall.mockReset()
243
+ mockAICall.mockResolvedValue('result')
244
+ })
245
+
246
+ it('passes model option', async () => {
247
+ await generate('text', 'test', { model: 'claude-opus-4-5' })
248
+
249
+ expect(mockAICall).toHaveBeenCalledWith(
250
+ 'text',
251
+ 'test',
252
+ expect.objectContaining({ model: 'claude-opus-4-5' })
253
+ )
254
+ })
255
+
256
+ it('passes temperature option', async () => {
257
+ await generate('text', 'creative writing', { temperature: 0.9 })
258
+
259
+ expect(mockAICall).toHaveBeenCalledWith(
260
+ 'text',
261
+ 'creative writing',
262
+ expect.objectContaining({ temperature: 0.9 })
263
+ )
264
+ })
265
+
266
+ it('passes maxTokens option', async () => {
267
+ await generate('text', 'long article', { maxTokens: 4000 })
268
+
269
+ expect(mockAICall).toHaveBeenCalledWith(
270
+ 'text',
271
+ 'long article',
272
+ expect.objectContaining({ maxTokens: 4000 })
273
+ )
274
+ })
275
+
276
+ it('passes thinking option', async () => {
277
+ await generate('json', 'complex analysis', { thinking: 'high' })
278
+
279
+ expect(mockAICall).toHaveBeenCalledWith(
280
+ 'json',
281
+ 'complex analysis',
282
+ expect.objectContaining({ thinking: 'high' })
283
+ )
284
+ })
285
+
286
+ it('passes thinking as number (token budget)', async () => {
287
+ await generate('json', 'complex analysis', { thinking: 10000 })
288
+
289
+ expect(mockAICall).toHaveBeenCalledWith(
290
+ 'json',
291
+ 'complex analysis',
292
+ expect.objectContaining({ thinking: 10000 })
293
+ )
294
+ })
295
+ })
296
+
297
+ // ============================================================================
298
+ // All convenience functions use generate
299
+ // ============================================================================
300
+
301
+ describe('convenience functions map to generate', () => {
302
+ it('documents the mapping', () => {
303
+ // This test documents the expected mappings
304
+ const mappings = {
305
+ 'ai(prompt)': "generate('text', prompt)",
306
+ 'write(prompt)': "generate('text', prompt)",
307
+ 'code(prompt)': "generate('code', prompt)",
308
+ 'list(prompt)': "generate('list', prompt)",
309
+ 'lists(prompt)': "generate('lists', prompt)",
310
+ 'extract(prompt)': "generate('extract', prompt)",
311
+ 'summarize(prompt)': "generate('summary', prompt)",
312
+ 'diagram(prompt)': "generate('diagram', prompt)",
313
+ 'slides(prompt)': "generate('slides', prompt)",
314
+ 'is(prompt)': "generate('boolean', prompt)",
315
+ }
316
+
317
+ expect(Object.keys(mappings)).toHaveLength(10)
318
+ })
319
+ })
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Tests for generateObject and generateText
3
+ *
4
+ * These tests use real AI calls via the Cloudflare AI Gateway.
5
+ * The gateway caches responses, so repeated test runs are fast and free.
6
+ *
7
+ * Required env vars:
8
+ * - AI_GATEWAY_URL: Cloudflare AI Gateway URL
9
+ * - AI_GATEWAY_TOKEN: Gateway auth token (or individual provider keys)
10
+ */
11
+
12
+ import { describe, it, expect, beforeAll } from 'vitest'
13
+ import { generateObject, generateText } from '../src/index.js'
14
+
15
+ // Skip tests if no gateway configured
16
+ const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY
17
+
18
+ describe.skipIf(!hasGateway)('generateObject', () => {
19
+ it('generates a simple object with string fields', async () => {
20
+ const { object } = await generateObject({
21
+ model: 'sonnet',
22
+ schema: {
23
+ greeting: 'A friendly greeting',
24
+ language: 'The language of the greeting',
25
+ },
26
+ prompt: 'Generate a greeting in French',
27
+ })
28
+
29
+ expect(object).toBeDefined()
30
+ expect(typeof object.greeting).toBe('string')
31
+ expect(typeof object.language).toBe('string')
32
+ expect(object.greeting.length).toBeGreaterThan(0)
33
+ })
34
+
35
+ it('generates object with number fields', async () => {
36
+ const { object } = await generateObject({
37
+ model: 'sonnet',
38
+ schema: {
39
+ name: 'City name',
40
+ population: 'Population in millions (number)',
41
+ area: 'Area in square kilometers (number)',
42
+ },
43
+ prompt: 'Generate info about Tokyo',
44
+ })
45
+
46
+ expect(object).toBeDefined()
47
+ expect(typeof object.name).toBe('string')
48
+ expect(typeof object.population).toBe('number')
49
+ expect(typeof object.area).toBe('number')
50
+ })
51
+
52
+ it('generates object with array fields', async () => {
53
+ const { object } = await generateObject({
54
+ model: 'sonnet',
55
+ schema: {
56
+ title: 'Recipe title',
57
+ ingredients: ['List of ingredients'],
58
+ steps: ['Cooking steps'],
59
+ },
60
+ prompt: 'Generate a simple pasta recipe',
61
+ })
62
+
63
+ expect(object).toBeDefined()
64
+ expect(typeof object.title).toBe('string')
65
+ expect(Array.isArray(object.ingredients)).toBe(true)
66
+ expect(Array.isArray(object.steps)).toBe(true)
67
+ expect(object.ingredients.length).toBeGreaterThan(0)
68
+ expect(object.steps.length).toBeGreaterThan(0)
69
+ })
70
+
71
+ it('generates object with enum fields', async () => {
72
+ const { object } = await generateObject({
73
+ model: 'sonnet',
74
+ schema: {
75
+ sentiment: 'positive | negative | neutral',
76
+ confidence: 'Confidence score (number)',
77
+ },
78
+ prompt: 'Analyze sentiment: "I love this product!"',
79
+ })
80
+
81
+ expect(object).toBeDefined()
82
+ expect(['positive', 'negative', 'neutral']).toContain(object.sentiment)
83
+ expect(typeof object.confidence).toBe('number')
84
+ })
85
+
86
+ it('generates nested objects', async () => {
87
+ const { object } = await generateObject({
88
+ model: 'sonnet',
89
+ schema: {
90
+ person: {
91
+ name: 'Full name',
92
+ age: 'Age (number)',
93
+ },
94
+ address: {
95
+ city: 'City name',
96
+ country: 'Country name',
97
+ },
98
+ },
99
+ prompt: 'Generate a fictional person living in Japan',
100
+ })
101
+
102
+ expect(object).toBeDefined()
103
+ expect(typeof object.person.name).toBe('string')
104
+ expect(typeof object.person.age).toBe('number')
105
+ expect(typeof object.address.city).toBe('string')
106
+ expect(typeof object.address.country).toBe('string')
107
+ })
108
+
109
+ it('respects system prompt', async () => {
110
+ const { object } = await generateObject({
111
+ model: 'sonnet',
112
+ schema: {
113
+ response: 'Your response',
114
+ style: 'formal | casual | pirate',
115
+ },
116
+ system: 'You are a pirate. Respond in pirate speak.',
117
+ prompt: 'Say hello',
118
+ })
119
+
120
+ expect(object).toBeDefined()
121
+ expect(object.style).toBe('pirate')
122
+ })
123
+ })
124
+
125
+ describe.skipIf(!hasGateway)('generateText', () => {
126
+ it('generates simple text response', async () => {
127
+ const { text } = await generateText({
128
+ model: 'sonnet',
129
+ prompt: 'Say "Hello, World!" and nothing else.',
130
+ })
131
+
132
+ expect(text).toBeDefined()
133
+ expect(typeof text).toBe('string')
134
+ expect(text.toLowerCase()).toContain('hello')
135
+ })
136
+
137
+ it('respects system prompt', async () => {
138
+ const { text } = await generateText({
139
+ model: 'sonnet',
140
+ system: 'You only respond with exactly 3 words.',
141
+ prompt: 'How are you?',
142
+ })
143
+
144
+ expect(text).toBeDefined()
145
+ // Should be approximately 3 words
146
+ const wordCount = text.trim().split(/\s+/).length
147
+ expect(wordCount).toBeLessThanOrEqual(5) // Allow some flexibility
148
+ })
149
+
150
+ it('handles multi-turn messages', async () => {
151
+ const { text } = await generateText({
152
+ model: 'sonnet',
153
+ messages: [
154
+ { role: 'user', content: 'My name is Alice.' },
155
+ { role: 'assistant', content: 'Nice to meet you, Alice!' },
156
+ { role: 'user', content: 'What is my name?' },
157
+ ],
158
+ })
159
+
160
+ expect(text).toBeDefined()
161
+ expect(text.toLowerCase()).toContain('alice')
162
+ })
163
+ })