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,694 @@
1
+ /**
2
+ * Tests for core AI functions
3
+ *
4
+ * These tests verify the API contracts for each function.
5
+ * Tests marked with .skipIf(!hasGateway) require actual AI calls.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
9
+ import { stringify as yamlStringify } from 'yaml'
10
+
11
+ // Skip tests if no gateway configured
12
+ const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY
13
+
14
+ // ============================================================================
15
+ // Mock implementations for unit tests
16
+ // ============================================================================
17
+
18
+ // Mock generate function that all others should call
19
+ const mockGenerate = vi.fn()
20
+
21
+ // ============================================================================
22
+ // ai() - Direct text generation
23
+ // ============================================================================
24
+
25
+ describe('ai()', () => {
26
+ beforeEach(() => {
27
+ mockGenerate.mockReset()
28
+ mockGenerate.mockResolvedValue('Generated text')
29
+ })
30
+
31
+ it('should accept a string prompt', async () => {
32
+ const ai = createMockAi()
33
+ const result = await ai('Write a haiku')
34
+
35
+ expect(mockGenerate).toHaveBeenCalledWith(
36
+ 'text',
37
+ 'Write a haiku',
38
+ expect.any(Object)
39
+ )
40
+ expect(result).toBe('Generated text')
41
+ })
42
+
43
+ it('should accept tagged template literal', async () => {
44
+ const ai = createMockAi()
45
+ const topic = 'TypeScript'
46
+ const result = await ai`Write about ${topic}`
47
+
48
+ expect(mockGenerate).toHaveBeenCalled()
49
+ const [, prompt] = mockGenerate.mock.calls[0]
50
+ expect(prompt).toContain('TypeScript')
51
+ expect(result).toBe('Generated text')
52
+ })
53
+
54
+ it('should accept options parameter', async () => {
55
+ const ai = createMockAi()
56
+ await ai`test`({ model: 'claude-opus-4-5' })
57
+
58
+ expect(mockGenerate).toHaveBeenCalledWith(
59
+ 'text',
60
+ 'test',
61
+ expect.objectContaining({ model: 'claude-opus-4-5' })
62
+ )
63
+ })
64
+
65
+ it('should return string type', async () => {
66
+ const ai = createMockAi()
67
+ const result = await ai('test')
68
+ expect(typeof result).toBe('string')
69
+ })
70
+ })
71
+
72
+ // ============================================================================
73
+ // summarize() - Condense text
74
+ // ============================================================================
75
+
76
+ describe('summarize()', () => {
77
+ beforeEach(() => {
78
+ mockGenerate.mockReset()
79
+ mockGenerate.mockResolvedValue('Summary of content')
80
+ })
81
+
82
+ it('should accept text to summarize', async () => {
83
+ const summarize = createMockSummarize()
84
+ const result = await summarize`${longArticle}`
85
+
86
+ expect(mockGenerate).toHaveBeenCalledWith(
87
+ 'summary',
88
+ expect.stringContaining('article'),
89
+ expect.any(Object)
90
+ )
91
+ expect(result).toBe('Summary of content')
92
+ })
93
+
94
+ it('should support length option', async () => {
95
+ const summarize = createMockSummarize()
96
+ await summarize`${longArticle}`({ length: 'short' })
97
+
98
+ expect(mockGenerate).toHaveBeenCalledWith(
99
+ 'summary',
100
+ expect.any(String),
101
+ expect.objectContaining({ length: 'short' })
102
+ )
103
+ })
104
+
105
+ it('should support audience option', async () => {
106
+ const summarize = createMockSummarize()
107
+ await summarize`${technicalReport}${{ audience: 'executives', focus: 'business impact' }}`
108
+
109
+ const [, prompt] = mockGenerate.mock.calls[0]
110
+ expect(prompt).toContain('executives')
111
+ expect(prompt).toContain('business impact')
112
+ })
113
+ })
114
+
115
+ // ============================================================================
116
+ // is() - Boolean classification
117
+ // ============================================================================
118
+
119
+ describe('is()', () => {
120
+ beforeEach(() => {
121
+ mockGenerate.mockReset()
122
+ })
123
+
124
+ it('should return boolean true', async () => {
125
+ mockGenerate.mockResolvedValue(true)
126
+ const is = createMockIs()
127
+
128
+ const result = await is`${'hello@example.com'} a valid email?`
129
+ expect(result).toBe(true)
130
+ })
131
+
132
+ it('should return boolean false', async () => {
133
+ mockGenerate.mockResolvedValue(false)
134
+ const is = createMockIs()
135
+
136
+ const result = await is`${'not-an-email'} a valid email?`
137
+ expect(result).toBe(false)
138
+ })
139
+
140
+ it('should accept natural question format', async () => {
141
+ mockGenerate.mockResolvedValue(true)
142
+ const is = createMockIs()
143
+
144
+ await is`${'The product is amazing!'} positive sentiment?`
145
+
146
+ expect(mockGenerate).toHaveBeenCalledWith(
147
+ 'boolean',
148
+ expect.stringContaining('positive sentiment'),
149
+ expect.any(Object)
150
+ )
151
+ })
152
+
153
+ it('should support model option for complex classifications', async () => {
154
+ mockGenerate.mockResolvedValue(true)
155
+ const is = createMockIs()
156
+
157
+ await is`${claim} factually accurate?`({ model: 'claude-opus-4-5' })
158
+
159
+ expect(mockGenerate).toHaveBeenCalledWith(
160
+ 'boolean',
161
+ expect.any(String),
162
+ expect.objectContaining({ model: 'claude-opus-4-5' })
163
+ )
164
+ })
165
+ })
166
+
167
+ // ============================================================================
168
+ // list() - Generate a list
169
+ // ============================================================================
170
+
171
+ describe('list()', () => {
172
+ beforeEach(() => {
173
+ mockGenerate.mockReset()
174
+ mockGenerate.mockResolvedValue(['Item 1', 'Item 2', 'Item 3'])
175
+ })
176
+
177
+ it('should return an array of strings', async () => {
178
+ const list = createMockList()
179
+ const result = await list`startup ideas for ${industry}`
180
+
181
+ expect(Array.isArray(result)).toBe(true)
182
+ expect(result).toHaveLength(3)
183
+ expect(result.every((item: unknown) => typeof item === 'string')).toBe(true)
184
+ })
185
+
186
+ it('should respect count in prompt', async () => {
187
+ const list = createMockList()
188
+ await list`10 blog post titles for ${topic}`
189
+
190
+ expect(mockGenerate).toHaveBeenCalledWith(
191
+ 'list',
192
+ expect.stringContaining('10'),
193
+ expect.any(Object)
194
+ )
195
+ })
196
+
197
+ it('should support count option', async () => {
198
+ const list = createMockList()
199
+ await list('startup ideas', { count: 10 })
200
+
201
+ expect(mockGenerate).toHaveBeenCalledWith(
202
+ 'list',
203
+ 'startup ideas',
204
+ expect.objectContaining({ count: 10 })
205
+ )
206
+ })
207
+ })
208
+
209
+ // ============================================================================
210
+ // lists() - Generate multiple named lists
211
+ // ============================================================================
212
+
213
+ describe('lists()', () => {
214
+ beforeEach(() => {
215
+ mockGenerate.mockReset()
216
+ mockGenerate.mockResolvedValue({
217
+ pros: ['Pro 1', 'Pro 2'],
218
+ cons: ['Con 1', 'Con 2'],
219
+ })
220
+ })
221
+
222
+ it('should return named lists object', async () => {
223
+ const lists = createMockLists()
224
+ const result = await lists`pros and cons of ${topic}`
225
+
226
+ expect(result).toHaveProperty('pros')
227
+ expect(result).toHaveProperty('cons')
228
+ expect(Array.isArray(result.pros)).toBe(true)
229
+ expect(Array.isArray(result.cons)).toBe(true)
230
+ })
231
+
232
+ it('should support SWOT analysis format', async () => {
233
+ mockGenerate.mockResolvedValue({
234
+ strengths: ['S1'],
235
+ weaknesses: ['W1'],
236
+ opportunities: ['O1'],
237
+ threats: ['T1'],
238
+ })
239
+
240
+ const lists = createMockLists()
241
+ const result = await lists`SWOT analysis for ${{ company, market }}`
242
+
243
+ expect(result).toHaveProperty('strengths')
244
+ expect(result).toHaveProperty('weaknesses')
245
+ expect(result).toHaveProperty('opportunities')
246
+ expect(result).toHaveProperty('threats')
247
+ })
248
+ })
249
+
250
+ // ============================================================================
251
+ // extract() - Extract from text
252
+ // ============================================================================
253
+
254
+ describe('extract()', () => {
255
+ beforeEach(() => {
256
+ mockGenerate.mockReset()
257
+ mockGenerate.mockResolvedValue(['John Smith', 'Jane Doe'])
258
+ })
259
+
260
+ it('should extract items from text', async () => {
261
+ const extract = createMockExtract()
262
+ const result = await extract`person names from ${article}`
263
+
264
+ expect(Array.isArray(result)).toBe(true)
265
+ expect(result).toContain('John Smith')
266
+ expect(result).toContain('Jane Doe')
267
+ })
268
+
269
+ it('should support schema for structured extraction', async () => {
270
+ mockGenerate.mockResolvedValue([
271
+ { name: 'Acme Corp', role: 'competitor' },
272
+ { name: 'Beta Inc', role: 'partner' },
273
+ ])
274
+
275
+ const extract = createMockExtract()
276
+ const result = await extract`companies from ${text}${{
277
+ schema: {
278
+ name: 'Company name',
279
+ role: 'mentioned as: competitor | partner | customer',
280
+ },
281
+ }}`
282
+
283
+ expect(result[0]).toHaveProperty('name')
284
+ expect(result[0]).toHaveProperty('role')
285
+ })
286
+ })
287
+
288
+ // ============================================================================
289
+ // write() - Generate text content
290
+ // ============================================================================
291
+
292
+ describe('write()', () => {
293
+ beforeEach(() => {
294
+ mockGenerate.mockReset()
295
+ mockGenerate.mockResolvedValue('Generated content here...')
296
+ })
297
+
298
+ it('should generate text content', async () => {
299
+ const write = createMockWrite()
300
+ const result = await write`professional email to ${recipient} about ${subject}`
301
+
302
+ expect(typeof result).toBe('string')
303
+ expect(result.length).toBeGreaterThan(0)
304
+ })
305
+
306
+ it('should support tone option', async () => {
307
+ const write = createMockWrite()
308
+ await write('blog post', { tone: 'casual', topic: 'TypeScript' })
309
+
310
+ expect(mockGenerate).toHaveBeenCalledWith(
311
+ 'text',
312
+ 'blog post',
313
+ expect.objectContaining({ tone: 'casual' })
314
+ )
315
+ })
316
+
317
+ it('should support length option', async () => {
318
+ const write = createMockWrite()
319
+ await write('article', { length: 'long' })
320
+
321
+ expect(mockGenerate).toHaveBeenCalledWith(
322
+ 'text',
323
+ 'article',
324
+ expect.objectContaining({ length: 'long' })
325
+ )
326
+ })
327
+ })
328
+
329
+ // ============================================================================
330
+ // code() - Generate code
331
+ // ============================================================================
332
+
333
+ describe('code()', () => {
334
+ beforeEach(() => {
335
+ mockGenerate.mockReset()
336
+ mockGenerate.mockResolvedValue('function validate(email) { return email.includes("@"); }')
337
+ })
338
+
339
+ it('should generate code', async () => {
340
+ const code = createMockCode()
341
+ const result = await code`email validation function`
342
+
343
+ expect(typeof result).toBe('string')
344
+ expect(result).toContain('function')
345
+ })
346
+
347
+ it('should support language option', async () => {
348
+ const code = createMockCode()
349
+ await code('REST API endpoints', { language: 'typescript' })
350
+
351
+ expect(mockGenerate).toHaveBeenCalledWith(
352
+ 'code',
353
+ 'REST API endpoints',
354
+ expect.objectContaining({ language: 'typescript' })
355
+ )
356
+ })
357
+
358
+ it('should handle complex requirements via object interpolation', async () => {
359
+ const code = createMockCode()
360
+ const requirements = {
361
+ pages: ['home', 'about', 'pricing'],
362
+ features: ['dark mode', 'animations'],
363
+ stack: 'Next.js + Tailwind',
364
+ }
365
+
366
+ await code`marketing website${{ requirements }}`
367
+
368
+ const [, prompt] = mockGenerate.mock.calls[0]
369
+ expect(prompt).toContain('pages:')
370
+ expect(prompt).toContain('- home')
371
+ expect(prompt).toContain('features:')
372
+ })
373
+ })
374
+
375
+ // ============================================================================
376
+ // diagram() - Generate diagrams
377
+ // ============================================================================
378
+
379
+ describe('diagram()', () => {
380
+ beforeEach(() => {
381
+ mockGenerate.mockReset()
382
+ mockGenerate.mockResolvedValue('graph TD\n A --> B\n B --> C')
383
+ })
384
+
385
+ it('should generate mermaid diagrams', async () => {
386
+ const diagram = createMockDiagram()
387
+ const result = await diagram`user authentication flow`
388
+
389
+ expect(typeof result).toBe('string')
390
+ expect(result).toContain('graph')
391
+ })
392
+
393
+ it('should support format option', async () => {
394
+ const diagram = createMockDiagram()
395
+ await diagram('database schema', { format: 'mermaid', type: 'erd' })
396
+
397
+ expect(mockGenerate).toHaveBeenCalledWith(
398
+ 'diagram',
399
+ 'database schema',
400
+ expect.objectContaining({ format: 'mermaid', type: 'erd' })
401
+ )
402
+ })
403
+ })
404
+
405
+ // ============================================================================
406
+ // slides() - Generate presentations
407
+ // ============================================================================
408
+
409
+ describe('slides()', () => {
410
+ beforeEach(() => {
411
+ mockGenerate.mockReset()
412
+ mockGenerate.mockResolvedValue('---\ntheme: default\n---\n\n# Slide 1\n\nContent here')
413
+ })
414
+
415
+ it('should generate slidev-format markdown', async () => {
416
+ const slides = createMockSlides()
417
+ const result = await slides`${topic}`
418
+
419
+ expect(typeof result).toBe('string')
420
+ expect(result).toContain('---')
421
+ })
422
+
423
+ it('should support format option', async () => {
424
+ const slides = createMockSlides()
425
+ await slides('quarterly review', { format: 'marp', slides: 12 })
426
+
427
+ expect(mockGenerate).toHaveBeenCalledWith(
428
+ 'slides',
429
+ 'quarterly review',
430
+ expect.objectContaining({ format: 'marp', slides: 12 })
431
+ )
432
+ })
433
+
434
+ it('should support speaker notes', async () => {
435
+ const slides = createMockSlides()
436
+ await slides('workshop', { includeNotes: true, duration: '2 hours' })
437
+
438
+ expect(mockGenerate).toHaveBeenCalledWith(
439
+ 'slides',
440
+ 'workshop',
441
+ expect.objectContaining({ includeNotes: true })
442
+ )
443
+ })
444
+ })
445
+
446
+ // ============================================================================
447
+ // image() - Generate images
448
+ // ============================================================================
449
+
450
+ describe('image()', () => {
451
+ beforeEach(() => {
452
+ mockGenerate.mockReset()
453
+ mockGenerate.mockResolvedValue(Buffer.from('fake-image-data'))
454
+ })
455
+
456
+ it('should generate image buffer', async () => {
457
+ const image = createMockImage()
458
+ const result = await image`minimalist logo for ${companyName}`
459
+
460
+ expect(Buffer.isBuffer(result)).toBe(true)
461
+ })
462
+
463
+ it('should support size option', async () => {
464
+ const image = createMockImage()
465
+ await image('robot reading a book', { size: '1024x1024', style: 'cartoon' })
466
+
467
+ expect(mockGenerate).toHaveBeenCalledWith(
468
+ 'image',
469
+ 'robot reading a book',
470
+ expect.objectContaining({ size: '1024x1024', style: 'cartoon' })
471
+ )
472
+ })
473
+ })
474
+
475
+ // ============================================================================
476
+ // video() - Generate videos
477
+ // ============================================================================
478
+
479
+ describe('video()', () => {
480
+ beforeEach(() => {
481
+ mockGenerate.mockReset()
482
+ mockGenerate.mockResolvedValue(Buffer.from('fake-video-data'))
483
+ })
484
+
485
+ it('should generate video buffer', async () => {
486
+ const video = createMockVideo()
487
+ const result = await video`product demo for ${productName}`
488
+
489
+ expect(Buffer.isBuffer(result)).toBe(true)
490
+ })
491
+
492
+ it('should support duration and aspect options', async () => {
493
+ const video = createMockVideo()
494
+ await video('promotional video', { duration: 30, aspect: '16:9', style: 'motion graphics' })
495
+
496
+ expect(mockGenerate).toHaveBeenCalledWith(
497
+ 'video',
498
+ 'promotional video',
499
+ expect.objectContaining({ duration: 30, aspect: '16:9' })
500
+ )
501
+ })
502
+ })
503
+
504
+ // ============================================================================
505
+ // research() - Agentic research
506
+ // ============================================================================
507
+
508
+ describe('research()', () => {
509
+ beforeEach(() => {
510
+ mockGenerate.mockReset()
511
+ mockGenerate.mockResolvedValue({
512
+ summary: 'Key findings...',
513
+ sources: [{ url: 'https://example.com', title: 'Source 1' }],
514
+ findings: ['Finding 1', 'Finding 2'],
515
+ confidence: 0.85,
516
+ })
517
+ })
518
+
519
+ it('should return structured research results', async () => {
520
+ const research = createMockResearch()
521
+ const result = await research`${topic}`
522
+
523
+ expect(result).toHaveProperty('summary')
524
+ expect(result).toHaveProperty('sources')
525
+ expect(result).toHaveProperty('findings')
526
+ expect(result).toHaveProperty('confidence')
527
+ })
528
+
529
+ it('should support depth option', async () => {
530
+ const research = createMockResearch()
531
+ await research`market size for AI tools`({ depth: 'thorough' })
532
+
533
+ expect(mockGenerate).toHaveBeenCalledWith(
534
+ 'research',
535
+ expect.any(String),
536
+ expect.objectContaining({ depth: 'thorough' })
537
+ )
538
+ })
539
+ })
540
+
541
+ // ============================================================================
542
+ // do() - Single-pass task with tools
543
+ // ============================================================================
544
+
545
+ describe('do()', () => {
546
+ beforeEach(() => {
547
+ mockGenerate.mockReset()
548
+ mockGenerate.mockResolvedValue({ summary: 'Done', result: 'Task completed' })
549
+ })
550
+
551
+ it('should execute a task', async () => {
552
+ const doFn = createMockDo()
553
+ const result = await doFn`translate ${text} to Spanish`
554
+
555
+ expect(result).toBeDefined()
556
+ })
557
+
558
+ it('should handle complex multi-function tasks', async () => {
559
+ mockGenerate.mockResolvedValue({
560
+ summary: 'Article summary',
561
+ people: ['John', 'Jane'],
562
+ actionItems: ['Review', 'Follow up'],
563
+ })
564
+
565
+ const doFn = createMockDo()
566
+ const result = await doFn`
567
+ analyze this article and give me a summary,
568
+ key people mentioned, and action items
569
+ ${article}
570
+ `
571
+
572
+ expect(result).toHaveProperty('summary')
573
+ })
574
+
575
+ it('is single-pass, not agentic loop', async () => {
576
+ const doFn = createMockDo()
577
+ await doFn`analyze ${data}`
578
+
579
+ // Should only call generate once (single pass)
580
+ expect(mockGenerate).toHaveBeenCalledTimes(1)
581
+ })
582
+ })
583
+
584
+ // ============================================================================
585
+ // Helper functions to create mock implementations
586
+ // ============================================================================
587
+
588
+ function createMockAi() {
589
+ return createMockFunction('text')
590
+ }
591
+
592
+ function createMockSummarize() {
593
+ return createMockFunction('summary')
594
+ }
595
+
596
+ function createMockIs() {
597
+ return createMockFunction('boolean')
598
+ }
599
+
600
+ function createMockList() {
601
+ return createMockFunction('list')
602
+ }
603
+
604
+ function createMockLists() {
605
+ return createMockFunction('lists')
606
+ }
607
+
608
+ function createMockExtract() {
609
+ return createMockFunction('extract')
610
+ }
611
+
612
+ function createMockWrite() {
613
+ return createMockFunction('text')
614
+ }
615
+
616
+ function createMockCode() {
617
+ return createMockFunction('code')
618
+ }
619
+
620
+ function createMockDiagram() {
621
+ return createMockFunction('diagram')
622
+ }
623
+
624
+ function createMockSlides() {
625
+ return createMockFunction('slides')
626
+ }
627
+
628
+ function createMockImage() {
629
+ return createMockFunction('image')
630
+ }
631
+
632
+ function createMockVideo() {
633
+ return createMockFunction('video')
634
+ }
635
+
636
+ function createMockResearch() {
637
+ return createMockFunction('research')
638
+ }
639
+
640
+ function createMockDo() {
641
+ return createMockFunction('do')
642
+ }
643
+
644
+ function createMockFunction(type: string) {
645
+ return function (promptOrStrings: string | TemplateStringsArray, ...args: unknown[]) {
646
+ let prompt: string
647
+
648
+ if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
649
+ // Tagged template
650
+ prompt = (promptOrStrings as TemplateStringsArray).reduce((acc, str, i) => {
651
+ const value = args[i]
652
+ if (value === undefined) return acc + str
653
+ if (typeof value === 'object' && value !== null) {
654
+ // Convert objects to YAML for readability (matches real implementation)
655
+ return acc + str + '\n' + yamlStringify(value).trim()
656
+ }
657
+ return acc + str + String(value)
658
+ }, '')
659
+
660
+ // Return chainable for options - properly make it thenable
661
+ const basePromise = mockGenerate(type, prompt, {})
662
+ const chainable = (options?: Record<string, unknown>) => mockGenerate(type, prompt, options || {})
663
+
664
+ // Add then/catch to make it awaitable
665
+ ;(chainable as unknown as Promise<unknown>).then = basePromise.then.bind(basePromise)
666
+ ;(chainable as unknown as Promise<unknown>).catch = basePromise.catch.bind(basePromise)
667
+
668
+ return chainable
669
+ }
670
+
671
+ // Regular call
672
+ prompt = promptOrStrings as string
673
+ return mockGenerate(type, prompt, args[0] || {})
674
+ }
675
+ }
676
+
677
+ // ============================================================================
678
+ // Test fixtures
679
+ // ============================================================================
680
+
681
+ const longArticle = 'This is a long article about technology and innovation...'
682
+ const technicalReport = 'Technical analysis of system performance metrics...'
683
+ const industry = 'fintech'
684
+ const topic = 'TypeScript'
685
+ const claim = 'The Earth is round'
686
+ const article = 'Article mentioning John Smith and Jane Doe...'
687
+ const text = 'Some text content'
688
+ const company = 'Acme Corp'
689
+ const market = 'SaaS'
690
+ const recipient = 'John'
691
+ const subject = 'Project Update'
692
+ const companyName = 'TechCorp'
693
+ const productName = 'ProductX'
694
+ const data = { key: 'value' }