ai-functions 2.1.3 → 2.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +90 -1
- package/README.md +38 -0
- package/dist/ai-promise.d.ts +3 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +135 -64
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai-schemas.d.ts +56 -0
- package/dist/ai-schemas.d.ts.map +1 -0
- package/dist/ai-schemas.js +53 -0
- package/dist/ai-schemas.js.map +1 -0
- package/dist/ai.d.ts +16 -242
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +51 -858
- package/dist/ai.js.map +1 -1
- package/dist/batch/anthropic.d.ts +6 -4
- package/dist/batch/anthropic.d.ts.map +1 -1
- package/dist/batch/anthropic.js +83 -145
- package/dist/batch/anthropic.js.map +1 -1
- package/dist/batch/bedrock.d.ts +8 -30
- package/dist/batch/bedrock.d.ts.map +1 -1
- package/dist/batch/bedrock.js +155 -338
- package/dist/batch/bedrock.js.map +1 -1
- package/dist/batch/cloudflare.d.ts +8 -20
- package/dist/batch/cloudflare.d.ts.map +1 -1
- package/dist/batch/cloudflare.js +68 -189
- package/dist/batch/cloudflare.js.map +1 -1
- package/dist/batch/google.d.ts +6 -20
- package/dist/batch/google.d.ts.map +1 -1
- package/dist/batch/google.js +70 -238
- package/dist/batch/google.js.map +1 -1
- package/dist/batch/index.d.ts +4 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +4 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/memory.d.ts +1 -1
- package/dist/batch/memory.d.ts.map +1 -1
- package/dist/batch/memory.js +14 -10
- package/dist/batch/memory.js.map +1 -1
- package/dist/batch/openai.d.ts +11 -14
- package/dist/batch/openai.d.ts.map +1 -1
- package/dist/batch/openai.js +52 -156
- package/dist/batch/openai.js.map +1 -1
- package/dist/batch/provider.d.ts +111 -0
- package/dist/batch/provider.d.ts.map +1 -0
- package/dist/batch/provider.js +233 -0
- package/dist/batch/provider.js.map +1 -0
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +23 -17
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +65 -0
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +169 -14
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts.map +1 -1
- package/dist/budget.js +27 -14
- package/dist/budget.js.map +1 -1
- package/dist/cache.d.ts +23 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +36 -15
- package/dist/cache.js.map +1 -1
- package/dist/context.d.ts +26 -8
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +64 -62
- package/dist/context.js.map +1 -1
- package/dist/digital-objects-registry.d.ts +229 -0
- package/dist/digital-objects-registry.d.ts.map +1 -0
- package/dist/digital-objects-registry.js +617 -0
- package/dist/digital-objects-registry.js.map +1 -0
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval/runner.d.ts +8 -0
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +41 -35
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval-log/in-memory.d.ts +34 -0
- package/dist/eval-log/in-memory.d.ts.map +1 -0
- package/dist/eval-log/in-memory.js +84 -0
- package/dist/eval-log/in-memory.js.map +1 -0
- package/dist/eval-log/index.d.ts +29 -0
- package/dist/eval-log/index.d.ts.map +1 -0
- package/dist/eval-log/index.js +39 -0
- package/dist/eval-log/index.js.map +1 -0
- package/dist/eval-log/types.d.ts +101 -0
- package/dist/eval-log/types.d.ts.map +1 -0
- package/dist/eval-log/types.js +16 -0
- package/dist/eval-log/types.js.map +1 -0
- package/dist/function-registry.d.ts +176 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +685 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/generate.d.ts +9 -3
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +18 -18
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -18
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +118 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +187 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/budget.d.ts +84 -0
- package/dist/middleware/budget.d.ts.map +1 -0
- package/dist/middleware/budget.js +110 -0
- package/dist/middleware/budget.js.map +1 -0
- package/dist/middleware/cache.d.ts +103 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +228 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/embed-cache.d.ts +99 -0
- package/dist/middleware/embed-cache.d.ts.map +1 -0
- package/dist/middleware/embed-cache.js +128 -0
- package/dist/middleware/embed-cache.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/trace.d.ts +103 -0
- package/dist/middleware/trace.d.ts.map +1 -0
- package/dist/middleware/trace.js +176 -0
- package/dist/middleware/trace.js.map +1 -0
- package/dist/primitives.d.ts +120 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +398 -26
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +66 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +115 -8
- package/dist/retry.js.map +1 -1
- package/dist/sandbox.d.ts +36 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +44 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema.js +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +285 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -1
- package/dist/template.js.map +1 -1
- package/dist/tool-orchestration.d.ts +66 -4
- package/dist/tool-orchestration.d.ts.map +1 -1
- package/dist/tool-orchestration.js +123 -23
- package/dist/tool-orchestration.js.map +1 -1
- package/dist/type-guards.d.ts +28 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +29 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +155 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -1
- package/dist/types.js.map +1 -1
- package/dist/wrap-for-v3.d.ts +80 -0
- package/dist/wrap-for-v3.d.ts.map +1 -0
- package/dist/wrap-for-v3.js +89 -0
- package/dist/wrap-for-v3.js.map +1 -0
- package/examples/00-quickstart.ts +232 -0
- package/examples/01-rag-chatbot.ts +212 -0
- package/examples/02-multi-agent-research.ts +290 -0
- package/examples/03-email-classification.ts +379 -0
- package/examples/04-content-moderation.ts +400 -0
- package/examples/05-document-extraction.ts +455 -0
- package/examples/06-streaming-chat-nextjs.ts +437 -0
- package/examples/07-cloudflare-worker.ts +483 -0
- package/examples/08-batch-processing.ts +491 -0
- package/examples/09-budget-constrained.ts +527 -0
- package/examples/10-tool-orchestration.ts +565 -0
- package/examples/11-retry-resilience.ts +403 -0
- package/examples/12-caching-strategies.ts +422 -0
- package/examples/README.md +145 -0
- package/package.json +29 -25
- package/src/ai-promise.ts +226 -140
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +71 -1176
- package/src/batch/anthropic.ts +96 -161
- package/src/batch/bedrock.ts +203 -454
- package/src/batch/cloudflare.ts +99 -282
- package/src/batch/google.ts +91 -297
- package/src/batch/index.ts +4 -1
- package/src/batch/memory.ts +15 -10
- package/src/batch/openai.ts +65 -193
- package/src/batch/provider.ts +336 -0
- package/src/batch-map.ts +29 -24
- package/src/batch-queue.ts +200 -11
- package/src/budget.ts +31 -18
- package/src/cache.ts +45 -17
- package/src/context.ts +106 -77
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +60 -36
- package/src/eval-log/in-memory.ts +90 -0
- package/src/eval-log/index.ts +46 -0
- package/src/eval-log/types.ts +110 -0
- package/src/function-registry.ts +874 -0
- package/src/generate.ts +33 -28
- package/src/index.ts +122 -21
- package/src/logger.ts +232 -0
- package/src/middleware/budget.ts +171 -0
- package/src/middleware/cache.ts +299 -0
- package/src/middleware/embed-cache.ts +195 -0
- package/src/middleware/index.ts +23 -0
- package/src/middleware/trace.ts +248 -0
- package/src/primitives.ts +589 -62
- package/src/retry.ts +144 -18
- package/src/sandbox.ts +52 -0
- package/src/schema.ts +8 -8
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +213 -48
- package/src/type-guards.ts +31 -0
- package/src/types.ts +186 -27
- package/src/wrap-for-v3.ts +105 -0
- package/test/ai-promise.test.ts +1080 -0
- package/test/ai-proxy.test.ts +1 -1
- package/test/batch-autosubmit-errors.test.ts +49 -37
- package/test/batch-blog-posts.test.ts +87 -129
- package/test/core-functions.test.ts +183 -579
- package/test/decide.test.ts +154 -322
- package/test/define.test.ts +211 -8
- package/test/digital-objects-registry.test.ts +760 -0
- package/test/embedding-cache-middleware.test.ts +140 -0
- package/test/fill-template.test.ts +89 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/sandbox-execution.test.ts +155 -0
- package/test/schema.test.ts +55 -19
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +270 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/LICENSE +0 -21
- package/dist/rpc/auth.d.ts +0 -69
- package/dist/rpc/auth.d.ts.map +0 -1
- package/dist/rpc/auth.js +0 -136
- package/dist/rpc/auth.js.map +0 -1
- package/dist/rpc/client.d.ts +0 -62
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -103
- package/dist/rpc/client.js.map +0 -1
- package/dist/rpc/deferred.d.ts +0 -60
- package/dist/rpc/deferred.d.ts.map +0 -1
- package/dist/rpc/deferred.js +0 -96
- package/dist/rpc/deferred.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -22
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -38
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/local.d.ts +0 -42
- package/dist/rpc/local.d.ts.map +0 -1
- package/dist/rpc/local.js +0 -50
- package/dist/rpc/local.js.map +0 -1
- package/dist/rpc/server.d.ts +0 -165
- package/dist/rpc/server.d.ts.map +0 -1
- package/dist/rpc/server.js +0 -405
- package/dist/rpc/server.js.map +0 -1
- package/dist/rpc/session.d.ts +0 -32
- package/dist/rpc/session.d.ts.map +0 -1
- package/dist/rpc/session.js +0 -43
- package/dist/rpc/session.js.map +0 -1
- package/dist/rpc/transport.d.ts +0 -306
- package/dist/rpc/transport.d.ts.map +0 -1
- package/dist/rpc/transport.js +0 -731
- package/dist/rpc/transport.js.map +0 -1
- package/src/batch/anthropic.js +0 -256
- package/src/batch/bedrock.js +0 -584
- package/src/batch/cloudflare.js +0 -287
- package/src/batch/google.js +0 -359
- package/src/batch/index.js +0 -30
- package/src/batch/memory.js +0 -187
- package/src/batch/openai.js +0 -402
- package/src/eval/index.js +0 -7
- package/src/eval/models.js +0 -119
- package/src/eval/runner.js +0 -147
- package/test/schema.test.js +0 -96
package/test/ai-proxy.test.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { describe, it, expect, beforeEach } from 'vitest'
|
|
8
|
-
import { ai, AI, functions, withTemplate } from '../src/index.js'
|
|
8
|
+
import { aiProxy as ai, AI, functions, withTemplate } from '../src/index.js'
|
|
9
9
|
|
|
10
10
|
// Skip tests if no gateway configured
|
|
11
11
|
const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY
|
|
@@ -150,7 +150,7 @@ function createRateLimitAdapter(): BatchAdapter {
|
|
|
150
150
|
return createFailingAdapter(
|
|
151
151
|
Object.assign(new Error('Rate limit exceeded'), {
|
|
152
152
|
status: 429,
|
|
153
|
-
headers: { 'retry-after': '60' }
|
|
153
|
+
headers: { 'retry-after': '60' },
|
|
154
154
|
})
|
|
155
155
|
)
|
|
156
156
|
}
|
|
@@ -181,14 +181,16 @@ describe('Batch auto-submit error handling', () => {
|
|
|
181
181
|
const batch = createBatch({
|
|
182
182
|
provider: 'openai',
|
|
183
183
|
autoSubmit: true,
|
|
184
|
-
maxItems: 3
|
|
184
|
+
maxItems: 3,
|
|
185
185
|
})
|
|
186
186
|
|
|
187
187
|
// Subscribe to error events (this is what we expect to exist)
|
|
188
188
|
// This will fail because BatchQueue doesn't emit events
|
|
189
189
|
if ('on' in batch) {
|
|
190
|
-
(batch as BatchQueue & { on: (event: string, handler: (e: Error) => void) => void })
|
|
191
|
-
|
|
190
|
+
;(batch as BatchQueue & { on: (event: string, handler: (e: Error) => void) => void }).on(
|
|
191
|
+
'error',
|
|
192
|
+
errorHandler
|
|
193
|
+
)
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
// Add items to trigger auto-submit
|
|
@@ -197,7 +199,7 @@ describe('Batch auto-submit error handling', () => {
|
|
|
197
199
|
batch.add('prompt 3') // This should trigger auto-submit
|
|
198
200
|
|
|
199
201
|
// Wait for async auto-submit to complete
|
|
200
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
202
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
201
203
|
|
|
202
204
|
// FAILING: Currently errors are swallowed, errorHandler never called
|
|
203
205
|
// The error should be propagated to the error handler
|
|
@@ -211,7 +213,7 @@ describe('Batch auto-submit error handling', () => {
|
|
|
211
213
|
const batch = createBatch({
|
|
212
214
|
provider: 'openai',
|
|
213
215
|
autoSubmit: true,
|
|
214
|
-
maxItems: 3
|
|
216
|
+
maxItems: 3,
|
|
215
217
|
})
|
|
216
218
|
|
|
217
219
|
// Get item references before auto-submit triggers
|
|
@@ -220,7 +222,7 @@ describe('Batch auto-submit error handling', () => {
|
|
|
220
222
|
const item3 = batch.add('prompt 3') // Triggers auto-submit
|
|
221
223
|
|
|
222
224
|
// Wait for async auto-submit to complete
|
|
223
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
224
226
|
|
|
225
227
|
// FAILING: Items should have error status after failed auto-submit
|
|
226
228
|
// Currently they remain in 'pending' status with no indication of failure
|
|
@@ -237,7 +239,7 @@ describe('Batch auto-submit error handling', () => {
|
|
|
237
239
|
const batch = createBatch({
|
|
238
240
|
provider: 'openai',
|
|
239
241
|
autoSubmit: true,
|
|
240
|
-
maxItems: 3
|
|
242
|
+
maxItems: 3,
|
|
241
243
|
})
|
|
242
244
|
|
|
243
245
|
batch.add('prompt 1')
|
|
@@ -252,7 +254,8 @@ describe('Batch auto-submit error handling', () => {
|
|
|
252
254
|
expect('autoSubmitPromise' in batch).toBe(true)
|
|
253
255
|
|
|
254
256
|
// The promise should be available for awaiting
|
|
255
|
-
const autoSubmitPromise = (batch as BatchQueue & { autoSubmitPromise?: Promise<void> })
|
|
257
|
+
const autoSubmitPromise = (batch as BatchQueue & { autoSubmitPromise?: Promise<void> })
|
|
258
|
+
.autoSubmitPromise
|
|
256
259
|
expect(autoSubmitPromise).toBeDefined()
|
|
257
260
|
|
|
258
261
|
// Awaiting it should surface the error
|
|
@@ -267,13 +270,13 @@ describe('Batch auto-submit error handling', () => {
|
|
|
267
270
|
const batch = createBatch({
|
|
268
271
|
provider: 'openai',
|
|
269
272
|
autoSubmit: true,
|
|
270
|
-
maxItems: 2
|
|
273
|
+
maxItems: 2,
|
|
271
274
|
})
|
|
272
275
|
|
|
273
276
|
batch.add('prompt 1')
|
|
274
277
|
batch.add('prompt 2') // Triggers auto-submit
|
|
275
278
|
|
|
276
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
279
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
277
280
|
|
|
278
281
|
// FAILING: Rate limit error should be exposed to caller
|
|
279
282
|
// Currently it's only logged to console.error
|
|
@@ -292,13 +295,13 @@ describe('Batch auto-submit error handling', () => {
|
|
|
292
295
|
const batch = createBatch({
|
|
293
296
|
provider: 'openai',
|
|
294
297
|
autoSubmit: true,
|
|
295
|
-
maxItems: 2
|
|
298
|
+
maxItems: 2,
|
|
296
299
|
})
|
|
297
300
|
|
|
298
301
|
batch.add('prompt 1')
|
|
299
302
|
batch.add('prompt 2')
|
|
300
303
|
|
|
301
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
304
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
302
305
|
|
|
303
306
|
// FAILING: Rate limit metadata should be accessible
|
|
304
307
|
const job = batch.getJob()
|
|
@@ -315,17 +318,18 @@ describe('Batch auto-submit error handling', () => {
|
|
|
315
318
|
const batch = createBatch({
|
|
316
319
|
provider: 'openai',
|
|
317
320
|
autoSubmit: true,
|
|
318
|
-
maxItems: 2
|
|
321
|
+
maxItems: 2,
|
|
319
322
|
})
|
|
320
323
|
|
|
321
324
|
batch.add('prompt 1')
|
|
322
325
|
batch.add('prompt 2')
|
|
323
326
|
|
|
324
327
|
// Wait for timeout to occur
|
|
325
|
-
await new Promise(resolve => setTimeout(resolve, 200))
|
|
328
|
+
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
326
329
|
|
|
327
330
|
// FAILING: Timeout error should be captured and accessible
|
|
328
|
-
|
|
331
|
+
// Logger calls with format: 'Batch auto-submit failed:', error
|
|
332
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Batch auto-submit failed:', expect.any(Error))
|
|
329
333
|
|
|
330
334
|
// Items should reflect the failure
|
|
331
335
|
const items = batch.getItems()
|
|
@@ -342,13 +346,13 @@ describe('Batch auto-submit error handling', () => {
|
|
|
342
346
|
const batch = createBatch({
|
|
343
347
|
provider: 'openai',
|
|
344
348
|
autoSubmit: true,
|
|
345
|
-
maxItems: 2
|
|
349
|
+
maxItems: 2,
|
|
346
350
|
})
|
|
347
351
|
|
|
348
352
|
batch.add('prompt 1')
|
|
349
353
|
batch.add('prompt 2') // Triggers auto-submit (fails)
|
|
350
354
|
|
|
351
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
355
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
352
356
|
|
|
353
357
|
// Replace with working adapter
|
|
354
358
|
registerBatchAdapter('openai', createSuccessAdapter())
|
|
@@ -372,13 +376,13 @@ describe('Batch auto-submit error handling', () => {
|
|
|
372
376
|
const batch = createBatch({
|
|
373
377
|
provider: 'openai',
|
|
374
378
|
autoSubmit: true,
|
|
375
|
-
maxItems: 2
|
|
379
|
+
maxItems: 2,
|
|
376
380
|
})
|
|
377
381
|
|
|
378
382
|
batch.add('prompt 1')
|
|
379
383
|
batch.add('prompt 2') // Triggers auto-submit (fails)
|
|
380
384
|
|
|
381
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
385
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
382
386
|
|
|
383
387
|
// Replace with working adapter
|
|
384
388
|
registerBatchAdapter('openai', createSuccessAdapter())
|
|
@@ -411,8 +415,8 @@ describe('Batch auto-submit error handling', () => {
|
|
|
411
415
|
provider: 'openai',
|
|
412
416
|
status: 'completed',
|
|
413
417
|
totalItems: items.length,
|
|
414
|
-
completedItems: results.filter(r => r.status === 'completed').length,
|
|
415
|
-
failedItems: results.filter(r => r.status === 'failed').length,
|
|
418
|
+
completedItems: results.filter((r) => r.status === 'completed').length,
|
|
419
|
+
failedItems: results.filter((r) => r.status === 'failed').length,
|
|
416
420
|
createdAt: new Date(),
|
|
417
421
|
},
|
|
418
422
|
completion: Promise.resolve(results),
|
|
@@ -444,14 +448,17 @@ describe('Batch auto-submit error handling', () => {
|
|
|
444
448
|
const batch = createBatch({
|
|
445
449
|
provider: 'openai',
|
|
446
450
|
autoSubmit: true,
|
|
447
|
-
maxItems: 4
|
|
451
|
+
maxItems: 4,
|
|
448
452
|
})
|
|
449
453
|
|
|
450
454
|
// FAILING: There should be a way to subscribe to partial failure events
|
|
451
455
|
// This tests that callers can be notified when some items fail
|
|
452
456
|
if ('on' in batch) {
|
|
453
|
-
(
|
|
454
|
-
|
|
457
|
+
;(
|
|
458
|
+
batch as BatchQueue & {
|
|
459
|
+
on: (event: string, handler: (results: BatchResult[]) => void) => void
|
|
460
|
+
}
|
|
461
|
+
).on('partial-failure', partialFailureHandler)
|
|
455
462
|
}
|
|
456
463
|
|
|
457
464
|
batch.add('prompt 1')
|
|
@@ -460,13 +467,13 @@ describe('Batch auto-submit error handling', () => {
|
|
|
460
467
|
batch.add('prompt 4') // Triggers auto-submit
|
|
461
468
|
|
|
462
469
|
// Wait for auto-submit to complete
|
|
463
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
470
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
464
471
|
|
|
465
472
|
// FAILING: Partial failure handler should be called with failed items
|
|
466
473
|
expect(partialFailureHandler).toHaveBeenCalled()
|
|
467
474
|
expect(partialFailureHandler).toHaveBeenCalledWith(
|
|
468
475
|
expect.arrayContaining([
|
|
469
|
-
expect.objectContaining({ status: 'failed', error: 'Processing failed' })
|
|
476
|
+
expect.objectContaining({ status: 'failed', error: 'Processing failed' }),
|
|
470
477
|
])
|
|
471
478
|
)
|
|
472
479
|
})
|
|
@@ -487,8 +494,8 @@ describe('Batch auto-submit error handling', () => {
|
|
|
487
494
|
provider: 'openai',
|
|
488
495
|
status: 'completed',
|
|
489
496
|
totalItems: items.length,
|
|
490
|
-
completedItems: results.filter(r => r.status === 'completed').length,
|
|
491
|
-
failedItems: results.filter(r => r.status === 'failed').length,
|
|
497
|
+
completedItems: results.filter((r) => r.status === 'completed').length,
|
|
498
|
+
failedItems: results.filter((r) => r.status === 'failed').length,
|
|
492
499
|
createdAt: new Date(),
|
|
493
500
|
},
|
|
494
501
|
completion: Promise.resolve(results),
|
|
@@ -519,7 +526,7 @@ describe('Batch auto-submit error handling', () => {
|
|
|
519
526
|
const batch = createBatch({
|
|
520
527
|
provider: 'openai',
|
|
521
528
|
autoSubmit: true,
|
|
522
|
-
maxItems: 4
|
|
529
|
+
maxItems: 4,
|
|
523
530
|
})
|
|
524
531
|
|
|
525
532
|
batch.add('prompt 1')
|
|
@@ -527,10 +534,12 @@ describe('Batch auto-submit error handling', () => {
|
|
|
527
534
|
batch.add('prompt 3')
|
|
528
535
|
batch.add('prompt 4') // Triggers auto-submit
|
|
529
536
|
|
|
530
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
537
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
531
538
|
|
|
532
539
|
// FAILING: There should be a way to get failure summary
|
|
533
|
-
const failedItems = (
|
|
540
|
+
const failedItems = (
|
|
541
|
+
batch as BatchQueue & { getFailedItems?: () => BatchItem[] }
|
|
542
|
+
).getFailedItems?.()
|
|
534
543
|
expect(failedItems).toBeDefined()
|
|
535
544
|
expect(failedItems?.length).toBe(2)
|
|
536
545
|
})
|
|
@@ -544,16 +553,17 @@ describe('Batch auto-submit error handling', () => {
|
|
|
544
553
|
const batch = createBatch({
|
|
545
554
|
provider: 'openai',
|
|
546
555
|
autoSubmit: true,
|
|
547
|
-
maxItems: 2
|
|
556
|
+
maxItems: 2,
|
|
548
557
|
})
|
|
549
558
|
|
|
550
559
|
batch.add('prompt 1')
|
|
551
560
|
batch.add('prompt 2') // Triggers auto-submit
|
|
552
561
|
|
|
553
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
562
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
554
563
|
|
|
555
564
|
// This passes - errors ARE logged
|
|
556
|
-
|
|
565
|
+
// Logger calls with format: 'Batch auto-submit failed:', error
|
|
566
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Batch auto-submit failed:', testError)
|
|
557
567
|
|
|
558
568
|
// But there's no other way to access the error
|
|
559
569
|
// - No error event emitted
|
|
@@ -576,7 +586,7 @@ describe('Suggested API improvements', () => {
|
|
|
576
586
|
const batch = createBatch({
|
|
577
587
|
provider: 'openai',
|
|
578
588
|
autoSubmit: true,
|
|
579
|
-
maxItems: 5
|
|
589
|
+
maxItems: 5,
|
|
580
590
|
})
|
|
581
591
|
|
|
582
592
|
// 1. Event-based error handling
|
|
@@ -585,7 +595,9 @@ describe('Suggested API improvements', () => {
|
|
|
585
595
|
|
|
586
596
|
// 2. Promise-based error handling
|
|
587
597
|
expect('awaitAutoSubmit' in batch).toBe(true)
|
|
588
|
-
expect(typeof (batch as unknown as { awaitAutoSubmit?: unknown }).awaitAutoSubmit).toBe(
|
|
598
|
+
expect(typeof (batch as unknown as { awaitAutoSubmit?: unknown }).awaitAutoSubmit).toBe(
|
|
599
|
+
'function'
|
|
600
|
+
)
|
|
589
601
|
|
|
590
602
|
// 3. Error state inspection
|
|
591
603
|
expect('submissionError' in batch).toBe(true)
|
|
@@ -15,117 +15,71 @@
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { describe, it, expect,
|
|
19
|
-
import {
|
|
20
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
35
|
-
|
|
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
|
-
//
|
|
30
|
+
// Real AI Tests (require gateway)
|
|
83
31
|
// ============================================================================
|
|
84
32
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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 =
|
|
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(
|
|
138
|
-
expect(items).toHaveLength(
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
147
|
+
const titles = ['Title A', 'Title B', 'Title C']
|
|
200
148
|
|
|
201
|
-
const results = await
|
|
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(
|
|
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({
|
|
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
|
-
|
|
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(
|
|
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
|
|
252
|
+
describe('full workflow: list -> map -> batch', () => {
|
|
303
253
|
it('executes the complete blog post generation workflow', async () => {
|
|
304
|
-
// Step 1:
|
|
305
|
-
const titles =
|
|
306
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
289
|
+
expect(results).toHaveLength(3)
|
|
337
290
|
expect(results.every((r) => r.status === 'completed')).toBe(true)
|
|
338
291
|
|
|
339
|
-
// Verify results have
|
|
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
|
-
|
|
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(
|
|
343
|
+
expect(handlerCalled).toBe(true)
|
|
386
344
|
})
|
|
387
345
|
})
|