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.
Files changed (284) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +90 -1
  3. package/README.md +38 -0
  4. package/dist/ai-promise.d.ts +3 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +135 -64
  7. package/dist/ai-promise.js.map +1 -1
  8. package/dist/ai-schemas.d.ts +56 -0
  9. package/dist/ai-schemas.d.ts.map +1 -0
  10. package/dist/ai-schemas.js +53 -0
  11. package/dist/ai-schemas.js.map +1 -0
  12. package/dist/ai.d.ts +16 -242
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +51 -858
  15. package/dist/ai.js.map +1 -1
  16. package/dist/batch/anthropic.d.ts +6 -4
  17. package/dist/batch/anthropic.d.ts.map +1 -1
  18. package/dist/batch/anthropic.js +83 -145
  19. package/dist/batch/anthropic.js.map +1 -1
  20. package/dist/batch/bedrock.d.ts +8 -30
  21. package/dist/batch/bedrock.d.ts.map +1 -1
  22. package/dist/batch/bedrock.js +155 -338
  23. package/dist/batch/bedrock.js.map +1 -1
  24. package/dist/batch/cloudflare.d.ts +8 -20
  25. package/dist/batch/cloudflare.d.ts.map +1 -1
  26. package/dist/batch/cloudflare.js +68 -189
  27. package/dist/batch/cloudflare.js.map +1 -1
  28. package/dist/batch/google.d.ts +6 -20
  29. package/dist/batch/google.d.ts.map +1 -1
  30. package/dist/batch/google.js +70 -238
  31. package/dist/batch/google.js.map +1 -1
  32. package/dist/batch/index.d.ts +4 -1
  33. package/dist/batch/index.d.ts.map +1 -1
  34. package/dist/batch/index.js +4 -1
  35. package/dist/batch/index.js.map +1 -1
  36. package/dist/batch/memory.d.ts +1 -1
  37. package/dist/batch/memory.d.ts.map +1 -1
  38. package/dist/batch/memory.js +14 -10
  39. package/dist/batch/memory.js.map +1 -1
  40. package/dist/batch/openai.d.ts +11 -14
  41. package/dist/batch/openai.d.ts.map +1 -1
  42. package/dist/batch/openai.js +52 -156
  43. package/dist/batch/openai.js.map +1 -1
  44. package/dist/batch/provider.d.ts +111 -0
  45. package/dist/batch/provider.d.ts.map +1 -0
  46. package/dist/batch/provider.js +233 -0
  47. package/dist/batch/provider.js.map +1 -0
  48. package/dist/batch-map.d.ts.map +1 -1
  49. package/dist/batch-map.js +23 -17
  50. package/dist/batch-map.js.map +1 -1
  51. package/dist/batch-queue.d.ts +65 -0
  52. package/dist/batch-queue.d.ts.map +1 -1
  53. package/dist/batch-queue.js +169 -14
  54. package/dist/batch-queue.js.map +1 -1
  55. package/dist/budget.d.ts.map +1 -1
  56. package/dist/budget.js +27 -14
  57. package/dist/budget.js.map +1 -1
  58. package/dist/cache.d.ts +23 -0
  59. package/dist/cache.d.ts.map +1 -1
  60. package/dist/cache.js +36 -15
  61. package/dist/cache.js.map +1 -1
  62. package/dist/context.d.ts +26 -8
  63. package/dist/context.d.ts.map +1 -1
  64. package/dist/context.js +64 -62
  65. package/dist/context.js.map +1 -1
  66. package/dist/digital-objects-registry.d.ts +229 -0
  67. package/dist/digital-objects-registry.d.ts.map +1 -0
  68. package/dist/digital-objects-registry.js +617 -0
  69. package/dist/digital-objects-registry.js.map +1 -0
  70. package/dist/embeddings.d.ts +2 -2
  71. package/dist/embeddings.d.ts.map +1 -1
  72. package/dist/errors.d.ts +22 -0
  73. package/dist/errors.d.ts.map +1 -0
  74. package/dist/errors.js +35 -0
  75. package/dist/errors.js.map +1 -0
  76. package/dist/eval/runner.d.ts +8 -0
  77. package/dist/eval/runner.d.ts.map +1 -1
  78. package/dist/eval/runner.js +41 -35
  79. package/dist/eval/runner.js.map +1 -1
  80. package/dist/eval-log/in-memory.d.ts +34 -0
  81. package/dist/eval-log/in-memory.d.ts.map +1 -0
  82. package/dist/eval-log/in-memory.js +84 -0
  83. package/dist/eval-log/in-memory.js.map +1 -0
  84. package/dist/eval-log/index.d.ts +29 -0
  85. package/dist/eval-log/index.d.ts.map +1 -0
  86. package/dist/eval-log/index.js +39 -0
  87. package/dist/eval-log/index.js.map +1 -0
  88. package/dist/eval-log/types.d.ts +101 -0
  89. package/dist/eval-log/types.d.ts.map +1 -0
  90. package/dist/eval-log/types.js +16 -0
  91. package/dist/eval-log/types.js.map +1 -0
  92. package/dist/function-registry.d.ts +176 -0
  93. package/dist/function-registry.d.ts.map +1 -0
  94. package/dist/function-registry.js +685 -0
  95. package/dist/function-registry.js.map +1 -0
  96. package/dist/generate.d.ts +9 -3
  97. package/dist/generate.d.ts.map +1 -1
  98. package/dist/generate.js +18 -18
  99. package/dist/generate.js.map +1 -1
  100. package/dist/index.d.ts +18 -11
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +35 -18
  103. package/dist/index.js.map +1 -1
  104. package/dist/logger.d.ts +118 -0
  105. package/dist/logger.d.ts.map +1 -0
  106. package/dist/logger.js +187 -0
  107. package/dist/logger.js.map +1 -0
  108. package/dist/middleware/budget.d.ts +84 -0
  109. package/dist/middleware/budget.d.ts.map +1 -0
  110. package/dist/middleware/budget.js +110 -0
  111. package/dist/middleware/budget.js.map +1 -0
  112. package/dist/middleware/cache.d.ts +103 -0
  113. package/dist/middleware/cache.d.ts.map +1 -0
  114. package/dist/middleware/cache.js +228 -0
  115. package/dist/middleware/cache.js.map +1 -0
  116. package/dist/middleware/embed-cache.d.ts +99 -0
  117. package/dist/middleware/embed-cache.d.ts.map +1 -0
  118. package/dist/middleware/embed-cache.js +128 -0
  119. package/dist/middleware/embed-cache.js.map +1 -0
  120. package/dist/middleware/index.d.ts +11 -0
  121. package/dist/middleware/index.d.ts.map +1 -0
  122. package/dist/middleware/index.js +11 -0
  123. package/dist/middleware/index.js.map +1 -0
  124. package/dist/middleware/trace.d.ts +103 -0
  125. package/dist/middleware/trace.d.ts.map +1 -0
  126. package/dist/middleware/trace.js +176 -0
  127. package/dist/middleware/trace.js.map +1 -0
  128. package/dist/primitives.d.ts +120 -1
  129. package/dist/primitives.d.ts.map +1 -1
  130. package/dist/primitives.js +398 -26
  131. package/dist/primitives.js.map +1 -1
  132. package/dist/retry.d.ts +66 -1
  133. package/dist/retry.d.ts.map +1 -1
  134. package/dist/retry.js +115 -8
  135. package/dist/retry.js.map +1 -1
  136. package/dist/sandbox.d.ts +36 -0
  137. package/dist/sandbox.d.ts.map +1 -0
  138. package/dist/sandbox.js +44 -0
  139. package/dist/sandbox.js.map +1 -0
  140. package/dist/schema.js +2 -2
  141. package/dist/schema.js.map +1 -1
  142. package/dist/telemetry.d.ts +128 -0
  143. package/dist/telemetry.d.ts.map +1 -0
  144. package/dist/telemetry.js +285 -0
  145. package/dist/telemetry.js.map +1 -0
  146. package/dist/template.d.ts.map +1 -1
  147. package/dist/template.js +6 -1
  148. package/dist/template.js.map +1 -1
  149. package/dist/tool-orchestration.d.ts +66 -4
  150. package/dist/tool-orchestration.d.ts.map +1 -1
  151. package/dist/tool-orchestration.js +123 -23
  152. package/dist/tool-orchestration.js.map +1 -1
  153. package/dist/type-guards.d.ts +28 -0
  154. package/dist/type-guards.d.ts.map +1 -0
  155. package/dist/type-guards.js +29 -0
  156. package/dist/type-guards.js.map +1 -0
  157. package/dist/types.d.ts +155 -19
  158. package/dist/types.d.ts.map +1 -1
  159. package/dist/types.js +36 -1
  160. package/dist/types.js.map +1 -1
  161. package/dist/wrap-for-v3.d.ts +80 -0
  162. package/dist/wrap-for-v3.d.ts.map +1 -0
  163. package/dist/wrap-for-v3.js +89 -0
  164. package/dist/wrap-for-v3.js.map +1 -0
  165. package/examples/00-quickstart.ts +232 -0
  166. package/examples/01-rag-chatbot.ts +212 -0
  167. package/examples/02-multi-agent-research.ts +290 -0
  168. package/examples/03-email-classification.ts +379 -0
  169. package/examples/04-content-moderation.ts +400 -0
  170. package/examples/05-document-extraction.ts +455 -0
  171. package/examples/06-streaming-chat-nextjs.ts +437 -0
  172. package/examples/07-cloudflare-worker.ts +483 -0
  173. package/examples/08-batch-processing.ts +491 -0
  174. package/examples/09-budget-constrained.ts +527 -0
  175. package/examples/10-tool-orchestration.ts +565 -0
  176. package/examples/11-retry-resilience.ts +403 -0
  177. package/examples/12-caching-strategies.ts +422 -0
  178. package/examples/README.md +145 -0
  179. package/package.json +29 -25
  180. package/src/ai-promise.ts +226 -140
  181. package/src/ai-schemas.ts +122 -0
  182. package/src/ai.ts +71 -1176
  183. package/src/batch/anthropic.ts +96 -161
  184. package/src/batch/bedrock.ts +203 -454
  185. package/src/batch/cloudflare.ts +99 -282
  186. package/src/batch/google.ts +91 -297
  187. package/src/batch/index.ts +4 -1
  188. package/src/batch/memory.ts +15 -10
  189. package/src/batch/openai.ts +65 -193
  190. package/src/batch/provider.ts +336 -0
  191. package/src/batch-map.ts +29 -24
  192. package/src/batch-queue.ts +200 -11
  193. package/src/budget.ts +31 -18
  194. package/src/cache.ts +45 -17
  195. package/src/context.ts +106 -77
  196. package/src/digital-objects-registry.ts +750 -0
  197. package/src/errors.ts +37 -0
  198. package/src/eval/runner.ts +60 -36
  199. package/src/eval-log/in-memory.ts +90 -0
  200. package/src/eval-log/index.ts +46 -0
  201. package/src/eval-log/types.ts +110 -0
  202. package/src/function-registry.ts +874 -0
  203. package/src/generate.ts +33 -28
  204. package/src/index.ts +122 -21
  205. package/src/logger.ts +232 -0
  206. package/src/middleware/budget.ts +171 -0
  207. package/src/middleware/cache.ts +299 -0
  208. package/src/middleware/embed-cache.ts +195 -0
  209. package/src/middleware/index.ts +23 -0
  210. package/src/middleware/trace.ts +248 -0
  211. package/src/primitives.ts +589 -62
  212. package/src/retry.ts +144 -18
  213. package/src/sandbox.ts +52 -0
  214. package/src/schema.ts +8 -8
  215. package/src/telemetry.ts +403 -0
  216. package/src/template.ts +8 -4
  217. package/src/tool-orchestration.ts +213 -48
  218. package/src/type-guards.ts +31 -0
  219. package/src/types.ts +186 -27
  220. package/src/wrap-for-v3.ts +105 -0
  221. package/test/ai-promise.test.ts +1080 -0
  222. package/test/ai-proxy.test.ts +1 -1
  223. package/test/batch-autosubmit-errors.test.ts +49 -37
  224. package/test/batch-blog-posts.test.ts +87 -129
  225. package/test/core-functions.test.ts +183 -579
  226. package/test/decide.test.ts +154 -322
  227. package/test/define.test.ts +211 -8
  228. package/test/digital-objects-registry.test.ts +760 -0
  229. package/test/embedding-cache-middleware.test.ts +140 -0
  230. package/test/fill-template.test.ts +89 -0
  231. package/test/generate-core.test.ts +140 -229
  232. package/test/implicit-batch.test.ts +22 -65
  233. package/test/retry-policy-integration.test.ts +117 -0
  234. package/test/sandbox-execution.test.ts +155 -0
  235. package/test/schema.test.ts +55 -19
  236. package/test/template.test.ts +1164 -0
  237. package/test/tool-orchestration.test.ts +270 -0
  238. package/test/wrap-for-v3.test.ts +612 -0
  239. package/vitest.config.js +6 -0
  240. package/vitest.config.ts +20 -0
  241. package/LICENSE +0 -21
  242. package/dist/rpc/auth.d.ts +0 -69
  243. package/dist/rpc/auth.d.ts.map +0 -1
  244. package/dist/rpc/auth.js +0 -136
  245. package/dist/rpc/auth.js.map +0 -1
  246. package/dist/rpc/client.d.ts +0 -62
  247. package/dist/rpc/client.d.ts.map +0 -1
  248. package/dist/rpc/client.js +0 -103
  249. package/dist/rpc/client.js.map +0 -1
  250. package/dist/rpc/deferred.d.ts +0 -60
  251. package/dist/rpc/deferred.d.ts.map +0 -1
  252. package/dist/rpc/deferred.js +0 -96
  253. package/dist/rpc/deferred.js.map +0 -1
  254. package/dist/rpc/index.d.ts +0 -22
  255. package/dist/rpc/index.d.ts.map +0 -1
  256. package/dist/rpc/index.js +0 -38
  257. package/dist/rpc/index.js.map +0 -1
  258. package/dist/rpc/local.d.ts +0 -42
  259. package/dist/rpc/local.d.ts.map +0 -1
  260. package/dist/rpc/local.js +0 -50
  261. package/dist/rpc/local.js.map +0 -1
  262. package/dist/rpc/server.d.ts +0 -165
  263. package/dist/rpc/server.d.ts.map +0 -1
  264. package/dist/rpc/server.js +0 -405
  265. package/dist/rpc/server.js.map +0 -1
  266. package/dist/rpc/session.d.ts +0 -32
  267. package/dist/rpc/session.d.ts.map +0 -1
  268. package/dist/rpc/session.js +0 -43
  269. package/dist/rpc/session.js.map +0 -1
  270. package/dist/rpc/transport.d.ts +0 -306
  271. package/dist/rpc/transport.d.ts.map +0 -1
  272. package/dist/rpc/transport.js +0 -731
  273. package/dist/rpc/transport.js.map +0 -1
  274. package/src/batch/anthropic.js +0 -256
  275. package/src/batch/bedrock.js +0 -584
  276. package/src/batch/cloudflare.js +0 -287
  277. package/src/batch/google.js +0 -359
  278. package/src/batch/index.js +0 -30
  279. package/src/batch/memory.js +0 -187
  280. package/src/batch/openai.js +0 -402
  281. package/src/eval/index.js +0 -7
  282. package/src/eval/models.js +0 -119
  283. package/src/eval/runner.js +0 -147
  284. package/test/schema.test.js +0 -96
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Cloudflare AI Gateway Batch Adapter
2
+ * Cloudflare AI Gateway Adapter
3
3
  *
4
- * Implements batch processing using Cloudflare AI Gateway's batch endpoint.
5
- * Cloudflare's batch API is newer but integrates well with Workers.
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
- * Note: Cloudflare's batch API is evolving. This adapter implements
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
- type BatchStatus,
24
- } from '../batch-queue.js'
25
- import { schema as convertSchema } from '../schema.js'
26
+ } from './provider.js'
26
27
 
27
28
  // ============================================================================
28
- // Types
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
- if (options.baseUrl) baseUrl = options.baseUrl
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(): { accountId: string; gatewayId: string; apiToken: string } {
98
- const accId = accountId || process.env.CLOUDFLARE_ACCOUNT_ID
99
- const gwId = gatewayId || process.env.CLOUDFLARE_AI_GATEWAY_ID || process.env.AI_GATEWAY_ID
100
- const token = apiToken || process.env.CLOUDFLARE_API_TOKEN
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('Cloudflare account ID not configured. Set CLOUDFLARE_ACCOUNT_ID or call configureCloudflare()')
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('Cloudflare AI Gateway ID not configured. Set CLOUDFLARE_AI_GATEWAY_ID or call configureCloudflare()')
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('Cloudflare API token not configured. Set CLOUDFLARE_API_TOKEN or call configureCloudflare()')
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
- // In-memory job storage (for polling)
82
+ // Local job tracking
147
83
  // ============================================================================
148
84
 
149
- const pendingJobs = new Map<string, {
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 Batch Adapter
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
- // Store job state
182
- pendingJobs.set(jobId, {
183
- items,
184
- options,
185
- results: [],
186
- status: 'pending',
187
- createdAt: new Date(),
188
- })
189
-
190
- // Process requests concurrently (Cloudflare handles rate limiting)
191
- const completion = processCloudflareRequests(jobId, items, config, model, options)
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: jobId,
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: new Date(),
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
- const job = pendingJobs.get(batchId)
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
- const job = pendingJobs.get(batchId)
230
- if (job) {
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
- const job = pendingJobs.get(batchId)
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
- const job = pendingJobs.get(batchId)
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
- // Processing
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: { accountId: string; gatewayId: string; apiToken: string },
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?.temperature,
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 response format
376
- let content: string | undefined
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
- usage: data.usage
406
- ? {
407
- promptTokens: data.usage.prompt_tokens,
408
- completionTokens: data.usage.completion_tokens,
409
- totalTokens: data.usage.total_tokens,
410
- }
411
- : undefined,
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 Adapter
233
+ // Register adapter
417
234
  // ============================================================================
418
235
 
419
236
  registerBatchAdapter('cloudflare', cloudflareAdapter)