ai-functions 0.2.19 → 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.
Files changed (227) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-test.log +105 -0
  3. package/README.md +232 -37
  4. package/TODO.md +138 -0
  5. package/dist/ai-promise.d.ts +219 -0
  6. package/dist/ai-promise.d.ts.map +1 -0
  7. package/dist/ai-promise.js +610 -0
  8. package/dist/ai-promise.js.map +1 -0
  9. package/dist/ai.d.ts +285 -0
  10. package/dist/ai.d.ts.map +1 -0
  11. package/dist/ai.js +842 -0
  12. package/dist/ai.js.map +1 -0
  13. package/dist/batch/anthropic.d.ts +23 -0
  14. package/dist/batch/anthropic.d.ts.map +1 -0
  15. package/dist/batch/anthropic.js +257 -0
  16. package/dist/batch/anthropic.js.map +1 -0
  17. package/dist/batch/bedrock.d.ts +64 -0
  18. package/dist/batch/bedrock.d.ts.map +1 -0
  19. package/dist/batch/bedrock.js +586 -0
  20. package/dist/batch/bedrock.js.map +1 -0
  21. package/dist/batch/cloudflare.d.ts +37 -0
  22. package/dist/batch/cloudflare.d.ts.map +1 -0
  23. package/dist/batch/cloudflare.js +289 -0
  24. package/dist/batch/cloudflare.js.map +1 -0
  25. package/dist/batch/google.d.ts +41 -0
  26. package/dist/batch/google.d.ts.map +1 -0
  27. package/dist/batch/google.js +360 -0
  28. package/dist/batch/google.js.map +1 -0
  29. package/dist/batch/index.d.ts +31 -0
  30. package/dist/batch/index.d.ts.map +1 -0
  31. package/dist/batch/index.js +31 -0
  32. package/dist/batch/index.js.map +1 -0
  33. package/dist/batch/memory.d.ts +44 -0
  34. package/dist/batch/memory.d.ts.map +1 -0
  35. package/dist/batch/memory.js +188 -0
  36. package/dist/batch/memory.js.map +1 -0
  37. package/dist/batch/openai.d.ts +37 -0
  38. package/dist/batch/openai.d.ts.map +1 -0
  39. package/dist/batch/openai.js +403 -0
  40. package/dist/batch/openai.js.map +1 -0
  41. package/dist/batch-map.d.ts +125 -0
  42. package/dist/batch-map.d.ts.map +1 -0
  43. package/dist/batch-map.js +406 -0
  44. package/dist/batch-map.js.map +1 -0
  45. package/dist/batch-queue.d.ts +273 -0
  46. package/dist/batch-queue.d.ts.map +1 -0
  47. package/dist/batch-queue.js +271 -0
  48. package/dist/batch-queue.js.map +1 -0
  49. package/dist/context.d.ts +133 -0
  50. package/dist/context.d.ts.map +1 -0
  51. package/dist/context.js +267 -0
  52. package/dist/context.js.map +1 -0
  53. package/dist/embeddings.d.ts +123 -0
  54. package/dist/embeddings.d.ts.map +1 -0
  55. package/dist/embeddings.js +170 -0
  56. package/dist/embeddings.js.map +1 -0
  57. package/dist/eval/index.d.ts +8 -0
  58. package/dist/eval/index.d.ts.map +1 -0
  59. package/dist/eval/index.js +8 -0
  60. package/dist/eval/index.js.map +1 -0
  61. package/dist/eval/models.d.ts +66 -0
  62. package/dist/eval/models.d.ts.map +1 -0
  63. package/dist/eval/models.js +120 -0
  64. package/dist/eval/models.js.map +1 -0
  65. package/dist/eval/runner.d.ts +64 -0
  66. package/dist/eval/runner.d.ts.map +1 -0
  67. package/dist/eval/runner.js +148 -0
  68. package/dist/eval/runner.js.map +1 -0
  69. package/dist/generate.d.ts +168 -0
  70. package/dist/generate.d.ts.map +1 -0
  71. package/dist/generate.js +174 -0
  72. package/dist/generate.js.map +1 -0
  73. package/dist/index.d.ts +30 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +54 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/primitives.d.ts +292 -0
  78. package/dist/primitives.d.ts.map +1 -0
  79. package/dist/primitives.js +471 -0
  80. package/dist/primitives.js.map +1 -0
  81. package/dist/providers/cloudflare.d.ts +9 -0
  82. package/dist/providers/cloudflare.d.ts.map +1 -0
  83. package/dist/providers/cloudflare.js +9 -0
  84. package/dist/providers/cloudflare.js.map +1 -0
  85. package/dist/providers/index.d.ts +9 -0
  86. package/dist/providers/index.d.ts.map +1 -0
  87. package/dist/providers/index.js +9 -0
  88. package/dist/providers/index.js.map +1 -0
  89. package/dist/schema.d.ts +54 -0
  90. package/dist/schema.d.ts.map +1 -0
  91. package/dist/schema.js +109 -0
  92. package/dist/schema.js.map +1 -0
  93. package/dist/template.d.ts +73 -0
  94. package/dist/template.d.ts.map +1 -0
  95. package/dist/template.js +129 -0
  96. package/dist/template.js.map +1 -0
  97. package/dist/types.d.ts +481 -0
  98. package/dist/types.d.ts.map +1 -0
  99. package/dist/types.js +5 -0
  100. package/dist/types.js.map +1 -0
  101. package/evalite.config.ts +19 -0
  102. package/evals/README.md +212 -0
  103. package/evals/classification.eval.ts +108 -0
  104. package/evals/marketing.eval.ts +370 -0
  105. package/evals/math.eval.ts +94 -0
  106. package/evals/run-evals.ts +166 -0
  107. package/evals/structured-output.eval.ts +143 -0
  108. package/evals/writing.eval.ts +117 -0
  109. package/examples/batch-blog-posts.ts +160 -0
  110. package/package.json +59 -43
  111. package/src/ai-promise.ts +784 -0
  112. package/src/ai.ts +1183 -0
  113. package/src/batch/anthropic.ts +375 -0
  114. package/src/batch/bedrock.ts +801 -0
  115. package/src/batch/cloudflare.ts +421 -0
  116. package/src/batch/google.ts +491 -0
  117. package/src/batch/index.ts +31 -0
  118. package/src/batch/memory.ts +253 -0
  119. package/src/batch/openai.ts +557 -0
  120. package/src/batch-map.ts +534 -0
  121. package/src/batch-queue.ts +493 -0
  122. package/src/context.ts +332 -0
  123. package/src/embeddings.ts +244 -0
  124. package/src/eval/index.ts +8 -0
  125. package/src/eval/models.ts +158 -0
  126. package/src/eval/runner.ts +217 -0
  127. package/src/generate.ts +245 -0
  128. package/src/index.ts +154 -0
  129. package/src/primitives.ts +612 -0
  130. package/src/providers/cloudflare.ts +15 -0
  131. package/src/providers/index.ts +14 -0
  132. package/src/schema.ts +147 -0
  133. package/src/template.ts +209 -0
  134. package/src/types.ts +540 -0
  135. package/test/README.md +105 -0
  136. package/test/ai-proxy.test.ts +192 -0
  137. package/test/async-iterators.test.ts +327 -0
  138. package/test/batch-background.test.ts +482 -0
  139. package/test/batch-blog-posts.test.ts +387 -0
  140. package/test/blog-generation.test.ts +510 -0
  141. package/test/browse-read.test.ts +611 -0
  142. package/test/core-functions.test.ts +694 -0
  143. package/test/decide.test.ts +393 -0
  144. package/test/define.test.ts +274 -0
  145. package/test/e2e-bedrock-manual.ts +163 -0
  146. package/test/e2e-bedrock.test.ts +191 -0
  147. package/test/e2e-flex-gateway.ts +157 -0
  148. package/test/e2e-flex-manual.ts +183 -0
  149. package/test/e2e-flex.test.ts +209 -0
  150. package/test/e2e-google-manual.ts +178 -0
  151. package/test/e2e-google.test.ts +216 -0
  152. package/test/embeddings.test.ts +284 -0
  153. package/test/evals/define-function.eval.test.ts +379 -0
  154. package/test/evals/primitives.eval.test.ts +384 -0
  155. package/test/function-types.test.ts +492 -0
  156. package/test/generate-core.test.ts +319 -0
  157. package/test/generate.test.ts +163 -0
  158. package/test/implicit-batch.test.ts +422 -0
  159. package/test/schema.test.ts +109 -0
  160. package/test/tagged-templates.test.ts +302 -0
  161. package/tsconfig.json +8 -6
  162. package/vitest.config.ts +42 -0
  163. package/LICENSE +0 -21
  164. package/db/cache.ts +0 -6
  165. package/db/mongo.ts +0 -75
  166. package/dist/mjs/db/cache.d.ts +0 -1
  167. package/dist/mjs/db/cache.js +0 -5
  168. package/dist/mjs/db/mongo.d.ts +0 -31
  169. package/dist/mjs/db/mongo.js +0 -48
  170. package/dist/mjs/examples/data.d.ts +0 -1105
  171. package/dist/mjs/examples/data.js +0 -1105
  172. package/dist/mjs/functions/ai.d.ts +0 -20
  173. package/dist/mjs/functions/ai.js +0 -83
  174. package/dist/mjs/functions/ai.test.d.ts +0 -1
  175. package/dist/mjs/functions/ai.test.js +0 -29
  176. package/dist/mjs/functions/gpt.d.ts +0 -4
  177. package/dist/mjs/functions/gpt.js +0 -10
  178. package/dist/mjs/functions/list.d.ts +0 -7
  179. package/dist/mjs/functions/list.js +0 -72
  180. package/dist/mjs/index.d.ts +0 -3
  181. package/dist/mjs/index.js +0 -3
  182. package/dist/mjs/queue/kafka.d.ts +0 -0
  183. package/dist/mjs/queue/kafka.js +0 -1
  184. package/dist/mjs/queue/memory.d.ts +0 -0
  185. package/dist/mjs/queue/memory.js +0 -1
  186. package/dist/mjs/queue/mongo.d.ts +0 -30
  187. package/dist/mjs/queue/mongo.js +0 -42
  188. package/dist/mjs/streams/kafka.d.ts +0 -0
  189. package/dist/mjs/streams/kafka.js +0 -1
  190. package/dist/mjs/streams/memory.d.ts +0 -0
  191. package/dist/mjs/streams/memory.js +0 -1
  192. package/dist/mjs/streams/mongo.d.ts +0 -0
  193. package/dist/mjs/streams/mongo.js +0 -1
  194. package/dist/mjs/streams/types.d.ts +0 -0
  195. package/dist/mjs/streams/types.js +0 -1
  196. package/dist/mjs/types.d.ts +0 -11
  197. package/dist/mjs/types.js +0 -1
  198. package/dist/mjs/utils/completion.d.ts +0 -9
  199. package/dist/mjs/utils/completion.js +0 -20
  200. package/dist/mjs/utils/schema.d.ts +0 -10
  201. package/dist/mjs/utils/schema.js +0 -72
  202. package/dist/mjs/utils/schema.test.d.ts +0 -1
  203. package/dist/mjs/utils/schema.test.js +0 -60
  204. package/dist/mjs/utils/state.d.ts +0 -1
  205. package/dist/mjs/utils/state.js +0 -19
  206. package/examples/data.ts +0 -1105
  207. package/fixup +0 -11
  208. package/functions/ai.test.ts +0 -41
  209. package/functions/ai.ts +0 -115
  210. package/functions/gpt.ts +0 -12
  211. package/functions/list.ts +0 -84
  212. package/index.ts +0 -3
  213. package/queue/kafka.ts +0 -0
  214. package/queue/memory.ts +0 -0
  215. package/queue/mongo.ts +0 -88
  216. package/streams/kafka.ts +0 -0
  217. package/streams/memory.ts +0 -0
  218. package/streams/mongo.ts +0 -0
  219. package/streams/types.ts +0 -0
  220. package/tsconfig-backup.json +0 -105
  221. package/tsconfig-base.json +0 -26
  222. package/tsconfig-cjs.json +0 -8
  223. package/types.ts +0 -12
  224. package/utils/completion.ts +0 -28
  225. package/utils/schema.test.ts +0 -69
  226. package/utils/schema.ts +0 -74
  227. package/utils/state.ts +0 -23
@@ -0,0 +1,491 @@
1
+ /**
2
+ * Google GenAI (Gemini) Adapter
3
+ *
4
+ * Implements processing using Google's Generative AI API (Gemini models).
5
+ * Google doesn't have a native batch API like OpenAI/Anthropic, so this
6
+ * implements concurrent processing for the flex tier.
7
+ *
8
+ * @see https://ai.google.dev/gemini-api/docs
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+
13
+ import {
14
+ registerBatchAdapter,
15
+ registerFlexAdapter,
16
+ type BatchAdapter,
17
+ type FlexAdapter,
18
+ type BatchItem,
19
+ type BatchJob,
20
+ type BatchQueueOptions,
21
+ type BatchResult,
22
+ type BatchSubmitResult,
23
+ type BatchStatus,
24
+ } from '../batch-queue.js'
25
+
26
+ // ============================================================================
27
+ // Types
28
+ // ============================================================================
29
+
30
+ interface GeminiMessage {
31
+ role: 'user' | 'model'
32
+ parts: Array<{ text: string }>
33
+ }
34
+
35
+ interface GeminiResponse {
36
+ candidates: Array<{
37
+ content: {
38
+ parts: Array<{ text: string }>
39
+ role: string
40
+ }
41
+ finishReason: string
42
+ safetyRatings?: Array<{
43
+ category: string
44
+ probability: string
45
+ }>
46
+ }>
47
+ usageMetadata?: {
48
+ promptTokenCount: number
49
+ candidatesTokenCount: number
50
+ totalTokenCount: number
51
+ }
52
+ }
53
+
54
+ // ============================================================================
55
+ // Google GenAI Client Configuration
56
+ // ============================================================================
57
+
58
+ let googleApiKey: string | undefined
59
+ let googleBaseUrl = 'https://generativelanguage.googleapis.com/v1beta'
60
+
61
+ // AI Gateway configuration (optional - for routing through Cloudflare AI Gateway)
62
+ let gatewayUrl: string | undefined
63
+ let gatewayToken: string | undefined
64
+
65
+ /**
66
+ * Configure the Google GenAI client
67
+ */
68
+ export function configureGoogleGenAI(options: {
69
+ apiKey?: string
70
+ baseUrl?: string
71
+ /** Optional: Cloudflare AI Gateway URL for routing requests */
72
+ gatewayUrl?: string
73
+ /** Optional: Cloudflare AI Gateway token */
74
+ gatewayToken?: string
75
+ }): void {
76
+ if (options.apiKey) googleApiKey = options.apiKey
77
+ if (options.baseUrl) googleBaseUrl = options.baseUrl
78
+ if (options.gatewayUrl) gatewayUrl = options.gatewayUrl
79
+ if (options.gatewayToken) gatewayToken = options.gatewayToken
80
+ }
81
+
82
+ function getConfig(): { apiKey: string; baseUrl: string; gatewayUrl?: string; gatewayToken?: string } {
83
+ // Check for AI Gateway configuration
84
+ const gwUrl = gatewayUrl || process.env.AI_GATEWAY_URL
85
+ const gwToken = gatewayToken || process.env.AI_GATEWAY_TOKEN
86
+
87
+ // If using gateway, we don't need a direct API key
88
+ if (gwUrl && gwToken) {
89
+ return {
90
+ apiKey: '',
91
+ baseUrl: googleBaseUrl,
92
+ gatewayUrl: gwUrl,
93
+ gatewayToken: gwToken,
94
+ }
95
+ }
96
+
97
+ const key = googleApiKey || process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY
98
+ if (!key) {
99
+ throw new Error(
100
+ 'Google API key not configured. Set GOOGLE_API_KEY or GEMINI_API_KEY, or use AI_GATEWAY_URL and AI_GATEWAY_TOKEN'
101
+ )
102
+ }
103
+ return { apiKey: key, baseUrl: googleBaseUrl, gatewayUrl: undefined, gatewayToken: undefined }
104
+ }
105
+
106
+ // ============================================================================
107
+ // In-memory job tracking
108
+ // ============================================================================
109
+
110
+ const pendingJobs = new Map<
111
+ string,
112
+ {
113
+ items: BatchItem[]
114
+ options: BatchQueueOptions
115
+ results: BatchResult[]
116
+ status: BatchStatus
117
+ createdAt: Date
118
+ completedAt?: Date
119
+ }
120
+ >()
121
+
122
+ let jobCounter = 0
123
+
124
+ // ============================================================================
125
+ // Google GenAI Batch Adapter
126
+ // ============================================================================
127
+
128
+ /**
129
+ * Google GenAI batch adapter
130
+ *
131
+ * Note: Google doesn't have a native batch API like OpenAI/Anthropic.
132
+ * This adapter implements batch processing via concurrent requests.
133
+ * For true async batch processing, consider using Google Cloud Batch
134
+ * with Vertex AI.
135
+ */
136
+ const googleAdapter: BatchAdapter = {
137
+ async submit(items: BatchItem[], options: BatchQueueOptions): Promise<BatchSubmitResult> {
138
+ const jobId = `google_batch_${++jobCounter}_${Date.now()}`
139
+ const model = options.model || 'gemini-2.0-flash'
140
+
141
+ // Store job state
142
+ pendingJobs.set(jobId, {
143
+ items,
144
+ options,
145
+ results: [],
146
+ status: 'pending',
147
+ createdAt: new Date(),
148
+ })
149
+
150
+ // Process requests concurrently
151
+ const completion = processGoogleRequestsConcurrently(jobId, items, model, options)
152
+
153
+ const job: BatchJob = {
154
+ id: jobId,
155
+ provider: 'google',
156
+ status: 'pending',
157
+ totalItems: items.length,
158
+ completedItems: 0,
159
+ failedItems: 0,
160
+ createdAt: new Date(),
161
+ webhookUrl: options.webhookUrl,
162
+ }
163
+
164
+ return { job, completion }
165
+ },
166
+
167
+ async getStatus(batchId: string): Promise<BatchJob> {
168
+ const job = pendingJobs.get(batchId)
169
+ if (!job) {
170
+ throw new Error(`Batch not found: ${batchId}`)
171
+ }
172
+
173
+ const completedItems = job.results.filter((r) => r.status === 'completed').length
174
+ const failedItems = job.results.filter((r) => r.status === 'failed').length
175
+
176
+ return {
177
+ id: batchId,
178
+ provider: 'google',
179
+ status: job.status,
180
+ totalItems: job.items.length,
181
+ completedItems,
182
+ failedItems,
183
+ createdAt: job.createdAt,
184
+ completedAt: job.completedAt,
185
+ }
186
+ },
187
+
188
+ async cancel(batchId: string): Promise<void> {
189
+ const job = pendingJobs.get(batchId)
190
+ if (job) {
191
+ job.status = 'cancelled'
192
+ }
193
+ },
194
+
195
+ async getResults(batchId: string): Promise<BatchResult[]> {
196
+ const job = pendingJobs.get(batchId)
197
+ if (!job) {
198
+ throw new Error(`Batch not found: ${batchId}`)
199
+ }
200
+ return job.results
201
+ },
202
+
203
+ async waitForCompletion(batchId: string, pollInterval = 1000): Promise<BatchResult[]> {
204
+ const job = pendingJobs.get(batchId)
205
+ if (!job) {
206
+ throw new Error(`Batch not found: ${batchId}`)
207
+ }
208
+
209
+ while (job.status !== 'completed' && job.status !== 'failed' && job.status !== 'cancelled') {
210
+ await new Promise((resolve) => setTimeout(resolve, pollInterval))
211
+ }
212
+
213
+ return job.results
214
+ },
215
+ }
216
+
217
+ // ============================================================================
218
+ // Google GenAI Flex Adapter
219
+ // ============================================================================
220
+
221
+ /**
222
+ * Google GenAI Flex Adapter
223
+ *
224
+ * Implements concurrent processing for medium-sized batches.
225
+ * Uses the Gemini API for fast turnaround.
226
+ */
227
+ const googleFlexAdapter: FlexAdapter = {
228
+ async submitFlex(items: BatchItem[], options: { model?: string }): Promise<BatchResult[]> {
229
+ const model = options.model || 'gemini-2.0-flash'
230
+ const CONCURRENCY = 10
231
+
232
+ const results: BatchResult[] = []
233
+
234
+ // Process items concurrently
235
+ for (let i = 0; i < items.length; i += CONCURRENCY) {
236
+ const batch = items.slice(i, i + CONCURRENCY)
237
+
238
+ const batchResults = await Promise.all(
239
+ batch.map(async (item) => {
240
+ try {
241
+ return await processGoogleItem(item, model)
242
+ } catch (error) {
243
+ return {
244
+ id: item.id,
245
+ customId: item.id,
246
+ status: 'failed' as const,
247
+ error: error instanceof Error ? error.message : 'Unknown error',
248
+ }
249
+ }
250
+ })
251
+ )
252
+
253
+ results.push(...batchResults)
254
+ }
255
+
256
+ return results
257
+ },
258
+ }
259
+
260
+ // ============================================================================
261
+ // Processing
262
+ // ============================================================================
263
+
264
+ async function processGoogleRequestsConcurrently(
265
+ jobId: string,
266
+ items: BatchItem[],
267
+ model: string,
268
+ options: BatchQueueOptions
269
+ ): Promise<BatchResult[]> {
270
+ const job = pendingJobs.get(jobId)
271
+ if (!job) {
272
+ throw new Error(`Job not found: ${jobId}`)
273
+ }
274
+
275
+ job.status = 'in_progress'
276
+
277
+ const CONCURRENCY = 10
278
+ const results: BatchResult[] = []
279
+
280
+ for (let i = 0; i < items.length; i += CONCURRENCY) {
281
+ const batch = items.slice(i, i + CONCURRENCY)
282
+
283
+ const batchResults = await Promise.all(
284
+ batch.map(async (item) => {
285
+ try {
286
+ return await processGoogleItem(item, model)
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
+ async function processGoogleItem(item: BatchItem, model: string): Promise<BatchResult> {
309
+ const config = getConfig()
310
+
311
+ // Check if using AI Gateway
312
+ if (config.gatewayUrl && config.gatewayToken) {
313
+ return processGoogleItemViaGateway(item, config, model)
314
+ }
315
+
316
+ // Build the model name (add models/ prefix if not present)
317
+ const modelName = model.startsWith('models/') ? model : `models/${model}`
318
+
319
+ const url = `${config.baseUrl}/${modelName}:generateContent?key=${config.apiKey}`
320
+
321
+ // Build messages
322
+ const contents: GeminiMessage[] = []
323
+
324
+ // Add system instruction as a user message if provided (Gemini handles this differently)
325
+ if (item.options?.system) {
326
+ contents.push({
327
+ role: 'user',
328
+ parts: [{ text: `System instruction: ${item.options.system}\n\nUser request: ${item.prompt}` }],
329
+ })
330
+ } else {
331
+ contents.push({
332
+ role: 'user',
333
+ parts: [{ text: item.prompt }],
334
+ })
335
+ }
336
+
337
+ const body: Record<string, unknown> = {
338
+ contents,
339
+ generationConfig: {
340
+ maxOutputTokens: item.options?.maxTokens || 8192,
341
+ temperature: item.options?.temperature,
342
+ },
343
+ }
344
+
345
+ // Add JSON mode if schema is provided
346
+ if (item.schema) {
347
+ body.generationConfig = {
348
+ ...(body.generationConfig as object),
349
+ responseMimeType: 'application/json',
350
+ }
351
+ }
352
+
353
+ const response = await fetch(url, {
354
+ method: 'POST',
355
+ headers: {
356
+ 'Content-Type': 'application/json',
357
+ },
358
+ body: JSON.stringify(body),
359
+ })
360
+
361
+ if (!response.ok) {
362
+ const error = await response.text()
363
+ throw new Error(`Google GenAI API error: ${response.status} ${error}`)
364
+ }
365
+
366
+ const data = (await response.json()) as GeminiResponse
367
+
368
+ // Extract content
369
+ const content = data.candidates?.[0]?.content?.parts?.[0]?.text
370
+
371
+ let result: unknown = content
372
+
373
+ // Try to parse JSON if schema was provided or content looks like JSON
374
+ if (content && (item.schema || content.trim().startsWith('{') || content.trim().startsWith('['))) {
375
+ try {
376
+ result = JSON.parse(content)
377
+ } catch {
378
+ // Keep as string
379
+ }
380
+ }
381
+
382
+ return {
383
+ id: item.id,
384
+ customId: item.id,
385
+ status: 'completed',
386
+ result,
387
+ usage: data.usageMetadata
388
+ ? {
389
+ promptTokens: data.usageMetadata.promptTokenCount,
390
+ completionTokens: data.usageMetadata.candidatesTokenCount,
391
+ totalTokens: data.usageMetadata.totalTokenCount,
392
+ }
393
+ : undefined,
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Process a Google GenAI item via Cloudflare AI Gateway
399
+ * Gateway URL format: {gateway_url}/google-ai-studio/v1beta/models/{model}:generateContent
400
+ */
401
+ async function processGoogleItemViaGateway(
402
+ item: BatchItem,
403
+ config: ReturnType<typeof getConfig>,
404
+ model: string
405
+ ): Promise<BatchResult> {
406
+ // AI Gateway URL for Google AI Studio
407
+ // Format: {gateway_url}/google-ai-studio/v1beta/models/{model}:generateContent
408
+ const modelName = model.startsWith('models/') ? model.replace('models/', '') : model
409
+ const url = `${config.gatewayUrl}/google-ai-studio/v1beta/models/${modelName}:generateContent`
410
+
411
+ // Build messages
412
+ const contents: GeminiMessage[] = []
413
+
414
+ if (item.options?.system) {
415
+ contents.push({
416
+ role: 'user',
417
+ parts: [{ text: `System instruction: ${item.options.system}\n\nUser request: ${item.prompt}` }],
418
+ })
419
+ } else {
420
+ contents.push({
421
+ role: 'user',
422
+ parts: [{ text: item.prompt }],
423
+ })
424
+ }
425
+
426
+ const body: Record<string, unknown> = {
427
+ contents,
428
+ generationConfig: {
429
+ maxOutputTokens: item.options?.maxTokens || 8192,
430
+ temperature: item.options?.temperature,
431
+ },
432
+ }
433
+
434
+ if (item.schema) {
435
+ body.generationConfig = {
436
+ ...(body.generationConfig as object),
437
+ responseMimeType: 'application/json',
438
+ }
439
+ }
440
+
441
+ const response = await fetch(url, {
442
+ method: 'POST',
443
+ headers: {
444
+ 'cf-aig-authorization': `Bearer ${config.gatewayToken}`,
445
+ 'Content-Type': 'application/json',
446
+ },
447
+ body: JSON.stringify(body),
448
+ })
449
+
450
+ if (!response.ok) {
451
+ const error = await response.text()
452
+ throw new Error(`Google GenAI via Gateway error: ${response.status} ${error}`)
453
+ }
454
+
455
+ const data = (await response.json()) as GeminiResponse
456
+
457
+ const content = data.candidates?.[0]?.content?.parts?.[0]?.text
458
+
459
+ let result: unknown = content
460
+
461
+ if (content && (item.schema || content.trim().startsWith('{') || content.trim().startsWith('['))) {
462
+ try {
463
+ result = JSON.parse(content)
464
+ } catch {
465
+ // Keep as string
466
+ }
467
+ }
468
+
469
+ return {
470
+ id: item.id,
471
+ customId: item.id,
472
+ status: 'completed',
473
+ result,
474
+ usage: data.usageMetadata
475
+ ? {
476
+ promptTokens: data.usageMetadata.promptTokenCount,
477
+ completionTokens: data.usageMetadata.candidatesTokenCount,
478
+ totalTokens: data.usageMetadata.totalTokenCount,
479
+ }
480
+ : undefined,
481
+ }
482
+ }
483
+
484
+ // ============================================================================
485
+ // Register Adapters
486
+ // ============================================================================
487
+
488
+ registerBatchAdapter('google', googleAdapter)
489
+ registerFlexAdapter('google', googleFlexAdapter)
490
+
491
+ export { googleAdapter, googleFlexAdapter }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Batch Adapters Index
3
+ *
4
+ * Import specific adapters to register them:
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * // Import to register the OpenAI batch adapter
9
+ * import 'ai-functions/batch/openai'
10
+ *
11
+ * // Import to register the Anthropic batch adapter
12
+ * import 'ai-functions/batch/anthropic'
13
+ *
14
+ * // Import to register the Cloudflare adapter
15
+ * import 'ai-functions/batch/cloudflare'
16
+ *
17
+ * // Import to register the AWS Bedrock adapter
18
+ * import 'ai-functions/batch/bedrock'
19
+ *
20
+ * // Or import the in-memory adapter for testing
21
+ * import 'ai-functions/batch/memory'
22
+ * ```
23
+ *
24
+ * @packageDocumentation
25
+ */
26
+
27
+ export * from './openai.js'
28
+ export * from './anthropic.js'
29
+ export * from './cloudflare.js'
30
+ export * from './bedrock.js'
31
+ export * from './memory.js'