ai-functions 2.1.1 → 2.3.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 (286) hide show
  1. package/.turbo/turbo-build.log +1 -4
  2. package/CHANGELOG.md +68 -1
  3. package/README.md +397 -157
  4. package/dist/ai-promise.d.ts +50 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +410 -51
  7. package/dist/ai-promise.js.map +1 -1
  8. package/dist/ai-schemas.d.ts +56 -0
  9. package/dist/ai-schemas.d.ts.map +1 -0
  10. package/dist/ai-schemas.js +53 -0
  11. package/dist/ai-schemas.js.map +1 -0
  12. package/dist/ai.d.ts +16 -242
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +54 -837
  15. package/dist/ai.js.map +1 -1
  16. package/dist/batch/anthropic.d.ts +6 -4
  17. package/dist/batch/anthropic.d.ts.map +1 -1
  18. package/dist/batch/anthropic.js +83 -145
  19. package/dist/batch/anthropic.js.map +1 -1
  20. package/dist/batch/bedrock.d.ts +8 -30
  21. package/dist/batch/bedrock.d.ts.map +1 -1
  22. package/dist/batch/bedrock.js +155 -338
  23. package/dist/batch/bedrock.js.map +1 -1
  24. package/dist/batch/cloudflare.d.ts +8 -20
  25. package/dist/batch/cloudflare.d.ts.map +1 -1
  26. package/dist/batch/cloudflare.js +68 -189
  27. package/dist/batch/cloudflare.js.map +1 -1
  28. package/dist/batch/google.d.ts +6 -20
  29. package/dist/batch/google.d.ts.map +1 -1
  30. package/dist/batch/google.js +70 -238
  31. package/dist/batch/google.js.map +1 -1
  32. package/dist/batch/index.d.ts +4 -1
  33. package/dist/batch/index.d.ts.map +1 -1
  34. package/dist/batch/index.js +4 -1
  35. package/dist/batch/index.js.map +1 -1
  36. package/dist/batch/memory.d.ts +1 -1
  37. package/dist/batch/memory.d.ts.map +1 -1
  38. package/dist/batch/memory.js +14 -10
  39. package/dist/batch/memory.js.map +1 -1
  40. package/dist/batch/openai.d.ts +11 -14
  41. package/dist/batch/openai.d.ts.map +1 -1
  42. package/dist/batch/openai.js +52 -156
  43. package/dist/batch/openai.js.map +1 -1
  44. package/dist/batch/provider.d.ts +111 -0
  45. package/dist/batch/provider.d.ts.map +1 -0
  46. package/dist/batch/provider.js +233 -0
  47. package/dist/batch/provider.js.map +1 -0
  48. package/dist/batch-map.d.ts.map +1 -1
  49. package/dist/batch-map.js +23 -17
  50. package/dist/batch-map.js.map +1 -1
  51. package/dist/batch-queue.d.ts +65 -0
  52. package/dist/batch-queue.d.ts.map +1 -1
  53. package/dist/batch-queue.js +169 -14
  54. package/dist/batch-queue.js.map +1 -1
  55. package/dist/budget.d.ts +272 -0
  56. package/dist/budget.d.ts.map +1 -0
  57. package/dist/budget.js +513 -0
  58. package/dist/budget.js.map +1 -0
  59. package/dist/cache.d.ts +295 -0
  60. package/dist/cache.d.ts.map +1 -0
  61. package/dist/cache.js +433 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/context.d.ts +42 -8
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +64 -62
  66. package/dist/context.js.map +1 -1
  67. package/dist/digital-objects-registry.d.ts +229 -0
  68. package/dist/digital-objects-registry.d.ts.map +1 -0
  69. package/dist/digital-objects-registry.js +617 -0
  70. package/dist/digital-objects-registry.js.map +1 -0
  71. package/dist/embeddings.d.ts +2 -2
  72. package/dist/embeddings.d.ts.map +1 -1
  73. package/dist/errors.d.ts +22 -0
  74. package/dist/errors.d.ts.map +1 -0
  75. package/dist/errors.js +35 -0
  76. package/dist/errors.js.map +1 -0
  77. package/dist/eval/runner.d.ts +10 -1
  78. package/dist/eval/runner.d.ts.map +1 -1
  79. package/dist/eval/runner.js +41 -35
  80. package/dist/eval/runner.js.map +1 -1
  81. package/dist/eval-log/in-memory.d.ts +34 -0
  82. package/dist/eval-log/in-memory.d.ts.map +1 -0
  83. package/dist/eval-log/in-memory.js +84 -0
  84. package/dist/eval-log/in-memory.js.map +1 -0
  85. package/dist/eval-log/index.d.ts +29 -0
  86. package/dist/eval-log/index.d.ts.map +1 -0
  87. package/dist/eval-log/index.js +39 -0
  88. package/dist/eval-log/index.js.map +1 -0
  89. package/dist/eval-log/types.d.ts +101 -0
  90. package/dist/eval-log/types.d.ts.map +1 -0
  91. package/dist/eval-log/types.js +16 -0
  92. package/dist/eval-log/types.js.map +1 -0
  93. package/dist/function-registry.d.ts +116 -0
  94. package/dist/function-registry.d.ts.map +1 -0
  95. package/dist/function-registry.js +546 -0
  96. package/dist/function-registry.js.map +1 -0
  97. package/dist/generate.d.ts +9 -3
  98. package/dist/generate.d.ts.map +1 -1
  99. package/dist/generate.js +18 -22
  100. package/dist/generate.js.map +1 -1
  101. package/dist/index.d.ts +35 -20
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +89 -42
  104. package/dist/index.js.map +1 -1
  105. package/dist/logger.d.ts +118 -0
  106. package/dist/logger.d.ts.map +1 -0
  107. package/dist/logger.js +187 -0
  108. package/dist/logger.js.map +1 -0
  109. package/dist/middleware/budget.d.ts +84 -0
  110. package/dist/middleware/budget.d.ts.map +1 -0
  111. package/dist/middleware/budget.js +110 -0
  112. package/dist/middleware/budget.js.map +1 -0
  113. package/dist/middleware/cache.d.ts +103 -0
  114. package/dist/middleware/cache.d.ts.map +1 -0
  115. package/dist/middleware/cache.js +228 -0
  116. package/dist/middleware/cache.js.map +1 -0
  117. package/dist/middleware/embed-cache.d.ts +99 -0
  118. package/dist/middleware/embed-cache.d.ts.map +1 -0
  119. package/dist/middleware/embed-cache.js +128 -0
  120. package/dist/middleware/embed-cache.js.map +1 -0
  121. package/dist/middleware/index.d.ts +11 -0
  122. package/dist/middleware/index.d.ts.map +1 -0
  123. package/dist/middleware/index.js +11 -0
  124. package/dist/middleware/index.js.map +1 -0
  125. package/dist/middleware/trace.d.ts +103 -0
  126. package/dist/middleware/trace.d.ts.map +1 -0
  127. package/dist/middleware/trace.js +176 -0
  128. package/dist/middleware/trace.js.map +1 -0
  129. package/dist/primitives.d.ts +120 -1
  130. package/dist/primitives.d.ts.map +1 -1
  131. package/dist/primitives.js +398 -26
  132. package/dist/primitives.js.map +1 -1
  133. package/dist/retry.d.ts +368 -0
  134. package/dist/retry.d.ts.map +1 -0
  135. package/dist/retry.js +646 -0
  136. package/dist/retry.js.map +1 -0
  137. package/dist/schema.d.ts.map +1 -1
  138. package/dist/schema.js +2 -10
  139. package/dist/schema.js.map +1 -1
  140. package/dist/telemetry.d.ts +128 -0
  141. package/dist/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry.js +285 -0
  143. package/dist/telemetry.js.map +1 -0
  144. package/dist/template.d.ts.map +1 -1
  145. package/dist/template.js +6 -1
  146. package/dist/template.js.map +1 -1
  147. package/dist/tool-orchestration.d.ts +453 -0
  148. package/dist/tool-orchestration.d.ts.map +1 -0
  149. package/dist/tool-orchestration.js +763 -0
  150. package/dist/tool-orchestration.js.map +1 -0
  151. package/dist/type-guards.d.ts +28 -0
  152. package/dist/type-guards.d.ts.map +1 -0
  153. package/dist/type-guards.js +29 -0
  154. package/dist/type-guards.js.map +1 -0
  155. package/dist/types.d.ts +135 -17
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js +36 -1
  158. package/dist/types.js.map +1 -1
  159. package/dist/wrap-for-v3.d.ts +80 -0
  160. package/dist/wrap-for-v3.d.ts.map +1 -0
  161. package/dist/wrap-for-v3.js +89 -0
  162. package/dist/wrap-for-v3.js.map +1 -0
  163. package/examples/00-quickstart.ts +232 -0
  164. package/examples/01-rag-chatbot.ts +212 -0
  165. package/examples/02-multi-agent-research.ts +290 -0
  166. package/examples/03-email-classification.ts +379 -0
  167. package/examples/04-content-moderation.ts +400 -0
  168. package/examples/05-document-extraction.ts +455 -0
  169. package/examples/06-streaming-chat-nextjs.ts +437 -0
  170. package/examples/07-cloudflare-worker.ts +483 -0
  171. package/examples/08-batch-processing.ts +491 -0
  172. package/examples/09-budget-constrained.ts +527 -0
  173. package/examples/10-tool-orchestration.ts +565 -0
  174. package/examples/11-retry-resilience.ts +403 -0
  175. package/examples/12-caching-strategies.ts +422 -0
  176. package/examples/README.md +145 -0
  177. package/package.json +10 -6
  178. package/src/ai-promise.ts +528 -99
  179. package/src/ai-schemas.ts +122 -0
  180. package/src/ai.ts +69 -1153
  181. package/src/batch/anthropic.ts +96 -161
  182. package/src/batch/bedrock.ts +203 -454
  183. package/src/batch/cloudflare.ts +99 -282
  184. package/src/batch/google.ts +91 -297
  185. package/src/batch/index.ts +4 -1
  186. package/src/batch/memory.ts +15 -10
  187. package/src/batch/openai.ts +65 -193
  188. package/src/batch/provider.ts +336 -0
  189. package/src/batch-map.ts +29 -24
  190. package/src/batch-queue.ts +200 -11
  191. package/src/budget.ts +740 -0
  192. package/src/cache.ts +681 -0
  193. package/src/context.ts +122 -76
  194. package/src/digital-objects-registry.ts +750 -0
  195. package/src/errors.ts +37 -0
  196. package/src/eval/runner.ts +63 -38
  197. package/src/eval-log/in-memory.ts +90 -0
  198. package/src/eval-log/index.ts +46 -0
  199. package/src/eval-log/types.ts +110 -0
  200. package/src/function-registry.ts +671 -0
  201. package/src/generate.ts +33 -33
  202. package/src/index.ts +325 -49
  203. package/src/logger.ts +232 -0
  204. package/src/middleware/budget.ts +171 -0
  205. package/src/middleware/cache.ts +299 -0
  206. package/src/middleware/embed-cache.ts +195 -0
  207. package/src/middleware/index.ts +23 -0
  208. package/src/middleware/trace.ts +248 -0
  209. package/src/primitives.ts +589 -62
  210. package/src/retry.ts +902 -0
  211. package/src/schema.ts +8 -17
  212. package/src/telemetry.ts +403 -0
  213. package/src/template.ts +8 -4
  214. package/src/tool-orchestration.ts +1173 -0
  215. package/src/type-guards.ts +31 -0
  216. package/src/types.ts +164 -25
  217. package/src/wrap-for-v3.ts +105 -0
  218. package/test/ai-promise.test.ts +1080 -0
  219. package/test/ai-proxy.test.ts +1 -1
  220. package/test/backward-compat.test.ts +147 -0
  221. package/test/batch-autosubmit-errors.test.ts +610 -0
  222. package/test/batch-blog-posts.test.ts +87 -129
  223. package/test/budget-tracking.test.ts +800 -0
  224. package/test/cache.test.ts +712 -0
  225. package/test/context-isolation.test.ts +687 -0
  226. package/test/core-functions.test.ts +183 -579
  227. package/test/decide.test.ts +154 -322
  228. package/test/define.test.ts +211 -8
  229. package/test/digital-objects-registry.test.ts +760 -0
  230. package/test/embedding-cache-middleware.test.ts +140 -0
  231. package/test/evals/deterministic.eval.test.ts +376 -0
  232. package/test/generate-core.test.ts +140 -229
  233. package/test/implicit-batch.test.ts +22 -65
  234. package/test/json-parse-error-handling.test.ts +463 -0
  235. package/test/retry-policy-integration.test.ts +117 -0
  236. package/test/retry.test.ts +1016 -0
  237. package/test/schema.test.ts +55 -19
  238. package/test/streaming.test.ts +316 -0
  239. package/test/template.test.ts +1164 -0
  240. package/test/tool-orchestration.test.ts +1040 -0
  241. package/test/wrap-for-v3.test.ts +612 -0
  242. package/vitest.config.js +6 -0
  243. package/vitest.config.ts +20 -0
  244. package/dist/rpc/auth.d.ts +0 -69
  245. package/dist/rpc/auth.d.ts.map +0 -1
  246. package/dist/rpc/auth.js +0 -136
  247. package/dist/rpc/auth.js.map +0 -1
  248. package/dist/rpc/client.d.ts +0 -62
  249. package/dist/rpc/client.d.ts.map +0 -1
  250. package/dist/rpc/client.js +0 -103
  251. package/dist/rpc/client.js.map +0 -1
  252. package/dist/rpc/deferred.d.ts +0 -60
  253. package/dist/rpc/deferred.d.ts.map +0 -1
  254. package/dist/rpc/deferred.js +0 -96
  255. package/dist/rpc/deferred.js.map +0 -1
  256. package/dist/rpc/index.d.ts +0 -22
  257. package/dist/rpc/index.d.ts.map +0 -1
  258. package/dist/rpc/index.js +0 -38
  259. package/dist/rpc/index.js.map +0 -1
  260. package/dist/rpc/local.d.ts +0 -42
  261. package/dist/rpc/local.d.ts.map +0 -1
  262. package/dist/rpc/local.js +0 -50
  263. package/dist/rpc/local.js.map +0 -1
  264. package/dist/rpc/server.d.ts +0 -165
  265. package/dist/rpc/server.d.ts.map +0 -1
  266. package/dist/rpc/server.js +0 -405
  267. package/dist/rpc/server.js.map +0 -1
  268. package/dist/rpc/session.d.ts +0 -32
  269. package/dist/rpc/session.d.ts.map +0 -1
  270. package/dist/rpc/session.js +0 -43
  271. package/dist/rpc/session.js.map +0 -1
  272. package/dist/rpc/transport.d.ts +0 -306
  273. package/dist/rpc/transport.d.ts.map +0 -1
  274. package/dist/rpc/transport.js +0 -731
  275. package/dist/rpc/transport.js.map +0 -1
  276. package/src/batch/anthropic.js +0 -256
  277. package/src/batch/bedrock.js +0 -584
  278. package/src/batch/cloudflare.js +0 -287
  279. package/src/batch/google.js +0 -359
  280. package/src/batch/index.js +0 -30
  281. package/src/batch/memory.js +0 -187
  282. package/src/batch/openai.js +0 -402
  283. package/src/eval/index.js +0 -7
  284. package/src/eval/models.js +0 -119
  285. package/src/eval/runner.js +0 -147
  286. package/test/schema.test.js +0 -96
@@ -15,117 +15,71 @@
15
15
  * ```
16
16
  */
17
17
 
18
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
19
- import {
20
- createBatch,
21
- withBatch,
22
- type BatchQueue,
23
- type BatchResult,
24
- } from '../src/batch-queue.js'
25
-
26
- // Import memory adapter to register it
27
- import '../src/batch/memory.js'
28
- import { configureMemoryAdapter, clearBatches } from '../src/batch/memory.js'
18
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
19
+ import { createBatch, withBatchQueue, generateObject, generateText } from '../src/index.js'
20
+ import { z } from 'zod'
29
21
 
30
- // ============================================================================
31
- // Mock Setup
32
- // ============================================================================
22
+ // Memory adapter for testing - simulates batch processing locally
23
+ // Import from .ts file for proper vite resolution
24
+ import { configureMemoryAdapter, clearBatches } from '../src/batch/memory.ts'
33
25
 
34
- // Mock the generate functions
35
- vi.mock('../src/generate.js', () => ({
36
- generateObject: vi.fn().mockImplementation(async ({ prompt, schema }) => {
37
- // Simulate list generation
38
- if (schema?.items) {
39
- return {
40
- object: {
41
- items: [
42
- 'How AI is Revolutionizing Startup Fundraising in 2026',
43
- 'The Rise of Solo Founders: Building $10M ARR Companies Alone',
44
- 'Why Remote-First is Non-Negotiable for 2026 Startups',
45
- 'Sustainable Growth vs Hypergrowth: The 2026 Paradigm Shift',
46
- 'Building in Public: How Transparency Became a Competitive Advantage',
47
- 'The API-First Startup: Lessons from 2026 Unicorns',
48
- 'From Side Project to Series A: The 2026 Playbook',
49
- 'Climate Tech Startups: The Hottest Sector of 2026',
50
- 'The Death of Traditional MVPs: Ship Faster, Learn Faster',
51
- 'Community-Led Growth: The New GTM Strategy for 2026',
52
- ],
53
- },
54
- }
55
- }
56
- // Simulate blog post generation
57
- if (prompt.includes('blog post about')) {
58
- const titleMatch = prompt.match(/blog post about (.+)/)
59
- const title = titleMatch?.[1] || 'Unknown Topic'
60
- return {
61
- object: {
62
- text: `# ${title}\n\nThis is a comprehensive blog post about ${title}.\n\n## Introduction\n\nIn 2026, the startup landscape continues to evolve...\n\n## Key Takeaways\n\n1. Innovation is key\n2. Focus on customer value\n3. Build sustainable businesses\n\n## Conclusion\n\nThe future of startups is bright for those who adapt.`,
63
- },
64
- }
65
- }
66
- return { object: { result: 'Generated content' } }
67
- }),
68
- generateText: vi.fn().mockImplementation(async ({ prompt }) => {
69
- // Simulate blog post text generation
70
- if (prompt.includes('blog post about')) {
71
- const titleMatch = prompt.match(/blog post about (.+)/)
72
- const title = titleMatch?.[1] || 'Unknown Topic'
73
- return {
74
- text: `# ${title}\n\nThis is a comprehensive blog post about ${title}.\n\n## Introduction\n\nIn 2026, the startup landscape continues to evolve rapidly. Entrepreneurs are finding new ways to build, scale, and succeed.\n\n## The State of Startups in 2026\n\nThe ecosystem has matured significantly. AI tools have become indispensable, funding patterns have shifted, and remote work is now the default.\n\n## Key Strategies for Success\n\n1. **Leverage AI Wisely** - Use AI as a multiplier, not a replacement\n2. **Build Community First** - Your early adopters are your growth engine\n3. **Focus on Unit Economics** - Hypergrowth without sustainability is dead\n4. **Embrace Transparency** - Building in public creates trust and accountability\n\n## Practical Steps\n\n- Start with a problem you deeply understand\n- Validate with paying customers, not surveys\n- Build the smallest thing that delivers value\n- Iterate based on real usage data\n\n## Conclusion\n\nBuilding a startup in 2026 requires a blend of traditional business fundamentals and modern tools. The founders who succeed will be those who can navigate this balance effectively.`,
75
- }
76
- }
77
- return { text: 'Generated text content' }
78
- }),
79
- }))
26
+ // Skip AI-dependent tests if no gateway configured
27
+ const hasGateway = !!process.env.AI_GATEWAY_URL
80
28
 
81
29
  // ============================================================================
82
- // Test Helpers
30
+ // Real AI Tests (require gateway)
83
31
  // ============================================================================
84
32
 
85
- /**
86
- * Simulate the list template function
87
- */
88
- async function mockList(prompt: string): Promise<string[]> {
89
- const { generateObject } = await import('../src/generate.js')
90
- const result = await generateObject({
91
- model: 'sonnet',
92
- schema: { items: ['List items'] },
93
- prompt,
33
+ describe.skipIf(!hasGateway)('Batch Blog Post Generation with Real AI', () => {
34
+ it('generates blog post titles using real AI', async () => {
35
+ const result = await generateObject({
36
+ model: 'haiku',
37
+ schema: z.object({
38
+ titles: z.array(z.string()).describe('List of blog post titles'),
39
+ }),
40
+ prompt: 'Generate exactly 3 blog post titles about building startups.',
41
+ })
42
+
43
+ expect(result.object.titles).toHaveLength(3)
44
+ expect(result.object.titles.every((t: string) => typeof t === 'string')).toBe(true)
45
+ })
46
+
47
+ it('generates a single blog post using real AI', async () => {
48
+ const result = await generateText({
49
+ model: 'haiku',
50
+ prompt: 'Write a very short blog post intro (2-3 sentences) about TypeScript.',
51
+ })
52
+
53
+ expect(result.text).toBeDefined()
54
+ expect(result.text.length).toBeGreaterThan(50)
94
55
  })
95
- return (result.object as { items: string[] }).items
96
- }
56
+ })
97
57
 
98
58
  // ============================================================================
99
- // Tests
59
+ // Batch Queue Mechanics Tests (use memory adapter - no AI needed)
100
60
  // ============================================================================
101
61
 
102
- describe('Batch Blog Post Generation', () => {
62
+ describe('Batch Queue Mechanics', () => {
63
+ // Use a simple handler that doesn't call real AI
64
+ const mockHandler = async (item: { prompt: string }) => {
65
+ return `Generated content for: ${item.prompt.substring(0, 50)}...`
66
+ }
67
+
103
68
  beforeEach(() => {
104
- vi.clearAllMocks()
105
69
  clearBatches()
106
- // Use default handler that calls the mock
107
- configureMemoryAdapter({})
70
+ // Configure with mock handler to avoid real AI calls in batch tests
71
+ configureMemoryAdapter({ handler: mockHandler })
108
72
  })
109
73
 
110
74
  afterEach(() => {
111
75
  clearBatches()
112
76
  })
113
77
 
114
- describe('list` immediate execution', () => {
115
- it('list` executes immediately and returns titles', async () => {
116
- const titles = await mockList('10 blog post titles about building startups in 2026')
117
-
118
- expect(titles).toHaveLength(10)
119
- expect(titles[0]).toBe('How AI is Revolutionizing Startup Fundraising in 2026')
120
- expect(titles[9]).toBe('Community-Led Growth: The New GTM Strategy for 2026')
121
- })
122
- })
123
-
124
78
  describe('batch processing workflow', () => {
125
79
  it('creates batch queue and adds items', async () => {
126
80
  const batch = createBatch({ provider: 'openai', model: 'gpt-4o' })
127
81
 
128
- const titles = await mockList('10 blog post titles about building startups in 2026')
82
+ const titles = ['Title 1', 'Title 2', 'Title 3']
129
83
 
130
84
  // Add each title to the batch
131
85
  const items = titles.map((title) =>
@@ -134,45 +88,41 @@ describe('Batch Blog Post Generation', () => {
134
88
  })
135
89
  )
136
90
 
137
- expect(batch.size).toBe(10)
138
- expect(items).toHaveLength(10)
91
+ expect(batch.size).toBe(3)
92
+ expect(items).toHaveLength(3)
139
93
  expect(items[0].status).toBe('pending')
140
94
  })
141
95
 
142
96
  it('submits batch and returns job info', async () => {
143
97
  const batch = createBatch({ provider: 'openai', model: 'gpt-4o' })
144
98
 
145
- const titles = await mockList('10 blog post titles about building startups in 2026')
99
+ const titles = ['Title 1', 'Title 2', 'Title 3']
146
100
 
147
- titles.forEach((title) =>
148
- batch.add(`Write a comprehensive blog post about: ${title}`)
149
- )
101
+ titles.forEach((title) => batch.add(`Write a comprehensive blog post about: ${title}`))
150
102
 
151
103
  const { job, completion } = await batch.submit()
152
104
 
153
105
  expect(job.id).toMatch(/^batch_memory_/)
154
106
  expect(job.provider).toBe('openai')
155
- expect(job.totalItems).toBe(10)
107
+ expect(job.totalItems).toBe(3)
156
108
  expect(job.status).toBe('pending')
157
109
 
158
110
  // Wait for completion
159
111
  const results = await completion
160
- expect(results).toHaveLength(10)
112
+ expect(results).toHaveLength(3)
161
113
  })
162
114
 
163
115
  it('waits for batch completion and returns results', async () => {
164
116
  const batch = createBatch({ provider: 'openai', model: 'gpt-4o' })
165
117
 
166
- const titles = await mockList('10 blog post titles about building startups in 2026')
118
+ const titles = ['Title 1', 'Title 2', 'Title 3']
167
119
 
168
- titles.forEach((title) =>
169
- batch.add(`Write a comprehensive blog post about: ${title}`)
170
- )
120
+ titles.forEach((title) => batch.add(`Write a comprehensive blog post about: ${title}`))
171
121
 
172
122
  await batch.submit()
173
123
  const results = await batch.wait()
174
124
 
175
- expect(results).toHaveLength(10)
125
+ expect(results).toHaveLength(3)
176
126
  expect(results.every((r) => r.status === 'completed')).toBe(true)
177
127
  expect(results[0].result).toBeDefined()
178
128
  })
@@ -181,9 +131,7 @@ describe('Batch Blog Post Generation', () => {
181
131
  const batch = createBatch({ provider: 'openai' })
182
132
 
183
133
  const titles = ['First', 'Second', 'Third']
184
- const items = titles.map((title, i) =>
185
- batch.add(`Write about: ${title}`, { customId: `item_${i}` })
186
- )
134
+ titles.map((title, i) => batch.add(`Write about: ${title}`, { customId: `item_${i}` }))
187
135
 
188
136
  await batch.submit()
189
137
  const results = await batch.wait()
@@ -196,17 +144,14 @@ describe('Batch Blog Post Generation', () => {
196
144
 
197
145
  describe('withBatchQueue helper', () => {
198
146
  it('provides convenient batch execution', async () => {
199
- const titles = await mockList('10 blog post titles about building startups in 2026')
147
+ const titles = ['Title A', 'Title B', 'Title C']
200
148
 
201
- const results = await withBatch(
202
- (batch) =>
203
- titles.map((title) =>
204
- batch.add(`Write a blog post about: ${title}`)
205
- ),
149
+ const results = await withBatchQueue(
150
+ (batch) => titles.map((title) => batch.add(`Write a blog post about: ${title}`)),
206
151
  { provider: 'openai', model: 'gpt-4o' }
207
152
  )
208
153
 
209
- expect(results).toHaveLength(10)
154
+ expect(results).toHaveLength(3)
210
155
  expect(results.every((r) => r.status === 'completed')).toBe(true)
211
156
  })
212
157
  })
@@ -233,8 +178,11 @@ describe('Batch Blog Post Generation', () => {
233
178
 
234
179
  describe('error handling', () => {
235
180
  it('handles partial failures', async () => {
236
- // Configure adapter to fail 30% of requests
237
- configureMemoryAdapter({ failureRate: 0.3 })
181
+ // Configure adapter to fail 30% of requests with a mock handler
182
+ configureMemoryAdapter({
183
+ failureRate: 0.3,
184
+ handler: async (item: { prompt: string }) => `Result for: ${item.prompt}`,
185
+ })
238
186
 
239
187
  const batch = createBatch({ provider: 'openai' })
240
188
 
@@ -280,9 +228,11 @@ describe('Batch Blog Post Generation', () => {
280
228
 
281
229
  describe('batch with custom handler', () => {
282
230
  it('uses custom handler for processing', async () => {
283
- const customHandler = vi.fn().mockImplementation(async (item) => {
231
+ let callCount = 0
232
+ const customHandler = async (item: { prompt: string }) => {
233
+ callCount++
284
234
  return `Custom result for: ${item.prompt}`
285
- })
235
+ }
286
236
 
287
237
  configureMemoryAdapter({ handler: customHandler })
288
238
 
@@ -293,17 +243,20 @@ describe('Batch Blog Post Generation', () => {
293
243
  await batch.submit()
294
244
  const results = await batch.wait()
295
245
 
296
- expect(customHandler).toHaveBeenCalledTimes(2)
246
+ expect(callCount).toBe(2)
297
247
  expect(results[0].result).toBe('Custom result for: Topic 1')
298
248
  expect(results[1].result).toBe('Custom result for: Topic 2')
299
249
  })
300
250
  })
301
251
 
302
- describe('full workflow: list map batch', () => {
252
+ describe('full workflow: list -> map -> batch', () => {
303
253
  it('executes the complete blog post generation workflow', async () => {
304
- // Step 1: Get titles (executes immediately)
305
- const titles = await mockList('10 blog post titles about building startups in 2026')
306
- expect(titles).toHaveLength(10)
254
+ // Step 1: Simulate getting titles (in real usage, this would be AI-generated)
255
+ const titles = [
256
+ 'How AI is Revolutionizing Startup Fundraising',
257
+ 'The Rise of Solo Founders',
258
+ 'Remote-First is Non-Negotiable',
259
+ ]
307
260
 
308
261
  // Step 2: Create batch for blog posts (deferred)
309
262
  const batch = createBatch({
@@ -320,28 +273,26 @@ describe('Batch Blog Post Generation', () => {
320
273
  })
321
274
  )
322
275
 
323
- expect(batch.size).toBe(10)
276
+ expect(batch.size).toBe(3)
324
277
  expect(blogItems.every((item) => item.status === 'pending')).toBe(true)
325
278
 
326
279
  // Step 4: Submit the batch
327
280
  const { job, completion } = await batch.submit()
328
281
 
329
282
  expect(job.id).toBeDefined()
330
- expect(job.totalItems).toBe(10)
283
+ expect(job.totalItems).toBe(3)
331
284
  expect(batch.isSubmitted).toBe(true)
332
285
 
333
286
  // Step 5: Wait for results
334
287
  const results = await completion
335
288
 
336
- expect(results).toHaveLength(10)
289
+ expect(results).toHaveLength(3)
337
290
  expect(results.every((r) => r.status === 'completed')).toBe(true)
338
291
 
339
- // Verify results have blog post content
292
+ // Verify results have content
340
293
  for (const result of results) {
341
294
  expect(result.result).toBeDefined()
342
295
  expect(typeof result.result).toBe('string')
343
- // Blog posts should have some content
344
- expect((result.result as string).length).toBeGreaterThan(100)
345
296
  }
346
297
 
347
298
  // Verify items are updated after completion
@@ -351,9 +302,12 @@ describe('Batch Blog Post Generation', () => {
351
302
  })
352
303
 
353
304
  describe('Provider-specific batch behavior', () => {
305
+ // Use a simple handler that doesn't call real AI
306
+ const mockHandler = async () => 'Test result'
307
+
354
308
  beforeEach(() => {
355
309
  clearBatches()
356
- configureMemoryAdapter({})
310
+ configureMemoryAdapter({ handler: mockHandler })
357
311
  })
358
312
 
359
313
  it('uses specified provider', async () => {
@@ -372,7 +326,11 @@ describe('Provider-specific batch behavior', () => {
372
326
  })
373
327
 
374
328
  it('respects model configuration', async () => {
375
- const customHandler = vi.fn().mockResolvedValue('Result')
329
+ let handlerCalled = false
330
+ const customHandler = async () => {
331
+ handlerCalled = true
332
+ return 'Result'
333
+ }
376
334
  configureMemoryAdapter({ handler: customHandler })
377
335
 
378
336
  const batch = createBatch({ provider: 'openai', model: 'gpt-4o-mini' })
@@ -382,6 +340,6 @@ describe('Provider-specific batch behavior', () => {
382
340
 
383
341
  // The model should be passed to the handler via batch options
384
342
  // (memory adapter doesn't use it, but real adapters would)
385
- expect(customHandler).toHaveBeenCalled()
343
+ expect(handlerCalled).toBe(true)
386
344
  })
387
345
  })