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.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-test.log +105 -0
- package/README.md +190 -86
- package/TODO.md +138 -0
- package/dist/ai-promise.d.ts +219 -0
- package/dist/ai-promise.d.ts.map +1 -0
- package/dist/ai-promise.js +610 -0
- package/dist/ai-promise.js.map +1 -0
- package/dist/ai.d.ts +285 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +842 -0
- package/dist/ai.js.map +1 -0
- package/dist/batch/anthropic.d.ts +23 -0
- package/dist/batch/anthropic.d.ts.map +1 -0
- package/dist/batch/anthropic.js +257 -0
- package/dist/batch/anthropic.js.map +1 -0
- package/dist/batch/bedrock.d.ts +64 -0
- package/dist/batch/bedrock.d.ts.map +1 -0
- package/dist/batch/bedrock.js +586 -0
- package/dist/batch/bedrock.js.map +1 -0
- package/dist/batch/cloudflare.d.ts +37 -0
- package/dist/batch/cloudflare.d.ts.map +1 -0
- package/dist/batch/cloudflare.js +289 -0
- package/dist/batch/cloudflare.js.map +1 -0
- package/dist/batch/google.d.ts +41 -0
- package/dist/batch/google.d.ts.map +1 -0
- package/dist/batch/google.js +360 -0
- package/dist/batch/google.js.map +1 -0
- package/dist/batch/index.d.ts +31 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +31 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/batch/memory.d.ts +44 -0
- package/dist/batch/memory.d.ts.map +1 -0
- package/dist/batch/memory.js +188 -0
- package/dist/batch/memory.js.map +1 -0
- package/dist/batch/openai.d.ts +37 -0
- package/dist/batch/openai.d.ts.map +1 -0
- package/dist/batch/openai.js +403 -0
- package/dist/batch/openai.js.map +1 -0
- package/dist/batch-map.d.ts +125 -0
- package/dist/batch-map.d.ts.map +1 -0
- package/dist/batch-map.js +406 -0
- package/dist/batch-map.js.map +1 -0
- package/dist/batch-queue.d.ts +273 -0
- package/dist/batch-queue.d.ts.map +1 -0
- package/dist/batch-queue.js +271 -0
- package/dist/batch-queue.js.map +1 -0
- package/dist/context.d.ts +133 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +267 -0
- package/dist/context.js.map +1 -0
- package/dist/embeddings.d.ts +123 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +170 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/eval/index.d.ts +8 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +8 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/models.d.ts +66 -0
- package/dist/eval/models.d.ts.map +1 -0
- package/dist/eval/models.js +120 -0
- package/dist/eval/models.js.map +1 -0
- package/dist/eval/runner.d.ts +64 -0
- package/dist/eval/runner.d.ts.map +1 -0
- package/dist/eval/runner.js +148 -0
- package/dist/eval/runner.js.map +1 -0
- package/dist/generate.d.ts +168 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +174 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +29 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -52
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +292 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +471 -0
- package/dist/primitives.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +9 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +9 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/schema.d.ts +54 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +109 -0
- package/dist/schema.js.map +1 -0
- package/dist/template.d.ts +73 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +129 -0
- package/dist/template.js.map +1 -0
- package/dist/types.d.ts +474 -106
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -8
- package/dist/types.js.map +1 -1
- package/evalite.config.ts +19 -0
- package/evals/README.md +212 -0
- package/evals/classification.eval.ts +108 -0
- package/evals/marketing.eval.ts +370 -0
- package/evals/math.eval.ts +94 -0
- package/evals/run-evals.ts +166 -0
- package/evals/structured-output.eval.ts +143 -0
- package/evals/writing.eval.ts +117 -0
- package/examples/batch-blog-posts.ts +160 -0
- package/package.json +57 -57
- package/src/ai-promise.ts +784 -0
- package/src/ai.ts +1183 -0
- package/src/batch/anthropic.ts +375 -0
- package/src/batch/bedrock.ts +801 -0
- package/src/batch/cloudflare.ts +421 -0
- package/src/batch/google.ts +491 -0
- package/src/batch/index.ts +31 -0
- package/src/batch/memory.ts +253 -0
- package/src/batch/openai.ts +557 -0
- package/src/batch-map.ts +534 -0
- package/src/batch-queue.ts +493 -0
- package/src/context.ts +332 -0
- package/src/embeddings.ts +244 -0
- package/src/eval/index.ts +8 -0
- package/src/eval/models.ts +158 -0
- package/src/eval/runner.ts +217 -0
- package/src/generate.ts +245 -0
- package/src/index.ts +154 -0
- package/src/primitives.ts +612 -0
- package/src/providers/cloudflare.ts +15 -0
- package/src/providers/index.ts +14 -0
- package/src/schema.ts +147 -0
- package/src/template.ts +209 -0
- package/src/types.ts +540 -0
- package/test/README.md +105 -0
- package/test/ai-proxy.test.ts +192 -0
- package/test/async-iterators.test.ts +327 -0
- package/test/batch-background.test.ts +482 -0
- package/test/batch-blog-posts.test.ts +387 -0
- package/test/blog-generation.test.ts +510 -0
- package/test/browse-read.test.ts +611 -0
- package/test/core-functions.test.ts +694 -0
- package/test/decide.test.ts +393 -0
- package/test/define.test.ts +274 -0
- package/test/e2e-bedrock-manual.ts +163 -0
- package/test/e2e-bedrock.test.ts +191 -0
- package/test/e2e-flex-gateway.ts +157 -0
- package/test/e2e-flex-manual.ts +183 -0
- package/test/e2e-flex.test.ts +209 -0
- package/test/e2e-google-manual.ts +178 -0
- package/test/e2e-google.test.ts +216 -0
- package/test/embeddings.test.ts +284 -0
- package/test/evals/define-function.eval.test.ts +379 -0
- package/test/evals/primitives.eval.test.ts +384 -0
- package/test/function-types.test.ts +492 -0
- package/test/generate-core.test.ts +319 -0
- package/test/generate.test.ts +163 -0
- package/test/implicit-batch.test.ts +422 -0
- package/test/schema.test.ts +109 -0
- package/test/tagged-templates.test.ts +302 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +42 -0
- package/LICENSE +0 -21
- package/bin/cli.js +0 -5
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -38
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/index.test.d.ts +0 -2
- package/dist/cli/index.test.d.ts.map +0 -1
- package/dist/cli/index.test.js +0 -35
- package/dist/cli/index.test.js.map +0 -1
- package/dist/constants/models.d.ts +0 -10
- package/dist/constants/models.d.ts.map +0 -1
- package/dist/constants/models.js +0 -12
- package/dist/constants/models.js.map +0 -1
- package/dist/converters/index.d.ts +0 -3
- package/dist/converters/index.d.ts.map +0 -1
- package/dist/converters/index.js +0 -3
- package/dist/converters/index.js.map +0 -1
- package/dist/converters/model.d.ts +0 -4
- package/dist/converters/model.d.ts.map +0 -1
- package/dist/converters/model.js +0 -19
- package/dist/converters/model.js.map +0 -1
- package/dist/converters/schema.d.ts +0 -4
- package/dist/converters/schema.d.ts.map +0 -1
- package/dist/converters/schema.js +0 -25
- package/dist/converters/schema.js.map +0 -1
- package/dist/core/responses.d.ts +0 -5
- package/dist/core/responses.d.ts.map +0 -1
- package/dist/core/responses.js +0 -16
- package/dist/core/responses.js.map +0 -1
- package/dist/core/responses.test.d.ts +0 -2
- package/dist/core/responses.test.d.ts.map +0 -1
- package/dist/core/responses.test.js +0 -31
- package/dist/core/responses.test.js.map +0 -1
- package/dist/errors.d.ts +0 -6
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -9
- package/dist/errors.js.map +0 -1
- package/dist/examples/streaming.test.d.ts +0 -2
- package/dist/examples/streaming.test.d.ts.map +0 -1
- package/dist/examples/streaming.test.js +0 -176
- package/dist/examples/streaming.test.js.map +0 -1
- package/dist/factory/__tests__/index.test.d.ts +0 -2
- package/dist/factory/__tests__/index.test.d.ts.map +0 -1
- package/dist/factory/__tests__/index.test.js +0 -430
- package/dist/factory/__tests__/index.test.js.map +0 -1
- package/dist/factory/__tests__/list.test.d.ts +0 -2
- package/dist/factory/__tests__/list.test.d.ts.map +0 -1
- package/dist/factory/__tests__/list.test.js +0 -92
- package/dist/factory/__tests__/list.test.js.map +0 -1
- package/dist/factory/index.d.ts +0 -20
- package/dist/factory/index.d.ts.map +0 -1
- package/dist/factory/index.js +0 -287
- package/dist/factory/index.js.map +0 -1
- package/dist/factory/index.test.d.ts +0 -2
- package/dist/factory/index.test.d.ts.map +0 -1
- package/dist/factory/index.test.js +0 -287
- package/dist/factory/index.test.js.map +0 -1
- package/dist/factory/list.d.ts +0 -3
- package/dist/factory/list.d.ts.map +0 -1
- package/dist/factory/list.js +0 -221
- package/dist/factory/list.js.map +0 -1
- package/dist/factory/list.test.d.ts +0 -2
- package/dist/factory/list.test.d.ts.map +0 -1
- package/dist/factory/list.test.js +0 -84
- package/dist/factory/list.test.js.map +0 -1
- package/dist/generate/index.d.ts +0 -5
- package/dist/generate/index.d.ts.map +0 -1
- package/dist/generate/index.js +0 -17
- package/dist/generate/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -59
- package/dist/index.test.js.map +0 -1
- package/dist/list/await.d.ts +0 -3
- package/dist/list/await.d.ts.map +0 -1
- package/dist/list/await.js +0 -28
- package/dist/list/await.js.map +0 -1
- package/dist/list/constants.d.ts +0 -4
- package/dist/list/constants.d.ts.map +0 -1
- package/dist/list/constants.js +0 -5
- package/dist/list/constants.js.map +0 -1
- package/dist/list/create-function.d.ts +0 -3
- package/dist/list/create-function.d.ts.map +0 -1
- package/dist/list/create-function.js +0 -11
- package/dist/list/create-function.js.map +0 -1
- package/dist/list/index.d.ts +0 -4
- package/dist/list/index.d.ts.map +0 -1
- package/dist/list/index.js +0 -5
- package/dist/list/index.js.map +0 -1
- package/dist/list/prompt.d.ts +0 -3
- package/dist/list/prompt.d.ts.map +0 -1
- package/dist/list/prompt.js +0 -6
- package/dist/list/prompt.js.map +0 -1
- package/dist/list/schemas.d.ts +0 -4
- package/dist/list/schemas.d.ts.map +0 -1
- package/dist/list/schemas.js +0 -8
- package/dist/list/schemas.js.map +0 -1
- package/dist/list/stream.d.ts +0 -3
- package/dist/list/stream.d.ts.map +0 -1
- package/dist/list/stream.js +0 -33
- package/dist/list/stream.js.map +0 -1
- package/dist/list/types.d.ts +0 -11
- package/dist/list/types.d.ts.map +0 -1
- package/dist/list/types.js +0 -2
- package/dist/list/types.js.map +0 -1
- package/dist/list/validation.d.ts +0 -3
- package/dist/list/validation.d.ts.map +0 -1
- package/dist/list/validation.js +0 -12
- package/dist/list/validation.js.map +0 -1
- package/dist/providers/config.d.ts +0 -4
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -21
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/config.test.d.ts +0 -2
- package/dist/providers/config.test.d.ts.map +0 -1
- package/dist/providers/config.test.js +0 -37
- package/dist/providers/config.test.js.map +0 -1
- package/dist/proxy/constants.d.ts +0 -4
- package/dist/proxy/constants.d.ts.map +0 -1
- package/dist/proxy/constants.js +0 -5
- package/dist/proxy/constants.js.map +0 -1
- package/dist/proxy/create-function.d.ts +0 -4
- package/dist/proxy/create-function.d.ts.map +0 -1
- package/dist/proxy/create-function.js +0 -24
- package/dist/proxy/create-function.js.map +0 -1
- package/dist/proxy/create-proxy.d.ts +0 -2
- package/dist/proxy/create-proxy.d.ts.map +0 -1
- package/dist/proxy/create-proxy.js +0 -11
- package/dist/proxy/create-proxy.js.map +0 -1
- package/dist/proxy/function-generator.d.ts +0 -9
- package/dist/proxy/function-generator.d.ts.map +0 -1
- package/dist/proxy/function-generator.js +0 -29
- package/dist/proxy/function-generator.js.map +0 -1
- package/dist/proxy/index.d.ts +0 -4
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js +0 -4
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/prompt.d.ts +0 -2
- package/dist/proxy/prompt.d.ts.map +0 -1
- package/dist/proxy/prompt.js +0 -6
- package/dist/proxy/prompt.js.map +0 -1
- package/dist/proxy/types.d.ts +0 -7
- package/dist/proxy/types.d.ts.map +0 -1
- package/dist/proxy/types.js +0 -2
- package/dist/proxy/types.js.map +0 -1
- package/dist/queue/manager.d.ts +0 -5
- package/dist/queue/manager.d.ts.map +0 -1
- package/dist/queue/manager.js +0 -37
- package/dist/queue/manager.js.map +0 -1
- package/dist/queue/manager.test.d.ts +0 -2
- package/dist/queue/manager.test.d.ts.map +0 -1
- package/dist/queue/manager.test.js +0 -52
- package/dist/queue/manager.test.js.map +0 -1
- package/dist/schema-converter.d.ts +0 -4
- package/dist/schema-converter.d.ts.map +0 -1
- package/dist/schema-converter.js +0 -30
- package/dist/schema-converter.js.map +0 -1
- package/dist/stream/index.d.ts +0 -7
- package/dist/stream/index.d.ts.map +0 -1
- package/dist/stream/index.js +0 -23
- package/dist/stream/index.js.map +0 -1
- package/dist/streaming/utils.d.ts +0 -4
- package/dist/streaming/utils.d.ts.map +0 -1
- package/dist/streaming/utils.js +0 -131
- package/dist/streaming/utils.js.map +0 -1
- package/dist/streaming/utils.test.d.ts +0 -2
- package/dist/streaming/utils.test.d.ts.map +0 -1
- package/dist/streaming/utils.test.js +0 -84
- package/dist/streaming/utils.test.js.map +0 -1
- package/dist/templates/result.d.ts +0 -7
- package/dist/templates/result.d.ts.map +0 -1
- package/dist/templates/result.js +0 -40
- package/dist/templates/result.js.map +0 -1
- package/dist/templates/result.test.d.ts +0 -2
- package/dist/templates/result.test.d.ts.map +0 -1
- package/dist/templates/result.test.js +0 -75
- package/dist/templates/result.test.js.map +0 -1
- package/dist/test/setup.d.ts +0 -2
- package/dist/test/setup.d.ts.map +0 -1
- package/dist/test/setup.js +0 -21
- package/dist/test/setup.js.map +0 -1
- package/dist/test-types.d.ts +0 -13
- package/dist/test-types.d.ts.map +0 -1
- package/dist/test-types.js +0 -55
- package/dist/test-types.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/list.d.ts +0 -10
- package/dist/types/list.d.ts.map +0 -1
- package/dist/types/list.js +0 -2
- package/dist/types/list.js.map +0 -1
- package/dist/types/model.d.ts +0 -7
- package/dist/types/model.d.ts.map +0 -1
- package/dist/types/model.js +0 -2
- package/dist/types/model.js.map +0 -1
- package/dist/types/options.d.ts +0 -25
- package/dist/types/options.d.ts.map +0 -1
- package/dist/types/options.js +0 -2
- package/dist/types/options.js.map +0 -1
- package/dist/types/schema.d.ts +0 -5
- package/dist/types/schema.d.ts.map +0 -1
- package/dist/types/schema.js +0 -2
- package/dist/types/schema.js.map +0 -1
- package/dist/utils/__tests__/request-handler.test.d.ts +0 -2
- package/dist/utils/__tests__/request-handler.test.d.ts.map +0 -1
- package/dist/utils/__tests__/request-handler.test.js +0 -134
- package/dist/utils/__tests__/request-handler.test.js.map +0 -1
- package/dist/utils/__tests__/schema.test.d.ts +0 -2
- package/dist/utils/__tests__/schema.test.d.ts.map +0 -1
- package/dist/utils/__tests__/schema.test.js +0 -49
- package/dist/utils/__tests__/schema.test.js.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.d.ts +0 -2
- package/dist/utils/__tests__/stream-progress.test.d.ts.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.js +0 -85
- package/dist/utils/__tests__/stream-progress.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/request-handler.d.ts +0 -17
- package/dist/utils/request-handler.d.ts.map +0 -1
- package/dist/utils/request-handler.js +0 -105
- package/dist/utils/request-handler.js.map +0 -1
- package/dist/utils/schema.d.ts +0 -11
- package/dist/utils/schema.d.ts.map +0 -1
- package/dist/utils/schema.js +0 -51
- package/dist/utils/schema.js.map +0 -1
- package/dist/utils/stream-progress.d.ts +0 -17
- package/dist/utils/stream-progress.d.ts.map +0 -1
- package/dist/utils/stream-progress.js +0 -86
- package/dist/utils/stream-progress.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -30
- package/dist/utils/validation.js.map +0 -1
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch Blog Post Generation Test
|
|
3
|
+
*
|
|
4
|
+
* Tests the batch processing workflow where:
|
|
5
|
+
* 1. list`10 blog post titles` executes immediately
|
|
6
|
+
* 2. The mapped write operations are deferred to a batch
|
|
7
|
+
* 3. The batch is submitted to the provider (OpenAI/Anthropic)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const titles = await list`10 blog post titles about building startups in 2026`
|
|
12
|
+
* const posts = titles.map(title => batch.add(write`blog post about ${title}`))
|
|
13
|
+
* const job = await batch.submit()
|
|
14
|
+
* const results = await batch.wait()
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
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'
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Mock Setup
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
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
|
+
}))
|
|
80
|
+
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// Test Helpers
|
|
83
|
+
// ============================================================================
|
|
84
|
+
|
|
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,
|
|
94
|
+
})
|
|
95
|
+
return (result.object as { items: string[] }).items
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Tests
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
describe('Batch Blog Post Generation', () => {
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
vi.clearAllMocks()
|
|
105
|
+
clearBatches()
|
|
106
|
+
// Use default handler that calls the mock
|
|
107
|
+
configureMemoryAdapter({})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
afterEach(() => {
|
|
111
|
+
clearBatches()
|
|
112
|
+
})
|
|
113
|
+
|
|
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
|
+
describe('batch processing workflow', () => {
|
|
125
|
+
it('creates batch queue and adds items', async () => {
|
|
126
|
+
const batch = createBatch({ provider: 'openai', model: 'gpt-4o' })
|
|
127
|
+
|
|
128
|
+
const titles = await mockList('10 blog post titles about building startups in 2026')
|
|
129
|
+
|
|
130
|
+
// Add each title to the batch
|
|
131
|
+
const items = titles.map((title) =>
|
|
132
|
+
batch.add(`Write a comprehensive blog post about: ${title}`, {
|
|
133
|
+
customId: title.slice(0, 50).replace(/\s+/g, '-').toLowerCase(),
|
|
134
|
+
})
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
expect(batch.size).toBe(10)
|
|
138
|
+
expect(items).toHaveLength(10)
|
|
139
|
+
expect(items[0].status).toBe('pending')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('submits batch and returns job info', async () => {
|
|
143
|
+
const batch = createBatch({ provider: 'openai', model: 'gpt-4o' })
|
|
144
|
+
|
|
145
|
+
const titles = await mockList('10 blog post titles about building startups in 2026')
|
|
146
|
+
|
|
147
|
+
titles.forEach((title) =>
|
|
148
|
+
batch.add(`Write a comprehensive blog post about: ${title}`)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
const { job, completion } = await batch.submit()
|
|
152
|
+
|
|
153
|
+
expect(job.id).toMatch(/^batch_memory_/)
|
|
154
|
+
expect(job.provider).toBe('openai')
|
|
155
|
+
expect(job.totalItems).toBe(10)
|
|
156
|
+
expect(job.status).toBe('pending')
|
|
157
|
+
|
|
158
|
+
// Wait for completion
|
|
159
|
+
const results = await completion
|
|
160
|
+
expect(results).toHaveLength(10)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('waits for batch completion and returns results', async () => {
|
|
164
|
+
const batch = createBatch({ provider: 'openai', model: 'gpt-4o' })
|
|
165
|
+
|
|
166
|
+
const titles = await mockList('10 blog post titles about building startups in 2026')
|
|
167
|
+
|
|
168
|
+
titles.forEach((title) =>
|
|
169
|
+
batch.add(`Write a comprehensive blog post about: ${title}`)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
await batch.submit()
|
|
173
|
+
const results = await batch.wait()
|
|
174
|
+
|
|
175
|
+
expect(results).toHaveLength(10)
|
|
176
|
+
expect(results.every((r) => r.status === 'completed')).toBe(true)
|
|
177
|
+
expect(results[0].result).toBeDefined()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('processes items in order', async () => {
|
|
181
|
+
const batch = createBatch({ provider: 'openai' })
|
|
182
|
+
|
|
183
|
+
const titles = ['First', 'Second', 'Third']
|
|
184
|
+
const items = titles.map((title, i) =>
|
|
185
|
+
batch.add(`Write about: ${title}`, { customId: `item_${i}` })
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
await batch.submit()
|
|
189
|
+
const results = await batch.wait()
|
|
190
|
+
|
|
191
|
+
expect(results[0].id).toBe('item_0')
|
|
192
|
+
expect(results[1].id).toBe('item_1')
|
|
193
|
+
expect(results[2].id).toBe('item_2')
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('withBatchQueue helper', () => {
|
|
198
|
+
it('provides convenient batch execution', async () => {
|
|
199
|
+
const titles = await mockList('10 blog post titles about building startups in 2026')
|
|
200
|
+
|
|
201
|
+
const results = await withBatch(
|
|
202
|
+
(batch) =>
|
|
203
|
+
titles.map((title) =>
|
|
204
|
+
batch.add(`Write a blog post about: ${title}`)
|
|
205
|
+
),
|
|
206
|
+
{ provider: 'openai', model: 'gpt-4o' }
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
expect(results).toHaveLength(10)
|
|
210
|
+
expect(results.every((r) => r.status === 'completed')).toBe(true)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe('batch status tracking', () => {
|
|
215
|
+
it('tracks completion progress', async () => {
|
|
216
|
+
const batch = createBatch({ provider: 'openai' })
|
|
217
|
+
|
|
218
|
+
batch.add('Write post 1')
|
|
219
|
+
batch.add('Write post 2')
|
|
220
|
+
batch.add('Write post 3')
|
|
221
|
+
|
|
222
|
+
const { job } = await batch.submit()
|
|
223
|
+
expect(job.completedItems).toBe(0)
|
|
224
|
+
|
|
225
|
+
// Wait for completion
|
|
226
|
+
await batch.wait()
|
|
227
|
+
|
|
228
|
+
const finalStatus = await batch.getStatus()
|
|
229
|
+
expect(finalStatus.status).toBe('completed')
|
|
230
|
+
expect(finalStatus.completedItems).toBe(3)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
describe('error handling', () => {
|
|
235
|
+
it('handles partial failures', async () => {
|
|
236
|
+
// Configure adapter to fail 30% of requests
|
|
237
|
+
configureMemoryAdapter({ failureRate: 0.3 })
|
|
238
|
+
|
|
239
|
+
const batch = createBatch({ provider: 'openai' })
|
|
240
|
+
|
|
241
|
+
for (let i = 0; i < 10; i++) {
|
|
242
|
+
batch.add(`Write post ${i}`)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
await batch.submit()
|
|
246
|
+
const results = await batch.wait()
|
|
247
|
+
|
|
248
|
+
// Some should fail, some should succeed
|
|
249
|
+
const succeeded = results.filter((r) => r.status === 'completed').length
|
|
250
|
+
const failed = results.filter((r) => r.status === 'failed').length
|
|
251
|
+
|
|
252
|
+
expect(succeeded + failed).toBe(10)
|
|
253
|
+
// With 30% failure rate, expect roughly 3 failures (with some variance)
|
|
254
|
+
expect(failed).toBeGreaterThanOrEqual(0)
|
|
255
|
+
expect(failed).toBeLessThanOrEqual(10)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('prevents adding items after submission', async () => {
|
|
259
|
+
const batch = createBatch({ provider: 'openai' })
|
|
260
|
+
batch.add('Write post 1')
|
|
261
|
+
await batch.submit()
|
|
262
|
+
|
|
263
|
+
expect(() => batch.add('Write post 2')).toThrow('Cannot add items to a submitted batch')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('prevents double submission', async () => {
|
|
267
|
+
const batch = createBatch({ provider: 'openai' })
|
|
268
|
+
batch.add('Write post 1')
|
|
269
|
+
await batch.submit()
|
|
270
|
+
|
|
271
|
+
await expect(batch.submit()).rejects.toThrow('Batch has already been submitted')
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('prevents empty batch submission', async () => {
|
|
275
|
+
const batch = createBatch({ provider: 'openai' })
|
|
276
|
+
|
|
277
|
+
await expect(batch.submit()).rejects.toThrow('Cannot submit empty batch')
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
describe('batch with custom handler', () => {
|
|
282
|
+
it('uses custom handler for processing', async () => {
|
|
283
|
+
const customHandler = vi.fn().mockImplementation(async (item) => {
|
|
284
|
+
return `Custom result for: ${item.prompt}`
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
configureMemoryAdapter({ handler: customHandler })
|
|
288
|
+
|
|
289
|
+
const batch = createBatch({ provider: 'openai' })
|
|
290
|
+
batch.add('Topic 1')
|
|
291
|
+
batch.add('Topic 2')
|
|
292
|
+
|
|
293
|
+
await batch.submit()
|
|
294
|
+
const results = await batch.wait()
|
|
295
|
+
|
|
296
|
+
expect(customHandler).toHaveBeenCalledTimes(2)
|
|
297
|
+
expect(results[0].result).toBe('Custom result for: Topic 1')
|
|
298
|
+
expect(results[1].result).toBe('Custom result for: Topic 2')
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
describe('full workflow: list → map → batch', () => {
|
|
303
|
+
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)
|
|
307
|
+
|
|
308
|
+
// Step 2: Create batch for blog posts (deferred)
|
|
309
|
+
const batch = createBatch({
|
|
310
|
+
provider: 'openai',
|
|
311
|
+
model: 'gpt-4o',
|
|
312
|
+
metadata: { task: 'blog-generation', topic: 'startups-2026' },
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
// Step 3: Map titles to batch items
|
|
316
|
+
const blogItems = titles.map((title, index) =>
|
|
317
|
+
batch.add(`Write a comprehensive blog post about: ${title}`, {
|
|
318
|
+
customId: `blog-${index}`,
|
|
319
|
+
metadata: { title },
|
|
320
|
+
})
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
expect(batch.size).toBe(10)
|
|
324
|
+
expect(blogItems.every((item) => item.status === 'pending')).toBe(true)
|
|
325
|
+
|
|
326
|
+
// Step 4: Submit the batch
|
|
327
|
+
const { job, completion } = await batch.submit()
|
|
328
|
+
|
|
329
|
+
expect(job.id).toBeDefined()
|
|
330
|
+
expect(job.totalItems).toBe(10)
|
|
331
|
+
expect(batch.isSubmitted).toBe(true)
|
|
332
|
+
|
|
333
|
+
// Step 5: Wait for results
|
|
334
|
+
const results = await completion
|
|
335
|
+
|
|
336
|
+
expect(results).toHaveLength(10)
|
|
337
|
+
expect(results.every((r) => r.status === 'completed')).toBe(true)
|
|
338
|
+
|
|
339
|
+
// Verify results have blog post content
|
|
340
|
+
for (const result of results) {
|
|
341
|
+
expect(result.result).toBeDefined()
|
|
342
|
+
expect(typeof result.result).toBe('string')
|
|
343
|
+
// Blog posts should have some content
|
|
344
|
+
expect((result.result as string).length).toBeGreaterThan(100)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Verify items are updated after completion
|
|
348
|
+
expect(blogItems.every((item) => item.status === 'completed')).toBe(true)
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
describe('Provider-specific batch behavior', () => {
|
|
354
|
+
beforeEach(() => {
|
|
355
|
+
clearBatches()
|
|
356
|
+
configureMemoryAdapter({})
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('uses specified provider', async () => {
|
|
360
|
+
const openAIBatch = createBatch({ provider: 'openai' })
|
|
361
|
+
const anthropicBatch = createBatch({ provider: 'anthropic' })
|
|
362
|
+
|
|
363
|
+
openAIBatch.add('Test prompt')
|
|
364
|
+
anthropicBatch.add('Test prompt')
|
|
365
|
+
|
|
366
|
+
const { job: oaiJob } = await openAIBatch.submit()
|
|
367
|
+
const { job: antJob } = await anthropicBatch.submit()
|
|
368
|
+
|
|
369
|
+
// Memory adapter simulates OpenAI for all providers
|
|
370
|
+
expect(oaiJob.provider).toBe('openai')
|
|
371
|
+
expect(antJob.provider).toBe('openai')
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('respects model configuration', async () => {
|
|
375
|
+
const customHandler = vi.fn().mockResolvedValue('Result')
|
|
376
|
+
configureMemoryAdapter({ handler: customHandler })
|
|
377
|
+
|
|
378
|
+
const batch = createBatch({ provider: 'openai', model: 'gpt-4o-mini' })
|
|
379
|
+
batch.add('Test prompt')
|
|
380
|
+
await batch.submit()
|
|
381
|
+
await batch.wait()
|
|
382
|
+
|
|
383
|
+
// The model should be passed to the handler via batch options
|
|
384
|
+
// (memory adapter doesn't use it, but real adapters would)
|
|
385
|
+
expect(customHandler).toHaveBeenCalled()
|
|
386
|
+
})
|
|
387
|
+
})
|