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
@@ -0,0 +1,336 @@
1
+ /**
2
+ * BatchProvider Port
3
+ *
4
+ * Defines the explicit port (interface) that every batch provider adapter must
5
+ * satisfy, plus helpers that concentrate logic genuinely shared across adapters
6
+ * (polling, concurrent processing, in-memory job tracking, JSON-Schema conversion).
7
+ *
8
+ * Each provider file (`anthropic.ts`, `openai.ts`, `google.ts`, `bedrock.ts`,
9
+ * `cloudflare.ts`, `memory.ts`) is now a small adapter that satisfies this port
10
+ * and concentrates on provider-specific HTTP/SDK calls and request/response shapes.
11
+ *
12
+ * Port + 4 adapters = real seam. The port lives here so provider files don't
13
+ * import shared logic from each other and can't accidentally diverge.
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+
18
+ import type {
19
+ BatchAdapter,
20
+ BatchItem,
21
+ BatchJob,
22
+ BatchQueueOptions,
23
+ BatchResult,
24
+ BatchStatus,
25
+ } from '../batch-queue.js'
26
+
27
+ // Re-export the port and its types so adapters can import everything from
28
+ // `./provider.js` rather than reaching into `../batch-queue.js`.
29
+ export type {
30
+ BatchAdapter,
31
+ BatchItem,
32
+ BatchJob,
33
+ BatchQueueOptions,
34
+ BatchResult,
35
+ BatchSubmitResult,
36
+ BatchProvider,
37
+ BatchStatus,
38
+ FlexAdapter,
39
+ } from '../batch-queue.js'
40
+
41
+ export { registerBatchAdapter, registerFlexAdapter } from '../batch-queue.js'
42
+
43
+ // ============================================================================
44
+ // Polling helper (shared by all adapters that have async batch jobs)
45
+ // ============================================================================
46
+
47
+ /** Terminal states for a batch job — polling stops here. */
48
+ const TERMINAL_STATUSES: ReadonlySet<BatchStatus> = new Set([
49
+ 'completed',
50
+ 'failed',
51
+ 'cancelled',
52
+ 'expired',
53
+ ])
54
+
55
+ /** Statuses that should trigger result fetch. */
56
+ const RESULT_STATUSES: ReadonlySet<BatchStatus> = new Set(['completed', 'cancelled', 'failed'])
57
+
58
+ /**
59
+ * Default `waitForCompletion` implementation built on top of `getStatus` +
60
+ * `getResults`. Adapters with non-standard completion semantics can still
61
+ * override `waitForCompletion`, but most don't need to.
62
+ *
63
+ * @param adapter The batch adapter to poll
64
+ * @param batchId The batch id to poll
65
+ * @param options.pollInterval Poll interval in ms (default: 5000)
66
+ * @param options.fetchResultsOn Statuses for which results should be fetched.
67
+ * Defaults to `completed`, `cancelled`, `failed`.
68
+ * @param options.throwOn Statuses that should throw rather than fetch results.
69
+ * Useful for OpenAI which throws on `cancelled`/`expired`.
70
+ */
71
+ export async function pollUntilComplete(
72
+ adapter: Pick<BatchAdapter, 'getStatus' | 'getResults'>,
73
+ batchId: string,
74
+ options: {
75
+ pollInterval?: number
76
+ fetchResultsOn?: ReadonlySet<BatchStatus>
77
+ throwOn?: ReadonlySet<BatchStatus>
78
+ } = {}
79
+ ): Promise<BatchResult[]> {
80
+ const pollInterval = options.pollInterval ?? 5000
81
+ const fetchResultsOn = options.fetchResultsOn ?? RESULT_STATUSES
82
+ const throwOn = options.throwOn
83
+
84
+ while (true) {
85
+ const status = await adapter.getStatus(batchId)
86
+
87
+ if (throwOn?.has(status.status)) {
88
+ throw new Error(`Batch ${status.status}`)
89
+ }
90
+
91
+ if (fetchResultsOn.has(status.status) || TERMINAL_STATUSES.has(status.status)) {
92
+ return adapter.getResults(batchId)
93
+ }
94
+
95
+ await sleep(pollInterval)
96
+ }
97
+ }
98
+
99
+ // ============================================================================
100
+ // Concurrent processing helper (shared by flex adapters and "local" providers)
101
+ // ============================================================================
102
+
103
+ /**
104
+ * Run `processItem` over `items` with bounded concurrency, optionally
105
+ * sleeping between waves to respect provider rate limits. Per-item failures
106
+ * are caught and emitted as `{ status: 'failed', error }` results so a single
107
+ * bad item never poisons the whole batch.
108
+ *
109
+ * Used by flex adapters (OpenAI, Google, Bedrock) and by the "local" providers
110
+ * (Google, Bedrock, Cloudflare) that fake batch processing with concurrent
111
+ * direct API calls.
112
+ */
113
+ export async function processConcurrently(
114
+ items: BatchItem[],
115
+ processItem: (item: BatchItem) => Promise<BatchResult>,
116
+ options: {
117
+ concurrency?: number
118
+ delayBetweenWaves?: number
119
+ onWaveComplete?: (results: BatchResult[]) => void
120
+ } = {}
121
+ ): Promise<BatchResult[]> {
122
+ const concurrency = options.concurrency ?? 10
123
+ const delay = options.delayBetweenWaves ?? 0
124
+ const results: BatchResult[] = []
125
+
126
+ for (let i = 0; i < items.length; i += concurrency) {
127
+ const wave = items.slice(i, i + concurrency)
128
+
129
+ const waveResults = await Promise.all(
130
+ wave.map(async (item) => {
131
+ try {
132
+ return await processItem(item)
133
+ } catch (error) {
134
+ return failedResult(item, error)
135
+ }
136
+ })
137
+ )
138
+
139
+ results.push(...waveResults)
140
+ options.onWaveComplete?.(results)
141
+
142
+ if (delay > 0 && i + concurrency < items.length) {
143
+ await sleep(delay)
144
+ }
145
+ }
146
+
147
+ return results
148
+ }
149
+
150
+ /** Build a `failed` BatchResult from an unknown thrown value. */
151
+ export function failedResult(item: BatchItem, error: unknown): BatchResult {
152
+ return {
153
+ id: item.id,
154
+ customId: item.id,
155
+ status: 'failed',
156
+ error: error instanceof Error ? error.message : 'Unknown error',
157
+ }
158
+ }
159
+
160
+ // ============================================================================
161
+ // LocalJobStore — in-memory job tracking shared by google/bedrock/cloudflare
162
+ // ============================================================================
163
+
164
+ /**
165
+ * Internal job state for adapters that don't have a real provider-side batch
166
+ * API and need to track jobs locally (Google, Bedrock, Cloudflare).
167
+ */
168
+ export interface LocalJobState {
169
+ items: BatchItem[]
170
+ options: BatchQueueOptions
171
+ results: BatchResult[]
172
+ status: BatchStatus
173
+ createdAt: Date
174
+ completedAt?: Date
175
+ /** Optional adapter-specific metadata (e.g. Bedrock's jobArn) */
176
+ meta?: Record<string, unknown>
177
+ }
178
+
179
+ /**
180
+ * Per-provider in-memory job registry. Encapsulates the
181
+ * `Map<jobId, state>` + counter + status/result lookup pattern that
182
+ * google/bedrock/cloudflare were each duplicating.
183
+ */
184
+ export class LocalJobStore {
185
+ private readonly jobs = new Map<string, LocalJobState>()
186
+ private counter = 0
187
+
188
+ constructor(private readonly idPrefix: string) {}
189
+
190
+ create(items: BatchItem[], options: BatchQueueOptions): { id: string; state: LocalJobState } {
191
+ const id = `${this.idPrefix}_${++this.counter}_${Date.now()}`
192
+ const state: LocalJobState = {
193
+ items,
194
+ options,
195
+ results: [],
196
+ status: 'pending',
197
+ createdAt: new Date(),
198
+ }
199
+ this.jobs.set(id, state)
200
+ return { id, state }
201
+ }
202
+
203
+ get(id: string): LocalJobState {
204
+ const state = this.jobs.get(id)
205
+ if (!state) {
206
+ throw new Error(`Batch not found: ${id}`)
207
+ }
208
+ return state
209
+ }
210
+
211
+ has(id: string): boolean {
212
+ return this.jobs.has(id)
213
+ }
214
+
215
+ /** Build a `BatchJob` snapshot for a tracked job. */
216
+ snapshot(id: string, provider: BatchJob['provider']): BatchJob {
217
+ const state = this.get(id)
218
+ const completedItems = state.results.filter((r) => r.status === 'completed').length
219
+ const failedItems = state.results.filter((r) => r.status === 'failed').length
220
+
221
+ return {
222
+ id,
223
+ provider,
224
+ status: state.status,
225
+ totalItems: state.items.length,
226
+ completedItems,
227
+ failedItems,
228
+ createdAt: state.createdAt,
229
+ ...(state.completedAt && { completedAt: state.completedAt }),
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Wait for a tracked job to reach a terminal status by polling its in-memory
235
+ * state. Adapters that drive the state machine in a background promise can
236
+ * call this from `waitForCompletion`.
237
+ */
238
+ async waitForCompletion(id: string, pollInterval = 1000): Promise<BatchResult[]> {
239
+ const state = this.get(id)
240
+ while (
241
+ state.status !== 'completed' &&
242
+ state.status !== 'failed' &&
243
+ state.status !== 'cancelled'
244
+ ) {
245
+ await sleep(pollInterval)
246
+ }
247
+ return state.results
248
+ }
249
+
250
+ /** For tests: drop everything. */
251
+ clear(): void {
252
+ this.jobs.clear()
253
+ this.counter = 0
254
+ }
255
+ }
256
+
257
+ // ============================================================================
258
+ // JSON-Schema conversion (used by Anthropic + OpenAI adapters)
259
+ // ============================================================================
260
+
261
+ /** Zod schema definition structure for type introspection. */
262
+ interface ZodDef {
263
+ typeName?: string
264
+ type?: unknown
265
+ shape?: () => Record<string, unknown>
266
+ }
267
+
268
+ /** Zod schema with `_def` property for introspection. */
269
+ interface ZodSchemaLike {
270
+ _def?: ZodDef
271
+ }
272
+
273
+ /**
274
+ * Minimal Zod -> JSON Schema converter.
275
+ *
276
+ * This is the same simplified converter that previously lived (duplicated)
277
+ * inside `anthropic.ts` and `openai.ts`. Extracted here so both adapters call
278
+ * the same implementation. For richer conversion use `zod-to-json-schema`.
279
+ */
280
+ export function zodToJsonSchema(zodSchema: unknown): Record<string, unknown> {
281
+ const schema = zodSchema as ZodSchemaLike
282
+
283
+ if (!schema._def) {
284
+ return { type: 'object' }
285
+ }
286
+
287
+ switch (schema._def.typeName) {
288
+ case 'ZodString':
289
+ return { type: 'string' }
290
+ case 'ZodNumber':
291
+ return { type: 'number' }
292
+ case 'ZodBoolean':
293
+ return { type: 'boolean' }
294
+ case 'ZodArray':
295
+ return { type: 'array', items: zodToJsonSchema(schema._def.type) }
296
+ case 'ZodObject': {
297
+ const shape = schema._def.shape?.() ?? {}
298
+ const properties: Record<string, unknown> = {}
299
+ for (const [key, value] of Object.entries(shape)) {
300
+ properties[key] = zodToJsonSchema(value)
301
+ }
302
+ return { type: 'object', properties, required: Object.keys(properties) }
303
+ }
304
+ default:
305
+ return { type: 'object' }
306
+ }
307
+ }
308
+
309
+ // ============================================================================
310
+ // JSON parsing (used by every adapter that returns raw text)
311
+ // ============================================================================
312
+
313
+ /**
314
+ * Try to parse `text` as JSON when it looks like JSON or a schema is expected,
315
+ * otherwise return the text unchanged. Never throws.
316
+ */
317
+ export function tryParseJson(text: string | undefined, expectJson = false): unknown {
318
+ if (!text) return text
319
+ const trimmed = text.trim()
320
+ const looksLikeJson = trimmed.startsWith('{') || trimmed.startsWith('[')
321
+ if (!expectJson && !looksLikeJson) return text
322
+ try {
323
+ return JSON.parse(text)
324
+ } catch {
325
+ return text
326
+ }
327
+ }
328
+
329
+ // ============================================================================
330
+ // Misc
331
+ // ============================================================================
332
+
333
+ /** Promise-based setTimeout. */
334
+ export function sleep(ms: number): Promise<void> {
335
+ return new Promise((resolve) => setTimeout(resolve, ms))
336
+ }
package/src/batch-map.ts CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  isFlexAvailable,
28
28
  type ExecutionTier,
29
29
  } from './context.js'
30
+ import { getLogger } from './logger.js'
30
31
  import { createBatch, getBatchAdapter, type BatchItem, type BatchResult } from './batch-queue.js'
31
32
  import { generateObject, generateText } from './generate.js'
32
33
  import type { SimpleSchema } from './schema.js'
@@ -100,11 +101,7 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
100
101
  /** Cached resolver promise */
101
102
  private _resolver: Promise<T[]> | null = null
102
103
 
103
- constructor(
104
- items: unknown[],
105
- operations: CapturedOperation[][],
106
- options: BatchMapOptions = {}
107
- ) {
104
+ constructor(items: unknown[], operations: CapturedOperation[][], options: BatchMapOptions = {}) {
108
105
  this._items = items
109
106
  this._operations = operations
110
107
  this._options = options
@@ -144,7 +141,9 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
144
141
  if (isFlexAvailable()) {
145
142
  return this._resolveViaFlex()
146
143
  }
147
- console.warn(`Flex processing not available for ${getProvider()}, using immediate execution`)
144
+ getLogger().warn(
145
+ `Flex processing not available for ${getProvider()}, using immediate execution`
146
+ )
148
147
  return this._resolveImmediately()
149
148
 
150
149
  case 'batch':
@@ -186,8 +185,8 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
186
185
  batchItems.push({
187
186
  id,
188
187
  prompt,
189
- schema: op.schema,
190
- options: { system: op.system, model },
188
+ ...(op.schema !== undefined && { schema: op.schema }),
189
+ options: Object.assign({ model }, op.system !== undefined ? { system: op.system } : {}),
191
190
  status: 'pending',
192
191
  })
193
192
 
@@ -200,7 +199,7 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
200
199
  return this._reconstructResults(results, itemOperationMap)
201
200
  } catch {
202
201
  // Flex adapter not available, fall back to batch or immediate
203
- console.warn(`Flex adapter not available, falling back to batch API`)
202
+ getLogger().warn(`Flex adapter not available, falling back to batch API`)
204
203
  return this._resolveViaBatchAPI()
205
204
  }
206
205
  }
@@ -219,7 +218,9 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
219
218
  adapter = getBatchAdapter(provider)
220
219
  } catch {
221
220
  // Adapter not registered, fall back to immediate execution
222
- console.warn(`Batch adapter for ${provider} not available, falling back to immediate execution`)
221
+ getLogger().warn(
222
+ `Batch adapter for ${provider} not available, falling back to immediate execution`
223
+ )
223
224
  return this._resolveImmediately()
224
225
  }
225
226
 
@@ -243,8 +244,8 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
243
244
  batchItems.push({
244
245
  id,
245
246
  prompt,
246
- schema: op.schema,
247
- options: { system: op.system, model },
247
+ ...(op.schema !== undefined && { schema: op.schema }),
248
+ options: Object.assign({ model }, op.system !== undefined ? { system: op.system } : {}),
248
249
  status: 'pending',
249
250
  })
250
251
 
@@ -256,15 +257,15 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
256
257
  const batch = createBatch({
257
258
  provider,
258
259
  model,
259
- webhookUrl: ctx.webhookUrl,
260
- metadata: ctx.metadata,
260
+ ...(ctx.webhookUrl !== undefined && { webhookUrl: ctx.webhookUrl }),
261
+ ...(ctx.metadata !== undefined && { metadata: ctx.metadata }),
261
262
  })
262
263
 
263
264
  for (const item of batchItems) {
264
265
  batch.add(item.prompt, {
265
- schema: item.schema,
266
- options: item.options,
267
- customId: item.id,
266
+ ...(item.schema !== undefined && { schema: item.schema }),
267
+ ...(item.options !== undefined && { options: item.options }),
268
+ ...(item.id !== undefined && { customId: item.id }),
268
269
  })
269
270
  }
270
271
 
@@ -324,7 +325,11 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
324
325
  ): Promise<unknown> {
325
326
  switch (op.type) {
326
327
  case 'text':
327
- const textResult = await generateText({ model, prompt, system: op.system })
328
+ const textResult = await generateText({
329
+ model,
330
+ prompt,
331
+ ...(op.system !== undefined && { system: op.system }),
332
+ })
328
333
  return textResult.text
329
334
 
330
335
  case 'boolean':
@@ -341,7 +346,7 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
341
346
  model,
342
347
  schema: { items: ['List items'] },
343
348
  prompt,
344
- system: op.system,
349
+ ...(op.system !== undefined && { system: op.system }),
345
350
  })
346
351
  return (listResult.object as { items: string[] }).items
347
352
 
@@ -351,7 +356,7 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
351
356
  model,
352
357
  schema: op.schema || { result: 'The result' },
353
358
  prompt,
354
- system: op.system,
359
+ ...(op.system !== undefined && { system: op.system }),
355
360
  })
356
361
  return objResult.object
357
362
  }
@@ -387,7 +392,7 @@ export class BatchMapPromise<T> implements PromiseLike<T[]> {
387
392
  if (operations.length === 1) {
388
393
  results[itemIndex] = batchResult.result as T
389
394
  } else {
390
- (results[itemIndex] as Record<string, unknown>)[`op_${opIndex}`] = batchResult.result
395
+ ;(results[itemIndex] as Record<string, unknown>)[`op_${opIndex}`] = batchResult.result
391
396
  }
392
397
  }
393
398
 
@@ -499,9 +504,9 @@ export function captureOperation(
499
504
  id: `op_${++operationCounter}`,
500
505
  prompt,
501
506
  itemPlaceholder: currentItemPlaceholder,
502
- schema,
507
+ ...(schema !== undefined && { schema }),
503
508
  type,
504
- system,
509
+ ...(system !== undefined && { system }),
505
510
  })
506
511
  }
507
512
 
@@ -571,6 +576,6 @@ export function isBatchMapPromise(value: unknown): value is BatchMapPromise<unkn
571
576
  value !== null &&
572
577
  typeof value === 'object' &&
573
578
  BATCH_MAP_SYMBOL in value &&
574
- (value as any)[BATCH_MAP_SYMBOL] === true
579
+ (value as Record<symbol, unknown>)[BATCH_MAP_SYMBOL] === true
575
580
  )
576
581
  }