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,510 @@
1
+ /**
2
+ * Blog Post Generation Live Tests
3
+ *
4
+ * These tests run LIVE against real AI providers by default.
5
+ * They verify the complete blog generation workflow:
6
+ *
7
+ * ```ts
8
+ * const titles = await list`10 blog post titles about ${topic}`
9
+ * const posts = titles.map(title => write`a blog post starting with "# ${title}"`)
10
+ * ```
11
+ *
12
+ * Tests cover:
13
+ * - Real API calls to OpenAI, Anthropic, etc.
14
+ * - Action/event storage in the database
15
+ * - Both realtime and batch execution modes
16
+ * - Multiple providers
17
+ *
18
+ * Run with:
19
+ * ```bash
20
+ * pnpm test blog-generation.live
21
+ * ```
22
+ *
23
+ * Skip live tests (CI without API keys):
24
+ * ```bash
25
+ * SKIP_LIVE_TESTS=true pnpm test blog-generation.live
26
+ * ```
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+
31
+ import { describe, it, expect, beforeEach, afterEach, beforeAll } from 'vitest'
32
+ import {
33
+ configure,
34
+ resetContext,
35
+ withContext,
36
+ getProvider,
37
+ getModel,
38
+ getBatchMode,
39
+ } from '../src/context.js'
40
+ import { createBatch, withBatch, type BatchProvider } from '../src/batch-queue.js'
41
+ import { generateObject, generateText } from '../src/generate.js'
42
+
43
+ // Database provider for action/event storage
44
+ import { createMemoryProvider, type MemoryProvider, type Action, type Event } from '../../ai-database/src/memory-provider.js'
45
+
46
+ // Batch storage
47
+ import '../src/batch/memory.js'
48
+ import { configureMemoryAdapter, clearBatches, getBatches } from '../src/batch/memory.js'
49
+
50
+ // ============================================================================
51
+ // Configuration
52
+ // ============================================================================
53
+
54
+ const SKIP_LIVE = process.env.SKIP_LIVE_TESTS === 'true'
55
+ const describeLive = SKIP_LIVE ? describe.skip : describe
56
+
57
+ // Detect available providers
58
+ const hasOpenAI = !!process.env.OPENAI_API_KEY
59
+ const hasAnthropic = !!process.env.ANTHROPIC_API_KEY
60
+
61
+ // Provider configs
62
+ const PROVIDERS: Record<string, { model: string; available: boolean }> = {
63
+ openai: { model: 'gpt-4o-mini', available: hasOpenAI },
64
+ anthropic: { model: 'claude-sonnet-4-20250514', available: hasAnthropic },
65
+ }
66
+
67
+ // Get first available provider
68
+ const defaultProvider = Object.entries(PROVIDERS).find(([, cfg]) => cfg.available)?.[0] || 'openai'
69
+ const defaultModel = PROVIDERS[defaultProvider]?.model || 'gpt-4o-mini'
70
+
71
+ // ============================================================================
72
+ // Database Setup
73
+ // ============================================================================
74
+
75
+ let db: MemoryProvider
76
+ let capturedEvents: Event[] = []
77
+
78
+ // ============================================================================
79
+ // Test Helpers
80
+ // ============================================================================
81
+
82
+ async function createAction(data: {
83
+ action: string
84
+ object: string
85
+ objectData?: Record<string, unknown>
86
+ total?: number
87
+ }): Promise<Action> {
88
+ return db.createAction({
89
+ actor: 'test:live',
90
+ action: data.action,
91
+ object: data.object,
92
+ objectData: data.objectData,
93
+ total: data.total,
94
+ })
95
+ }
96
+
97
+ async function generateTitles(topic: string, count: number): Promise<{ titles: string[]; action: Action }> {
98
+ const action = await createAction({
99
+ action: 'generate',
100
+ object: 'BlogTitles',
101
+ objectData: { topic, count },
102
+ total: 1,
103
+ })
104
+
105
+ await db.updateAction(action.id, { status: 'active' })
106
+
107
+ const result = await generateObject({
108
+ model: getModel(),
109
+ schema: { titles: [`${count} blog post titles about ${topic}`] },
110
+ prompt: `Generate exactly ${count} creative blog post titles about "${topic}".`,
111
+ })
112
+
113
+ const titles = (result.object as { titles: string[] }).titles
114
+
115
+ await db.updateAction(action.id, {
116
+ status: 'completed',
117
+ progress: 1,
118
+ result: { titles },
119
+ })
120
+
121
+ return { titles, action }
122
+ }
123
+
124
+ async function generatePost(title: string): Promise<string> {
125
+ const result = await generateText({
126
+ model: getModel(),
127
+ prompt: `Write a blog post starting with "# ${title}"\n\nInclude an introduction, 2-3 sections, and conclusion. Be concise.`,
128
+ maxTokens: 800,
129
+ })
130
+ return result.text
131
+ }
132
+
133
+ async function generatePosts(
134
+ titles: string[],
135
+ mode: 'realtime' | 'batch' = 'realtime'
136
+ ): Promise<{ posts: string[]; action: Action }> {
137
+ const action = await createAction({
138
+ action: 'generate',
139
+ object: 'BlogPosts',
140
+ objectData: { titles, mode },
141
+ total: titles.length,
142
+ })
143
+
144
+ await db.updateAction(action.id, { status: 'active' })
145
+
146
+ const posts: string[] = []
147
+
148
+ if (mode === 'batch') {
149
+ const batch = createBatch({
150
+ provider: getProvider(),
151
+ model: getModel(),
152
+ metadata: { actionId: action.id },
153
+ })
154
+
155
+ titles.forEach((title, i) => {
156
+ batch.add(
157
+ `Write a blog post starting with "# ${title}"\n\nBe concise.`,
158
+ { customId: `post-${i}`, metadata: { title } }
159
+ )
160
+ })
161
+
162
+ const { completion } = await batch.submit()
163
+ const results = await completion
164
+
165
+ for (const result of results) {
166
+ posts.push(result.status === 'completed' ? (result.result as string) : `[Failed]`)
167
+ await db.updateAction(action.id, { progress: posts.length })
168
+ }
169
+ } else {
170
+ for (let i = 0; i < titles.length; i++) {
171
+ const post = await generatePost(titles[i])
172
+ posts.push(post)
173
+ await db.updateAction(action.id, { progress: i + 1 })
174
+ }
175
+ }
176
+
177
+ await db.updateAction(action.id, { status: 'completed', result: { count: posts.length } })
178
+
179
+ return { posts, action }
180
+ }
181
+
182
+ // ============================================================================
183
+ // Tests
184
+ // ============================================================================
185
+
186
+ describeLive('Blog Generation (Live)', () => {
187
+ beforeAll(() => {
188
+ console.log('\n🔴 LIVE TEST MODE')
189
+ console.log(` Default provider: ${defaultProvider}`)
190
+ console.log(` OpenAI available: ${hasOpenAI}`)
191
+ console.log(` Anthropic available: ${hasAnthropic}\n`)
192
+ })
193
+
194
+ beforeEach(() => {
195
+ resetContext()
196
+ db = createMemoryProvider()
197
+ capturedEvents = []
198
+ db.on('*', (e) => capturedEvents.push(e))
199
+ configure({ provider: defaultProvider as BatchProvider, model: defaultModel, batchMode: 'immediate' })
200
+ clearBatches()
201
+ configureMemoryAdapter({})
202
+ })
203
+
204
+ afterEach(() => {
205
+ resetContext()
206
+ clearBatches()
207
+ db.clear()
208
+ })
209
+
210
+ // ==========================================================================
211
+ // Core Pattern Tests
212
+ // ==========================================================================
213
+
214
+ describe('Core Pattern: list → write', () => {
215
+ it('generates titles from a topic', async () => {
216
+ const { titles, action } = await generateTitles('building AI products', 3)
217
+
218
+ expect(titles).toHaveLength(3)
219
+ titles.forEach((t) => expect(typeof t).toBe('string'))
220
+
221
+ // Verify action tracking
222
+ expect(action.status).toBe('completed')
223
+ expect(action.result).toEqual({ titles })
224
+ }, 30000)
225
+
226
+ it('generates a blog post from a title', async () => {
227
+ const title = 'The Future of AI Development'
228
+ const post = await generatePost(title)
229
+
230
+ expect(post).toContain(`# ${title}`)
231
+ expect(post.length).toBeGreaterThan(200)
232
+ }, 30000)
233
+
234
+ it('generates multiple posts from titles', async () => {
235
+ const { titles } = await generateTitles('startup growth', 2)
236
+ const { posts, action } = await generatePosts(titles)
237
+
238
+ expect(posts).toHaveLength(2)
239
+ posts.forEach((post, i) => {
240
+ expect(post).toContain(`# ${titles[i]}`)
241
+ })
242
+
243
+ expect(action.progress).toBe(2)
244
+ expect(action.total).toBe(2)
245
+ }, 60000)
246
+ })
247
+
248
+ // ==========================================================================
249
+ // Action & Event Storage
250
+ // ==========================================================================
251
+
252
+ describe('Action & Event Storage', () => {
253
+ it('creates actions with verb conjugation', async () => {
254
+ const { action } = await generateTitles('test topic', 2)
255
+
256
+ expect(action.action).toBe('generate')
257
+ expect(action.act).toBe('generates')
258
+ expect(action.activity).toBe('generating')
259
+ expect(action.actor).toBe('test:live')
260
+ }, 30000)
261
+
262
+ it('tracks action lifecycle timestamps', async () => {
263
+ const { action } = await generateTitles('test', 2)
264
+
265
+ const final = await db.getAction(action.id)
266
+ expect(final?.createdAt).toBeInstanceOf(Date)
267
+ expect(final?.startedAt).toBeInstanceOf(Date)
268
+ expect(final?.completedAt).toBeInstanceOf(Date)
269
+ expect(final?.startedAt!.getTime()).toBeGreaterThanOrEqual(final?.createdAt!.getTime()!)
270
+ expect(final?.completedAt!.getTime()).toBeGreaterThanOrEqual(final?.startedAt!.getTime()!)
271
+ }, 30000)
272
+
273
+ it('emits events for state transitions', async () => {
274
+ await generateTitles('test', 2)
275
+
276
+ const actionEvents = capturedEvents.filter((e) => e.event.startsWith('Action.'))
277
+ const eventTypes = actionEvents.map((e) => e.event)
278
+
279
+ expect(eventTypes).toContain('Action.created')
280
+ expect(eventTypes).toContain('Action.started')
281
+ expect(eventTypes).toContain('Action.completed')
282
+ }, 30000)
283
+
284
+ it('stores action result data', async () => {
285
+ const { titles, action } = await generateTitles('AI development', 3)
286
+
287
+ const stored = await db.getAction(action.id)
288
+ expect(stored?.result).toEqual({ titles })
289
+ expect(stored?.objectData).toEqual({ topic: 'AI development', count: 3 })
290
+ }, 30000)
291
+
292
+ it('queries actions by status', async () => {
293
+ await generateTitles('topic 1', 2)
294
+ await generateTitles('topic 2', 2)
295
+
296
+ const completed = await db.listActions({ status: 'completed' })
297
+ expect(completed.length).toBe(2)
298
+ }, 60000)
299
+
300
+ it('queries events by type pattern', async () => {
301
+ await generateTitles('test', 2)
302
+
303
+ const created = await db.listEvents({ event: 'Action.created' })
304
+ const allAction = await db.listEvents({ event: 'Action.*' })
305
+
306
+ expect(created.length).toBeGreaterThanOrEqual(1)
307
+ expect(allAction.length).toBeGreaterThanOrEqual(3) // created, started, completed
308
+ }, 30000)
309
+ })
310
+
311
+ // ==========================================================================
312
+ // Realtime Execution
313
+ // ==========================================================================
314
+
315
+ describe('Realtime Execution', () => {
316
+ beforeEach(() => {
317
+ configure({ batchMode: 'immediate' })
318
+ })
319
+
320
+ it('executes requests immediately', async () => {
321
+ const start = Date.now()
322
+ const { titles } = await generateTitles('quick test', 2)
323
+ const elapsed = Date.now() - start
324
+
325
+ expect(titles).toHaveLength(2)
326
+ // Should complete in reasonable time (not batched/delayed)
327
+ expect(elapsed).toBeLessThan(30000)
328
+ }, 30000)
329
+
330
+ it('tracks progress during sequential generation', async () => {
331
+ const titles = ['Post One', 'Post Two']
332
+ const { posts, action } = await generatePosts(titles, 'realtime')
333
+
334
+ expect(posts).toHaveLength(2)
335
+
336
+ const final = await db.getAction(action.id)
337
+ expect(final?.progress).toBe(2)
338
+ expect(final?.total).toBe(2)
339
+ }, 60000)
340
+ })
341
+
342
+ // ==========================================================================
343
+ // Batch Execution
344
+ // ==========================================================================
345
+
346
+ describe('Batch Execution', () => {
347
+ beforeEach(() => {
348
+ configure({ batchMode: 'deferred' })
349
+ })
350
+
351
+ it('creates and submits batch jobs', async () => {
352
+ const titles = ['Batch Post 1', 'Batch Post 2']
353
+ const { posts, action } = await generatePosts(titles, 'batch')
354
+
355
+ expect(posts).toHaveLength(2)
356
+
357
+ // Verify batch was stored
358
+ const batches = getBatches()
359
+ expect(batches.size).toBe(1)
360
+
361
+ const [, batch] = [...batches.entries()][0]
362
+ expect(batch.items).toHaveLength(2)
363
+ expect(batch.status).toBe('completed')
364
+ }, 90000)
365
+
366
+ it('stores batch metadata', async () => {
367
+ const batch = createBatch({
368
+ provider: getProvider(),
369
+ model: getModel(),
370
+ metadata: { task: 'test-batch', timestamp: Date.now() },
371
+ })
372
+
373
+ batch.add('Write a test post', { customId: 'test-1' })
374
+ const { job } = await batch.submit()
375
+ await batch.wait()
376
+
377
+ const stored = getBatches().get(job.id)
378
+ expect(stored?.options.metadata?.task).toBe('test-batch')
379
+ }, 30000)
380
+ })
381
+
382
+ // ==========================================================================
383
+ // Multi-Provider Tests
384
+ // ==========================================================================
385
+
386
+ describe('Multi-Provider', () => {
387
+ it.skipIf(!hasOpenAI)('generates with OpenAI', async () => {
388
+ configure({ provider: 'openai', model: 'gpt-4o-mini' })
389
+
390
+ const { titles } = await generateTitles('OpenAI test', 2)
391
+ expect(titles).toHaveLength(2)
392
+ expect(getProvider()).toBe('openai')
393
+ }, 30000)
394
+
395
+ it.skipIf(!hasAnthropic)('generates with Anthropic', async () => {
396
+ configure({ provider: 'anthropic', model: 'claude-sonnet-4-20250514' })
397
+
398
+ const { titles } = await generateTitles('Anthropic test', 2)
399
+ expect(titles).toHaveLength(2)
400
+ expect(getProvider()).toBe('anthropic')
401
+ }, 30000)
402
+
403
+ it.skipIf(!hasOpenAI || !hasAnthropic)('switches providers mid-workflow', async () => {
404
+ // Generate titles with OpenAI
405
+ configure({ provider: 'openai', model: 'gpt-4o-mini' })
406
+ const { titles } = await generateTitles('cross-provider test', 2)
407
+
408
+ // Generate posts with Anthropic
409
+ const posts = await withContext(
410
+ { provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
411
+ async () => {
412
+ expect(getProvider()).toBe('anthropic')
413
+ return Promise.all(titles.slice(0, 1).map(generatePost))
414
+ }
415
+ )
416
+
417
+ expect(posts).toHaveLength(1)
418
+ expect(getProvider()).toBe('openai') // restored
419
+ }, 60000)
420
+
421
+ it.skipIf(!hasOpenAI || !hasAnthropic)('runs providers in parallel', async () => {
422
+ const [openaiResult, anthropicResult] = await Promise.all([
423
+ withContext({ provider: 'openai', model: 'gpt-4o-mini' }, () =>
424
+ generateTitles('OpenAI parallel', 2)
425
+ ),
426
+ withContext({ provider: 'anthropic', model: 'claude-sonnet-4-20250514' }, () =>
427
+ generateTitles('Anthropic parallel', 2)
428
+ ),
429
+ ])
430
+
431
+ expect(openaiResult.titles).toHaveLength(2)
432
+ expect(anthropicResult.titles).toHaveLength(2)
433
+ }, 60000)
434
+ })
435
+
436
+ // ==========================================================================
437
+ // Error Handling
438
+ // ==========================================================================
439
+
440
+ describe('Error Handling', () => {
441
+ it('tracks failed actions', async () => {
442
+ const action = await createAction({
443
+ action: 'generate',
444
+ object: 'FailTest',
445
+ total: 1,
446
+ })
447
+
448
+ await db.updateAction(action.id, { status: 'active' })
449
+
450
+ // Simulate failure
451
+ await db.updateAction(action.id, {
452
+ status: 'failed',
453
+ error: 'Simulated failure',
454
+ })
455
+
456
+ const failed = await db.listActions({ status: 'failed' })
457
+ expect(failed).toHaveLength(1)
458
+ expect(failed[0].error).toBe('Simulated failure')
459
+
460
+ // Verify failure event
461
+ const failEvents = capturedEvents.filter((e) => e.event === 'Action.failed')
462
+ expect(failEvents.length).toBe(1)
463
+ })
464
+
465
+ it('handles invalid model gracefully', async () => {
466
+ configure({ model: 'invalid-model-xyz' })
467
+
468
+ const action = await createAction({
469
+ action: 'generate',
470
+ object: 'InvalidModelTest',
471
+ total: 1,
472
+ })
473
+
474
+ await db.updateAction(action.id, { status: 'active' })
475
+
476
+ try {
477
+ await generateObject({
478
+ model: 'invalid-model-xyz',
479
+ schema: { test: 'test' },
480
+ prompt: 'test',
481
+ })
482
+ } catch (e) {
483
+ await db.updateAction(action.id, {
484
+ status: 'failed',
485
+ error: e instanceof Error ? e.message : 'Unknown error',
486
+ })
487
+ }
488
+
489
+ const final = await db.getAction(action.id)
490
+ expect(final?.status).toBe('failed')
491
+ expect(final?.error).toBeDefined()
492
+ }, 30000)
493
+ })
494
+
495
+ // ==========================================================================
496
+ // Database Statistics
497
+ // ==========================================================================
498
+
499
+ describe('Database Statistics', () => {
500
+ it('tracks aggregate stats', async () => {
501
+ await generateTitles('stats test 1', 2)
502
+ await generateTitles('stats test 2', 2)
503
+
504
+ const stats = db.stats()
505
+
506
+ expect(stats.actions.completed).toBe(2)
507
+ expect(stats.events).toBeGreaterThan(0)
508
+ }, 60000)
509
+ })
510
+ })