ai-functions 2.1.3 → 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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +55 -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 +116 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +546 -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/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 +135 -17
- 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 +28 -25
- package/src/ai-promise.ts +226 -140
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +69 -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 +671 -0
- package/src/generate.ts +33 -28
- package/src/index.ts +119 -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/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 +164 -25
- 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/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/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/src/batch/cloudflare.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cloudflare AI Gateway
|
|
2
|
+
* Cloudflare AI Gateway Adapter
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Cloudflare's AI Gateway doesn't have a native batch API like OpenAI/Anthropic,
|
|
5
|
+
* so this adapter fakes batch processing via concurrent direct calls through
|
|
6
|
+
* the gateway and tracks state locally (`LocalJobStore` from `./provider.js`).
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
* the current batch capabilities and can be extended as new features land.
|
|
8
|
+
* For true async batch processing, consider Cloudflare Queues + Workers.
|
|
9
9
|
*
|
|
10
10
|
* @see https://developers.cloudflare.com/ai-gateway/
|
|
11
11
|
*
|
|
@@ -13,325 +13,149 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
|
+
LocalJobStore,
|
|
17
|
+
processConcurrently,
|
|
16
18
|
registerBatchAdapter,
|
|
19
|
+
tryParseJson,
|
|
17
20
|
type BatchAdapter,
|
|
18
21
|
type BatchItem,
|
|
19
22
|
type BatchJob,
|
|
20
23
|
type BatchQueueOptions,
|
|
21
24
|
type BatchResult,
|
|
22
25
|
type BatchSubmitResult,
|
|
23
|
-
|
|
24
|
-
} from '../batch-queue.js'
|
|
25
|
-
import { schema as convertSchema } from '../schema.js'
|
|
26
|
+
} from './provider.js'
|
|
26
27
|
|
|
27
28
|
// ============================================================================
|
|
28
|
-
//
|
|
29
|
-
// ============================================================================
|
|
30
|
-
|
|
31
|
-
interface CloudflareBatchRequest {
|
|
32
|
-
id: string
|
|
33
|
-
provider: string
|
|
34
|
-
endpoint: string
|
|
35
|
-
headers: Record<string, string>
|
|
36
|
-
query: {
|
|
37
|
-
model: string
|
|
38
|
-
messages: Array<{ role: string; content: string }>
|
|
39
|
-
response_format?: { type: 'json_object' }
|
|
40
|
-
max_tokens?: number
|
|
41
|
-
temperature?: number
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface CloudflareBatchResponse {
|
|
46
|
-
id: string
|
|
47
|
-
success: boolean
|
|
48
|
-
result?: {
|
|
49
|
-
response: string
|
|
50
|
-
usage?: {
|
|
51
|
-
prompt_tokens: number
|
|
52
|
-
completion_tokens: number
|
|
53
|
-
total_tokens: number
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
error?: {
|
|
57
|
-
message: string
|
|
58
|
-
code?: string
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface CloudflareBatchJob {
|
|
63
|
-
id: string
|
|
64
|
-
status: 'pending' | 'processing' | 'completed' | 'failed'
|
|
65
|
-
created_at: string
|
|
66
|
-
completed_at?: string
|
|
67
|
-
request_count: number
|
|
68
|
-
completed_count: number
|
|
69
|
-
failed_count: number
|
|
70
|
-
results_url?: string
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ============================================================================
|
|
74
|
-
// Cloudflare Client Configuration
|
|
29
|
+
// Cloudflare client configuration
|
|
75
30
|
// ============================================================================
|
|
76
31
|
|
|
77
32
|
let accountId: string | undefined
|
|
78
33
|
let gatewayId: string | undefined
|
|
79
34
|
let apiToken: string | undefined
|
|
80
|
-
let baseUrl = 'https://api.cloudflare.com/client/v4'
|
|
81
35
|
|
|
82
|
-
/**
|
|
83
|
-
* Configure the Cloudflare client
|
|
84
|
-
*/
|
|
36
|
+
/** Configure the Cloudflare client. */
|
|
85
37
|
export function configureCloudflare(options: {
|
|
86
38
|
accountId?: string
|
|
87
39
|
gatewayId?: string
|
|
88
40
|
apiToken?: string
|
|
41
|
+
/** Reserved for tests / custom hosts; the gateway URL itself is fixed. */
|
|
89
42
|
baseUrl?: string
|
|
90
43
|
}): void {
|
|
91
44
|
if (options.accountId) accountId = options.accountId
|
|
92
45
|
if (options.gatewayId) gatewayId = options.gatewayId
|
|
93
46
|
if (options.apiToken) apiToken = options.apiToken
|
|
94
|
-
|
|
47
|
+
// baseUrl is accepted for backwards compatibility but unused.
|
|
48
|
+
void options.baseUrl
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CloudflareConfig {
|
|
52
|
+
accountId: string
|
|
53
|
+
gatewayId: string
|
|
54
|
+
apiToken: string
|
|
95
55
|
}
|
|
96
56
|
|
|
97
|
-
function getConfig():
|
|
98
|
-
const accId = accountId || process.env
|
|
99
|
-
const gwId = gatewayId || process.env
|
|
100
|
-
const token = apiToken || process.env
|
|
57
|
+
function getConfig(): CloudflareConfig {
|
|
58
|
+
const accId = accountId || process.env['CLOUDFLARE_ACCOUNT_ID']
|
|
59
|
+
const gwId = gatewayId || process.env['CLOUDFLARE_AI_GATEWAY_ID'] || process.env['AI_GATEWAY_ID']
|
|
60
|
+
const token = apiToken || process.env['CLOUDFLARE_API_TOKEN']
|
|
101
61
|
|
|
102
62
|
if (!accId) {
|
|
103
|
-
throw new Error(
|
|
63
|
+
throw new Error(
|
|
64
|
+
'Cloudflare account ID not configured. Set CLOUDFLARE_ACCOUNT_ID or call configureCloudflare()'
|
|
65
|
+
)
|
|
104
66
|
}
|
|
105
67
|
if (!gwId) {
|
|
106
|
-
throw new Error(
|
|
68
|
+
throw new Error(
|
|
69
|
+
'Cloudflare AI Gateway ID not configured. Set CLOUDFLARE_AI_GATEWAY_ID or call configureCloudflare()'
|
|
70
|
+
)
|
|
107
71
|
}
|
|
108
72
|
if (!token) {
|
|
109
|
-
throw new Error(
|
|
73
|
+
throw new Error(
|
|
74
|
+
'Cloudflare API token not configured. Set CLOUDFLARE_API_TOKEN or call configureCloudflare()'
|
|
75
|
+
)
|
|
110
76
|
}
|
|
111
77
|
|
|
112
78
|
return { accountId: accId, gatewayId: gwId, apiToken: token }
|
|
113
79
|
}
|
|
114
80
|
|
|
115
|
-
async function cloudflareRequest<T>(
|
|
116
|
-
method: 'GET' | 'POST',
|
|
117
|
-
path: string,
|
|
118
|
-
body?: unknown
|
|
119
|
-
): Promise<T> {
|
|
120
|
-
const config = getConfig()
|
|
121
|
-
const url = `${baseUrl}${path}`
|
|
122
|
-
|
|
123
|
-
const response = await fetch(url, {
|
|
124
|
-
method,
|
|
125
|
-
headers: {
|
|
126
|
-
Authorization: `Bearer ${config.apiToken}`,
|
|
127
|
-
'Content-Type': 'application/json',
|
|
128
|
-
},
|
|
129
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
const error = await response.text()
|
|
134
|
-
throw new Error(`Cloudflare API error: ${response.status} ${error}`)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const data = await response.json() as { success: boolean; result: T; errors?: unknown[] }
|
|
138
|
-
if (!data.success) {
|
|
139
|
-
throw new Error(`Cloudflare API error: ${JSON.stringify(data.errors)}`)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return data.result
|
|
143
|
-
}
|
|
144
|
-
|
|
145
81
|
// ============================================================================
|
|
146
|
-
//
|
|
82
|
+
// Local job tracking
|
|
147
83
|
// ============================================================================
|
|
148
84
|
|
|
149
|
-
const
|
|
150
|
-
items: BatchItem[]
|
|
151
|
-
options: BatchQueueOptions
|
|
152
|
-
results: BatchResult[]
|
|
153
|
-
status: BatchStatus
|
|
154
|
-
createdAt: Date
|
|
155
|
-
completedAt?: Date
|
|
156
|
-
}>()
|
|
157
|
-
|
|
158
|
-
let jobCounter = 0
|
|
85
|
+
const jobs = new LocalJobStore('cf_batch')
|
|
159
86
|
|
|
160
87
|
// ============================================================================
|
|
161
|
-
// Cloudflare
|
|
88
|
+
// Cloudflare batch adapter (BatchProvider port)
|
|
162
89
|
// ============================================================================
|
|
163
90
|
|
|
164
|
-
/**
|
|
165
|
-
* Cloudflare batch adapter
|
|
166
|
-
*
|
|
167
|
-
* Note: Cloudflare's AI Gateway doesn't have a native batch API like OpenAI/Anthropic.
|
|
168
|
-
* This adapter implements batch processing by:
|
|
169
|
-
* 1. Sending requests concurrently through the gateway
|
|
170
|
-
* 2. Utilizing Cloudflare's caching and rate limiting
|
|
171
|
-
* 3. Tracking job state locally (or in D1/KV for production)
|
|
172
|
-
*
|
|
173
|
-
* For true async batch processing, consider using Cloudflare Queues + Workers.
|
|
174
|
-
*/
|
|
175
91
|
const cloudflareAdapter: BatchAdapter = {
|
|
176
92
|
async submit(items: BatchItem[], options: BatchQueueOptions): Promise<BatchSubmitResult> {
|
|
177
93
|
const config = getConfig()
|
|
178
|
-
const jobId = `cf_batch_${++jobCounter}_${Date.now()}`
|
|
179
94
|
const model = options.model || 'mistral/mistral-7b-instruct-v0.1'
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
95
|
+
const { id, state } = jobs.create(items, options)
|
|
96
|
+
|
|
97
|
+
const completion = (async () => {
|
|
98
|
+
state.status = 'in_progress'
|
|
99
|
+
const results = await processConcurrently(
|
|
100
|
+
items,
|
|
101
|
+
(item) => processCloudflareItem(item, config, model),
|
|
102
|
+
{
|
|
103
|
+
concurrency: 10,
|
|
104
|
+
onWaveComplete: (partial) => {
|
|
105
|
+
state.results = partial
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
state.results = results
|
|
110
|
+
state.status = results.every((r) => r.status === 'completed') ? 'completed' : 'failed'
|
|
111
|
+
state.completedAt = new Date()
|
|
112
|
+
return results
|
|
113
|
+
})()
|
|
192
114
|
|
|
193
115
|
const job: BatchJob = {
|
|
194
|
-
id
|
|
116
|
+
id,
|
|
195
117
|
provider: 'cloudflare',
|
|
196
118
|
status: 'pending',
|
|
197
119
|
totalItems: items.length,
|
|
198
120
|
completedItems: 0,
|
|
199
121
|
failedItems: 0,
|
|
200
|
-
createdAt:
|
|
201
|
-
webhookUrl: options.webhookUrl,
|
|
122
|
+
createdAt: state.createdAt,
|
|
123
|
+
...(options.webhookUrl !== undefined && { webhookUrl: options.webhookUrl }),
|
|
202
124
|
}
|
|
203
125
|
|
|
204
126
|
return { job, completion }
|
|
205
127
|
},
|
|
206
128
|
|
|
207
129
|
async getStatus(batchId: string): Promise<BatchJob> {
|
|
208
|
-
|
|
209
|
-
if (!job) {
|
|
210
|
-
throw new Error(`Batch not found: ${batchId}`)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const completedItems = job.results.filter((r) => r.status === 'completed').length
|
|
214
|
-
const failedItems = job.results.filter((r) => r.status === 'failed').length
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
id: batchId,
|
|
218
|
-
provider: 'cloudflare',
|
|
219
|
-
status: job.status,
|
|
220
|
-
totalItems: job.items.length,
|
|
221
|
-
completedItems,
|
|
222
|
-
failedItems,
|
|
223
|
-
createdAt: job.createdAt,
|
|
224
|
-
completedAt: job.completedAt,
|
|
225
|
-
}
|
|
130
|
+
return jobs.snapshot(batchId, 'cloudflare')
|
|
226
131
|
},
|
|
227
132
|
|
|
228
133
|
async cancel(batchId: string): Promise<void> {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
job.status = 'cancelled'
|
|
134
|
+
if (jobs.has(batchId)) {
|
|
135
|
+
jobs.get(batchId).status = 'cancelled'
|
|
232
136
|
}
|
|
233
137
|
},
|
|
234
138
|
|
|
235
139
|
async getResults(batchId: string): Promise<BatchResult[]> {
|
|
236
|
-
|
|
237
|
-
if (!job) {
|
|
238
|
-
throw new Error(`Batch not found: ${batchId}`)
|
|
239
|
-
}
|
|
240
|
-
return job.results
|
|
140
|
+
return jobs.get(batchId).results
|
|
241
141
|
},
|
|
242
142
|
|
|
243
143
|
async waitForCompletion(batchId: string, pollInterval = 1000): Promise<BatchResult[]> {
|
|
244
|
-
|
|
245
|
-
if (!job) {
|
|
246
|
-
throw new Error(`Batch not found: ${batchId}`)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
while (job.status !== 'completed' && job.status !== 'failed' && job.status !== 'cancelled') {
|
|
250
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval))
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return job.results
|
|
144
|
+
return jobs.waitForCompletion(batchId, pollInterval)
|
|
254
145
|
},
|
|
255
146
|
}
|
|
256
147
|
|
|
257
148
|
// ============================================================================
|
|
258
|
-
//
|
|
149
|
+
// Per-item processing
|
|
259
150
|
// ============================================================================
|
|
260
151
|
|
|
261
|
-
async function processCloudflareRequests(
|
|
262
|
-
jobId: string,
|
|
263
|
-
items: BatchItem[],
|
|
264
|
-
config: { accountId: string; gatewayId: string; apiToken: string },
|
|
265
|
-
model: string,
|
|
266
|
-
options: BatchQueueOptions
|
|
267
|
-
): Promise<BatchResult[]> {
|
|
268
|
-
const job = pendingJobs.get(jobId)
|
|
269
|
-
if (!job) {
|
|
270
|
-
throw new Error(`Job not found: ${jobId}`)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
job.status = 'in_progress'
|
|
274
|
-
|
|
275
|
-
// Process all requests concurrently with concurrency limit
|
|
276
|
-
const CONCURRENCY = 10
|
|
277
|
-
const results: BatchResult[] = []
|
|
278
|
-
|
|
279
|
-
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
280
|
-
const batch = items.slice(i, i + CONCURRENCY)
|
|
281
|
-
|
|
282
|
-
const batchResults = await Promise.all(
|
|
283
|
-
batch.map(async (item) => {
|
|
284
|
-
try {
|
|
285
|
-
const result = await processCloudflareItem(item, config, model)
|
|
286
|
-
return result
|
|
287
|
-
} catch (error) {
|
|
288
|
-
return {
|
|
289
|
-
id: item.id,
|
|
290
|
-
customId: item.id,
|
|
291
|
-
status: 'failed' as const,
|
|
292
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
})
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
results.push(...batchResults)
|
|
299
|
-
job.results = results
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
job.status = results.every((r) => r.status === 'completed') ? 'completed' : 'failed'
|
|
303
|
-
job.completedAt = new Date()
|
|
304
|
-
|
|
305
|
-
return results
|
|
306
|
-
}
|
|
307
|
-
|
|
308
152
|
async function processCloudflareItem(
|
|
309
153
|
item: BatchItem,
|
|
310
|
-
config:
|
|
154
|
+
config: CloudflareConfig,
|
|
311
155
|
model: string
|
|
312
156
|
): Promise<BatchResult> {
|
|
313
|
-
// Route through AI Gateway
|
|
314
157
|
const gatewayUrl = `https://gateway.ai.cloudflare.com/v1/${config.accountId}/${config.gatewayId}`
|
|
315
|
-
|
|
316
|
-
// Determine provider from model
|
|
317
|
-
let provider = 'workers-ai'
|
|
318
|
-
let endpoint = ''
|
|
319
|
-
|
|
320
|
-
if (model.startsWith('openai/') || model.startsWith('gpt-')) {
|
|
321
|
-
provider = 'openai'
|
|
322
|
-
endpoint = '/chat/completions'
|
|
323
|
-
} else if (model.startsWith('anthropic/') || model.startsWith('claude-')) {
|
|
324
|
-
provider = 'anthropic'
|
|
325
|
-
endpoint = '/messages'
|
|
326
|
-
} else if (model.startsWith('@cf/') || model.startsWith('workers-ai/')) {
|
|
327
|
-
provider = 'workers-ai'
|
|
328
|
-
endpoint = `/ai/run/${model.replace('workers-ai/', '').replace('@cf/', '')}`
|
|
329
|
-
} else {
|
|
330
|
-
// Default to OpenAI-compatible
|
|
331
|
-
provider = 'openai'
|
|
332
|
-
endpoint = '/chat/completions'
|
|
333
|
-
}
|
|
334
|
-
|
|
158
|
+
const { provider, endpoint } = routeForModel(model)
|
|
335
159
|
const url = `${gatewayUrl}/${provider}${endpoint}`
|
|
336
160
|
|
|
337
161
|
const messages = [
|
|
@@ -343,12 +167,8 @@ async function processCloudflareItem(
|
|
|
343
167
|
model: model.replace(`${provider}/`, ''),
|
|
344
168
|
messages,
|
|
345
169
|
max_tokens: item.options?.maxTokens || 4096,
|
|
346
|
-
temperature: item.options
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// Add JSON mode if schema is provided
|
|
350
|
-
if (item.schema) {
|
|
351
|
-
body.response_format = { type: 'json_object' }
|
|
170
|
+
...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
|
|
171
|
+
...(item.schema && { response_format: { type: 'json_object' } }),
|
|
352
172
|
}
|
|
353
173
|
|
|
354
174
|
const response = await fetch(url, {
|
|
@@ -365,55 +185,52 @@ async function processCloudflareItem(
|
|
|
365
185
|
throw new Error(`Cloudflare Gateway error: ${response.status} ${error}`)
|
|
366
186
|
}
|
|
367
187
|
|
|
368
|
-
const data = await response.json() as {
|
|
188
|
+
const data = (await response.json()) as {
|
|
369
189
|
choices?: Array<{ message: { content: string } }>
|
|
370
190
|
content?: Array<{ text: string }>
|
|
371
191
|
response?: string
|
|
372
192
|
usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }
|
|
373
193
|
}
|
|
374
194
|
|
|
375
|
-
// Extract content based on
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (data.choices?.[0]?.message?.content) {
|
|
379
|
-
// OpenAI format
|
|
380
|
-
content = data.choices[0].message.content
|
|
381
|
-
} else if (data.content?.[0]?.text) {
|
|
382
|
-
// Anthropic format
|
|
383
|
-
content = data.content[0].text
|
|
384
|
-
} else if (data.response) {
|
|
385
|
-
// Workers AI format
|
|
386
|
-
content = data.response
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
let result: unknown = content
|
|
390
|
-
|
|
391
|
-
// Try to parse JSON if schema was provided
|
|
392
|
-
if (item.schema && content) {
|
|
393
|
-
try {
|
|
394
|
-
result = JSON.parse(content)
|
|
395
|
-
} catch {
|
|
396
|
-
// Keep as string
|
|
397
|
-
}
|
|
398
|
-
}
|
|
195
|
+
// Extract content based on which downstream provider answered.
|
|
196
|
+
const content =
|
|
197
|
+
data.choices?.[0]?.message?.content ?? data.content?.[0]?.text ?? data.response ?? undefined
|
|
399
198
|
|
|
400
199
|
return {
|
|
401
200
|
id: item.id,
|
|
402
201
|
customId: item.id,
|
|
403
202
|
status: 'completed',
|
|
404
|
-
result,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
203
|
+
result: tryParseJson(content, !!item.schema),
|
|
204
|
+
...(data.usage && {
|
|
205
|
+
usage: {
|
|
206
|
+
promptTokens: data.usage.prompt_tokens,
|
|
207
|
+
completionTokens: data.usage.completion_tokens,
|
|
208
|
+
totalTokens: data.usage.total_tokens,
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Map a model id to the AI Gateway provider segment + endpoint path. */
|
|
215
|
+
function routeForModel(model: string): { provider: string; endpoint: string } {
|
|
216
|
+
if (model.startsWith('openai/') || model.startsWith('gpt-')) {
|
|
217
|
+
return { provider: 'openai', endpoint: '/chat/completions' }
|
|
218
|
+
}
|
|
219
|
+
if (model.startsWith('anthropic/') || model.startsWith('claude-')) {
|
|
220
|
+
return { provider: 'anthropic', endpoint: '/messages' }
|
|
221
|
+
}
|
|
222
|
+
if (model.startsWith('@cf/') || model.startsWith('workers-ai/')) {
|
|
223
|
+
return {
|
|
224
|
+
provider: 'workers-ai',
|
|
225
|
+
endpoint: `/ai/run/${model.replace('workers-ai/', '').replace('@cf/', '')}`,
|
|
226
|
+
}
|
|
412
227
|
}
|
|
228
|
+
// Default: assume an OpenAI-compatible downstream.
|
|
229
|
+
return { provider: 'openai', endpoint: '/chat/completions' }
|
|
413
230
|
}
|
|
414
231
|
|
|
415
232
|
// ============================================================================
|
|
416
|
-
// Register
|
|
233
|
+
// Register adapter
|
|
417
234
|
// ============================================================================
|
|
418
235
|
|
|
419
236
|
registerBatchAdapter('cloudflare', cloudflareAdapter)
|